use super::{ChoiceGuard, Protocol, Role, ValidationError};
use proc_macro2::Ident;
use serde::{Deserialize, Serialize};
use std::collections::{BTreeSet, HashMap};
const ATTR_THEOREM_PACKS: &str = "dsl.proof_bundles";
const ATTR_REQUIRED_THEOREM_PACKS: &str = "dsl.required_proof_bundles";
const ATTR_INFERRED_REQUIRED_THEOREM_PACKS: &str = "dsl.inferred_required_proof_bundles";
const ATTR_ROLE_SETS: &str = "dsl.role_sets";
const ATTR_TOPOLOGIES: &str = "dsl.topologies";
const ATTR_TYPE_DECLARATIONS: &str = "dsl.type_decls";
const ATTR_EFFECT_INTERFACE_DECLARATIONS: &str = "dsl.effect_decls";
const ATTR_PROTOCOL_USES: &str = "dsl.protocol_uses";
const ATTR_REGION_DECLARATIONS: &str = "dsl.fragment_decls";
const ATTR_OPERATION_DECLARATIONS: &str = "dsl.operation_decls";
const ATTR_GUEST_RUNTIME_DECLARATIONS: &str = "dsl.guest_runtime_decls";
const ATTR_EXECUTION_PROFILE_DECLARATIONS: &str = "dsl.execution_profile_decls";
const ATTR_PROTOCOL_EXECUTION_PROFILES: &str = "dsl.protocol_execution_profiles";
const ATTR_AGREEMENT_PROFILE_DECLARATIONS: &str = "dsl.agreement_profile_decls";
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TheoremPackDeclaration {
pub name: String,
#[serde(default)]
pub capabilities: Vec<String>,
#[serde(default)]
pub version: Option<String>,
#[serde(default)]
pub issuer: Option<String>,
#[serde(default)]
pub constraints: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RoleSetDeclaration {
pub name: String,
#[serde(default)]
pub members: Vec<String>,
#[serde(default)]
pub subset_of: Option<String>,
#[serde(default)]
pub subset_start: Option<u32>,
#[serde(default)]
pub subset_end: Option<u32>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TopologyDeclaration {
pub kind: String,
pub name: String,
#[serde(default)]
pub members: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TypeDeclaration {
pub name: String,
pub is_alias: bool,
#[serde(default)]
pub alias_of: Option<String>,
#[serde(default)]
pub constructors: Vec<TypeConstructorDeclaration>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TypeConstructorDeclaration {
pub name: String,
#[serde(default)]
pub payload_type: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct EffectInterfaceDeclaration {
pub name: String,
#[serde(default)]
pub operations: Vec<EffectOperationDeclaration>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum EffectAuthorityClass {
Authoritative,
#[default]
Command,
Observe,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct EffectOperationDeclaration {
pub name: String,
#[serde(default)]
pub authority_class: EffectAuthorityClass,
pub semantic_class: String,
pub progress: String,
pub region: String,
pub agreement_use: String,
pub reentrancy: String,
pub input_type: String,
pub output_type: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct EffectContractDeclaration {
pub interface_name: String,
pub operation_name: String,
pub authority_class: EffectAuthorityClass,
pub semantic_class: String,
pub progress: String,
pub region: String,
pub agreement_use: String,
pub admissibility: String,
pub totality: String,
pub timeout_policy: String,
pub reentrancy_policy: String,
pub handler_domain: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RegionDeclaration {
pub name: String,
#[serde(default)]
pub params: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct OperationParameterDeclaration {
pub name: String,
pub type_name: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ProgressAttachment {
pub contract_name: String,
#[serde(default)]
pub requires_profile: Option<String>,
#[serde(default)]
pub within_window: Option<String>,
#[serde(default)]
pub on_timeout: Option<String>,
#[serde(default)]
pub on_stall: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AgreementProfileDeclaration {
pub name: String,
pub visibility: String,
pub rule: String,
pub usable_at: String,
pub finalized_at: String,
pub evidence: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct OperationAgreementAttachment {
pub profile_name: String,
#[serde(default)]
pub prestate: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct OperationDeclaration {
pub name: String,
#[serde(default)]
pub params: Vec<OperationParameterDeclaration>,
pub owner_role: String,
#[serde(default)]
pub within: Option<String>,
#[serde(default)]
pub progress_contract: Option<ProgressAttachment>,
#[serde(default)]
pub agreement: Option<OperationAgreementAttachment>,
#[serde(default)]
pub child_effect_aggregation: Option<ChildEffectAggregation>,
pub body_source: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum ChildEffectAggregationPolicy {
All,
First,
Threshold {
required_successes: u64,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ChildEffectAggregation {
pub policy: ChildEffectAggregationPolicy,
}
impl ChildEffectAggregationPolicy {
#[must_use]
pub fn dsl_name(&self) -> String {
match self {
Self::All => "all_success".to_string(),
Self::First => "first_success".to_string(),
Self::Threshold { required_successes } => {
format!("threshold_success({required_successes})")
}
}
}
}
impl ChildEffectAggregation {
#[must_use]
pub fn dsl_name(&self) -> String {
self.policy.dsl_name()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct GuestRuntimeDeclaration {
pub name: String,
#[serde(default)]
pub uses: Vec<String>,
pub entry: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ExecutionProfileDeclaration {
pub name: String,
#[serde(default)]
pub fairness: Option<String>,
#[serde(default)]
pub admissibility: Option<String>,
#[serde(default)]
pub escalation_window: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum LanguageTier {
FullSpec,
SessionProjectable,
ProtocolMachineExecutable,
ProofOnly,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AuthorityMetatheoryTier {
SessionTypedCoordination,
EvidencePublicationSemanticObjects,
RuntimeSemanticOnly,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AuthorityMetatheoryStatus {
pub strongest_tier: AuthorityMetatheoryTier,
pub diagnostic: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct LanguageTierStatus {
pub strongest_tier: LanguageTier,
pub session_projectable: bool,
pub protocol_machine_executable: bool,
pub theory_convertible: bool,
pub proof_only: bool,
pub diagnostic: String,
}
#[derive(Debug)]
pub struct Choreography {
pub name: Ident,
pub namespace: Option<String>,
pub roles: Vec<Role>,
pub protocol: Protocol,
pub attrs: HashMap<String, String>,
}
impl Choreography {
#[must_use]
pub fn qualified_name(&self) -> String {
match &self.namespace {
Some(ns) => format!("{}::{}", ns, self.name),
None => self.name.to_string(),
}
}
pub fn validate(&self) -> Result<(), ValidationError> {
for role in &self.roles {
if !self.protocol.mentions_role(role) {
return Err(ValidationError::UnusedRole(role.name().to_string()));
}
}
self.protocol.validate(&self.roles)?;
self.validate_proof_bundles()?;
self.validate_effect_surface()?;
self.validate_execution_profile_surface()?;
self.validate_operation_surface()?;
Ok(())
}
fn validate_proof_bundles(&self) -> Result<(), ValidationError> {
let bundles = self.theorem_packs();
let mut declared: BTreeSet<String> = BTreeSet::new();
for bundle in &bundles {
if !declared.insert(bundle.name.clone()) {
return Err(ValidationError::DuplicateProofBundle(bundle.name.clone()));
}
}
for required in self.required_theorem_packs() {
if !declared.contains(&required) {
return Err(ValidationError::MissingProofBundle(required));
}
}
let required_caps = self.required_theorem_pack_capabilities();
for capability in self.required_protocol_machine_core_capabilities() {
if !required_caps.contains(&capability) {
return Err(ValidationError::MissingCapability(capability));
}
}
Ok(())
}
fn validate_effect_surface(&self) -> Result<(), ValidationError> {
let mut effect_names = BTreeSet::new();
let mut effect_ops: HashMap<String, HashMap<String, EffectOperationDeclaration>> =
HashMap::new();
for effect in self.effect_interface_declarations() {
if !effect_names.insert(effect.name.clone()) {
return Err(ValidationError::ExtensionError(format!(
"duplicate effect interface declaration `{}`",
effect.name
)));
}
let mut ops = HashMap::new();
for op in effect.operations {
let semantic_class_ok = matches!(
op.semantic_class.as_str(),
"authoritative" | "observational" | "best_effort"
);
if !semantic_class_ok {
return Err(ValidationError::ExtensionError(format!(
"effect operation `{}.{}` has unsupported `class {}`",
effect.name, op.name, op.semantic_class
)));
}
let progress_ok = matches!(op.progress.as_str(), "immediate" | "may_block");
if !progress_ok {
return Err(ValidationError::ExtensionError(format!(
"effect operation `{}.{}` has unsupported `progress {}`",
effect.name, op.name, op.progress
)));
}
if !matches!(op.region.as_str(), "session" | "fragment" | "global") {
return Err(ValidationError::ExtensionError(format!(
"effect operation `{}.{}` has unsupported `region {}`",
effect.name, op.name, op.region
)));
}
if !matches!(op.agreement_use.as_str(), "required" | "none" | "forbidden") {
return Err(ValidationError::ExtensionError(format!(
"effect operation `{}.{}` has unsupported `agreement_use {}`",
effect.name, op.name, op.agreement_use
)));
}
if !matches!(
op.reentrancy.as_str(),
"allow" | "reject_same_operation" | "reject_same_fragment"
) {
return Err(ValidationError::ExtensionError(format!(
"effect operation `{}.{}` has unsupported `reentrancy {}`",
effect.name, op.name, op.reentrancy
)));
}
if matches!(op.authority_class, EffectAuthorityClass::Observe)
&& op.semantic_class != "observational"
{
return Err(ValidationError::ExtensionError(format!(
"effect operation `{}.{}` is observational and must declare `class : observational`",
effect.name, op.name
)));
}
if matches!(op.authority_class, EffectAuthorityClass::Authoritative)
&& op.semantic_class != "authoritative"
{
return Err(ValidationError::ExtensionError(format!(
"effect operation `{}.{}` is authoritative and must declare `class : authoritative`",
effect.name, op.name
)));
}
if op.progress == "immediate"
&& matches!(op.authority_class, EffectAuthorityClass::Authoritative)
{
return Err(ValidationError::ExtensionError(format!(
"effect operation `{}.{}` may not be both `authoritative` and `progress immediate`",
effect.name, op.name
)));
}
if op.agreement_use == "required"
&& matches!(op.authority_class, EffectAuthorityClass::Observe)
{
return Err(ValidationError::ExtensionError(format!(
"effect operation `{}.{}` may not require agreement use on an observational surface",
effect.name, op.name
)));
}
if ops.insert(op.name.clone(), op.clone()).is_some() {
return Err(ValidationError::ExtensionError(format!(
"duplicate effect operation `{}.{}`",
effect.name, op.name
)));
}
}
effect_ops.insert(effect.name, ops);
}
let declared = effect_names;
let used: BTreeSet<String> = self.protocol_uses().into_iter().collect();
for effect in &used {
if !declared.contains(effect) {
return Err(ValidationError::ExtensionError(format!(
"protocol uses undeclared effect interface `{effect}`"
)));
}
}
fn validate_expr(
expr: &super::AuthorityExpr,
effect_ops: &HashMap<String, HashMap<String, EffectOperationDeclaration>>,
used: &BTreeSet<String>,
) -> Result<(), ValidationError> {
match expr {
super::AuthorityExpr::Check {
effect, operation, ..
} => {
if !used.contains(effect) {
return Err(ValidationError::ExtensionError(format!(
"effect invocation `{effect}.{operation}` is not allowed without `uses {effect}`"
)));
}
let Some(ops) = effect_ops.get(effect) else {
return Err(ValidationError::ExtensionError(format!(
"effect invocation references undeclared interface `{effect}`"
)));
};
let Some(op_decl) = ops.get(operation) else {
return Err(ValidationError::ExtensionError(format!(
"effect invocation references undeclared operation `{effect}.{operation}`"
)));
};
if matches!(op_decl.authority_class, EffectAuthorityClass::Observe) {
return Err(ValidationError::ExtensionError(format!(
"effect invocation `{effect}.{operation}` is observational and may not be invoked with `check`"
)));
}
Ok(())
}
super::AuthorityExpr::Observe {
effect, operation, ..
} => {
if !used.contains(effect) {
return Err(ValidationError::ExtensionError(format!(
"effect invocation `{effect}.{operation}` is not allowed without `uses {effect}`"
)));
}
let Some(ops) = effect_ops.get(effect) else {
return Err(ValidationError::ExtensionError(format!(
"effect invocation references undeclared interface `{effect}`"
)));
};
let Some(op_decl) = ops.get(operation) else {
return Err(ValidationError::ExtensionError(format!(
"effect invocation references undeclared operation `{effect}.{operation}`"
)));
};
if !matches!(op_decl.authority_class, EffectAuthorityClass::Observe) {
return Err(ValidationError::ExtensionError(format!(
"effect invocation `{effect}.{operation}` is not observational and may not be invoked with `observe`"
)));
}
Ok(())
}
super::AuthorityExpr::Var(_)
| super::AuthorityExpr::Transfer { .. }
| super::AuthorityExpr::Constructor { .. }
| super::AuthorityExpr::Call { .. } => Ok(()),
}
}
fn validate_protocol_effects(
protocol: &Protocol,
effect_ops: &HashMap<String, HashMap<String, EffectOperationDeclaration>>,
used: &BTreeSet<String>,
) -> Result<(), ValidationError> {
match protocol {
Protocol::Begin { continuation, .. }
| Protocol::Await { continuation, .. }
| Protocol::Resolve { continuation, .. }
| Protocol::Invalidate { continuation, .. }
| Protocol::Send { continuation, .. }
| Protocol::Broadcast { continuation, .. }
| Protocol::Extension { continuation, .. }
| Protocol::Let { continuation, .. }
| Protocol::Publish { continuation, .. }
| Protocol::PublishAuthority { continuation, .. }
| Protocol::Materialize { continuation, .. }
| Protocol::Handoff { continuation, .. }
| Protocol::DependentWork { continuation, .. } => {
if let Protocol::Let { mode, expr, .. } = protocol {
validate_expr(expr, effect_ops, used)?;
match (mode, expr) {
(
super::AuthorityBindingMode::Plain,
super::AuthorityExpr::Check {
effect, operation, ..
},
) => {
let op_decl = effect_ops
.get(effect)
.and_then(|ops| ops.get(operation))
.expect("validated effect operation should exist");
if matches!(
op_decl.authority_class,
EffectAuthorityClass::Authoritative
) {
return Err(ValidationError::ExtensionError(format!(
"authoritative effect invocation `{effect}.{operation}` must bind through `authoritative let`"
)));
}
}
(
super::AuthorityBindingMode::Plain,
super::AuthorityExpr::Observe { .. },
) => {
return Err(ValidationError::ExtensionError(
"`observe` expressions must bind through `observe let`"
.to_string(),
));
}
(
super::AuthorityBindingMode::Authoritative,
super::AuthorityExpr::Check {
effect, operation, ..
},
) => {
let op_decl = effect_ops
.get(effect)
.and_then(|ops| ops.get(operation))
.expect("validated effect operation should exist");
if !matches!(
op_decl.authority_class,
EffectAuthorityClass::Authoritative
) {
return Err(ValidationError::ExtensionError(format!(
"`authoritative let` may only bind authoritative effect invocations; `{effect}.{operation}` is {:?}",
op_decl.authority_class
)));
}
}
(
super::AuthorityBindingMode::Observe,
super::AuthorityExpr::Observe { .. },
) => {}
(super::AuthorityBindingMode::Plain, _) => {}
(super::AuthorityBindingMode::Authoritative, _) => {
return Err(ValidationError::ExtensionError(
"`authoritative let` must bind a `check` expression"
.to_string(),
));
}
(super::AuthorityBindingMode::Observe, _) => {
return Err(ValidationError::ExtensionError(
"`observe let` must bind an `observe` expression".to_string(),
));
}
}
}
validate_protocol_effects(continuation, effect_ops, used)
}
Protocol::Choice { branches, .. } => {
for branch in branches {
if let Some(ChoiceGuard::Evidence {
effect, operation, ..
}) = &branch.guard
{
if !used.contains(effect) {
return Err(ValidationError::ExtensionError(format!(
"effect guard `{effect}.{operation}` is not allowed without `uses {effect}`"
)));
}
let Some(ops) = effect_ops.get(effect) else {
return Err(ValidationError::ExtensionError(format!(
"effect guard references undeclared interface `{effect}`"
)));
};
let Some(op_decl) = ops.get(operation) else {
return Err(ValidationError::ExtensionError(format!(
"effect guard references undeclared operation `{effect}.{operation}`"
)));
};
if matches!(op_decl.authority_class, EffectAuthorityClass::Observe) {
return Err(ValidationError::ExtensionError(format!(
"effect guard `{effect}.{operation}` is observational and may not be invoked with `check`"
)));
}
}
validate_protocol_effects(&branch.protocol, effect_ops, used)?;
}
Ok(())
}
Protocol::Case { expr, branches } => {
validate_expr(expr, effect_ops, used)?;
for branch in branches {
validate_protocol_effects(&branch.protocol, effect_ops, used)?;
}
Ok(())
}
Protocol::Timeout {
body,
on_timeout,
on_cancel,
..
} => {
validate_protocol_effects(body, effect_ops, used)?;
validate_protocol_effects(on_timeout, effect_ops, used)?;
if let Some(on_cancel) = on_cancel.as_deref() {
validate_protocol_effects(on_cancel, effect_ops, used)?;
}
Ok(())
}
Protocol::Loop { body, .. } | Protocol::Rec { body, .. } => {
validate_protocol_effects(body, effect_ops, used)
}
Protocol::Parallel { protocols } => {
for protocol in protocols {
validate_protocol_effects(protocol, effect_ops, used)?;
}
Ok(())
}
Protocol::Var(_) | Protocol::End => Ok(()),
}
}
validate_protocol_effects(&self.protocol, &effect_ops, &used)
}
fn validate_execution_profile_surface(&self) -> Result<(), ValidationError> {
let mut declared = BTreeSet::new();
for profile in self.execution_profile_declarations() {
if !declared.insert(profile.name.clone()) {
return Err(ValidationError::ExtensionError(format!(
"duplicate execution profile declaration `{}`",
profile.name
)));
}
}
for profile in self.protocol_execution_profiles() {
if !declared.contains(&profile) {
return Err(ValidationError::ExtensionError(format!(
"protocol references undeclared execution profile `{profile}`"
)));
}
}
Ok(())
}
fn validate_operation_surface(&self) -> Result<(), ValidationError> {
let declared_agreement_profiles = self
.agreement_profile_declarations()
.into_iter()
.map(|profile| profile.name)
.collect::<BTreeSet<_>>();
for operation in self.operation_declarations() {
if operation.progress_contract.is_none() {
return Err(ValidationError::ExtensionError(format!(
"operation `{}` is parity-critical and must declare a progress contract",
operation.name
)));
}
if !operation
.progress_contract
.as_ref()
.is_some_and(|progress| progress.is_explicit())
{
return Err(ValidationError::ExtensionError(format!(
"operation `{}` must declare explicit progress metadata using `requires`, `within`, `on timeout`, or `on stall`",
operation.name
)));
}
let Some(agreement) = operation.agreement.as_ref() else {
return Err(ValidationError::ExtensionError(format!(
"operation `{}` must attach a named agreement profile",
operation.name
)));
};
if !declared_agreement_profiles.contains(&agreement.profile_name) {
return Err(ValidationError::ExtensionError(format!(
"operation `{}` references undeclared agreement profile `{}`",
operation.name, agreement.profile_name
)));
}
}
Ok(())
}
#[must_use]
pub fn language_tier_status(&self) -> LanguageTierStatus {
let theory_convertible = super::convert::protocol_to_global(&self.protocol).is_ok();
let session_blocker = find_session_projection_blocker(&self.protocol);
let missing_progress = self
.operation_declarations()
.iter()
.find(|operation| {
operation.progress_contract.is_none()
|| !operation
.progress_contract
.as_ref()
.is_some_and(|progress| progress.is_explicit())
})
.map(|operation| {
format!(
"operation `{}` is missing the required progress contract",
operation.name
)
});
let protocol_machine_executable = missing_progress.is_none();
let session_projectable = session_blocker.is_none();
let strongest_tier = match (session_projectable, protocol_machine_executable) {
(true, true) => LanguageTier::SessionProjectable,
(false, true) => LanguageTier::ProtocolMachineExecutable,
(_, false) => LanguageTier::ProofOnly,
};
let diagnostic = if let Some(blocker) = session_blocker {
format!(
"full spec is valid, protocol-machine execution is available, but session projection is blocked: {blocker}"
)
} else if let Some(missing_progress) = missing_progress {
format!(
"full spec is valid for proof analysis only; protocol-machine execution is blocked: {missing_progress}"
)
} else if !theory_convertible {
"full spec is valid, protocol-machine execution is available, and the protocol is session-projectable, but theory conversion remains explicitly unavailable for this surface".to_string()
} else {
"full spec is valid, protocol-machine execution is available, the protocol is session-projectable, and theory conversion is available".to_string()
};
LanguageTierStatus {
strongest_tier,
session_projectable,
protocol_machine_executable,
theory_convertible,
proof_only: matches!(strongest_tier, LanguageTier::ProofOnly),
diagnostic,
}
}
#[must_use]
pub fn authority_metatheory_status(&self) -> AuthorityMetatheoryStatus {
let strongest_tier = authority_metatheory_tier(&self.protocol);
let diagnostic = match authority_metatheory_blocker(&self.protocol) {
Some(blocker) => format!(
"authority-bearing constructs remain executable, but the supported theorem slice stops before this runtime semantic surface: {blocker}"
),
None => match strongest_tier {
AuthorityMetatheoryTier::SessionTypedCoordination =>
"the protocol stays within ordinary session-typed coordination; no authority-specific semantic proof obligations are introduced".to_string(),
AuthorityMetatheoryTier::EvidencePublicationSemanticObjects =>
"the protocol stays within the supported authority theorem slice: evidence-bearing reads and canonical publication/materialization are justified at the protocol-machine semantic-object layer, while session typing continues to cover only the coordination skeleton".to_string(),
AuthorityMetatheoryTier::RuntimeSemanticOnly =>
"authority-bearing runtime semantics are present, but they currently sit outside the supported theorem slice".to_string(),
},
};
AuthorityMetatheoryStatus {
strongest_tier,
diagnostic,
}
}
#[must_use]
pub fn get_attributes(&self) -> &HashMap<String, String> {
&self.attrs
}
pub fn get_attributes_mut(&mut self) -> &mut HashMap<String, String> {
&mut self.attrs
}
#[must_use]
pub fn get_attribute(&self, key: &str) -> Option<&String> {
self.attrs.get(key)
}
pub fn set_attribute(&mut self, key: String, value: String) {
self.attrs.insert(key, value);
}
pub fn remove_attribute(&mut self, key: &str) -> Option<String> {
self.attrs.remove(key)
}
#[must_use]
pub fn has_attribute(&self, key: &str) -> bool {
self.attrs.contains_key(key)
}
pub fn get_attribute_as<T>(&self, key: &str) -> Option<T>
where
T: std::str::FromStr,
{
self.get_attribute(key)?.parse().ok()
}
pub fn get_attribute_as_bool(&self, key: &str) -> Option<bool> {
let value = self.get_attribute(key)?;
match value.to_lowercase().as_str() {
"true" | "1" | "yes" | "on" => Some(true),
"false" | "0" | "no" | "off" => Some(false),
_ => None,
}
}
pub fn clear_attributes(&mut self) {
self.attrs.clear();
}
pub fn attribute_count(&self) -> usize {
self.attrs.len()
}
pub fn attribute_keys(&self) -> Vec<&String> {
self.attrs.keys().collect()
}
pub fn validate_required_attributes(&self, required_keys: &[&str]) -> Result<(), Vec<String>> {
let missing: Vec<String> = required_keys
.iter()
.filter(|&key| !self.has_attribute(key))
.map(|&key| key.to_string())
.collect();
if missing.is_empty() {
Ok(())
} else {
Err(missing)
}
}
pub fn find_nodes_with_annotation(&self, key: &str) -> Vec<&Protocol> {
let mut nodes = Vec::new();
self.protocol.collect_nodes_with_annotation(key, &mut nodes);
nodes
}
pub fn find_nodes_with_annotation_value(&self, key: &str, value: &str) -> Vec<&Protocol> {
let mut nodes = Vec::new();
self.protocol
.collect_nodes_with_annotation_value(key, value, &mut nodes);
nodes
}
pub fn total_annotation_count(&self) -> usize {
self.attribute_count() + self.protocol.deep_annotation_count()
}
pub fn set_theorem_packs(
&mut self,
theorem_packs: &[TheoremPackDeclaration],
) -> Result<(), String> {
let encoded = serde_json::to_string(theorem_packs)
.map_err(|e| format!("encode theorem packs: {e}"))?;
self.attrs.insert(ATTR_THEOREM_PACKS.to_string(), encoded);
Ok(())
}
#[must_use]
pub fn theorem_packs(&self) -> Vec<TheoremPackDeclaration> {
self.attrs
.get(ATTR_THEOREM_PACKS)
.and_then(|s| serde_json::from_str::<Vec<TheoremPackDeclaration>>(s).ok())
.unwrap_or_default()
}
pub fn set_required_theorem_packs(&mut self, required: &[String]) -> Result<(), String> {
let encoded = serde_json::to_string(required)
.map_err(|e| format!("encode required theorem packs: {e}"))?;
self.attrs
.insert(ATTR_REQUIRED_THEOREM_PACKS.to_string(), encoded);
Ok(())
}
#[must_use]
pub fn required_theorem_packs(&self) -> Vec<String> {
self.attrs
.get(ATTR_REQUIRED_THEOREM_PACKS)
.and_then(|s| serde_json::from_str::<Vec<String>>(s).ok())
.unwrap_or_default()
}
pub fn set_inferred_required_theorem_packs(
&mut self,
required: &[String],
) -> Result<(), String> {
let encoded = serde_json::to_string(required)
.map_err(|e| format!("encode inferred theorem packs: {e}"))?;
self.attrs
.insert(ATTR_INFERRED_REQUIRED_THEOREM_PACKS.to_string(), encoded);
Ok(())
}
#[must_use]
pub fn inferred_required_theorem_packs(&self) -> Vec<String> {
self.attrs
.get(ATTR_INFERRED_REQUIRED_THEOREM_PACKS)
.and_then(|s| serde_json::from_str::<Vec<String>>(s).ok())
.unwrap_or_default()
}
pub fn set_role_sets(&mut self, role_sets: &[RoleSetDeclaration]) -> Result<(), String> {
let encoded =
serde_json::to_string(role_sets).map_err(|e| format!("encode role sets: {e}"))?;
self.attrs.insert(ATTR_ROLE_SETS.to_string(), encoded);
Ok(())
}
#[must_use]
pub fn role_sets(&self) -> Vec<RoleSetDeclaration> {
self.attrs
.get(ATTR_ROLE_SETS)
.and_then(|s| serde_json::from_str::<Vec<RoleSetDeclaration>>(s).ok())
.unwrap_or_default()
}
pub fn set_topologies(&mut self, topologies: &[TopologyDeclaration]) -> Result<(), String> {
let encoded =
serde_json::to_string(topologies).map_err(|e| format!("encode topologies: {e}"))?;
self.attrs.insert(ATTR_TOPOLOGIES.to_string(), encoded);
Ok(())
}
#[must_use]
pub fn topologies(&self) -> Vec<TopologyDeclaration> {
self.attrs
.get(ATTR_TOPOLOGIES)
.and_then(|s| serde_json::from_str::<Vec<TopologyDeclaration>>(s).ok())
.unwrap_or_default()
}
pub fn set_type_declarations(&mut self, decls: &[TypeDeclaration]) -> Result<(), String> {
let encoded =
serde_json::to_string(decls).map_err(|e| format!("encode type declarations: {e}"))?;
self.attrs
.insert(ATTR_TYPE_DECLARATIONS.to_string(), encoded);
Ok(())
}
#[must_use]
pub fn type_declarations(&self) -> Vec<TypeDeclaration> {
self.attrs
.get(ATTR_TYPE_DECLARATIONS)
.and_then(|s| serde_json::from_str::<Vec<TypeDeclaration>>(s).ok())
.unwrap_or_default()
}
pub fn set_effect_interface_declarations(
&mut self,
decls: &[EffectInterfaceDeclaration],
) -> Result<(), String> {
let encoded =
serde_json::to_string(decls).map_err(|e| format!("encode effect declarations: {e}"))?;
self.attrs
.insert(ATTR_EFFECT_INTERFACE_DECLARATIONS.to_string(), encoded);
Ok(())
}
#[must_use]
pub fn effect_interface_declarations(&self) -> Vec<EffectInterfaceDeclaration> {
self.attrs
.get(ATTR_EFFECT_INTERFACE_DECLARATIONS)
.and_then(|s| serde_json::from_str::<Vec<EffectInterfaceDeclaration>>(s).ok())
.unwrap_or_default()
}
#[must_use]
pub fn effect_contract_declarations(&self) -> Vec<EffectContractDeclaration> {
let used: BTreeSet<String> = self.protocol_uses().into_iter().collect();
self.effect_interface_declarations()
.into_iter()
.flat_map(|effect| {
let is_used = used.contains(effect.name.as_str());
effect.operations.into_iter().map(move |op| {
let admissibility = if is_used {
"declared_use_only"
} else {
"internal_only"
};
let (totality, timeout_policy) = match op.progress.as_str() {
"immediate" => ("immediate", "none"),
"may_block" => ("may_block", "required"),
_ => ("may_block", "required"),
};
let handler_domain = if is_used { "external" } else { "internal" };
EffectContractDeclaration {
interface_name: effect.name.clone(),
operation_name: op.name,
authority_class: op.authority_class,
semantic_class: op.semantic_class.clone(),
progress: op.progress.clone(),
region: op.region.clone(),
agreement_use: op.agreement_use.clone(),
admissibility: admissibility.to_string(),
totality: totality.to_string(),
timeout_policy: timeout_policy.to_string(),
reentrancy_policy: op.reentrancy,
handler_domain: handler_domain.to_string(),
}
})
})
.collect()
}
pub fn set_protocol_uses(&mut self, uses: &[String]) -> Result<(), String> {
let encoded =
serde_json::to_string(uses).map_err(|e| format!("encode protocol uses: {e}"))?;
self.attrs.insert(ATTR_PROTOCOL_USES.to_string(), encoded);
Ok(())
}
#[must_use]
pub fn protocol_uses(&self) -> Vec<String> {
self.attrs
.get(ATTR_PROTOCOL_USES)
.and_then(|s| serde_json::from_str::<Vec<String>>(s).ok())
.unwrap_or_default()
}
pub fn set_region_declarations(&mut self, decls: &[RegionDeclaration]) -> Result<(), String> {
let encoded =
serde_json::to_string(decls).map_err(|e| format!("encode region declarations: {e}"))?;
self.attrs
.insert(ATTR_REGION_DECLARATIONS.to_string(), encoded);
Ok(())
}
#[must_use]
pub fn region_declarations(&self) -> Vec<RegionDeclaration> {
self.attrs
.get(ATTR_REGION_DECLARATIONS)
.and_then(|s| serde_json::from_str::<Vec<RegionDeclaration>>(s).ok())
.unwrap_or_default()
}
pub fn set_operation_declarations(
&mut self,
decls: &[OperationDeclaration],
) -> Result<(), String> {
let encoded = serde_json::to_string(decls)
.map_err(|e| format!("encode operation declarations: {e}"))?;
self.attrs
.insert(ATTR_OPERATION_DECLARATIONS.to_string(), encoded);
Ok(())
}
#[must_use]
pub fn operation_declarations(&self) -> Vec<OperationDeclaration> {
self.attrs
.get(ATTR_OPERATION_DECLARATIONS)
.and_then(|s| serde_json::from_str::<Vec<OperationDeclaration>>(s).ok())
.unwrap_or_default()
}
pub fn set_guest_runtime_declarations(
&mut self,
decls: &[GuestRuntimeDeclaration],
) -> Result<(), String> {
let encoded = serde_json::to_string(decls)
.map_err(|e| format!("encode guest runtime declarations: {e}"))?;
self.attrs
.insert(ATTR_GUEST_RUNTIME_DECLARATIONS.to_string(), encoded);
Ok(())
}
#[must_use]
pub fn guest_runtime_declarations(&self) -> Vec<GuestRuntimeDeclaration> {
self.attrs
.get(ATTR_GUEST_RUNTIME_DECLARATIONS)
.and_then(|s| serde_json::from_str::<Vec<GuestRuntimeDeclaration>>(s).ok())
.unwrap_or_default()
}
pub fn set_execution_profile_declarations(
&mut self,
decls: &[ExecutionProfileDeclaration],
) -> Result<(), String> {
let encoded = serde_json::to_string(decls)
.map_err(|e| format!("encode execution profile declarations: {e}"))?;
self.attrs
.insert(ATTR_EXECUTION_PROFILE_DECLARATIONS.to_string(), encoded);
Ok(())
}
#[must_use]
pub fn execution_profile_declarations(&self) -> Vec<ExecutionProfileDeclaration> {
self.attrs
.get(ATTR_EXECUTION_PROFILE_DECLARATIONS)
.and_then(|s| serde_json::from_str::<Vec<ExecutionProfileDeclaration>>(s).ok())
.unwrap_or_default()
}
pub fn set_protocol_execution_profiles(&mut self, profiles: &[String]) -> Result<(), String> {
let encoded = serde_json::to_string(profiles)
.map_err(|e| format!("encode protocol execution profiles: {e}"))?;
self.attrs
.insert(ATTR_PROTOCOL_EXECUTION_PROFILES.to_string(), encoded);
Ok(())
}
#[must_use]
pub fn protocol_execution_profiles(&self) -> Vec<String> {
self.attrs
.get(ATTR_PROTOCOL_EXECUTION_PROFILES)
.and_then(|s| serde_json::from_str::<Vec<String>>(s).ok())
.unwrap_or_default()
}
pub fn set_agreement_profile_declarations(
&mut self,
decls: &[AgreementProfileDeclaration],
) -> Result<(), String> {
let encoded = serde_json::to_string(decls)
.map_err(|e| format!("encode agreement profile declarations: {e}"))?;
self.attrs
.insert(ATTR_AGREEMENT_PROFILE_DECLARATIONS.to_string(), encoded);
Ok(())
}
#[must_use]
pub fn agreement_profile_declarations(&self) -> Vec<AgreementProfileDeclaration> {
self.attrs
.get(ATTR_AGREEMENT_PROFILE_DECLARATIONS)
.and_then(|s| serde_json::from_str::<Vec<AgreementProfileDeclaration>>(s).ok())
.unwrap_or_default()
}
fn required_theorem_pack_capabilities(&self) -> BTreeSet<String> {
let required = self.required_theorem_packs();
let required_set: BTreeSet<&str> = required.iter().map(String::as_str).collect();
self.theorem_packs()
.into_iter()
.filter(|bundle| required_set.contains(bundle.name.as_str()))
.flat_map(|bundle| bundle.capabilities.into_iter())
.collect()
}
fn required_protocol_machine_core_capabilities(&self) -> BTreeSet<String> {
fn collect(protocol: &Protocol, out: &mut BTreeSet<String>) {
if let Some(cap) = protocol.get_annotations().custom("required_capability") {
out.insert(cap.to_string());
}
match protocol {
Protocol::Begin { continuation, .. }
| Protocol::Await { continuation, .. }
| Protocol::Resolve { continuation, .. }
| Protocol::Invalidate { continuation, .. }
| Protocol::Send { continuation, .. }
| Protocol::Broadcast { continuation, .. }
| Protocol::Extension { continuation, .. }
| Protocol::Publish { continuation, .. }
| Protocol::PublishAuthority { continuation, .. }
| Protocol::Materialize { continuation, .. }
| Protocol::Handoff { continuation, .. }
| Protocol::DependentWork { continuation, .. } => collect(continuation, out),
Protocol::Choice { branches, .. } => {
for branch in branches {
collect(&branch.protocol, out);
}
}
Protocol::Loop { body, .. } | Protocol::Rec { body, .. } => collect(body, out),
Protocol::Let { continuation, .. } => collect(continuation, out),
Protocol::Case { branches, .. } => {
for branch in branches {
collect(&branch.protocol, out);
}
}
Protocol::Timeout {
body,
on_timeout,
on_cancel,
..
} => {
collect(body, out);
collect(on_timeout, out);
if let Some(on_cancel) = on_cancel.as_deref() {
collect(on_cancel, out);
}
}
Protocol::Parallel { protocols } => {
for p in protocols {
collect(p, out);
}
}
Protocol::Var(_) | Protocol::End => {}
}
}
let mut out = BTreeSet::new();
collect(&self.protocol, &mut out);
out
}
}
impl ProgressAttachment {
#[must_use]
pub fn is_explicit(&self) -> bool {
self.requires_profile.is_some()
|| self.within_window.is_some()
|| self.on_timeout.is_some()
|| self.on_stall.is_some()
}
}
fn find_session_projection_blocker(protocol: &Protocol) -> Option<&'static str> {
match protocol {
Protocol::Begin { .. } => Some("begin requires protocol-machine commitment semantics"),
Protocol::Await { .. } => Some("await requires protocol-machine commitment semantics"),
Protocol::Resolve { .. } => Some("resolve requires protocol-machine commitment semantics"),
Protocol::Invalidate { .. } => {
Some("invalidate requires protocol-machine commitment semantics")
}
Protocol::Case { .. } => Some("authority-local case/of requires protocol-machine lowering"),
Protocol::Timeout { .. } => Some("timeout requires protocol-machine progress semantics"),
Protocol::Publish { .. } => Some("publish requires publication/materialization semantics"),
Protocol::PublishAuthority { .. } => {
Some("publish requires publication/materialization semantics")
}
Protocol::Materialize { .. } => {
Some("materialize requires publication/materialization semantics")
}
Protocol::Handoff { .. } => Some("handoff requires semantic handoff semantics"),
Protocol::DependentWork { .. } => {
Some("dependent work requires protocol-machine commitment semantics")
}
Protocol::Send { continuation, .. }
| Protocol::Broadcast { continuation, .. }
| Protocol::Let { continuation, .. }
| Protocol::Extension { continuation, .. } => find_session_projection_blocker(continuation),
Protocol::Choice { branches, .. } => branches
.iter()
.find_map(|branch| find_session_projection_blocker(&branch.protocol)),
Protocol::Loop { body, .. } | Protocol::Rec { body, .. } => {
find_session_projection_blocker(body)
}
Protocol::Parallel { protocols } => {
protocols.iter().find_map(find_session_projection_blocker)
}
Protocol::Var(_) | Protocol::End => None,
}
}
fn authority_metatheory_tier(protocol: &Protocol) -> AuthorityMetatheoryTier {
match protocol {
Protocol::Begin { .. }
| Protocol::Await { .. }
| Protocol::Resolve { .. }
| Protocol::Invalidate { .. }
| Protocol::Case { .. }
| Protocol::Timeout { .. }
| Protocol::Handoff { .. }
| Protocol::DependentWork { .. }
| Protocol::Parallel { .. }
| Protocol::Extension { .. } => AuthorityMetatheoryTier::RuntimeSemanticOnly,
Protocol::Publish { continuation, .. }
| Protocol::PublishAuthority { continuation, .. }
| Protocol::Materialize { continuation, .. } => authority_metatheory_tier(continuation)
.max(AuthorityMetatheoryTier::EvidencePublicationSemanticObjects),
Protocol::Let {
expr, continuation, ..
} => {
let expr_tier = match expr {
super::AuthorityExpr::Check { .. } | super::AuthorityExpr::Observe { .. } => {
AuthorityMetatheoryTier::EvidencePublicationSemanticObjects
}
super::AuthorityExpr::Transfer { .. } => {
AuthorityMetatheoryTier::RuntimeSemanticOnly
}
super::AuthorityExpr::Var(_)
| super::AuthorityExpr::Constructor { .. }
| super::AuthorityExpr::Call { .. } => {
AuthorityMetatheoryTier::SessionTypedCoordination
}
};
expr_tier.max(authority_metatheory_tier(continuation))
}
Protocol::Choice { branches, .. } => branches
.iter()
.map(|branch| {
let guard_tier = match branch.guard.as_ref() {
Some(ChoiceGuard::Evidence { .. }) => {
AuthorityMetatheoryTier::EvidencePublicationSemanticObjects
}
Some(ChoiceGuard::Predicate(_)) | None => {
AuthorityMetatheoryTier::SessionTypedCoordination
}
};
guard_tier.max(authority_metatheory_tier(&branch.protocol))
})
.max()
.unwrap_or(AuthorityMetatheoryTier::SessionTypedCoordination),
Protocol::Send { continuation, .. } | Protocol::Broadcast { continuation, .. } => {
authority_metatheory_tier(continuation)
}
Protocol::Loop { body, .. } | Protocol::Rec { body, .. } => authority_metatheory_tier(body),
Protocol::Var(_) | Protocol::End => AuthorityMetatheoryTier::SessionTypedCoordination,
}
}
fn authority_metatheory_blocker(protocol: &Protocol) -> Option<&'static str> {
match protocol {
Protocol::Begin { .. }
| Protocol::Await { .. }
| Protocol::Resolve { .. }
| Protocol::Invalidate { .. } => {
Some("explicit commitment lifecycle still relies on protocol-machine semantic obligations")
}
Protocol::Case { .. } => Some("authority-local case/of still relies on runtime semantic obligations"),
Protocol::Timeout { .. } => Some("timeout/cancellation still relies on runtime progress semantics"),
Protocol::Handoff { .. } => Some("semantic handoff is still justified through protocol-machine conservation rather than the small authority theorem slice"),
Protocol::DependentWork { .. } => Some("dependent work remains a protocol-machine semantic obligation"),
Protocol::Parallel { .. } => Some("parallel authority composition remains outside the supported authority theorem slice"),
Protocol::Extension { .. } => Some("extension dispatch remains outside the authority theorem slice"),
Protocol::Let { expr, continuation, .. } => match expr {
super::AuthorityExpr::Transfer { .. } => {
Some("transfer receipts remain part of the wider runtime authority lifecycle")
}
_ => authority_metatheory_blocker(continuation),
},
Protocol::Choice { branches, .. } => branches
.iter()
.find_map(|branch| authority_metatheory_blocker(&branch.protocol)),
Protocol::Send { continuation, .. }
| Protocol::Broadcast { continuation, .. }
| Protocol::Publish { continuation, .. }
| Protocol::PublishAuthority { continuation, .. }
| Protocol::Materialize { continuation, .. } => authority_metatheory_blocker(continuation),
Protocol::Loop { body, .. } | Protocol::Rec { body, .. } => authority_metatheory_blocker(body),
Protocol::Var(_) | Protocol::End => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use quote::format_ident;
#[test]
fn proof_bundle_metadata_roundtrip() {
let mut choreo = Choreography {
name: format_ident!("RoundTrip"),
namespace: None,
roles: Vec::new(),
protocol: Protocol::End,
attrs: HashMap::new(),
};
let bundles = vec![
TheoremPackDeclaration {
name: "Base".to_string(),
capabilities: vec!["delegation".to_string()],
version: None,
issuer: None,
constraints: Vec::new(),
},
TheoremPackDeclaration {
name: "Guard".to_string(),
capabilities: vec!["guard_tokens".to_string()],
version: None,
issuer: None,
constraints: Vec::new(),
},
];
let required = vec!["Base".to_string()];
choreo
.set_theorem_packs(&bundles)
.expect("set proof bundles");
choreo
.set_required_theorem_packs(&required)
.expect("set required bundles");
assert_eq!(choreo.theorem_packs(), bundles);
assert_eq!(choreo.required_theorem_packs(), required);
}
}