1use super::{ChoiceGuard, Protocol, Role, ValidationError};
4use proc_macro2::Ident;
5use serde::{Deserialize, Serialize};
6use std::collections::{BTreeSet, HashMap};
7
8const ATTR_THEOREM_PACKS: &str = "dsl.proof_bundles";
9const ATTR_REQUIRED_THEOREM_PACKS: &str = "dsl.required_proof_bundles";
10const ATTR_INFERRED_REQUIRED_THEOREM_PACKS: &str = "dsl.inferred_required_proof_bundles";
11const ATTR_ROLE_SETS: &str = "dsl.role_sets";
12const ATTR_TOPOLOGIES: &str = "dsl.topologies";
13const ATTR_TYPE_DECLARATIONS: &str = "dsl.type_decls";
14const ATTR_EFFECT_INTERFACE_DECLARATIONS: &str = "dsl.effect_decls";
15const ATTR_PROTOCOL_USES: &str = "dsl.protocol_uses";
16const ATTR_REGION_DECLARATIONS: &str = "dsl.fragment_decls";
17const ATTR_OPERATION_DECLARATIONS: &str = "dsl.operation_decls";
18const ATTR_GUEST_RUNTIME_DECLARATIONS: &str = "dsl.guest_runtime_decls";
19const ATTR_EXECUTION_PROFILE_DECLARATIONS: &str = "dsl.execution_profile_decls";
20const ATTR_PROTOCOL_EXECUTION_PROFILES: &str = "dsl.protocol_execution_profiles";
21const ATTR_AGREEMENT_PROFILE_DECLARATIONS: &str = "dsl.agreement_profile_decls";
22
23#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
25pub struct TheoremPackDeclaration {
26 pub name: String,
28 #[serde(default)]
30 pub capabilities: Vec<String>,
31 #[serde(default)]
33 pub version: Option<String>,
34 #[serde(default)]
36 pub issuer: Option<String>,
37 #[serde(default)]
39 pub constraints: Vec<String>,
40}
41
42#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
44pub struct RoleSetDeclaration {
45 pub name: String,
47 #[serde(default)]
49 pub members: Vec<String>,
50 #[serde(default)]
52 pub subset_of: Option<String>,
53 #[serde(default)]
55 pub subset_start: Option<u32>,
56 #[serde(default)]
58 pub subset_end: Option<u32>,
59}
60
61#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
63pub struct TopologyDeclaration {
64 pub kind: String,
66 pub name: String,
68 #[serde(default)]
70 pub members: Vec<String>,
71}
72
73#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
75pub struct TypeDeclaration {
76 pub name: String,
78 pub is_alias: bool,
80 #[serde(default)]
82 pub alias_of: Option<String>,
83 #[serde(default)]
85 pub constructors: Vec<TypeConstructorDeclaration>,
86}
87
88#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
90pub struct TypeConstructorDeclaration {
91 pub name: String,
93 #[serde(default)]
95 pub payload_type: Option<String>,
96}
97
98#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
100pub struct EffectInterfaceDeclaration {
101 pub name: String,
103 #[serde(default)]
105 pub operations: Vec<EffectOperationDeclaration>,
106}
107
108#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
110#[serde(rename_all = "snake_case")]
111pub enum EffectAuthorityClass {
112 Authoritative,
114 #[default]
116 Command,
117 Observe,
119}
120
121#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
123pub struct EffectOperationDeclaration {
124 pub name: String,
126 #[serde(default)]
128 pub authority_class: EffectAuthorityClass,
129 pub semantic_class: String,
131 pub progress: String,
133 pub region: String,
135 pub agreement_use: String,
137 pub reentrancy: String,
139 pub input_type: String,
141 pub output_type: String,
143}
144
145#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
147pub struct EffectContractDeclaration {
148 pub interface_name: String,
150 pub operation_name: String,
152 pub authority_class: EffectAuthorityClass,
154 pub semantic_class: String,
156 pub progress: String,
158 pub region: String,
160 pub agreement_use: String,
162 pub admissibility: String,
164 pub totality: String,
166 pub timeout_policy: String,
168 pub reentrancy_policy: String,
170 pub handler_domain: String,
172}
173
174#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
176pub struct RegionDeclaration {
177 pub name: String,
179 #[serde(default)]
181 pub params: Vec<String>,
182}
183
184#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
186pub struct OperationParameterDeclaration {
187 pub name: String,
189 pub type_name: String,
191}
192
193#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
195pub struct ProgressAttachment {
196 pub contract_name: String,
198 #[serde(default)]
200 pub requires_profile: Option<String>,
201 #[serde(default)]
203 pub within_window: Option<String>,
204 #[serde(default)]
206 pub on_timeout: Option<String>,
207 #[serde(default)]
209 pub on_stall: Option<String>,
210}
211
212#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
214pub struct AgreementProfileDeclaration {
215 pub name: String,
217 pub visibility: String,
219 pub rule: String,
221 pub usable_at: String,
223 pub finalized_at: String,
225 pub evidence: String,
227}
228
229#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
231pub struct OperationAgreementAttachment {
232 pub profile_name: String,
234 #[serde(default)]
236 pub prestate: Option<String>,
237}
238
239#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
241pub struct OperationDeclaration {
242 pub name: String,
244 #[serde(default)]
246 pub params: Vec<OperationParameterDeclaration>,
247 pub owner_role: String,
249 #[serde(default)]
251 pub within: Option<String>,
252 #[serde(default)]
254 pub progress_contract: Option<ProgressAttachment>,
255 #[serde(default)]
257 pub agreement: Option<OperationAgreementAttachment>,
258 #[serde(default)]
260 pub child_effect_aggregation: Option<ChildEffectAggregation>,
261 pub body_source: String,
263}
264
265#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
267pub enum ChildEffectAggregationPolicy {
268 All,
270 First,
272 Threshold {
274 required_successes: u64,
276 },
277}
278
279#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
281pub struct ChildEffectAggregation {
282 pub policy: ChildEffectAggregationPolicy,
284}
285
286impl ChildEffectAggregationPolicy {
287 #[must_use]
289 pub fn dsl_name(&self) -> String {
290 match self {
291 Self::All => "all_success".to_string(),
292 Self::First => "first_success".to_string(),
293 Self::Threshold { required_successes } => {
294 format!("threshold_success({required_successes})")
295 }
296 }
297 }
298}
299
300impl ChildEffectAggregation {
301 #[must_use]
303 pub fn dsl_name(&self) -> String {
304 self.policy.dsl_name()
305 }
306}
307
308#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
310pub struct GuestRuntimeDeclaration {
311 pub name: String,
313 #[serde(default)]
315 pub uses: Vec<String>,
316 pub entry: String,
318}
319
320#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
322pub struct ExecutionProfileDeclaration {
323 pub name: String,
325 #[serde(default)]
327 pub fairness: Option<String>,
328 #[serde(default)]
330 pub admissibility: Option<String>,
331 #[serde(default)]
333 pub escalation_window: Option<String>,
334}
335
336#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
338#[serde(rename_all = "snake_case")]
339pub enum LanguageTier {
340 FullSpec,
341 SessionProjectable,
342 ProtocolMachineExecutable,
343 ProofOnly,
344}
345
346#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
348pub struct LanguageTierStatus {
349 pub strongest_tier: LanguageTier,
350 pub session_projectable: bool,
351 pub protocol_machine_executable: bool,
352 pub proof_only: bool,
353 pub diagnostic: String,
354}
355
356#[derive(Debug)]
358pub struct Choreography {
359 pub name: Ident,
361 pub namespace: Option<String>,
363 pub roles: Vec<Role>,
365 pub protocol: Protocol,
367 pub attrs: HashMap<String, String>,
369}
370
371impl Choreography {
372 #[must_use]
374 pub fn qualified_name(&self) -> String {
375 match &self.namespace {
376 Some(ns) => format!("{}::{}", ns, self.name),
377 None => self.name.to_string(),
378 }
379 }
380
381 pub fn validate(&self) -> Result<(), ValidationError> {
388 for role in &self.roles {
390 if !self.protocol.mentions_role(role) {
391 return Err(ValidationError::UnusedRole(role.name().to_string()));
392 }
393 }
394
395 self.protocol.validate(&self.roles)?;
397 self.validate_proof_bundles()?;
398 self.validate_effect_surface()?;
399 self.validate_execution_profile_surface()?;
400 self.validate_operation_surface()?;
401
402 Ok(())
403 }
404
405 fn validate_proof_bundles(&self) -> Result<(), ValidationError> {
406 let bundles = self.theorem_packs();
407 let mut declared: BTreeSet<String> = BTreeSet::new();
408 for bundle in &bundles {
409 if !declared.insert(bundle.name.clone()) {
410 return Err(ValidationError::DuplicateProofBundle(bundle.name.clone()));
411 }
412 }
413
414 for required in self.required_theorem_packs() {
415 if !declared.contains(&required) {
416 return Err(ValidationError::MissingProofBundle(required));
417 }
418 }
419
420 let required_caps = self.required_theorem_pack_capabilities();
421 for capability in self.required_protocol_machine_core_capabilities() {
422 if !required_caps.contains(&capability) {
423 return Err(ValidationError::MissingCapability(capability));
424 }
425 }
426
427 Ok(())
428 }
429
430 fn validate_effect_surface(&self) -> Result<(), ValidationError> {
431 let mut effect_names = BTreeSet::new();
432 let mut effect_ops: HashMap<String, HashMap<String, EffectOperationDeclaration>> =
433 HashMap::new();
434 for effect in self.effect_interface_declarations() {
435 if !effect_names.insert(effect.name.clone()) {
436 return Err(ValidationError::ExtensionError(format!(
437 "duplicate effect interface declaration `{}`",
438 effect.name
439 )));
440 }
441 let mut ops = HashMap::new();
442 for op in effect.operations {
443 let semantic_class_ok = matches!(
444 op.semantic_class.as_str(),
445 "authoritative" | "observational" | "best_effort"
446 );
447 if !semantic_class_ok {
448 return Err(ValidationError::ExtensionError(format!(
449 "effect operation `{}.{}` has unsupported `class {}`",
450 effect.name, op.name, op.semantic_class
451 )));
452 }
453 let progress_ok = matches!(op.progress.as_str(), "immediate" | "may_block");
454 if !progress_ok {
455 return Err(ValidationError::ExtensionError(format!(
456 "effect operation `{}.{}` has unsupported `progress {}`",
457 effect.name, op.name, op.progress
458 )));
459 }
460 if !matches!(op.region.as_str(), "session" | "fragment" | "global") {
461 return Err(ValidationError::ExtensionError(format!(
462 "effect operation `{}.{}` has unsupported `region {}`",
463 effect.name, op.name, op.region
464 )));
465 }
466 if !matches!(op.agreement_use.as_str(), "required" | "none" | "forbidden") {
467 return Err(ValidationError::ExtensionError(format!(
468 "effect operation `{}.{}` has unsupported `agreement_use {}`",
469 effect.name, op.name, op.agreement_use
470 )));
471 }
472 if !matches!(
473 op.reentrancy.as_str(),
474 "allow" | "reject_same_operation" | "reject_same_fragment"
475 ) {
476 return Err(ValidationError::ExtensionError(format!(
477 "effect operation `{}.{}` has unsupported `reentrancy {}`",
478 effect.name, op.name, op.reentrancy
479 )));
480 }
481 if matches!(op.authority_class, EffectAuthorityClass::Observe)
482 && op.semantic_class != "observational"
483 {
484 return Err(ValidationError::ExtensionError(format!(
485 "effect operation `{}.{}` is observational and must declare `class : observational`",
486 effect.name, op.name
487 )));
488 }
489 if matches!(op.authority_class, EffectAuthorityClass::Authoritative)
490 && op.semantic_class != "authoritative"
491 {
492 return Err(ValidationError::ExtensionError(format!(
493 "effect operation `{}.{}` is authoritative and must declare `class : authoritative`",
494 effect.name, op.name
495 )));
496 }
497 if op.progress == "immediate"
498 && matches!(op.authority_class, EffectAuthorityClass::Authoritative)
499 {
500 return Err(ValidationError::ExtensionError(format!(
501 "effect operation `{}.{}` may not be both `authoritative` and `progress immediate`",
502 effect.name, op.name
503 )));
504 }
505 if op.agreement_use == "required"
506 && matches!(op.authority_class, EffectAuthorityClass::Observe)
507 {
508 return Err(ValidationError::ExtensionError(format!(
509 "effect operation `{}.{}` may not require agreement use on an observational surface",
510 effect.name, op.name
511 )));
512 }
513 if ops.insert(op.name.clone(), op.clone()).is_some() {
514 return Err(ValidationError::ExtensionError(format!(
515 "duplicate effect operation `{}.{}`",
516 effect.name, op.name
517 )));
518 }
519 }
520 effect_ops.insert(effect.name, ops);
521 }
522
523 let declared = effect_names;
524 let used: BTreeSet<String> = self.protocol_uses().into_iter().collect();
525 for effect in &used {
526 if !declared.contains(effect) {
527 return Err(ValidationError::ExtensionError(format!(
528 "protocol uses undeclared effect interface `{effect}`"
529 )));
530 }
531 }
532
533 fn validate_expr(
534 expr: &super::AuthorityExpr,
535 effect_ops: &HashMap<String, HashMap<String, EffectOperationDeclaration>>,
536 used: &BTreeSet<String>,
537 ) -> Result<(), ValidationError> {
538 match expr {
539 super::AuthorityExpr::Check {
540 effect, operation, ..
541 } => {
542 if !used.contains(effect) {
543 return Err(ValidationError::ExtensionError(format!(
544 "effect invocation `{effect}.{operation}` is not allowed without `uses {effect}`"
545 )));
546 }
547 let Some(ops) = effect_ops.get(effect) else {
548 return Err(ValidationError::ExtensionError(format!(
549 "effect invocation references undeclared interface `{effect}`"
550 )));
551 };
552 let Some(op_decl) = ops.get(operation) else {
553 return Err(ValidationError::ExtensionError(format!(
554 "effect invocation references undeclared operation `{effect}.{operation}`"
555 )));
556 };
557 if matches!(op_decl.authority_class, EffectAuthorityClass::Observe) {
558 return Err(ValidationError::ExtensionError(format!(
559 "effect invocation `{effect}.{operation}` is observational and may not be invoked with `check`"
560 )));
561 }
562 Ok(())
563 }
564 super::AuthorityExpr::Observe {
565 effect, operation, ..
566 } => {
567 if !used.contains(effect) {
568 return Err(ValidationError::ExtensionError(format!(
569 "effect invocation `{effect}.{operation}` is not allowed without `uses {effect}`"
570 )));
571 }
572 let Some(ops) = effect_ops.get(effect) else {
573 return Err(ValidationError::ExtensionError(format!(
574 "effect invocation references undeclared interface `{effect}`"
575 )));
576 };
577 let Some(op_decl) = ops.get(operation) else {
578 return Err(ValidationError::ExtensionError(format!(
579 "effect invocation references undeclared operation `{effect}.{operation}`"
580 )));
581 };
582 if !matches!(op_decl.authority_class, EffectAuthorityClass::Observe) {
583 return Err(ValidationError::ExtensionError(format!(
584 "effect invocation `{effect}.{operation}` is not observational and may not be invoked with `observe`"
585 )));
586 }
587 Ok(())
588 }
589 super::AuthorityExpr::Var(_)
590 | super::AuthorityExpr::Transfer { .. }
591 | super::AuthorityExpr::Constructor { .. }
592 | super::AuthorityExpr::Call { .. } => Ok(()),
593 }
594 }
595
596 fn validate_protocol_effects(
597 protocol: &Protocol,
598 effect_ops: &HashMap<String, HashMap<String, EffectOperationDeclaration>>,
599 used: &BTreeSet<String>,
600 ) -> Result<(), ValidationError> {
601 match protocol {
602 Protocol::Begin { continuation, .. }
603 | Protocol::Await { continuation, .. }
604 | Protocol::Resolve { continuation, .. }
605 | Protocol::Invalidate { continuation, .. }
606 | Protocol::Send { continuation, .. }
607 | Protocol::Broadcast { continuation, .. }
608 | Protocol::Extension { continuation, .. }
609 | Protocol::Let { continuation, .. }
610 | Protocol::Publish { continuation, .. }
611 | Protocol::PublishAuthority { continuation, .. }
612 | Protocol::Materialize { continuation, .. }
613 | Protocol::Handoff { continuation, .. }
614 | Protocol::DependentWork { continuation, .. } => {
615 if let Protocol::Let { mode, expr, .. } = protocol {
616 validate_expr(expr, effect_ops, used)?;
617 match (mode, expr) {
618 (
619 super::AuthorityBindingMode::Plain,
620 super::AuthorityExpr::Check {
621 effect, operation, ..
622 },
623 ) => {
624 let op_decl = effect_ops
625 .get(effect)
626 .and_then(|ops| ops.get(operation))
627 .expect("validated effect operation should exist");
628 if matches!(
629 op_decl.authority_class,
630 EffectAuthorityClass::Authoritative
631 ) {
632 return Err(ValidationError::ExtensionError(format!(
633 "authoritative effect invocation `{effect}.{operation}` must bind through `authoritative let`"
634 )));
635 }
636 }
637 (
638 super::AuthorityBindingMode::Plain,
639 super::AuthorityExpr::Observe { .. },
640 ) => {
641 return Err(ValidationError::ExtensionError(
642 "`observe` expressions must bind through `observe let`"
643 .to_string(),
644 ));
645 }
646 (
647 super::AuthorityBindingMode::Authoritative,
648 super::AuthorityExpr::Check {
649 effect, operation, ..
650 },
651 ) => {
652 let op_decl = effect_ops
653 .get(effect)
654 .and_then(|ops| ops.get(operation))
655 .expect("validated effect operation should exist");
656 if !matches!(
657 op_decl.authority_class,
658 EffectAuthorityClass::Authoritative
659 ) {
660 return Err(ValidationError::ExtensionError(format!(
661 "`authoritative let` may only bind authoritative effect invocations; `{effect}.{operation}` is {:?}",
662 op_decl.authority_class
663 )));
664 }
665 }
666 (
667 super::AuthorityBindingMode::Observe,
668 super::AuthorityExpr::Observe { .. },
669 ) => {}
670 (super::AuthorityBindingMode::Plain, _) => {}
671 (super::AuthorityBindingMode::Authoritative, _) => {
672 return Err(ValidationError::ExtensionError(
673 "`authoritative let` must bind a `check` expression"
674 .to_string(),
675 ));
676 }
677 (super::AuthorityBindingMode::Observe, _) => {
678 return Err(ValidationError::ExtensionError(
679 "`observe let` must bind an `observe` expression".to_string(),
680 ));
681 }
682 }
683 }
684 validate_protocol_effects(continuation, effect_ops, used)
685 }
686 Protocol::Choice { branches, .. } => {
687 for branch in branches {
688 if let Some(ChoiceGuard::Evidence {
689 effect, operation, ..
690 }) = &branch.guard
691 {
692 if !used.contains(effect) {
693 return Err(ValidationError::ExtensionError(format!(
694 "effect guard `{effect}.{operation}` is not allowed without `uses {effect}`"
695 )));
696 }
697 let Some(ops) = effect_ops.get(effect) else {
698 return Err(ValidationError::ExtensionError(format!(
699 "effect guard references undeclared interface `{effect}`"
700 )));
701 };
702 let Some(op_decl) = ops.get(operation) else {
703 return Err(ValidationError::ExtensionError(format!(
704 "effect guard references undeclared operation `{effect}.{operation}`"
705 )));
706 };
707 if matches!(op_decl.authority_class, EffectAuthorityClass::Observe) {
708 return Err(ValidationError::ExtensionError(format!(
709 "effect guard `{effect}.{operation}` is observational and may not be invoked with `check`"
710 )));
711 }
712 }
713 validate_protocol_effects(&branch.protocol, effect_ops, used)?;
714 }
715 Ok(())
716 }
717 Protocol::Case { expr, branches } => {
718 validate_expr(expr, effect_ops, used)?;
719 for branch in branches {
720 validate_protocol_effects(&branch.protocol, effect_ops, used)?;
721 }
722 Ok(())
723 }
724 Protocol::Timeout {
725 body,
726 on_timeout,
727 on_cancel,
728 ..
729 } => {
730 validate_protocol_effects(body, effect_ops, used)?;
731 validate_protocol_effects(on_timeout, effect_ops, used)?;
732 if let Some(on_cancel) = on_cancel.as_deref() {
733 validate_protocol_effects(on_cancel, effect_ops, used)?;
734 }
735 Ok(())
736 }
737 Protocol::Loop { body, .. } | Protocol::Rec { body, .. } => {
738 validate_protocol_effects(body, effect_ops, used)
739 }
740 Protocol::Parallel { protocols } => {
741 for protocol in protocols {
742 validate_protocol_effects(protocol, effect_ops, used)?;
743 }
744 Ok(())
745 }
746 Protocol::Var(_) | Protocol::End => Ok(()),
747 }
748 }
749
750 validate_protocol_effects(&self.protocol, &effect_ops, &used)
751 }
752
753 fn validate_execution_profile_surface(&self) -> Result<(), ValidationError> {
754 let mut declared = BTreeSet::new();
755 for profile in self.execution_profile_declarations() {
756 if !declared.insert(profile.name.clone()) {
757 return Err(ValidationError::ExtensionError(format!(
758 "duplicate execution profile declaration `{}`",
759 profile.name
760 )));
761 }
762 }
763
764 for profile in self.protocol_execution_profiles() {
765 if !declared.contains(&profile) {
766 return Err(ValidationError::ExtensionError(format!(
767 "protocol references undeclared execution profile `{profile}`"
768 )));
769 }
770 }
771
772 Ok(())
773 }
774
775 fn validate_operation_surface(&self) -> Result<(), ValidationError> {
776 let declared_agreement_profiles = self
777 .agreement_profile_declarations()
778 .into_iter()
779 .map(|profile| profile.name)
780 .collect::<BTreeSet<_>>();
781
782 for operation in self.operation_declarations() {
783 if operation.progress_contract.is_none() {
784 return Err(ValidationError::ExtensionError(format!(
785 "operation `{}` is parity-critical and must declare a progress contract",
786 operation.name
787 )));
788 }
789 if !operation
790 .progress_contract
791 .as_ref()
792 .is_some_and(|progress| progress.is_explicit())
793 {
794 return Err(ValidationError::ExtensionError(format!(
795 "operation `{}` must declare explicit progress metadata using `requires`, `within`, `on timeout`, or `on stall`",
796 operation.name
797 )));
798 }
799 let Some(agreement) = operation.agreement.as_ref() else {
800 return Err(ValidationError::ExtensionError(format!(
801 "operation `{}` must attach a named agreement profile",
802 operation.name
803 )));
804 };
805 if !declared_agreement_profiles.contains(&agreement.profile_name) {
806 return Err(ValidationError::ExtensionError(format!(
807 "operation `{}` references undeclared agreement profile `{}`",
808 operation.name, agreement.profile_name
809 )));
810 }
811 }
812 Ok(())
813 }
814
815 #[must_use]
816 pub fn language_tier_status(&self) -> LanguageTierStatus {
817 let session_blocker = find_session_projection_blocker(&self.protocol);
818 let missing_progress = self
819 .operation_declarations()
820 .iter()
821 .find(|operation| {
822 operation.progress_contract.is_none()
823 || !operation
824 .progress_contract
825 .as_ref()
826 .is_some_and(|progress| progress.is_explicit())
827 })
828 .map(|operation| {
829 format!(
830 "operation `{}` is missing the required progress contract",
831 operation.name
832 )
833 });
834
835 let protocol_machine_executable = missing_progress.is_none();
836 let session_projectable = session_blocker.is_none();
837 let strongest_tier = match (session_projectable, protocol_machine_executable) {
838 (true, true) => LanguageTier::SessionProjectable,
839 (false, true) => LanguageTier::ProtocolMachineExecutable,
840 (_, false) => LanguageTier::ProofOnly,
841 };
842 let diagnostic = if let Some(blocker) = session_blocker {
843 format!(
844 "full spec is valid, protocol-machine execution is available, but session projection is blocked: {blocker}"
845 )
846 } else if let Some(missing_progress) = missing_progress {
847 format!(
848 "full spec is valid for proof analysis only; protocol-machine execution is blocked: {missing_progress}"
849 )
850 } else {
851 "full spec is valid, protocol-machine execution is available, and the protocol is session-projectable".to_string()
852 };
853
854 LanguageTierStatus {
855 strongest_tier,
856 session_projectable,
857 protocol_machine_executable,
858 proof_only: matches!(strongest_tier, LanguageTier::ProofOnly),
859 diagnostic,
860 }
861 }
862
863 #[must_use]
865 pub fn get_attributes(&self) -> &HashMap<String, String> {
866 &self.attrs
867 }
868
869 pub fn get_attributes_mut(&mut self) -> &mut HashMap<String, String> {
871 &mut self.attrs
872 }
873
874 #[must_use]
876 pub fn get_attribute(&self, key: &str) -> Option<&String> {
877 self.attrs.get(key)
878 }
879
880 pub fn set_attribute(&mut self, key: String, value: String) {
882 self.attrs.insert(key, value);
883 }
884
885 pub fn remove_attribute(&mut self, key: &str) -> Option<String> {
887 self.attrs.remove(key)
888 }
889
890 #[must_use]
892 pub fn has_attribute(&self, key: &str) -> bool {
893 self.attrs.contains_key(key)
894 }
895
896 pub fn get_attribute_as<T>(&self, key: &str) -> Option<T>
898 where
899 T: std::str::FromStr,
900 {
901 self.get_attribute(key)?.parse().ok()
902 }
903
904 pub fn get_attribute_as_bool(&self, key: &str) -> Option<bool> {
906 let value = self.get_attribute(key)?;
907 match value.to_lowercase().as_str() {
908 "true" | "1" | "yes" | "on" => Some(true),
909 "false" | "0" | "no" | "off" => Some(false),
910 _ => None,
911 }
912 }
913
914 pub fn clear_attributes(&mut self) {
916 self.attrs.clear();
917 }
918
919 pub fn attribute_count(&self) -> usize {
921 self.attrs.len()
922 }
923
924 pub fn attribute_keys(&self) -> Vec<&String> {
926 self.attrs.keys().collect()
927 }
928
929 pub fn validate_required_attributes(&self, required_keys: &[&str]) -> Result<(), Vec<String>> {
931 let missing: Vec<String> = required_keys
932 .iter()
933 .filter(|&key| !self.has_attribute(key))
934 .map(|&key| key.to_string())
935 .collect();
936
937 if missing.is_empty() {
938 Ok(())
939 } else {
940 Err(missing)
941 }
942 }
943
944 pub fn find_nodes_with_annotation(&self, key: &str) -> Vec<&Protocol> {
946 let mut nodes = Vec::new();
947 self.protocol.collect_nodes_with_annotation(key, &mut nodes);
948 nodes
949 }
950
951 pub fn find_nodes_with_annotation_value(&self, key: &str, value: &str) -> Vec<&Protocol> {
953 let mut nodes = Vec::new();
954 self.protocol
955 .collect_nodes_with_annotation_value(key, value, &mut nodes);
956 nodes
957 }
958
959 pub fn total_annotation_count(&self) -> usize {
961 self.attribute_count() + self.protocol.deep_annotation_count()
962 }
963
964 pub fn set_theorem_packs(
966 &mut self,
967 theorem_packs: &[TheoremPackDeclaration],
968 ) -> Result<(), String> {
969 let encoded = serde_json::to_string(theorem_packs)
970 .map_err(|e| format!("encode theorem packs: {e}"))?;
971 self.attrs.insert(ATTR_THEOREM_PACKS.to_string(), encoded);
972 Ok(())
973 }
974
975 #[must_use]
977 pub fn theorem_packs(&self) -> Vec<TheoremPackDeclaration> {
978 self.attrs
979 .get(ATTR_THEOREM_PACKS)
980 .and_then(|s| serde_json::from_str::<Vec<TheoremPackDeclaration>>(s).ok())
981 .unwrap_or_default()
982 }
983
984 pub fn set_required_theorem_packs(&mut self, required: &[String]) -> Result<(), String> {
986 let encoded = serde_json::to_string(required)
987 .map_err(|e| format!("encode required theorem packs: {e}"))?;
988 self.attrs
989 .insert(ATTR_REQUIRED_THEOREM_PACKS.to_string(), encoded);
990 Ok(())
991 }
992
993 #[must_use]
995 pub fn required_theorem_packs(&self) -> Vec<String> {
996 self.attrs
997 .get(ATTR_REQUIRED_THEOREM_PACKS)
998 .and_then(|s| serde_json::from_str::<Vec<String>>(s).ok())
999 .unwrap_or_default()
1000 }
1001
1002 pub fn set_inferred_required_theorem_packs(
1004 &mut self,
1005 required: &[String],
1006 ) -> Result<(), String> {
1007 let encoded = serde_json::to_string(required)
1008 .map_err(|e| format!("encode inferred theorem packs: {e}"))?;
1009 self.attrs
1010 .insert(ATTR_INFERRED_REQUIRED_THEOREM_PACKS.to_string(), encoded);
1011 Ok(())
1012 }
1013
1014 #[must_use]
1016 pub fn inferred_required_theorem_packs(&self) -> Vec<String> {
1017 self.attrs
1018 .get(ATTR_INFERRED_REQUIRED_THEOREM_PACKS)
1019 .and_then(|s| serde_json::from_str::<Vec<String>>(s).ok())
1020 .unwrap_or_default()
1021 }
1022
1023 pub fn set_role_sets(&mut self, role_sets: &[RoleSetDeclaration]) -> Result<(), String> {
1025 let encoded =
1026 serde_json::to_string(role_sets).map_err(|e| format!("encode role sets: {e}"))?;
1027 self.attrs.insert(ATTR_ROLE_SETS.to_string(), encoded);
1028 Ok(())
1029 }
1030
1031 #[must_use]
1033 pub fn role_sets(&self) -> Vec<RoleSetDeclaration> {
1034 self.attrs
1035 .get(ATTR_ROLE_SETS)
1036 .and_then(|s| serde_json::from_str::<Vec<RoleSetDeclaration>>(s).ok())
1037 .unwrap_or_default()
1038 }
1039
1040 pub fn set_topologies(&mut self, topologies: &[TopologyDeclaration]) -> Result<(), String> {
1042 let encoded =
1043 serde_json::to_string(topologies).map_err(|e| format!("encode topologies: {e}"))?;
1044 self.attrs.insert(ATTR_TOPOLOGIES.to_string(), encoded);
1045 Ok(())
1046 }
1047
1048 #[must_use]
1050 pub fn topologies(&self) -> Vec<TopologyDeclaration> {
1051 self.attrs
1052 .get(ATTR_TOPOLOGIES)
1053 .and_then(|s| serde_json::from_str::<Vec<TopologyDeclaration>>(s).ok())
1054 .unwrap_or_default()
1055 }
1056
1057 pub fn set_type_declarations(&mut self, decls: &[TypeDeclaration]) -> Result<(), String> {
1059 let encoded =
1060 serde_json::to_string(decls).map_err(|e| format!("encode type declarations: {e}"))?;
1061 self.attrs
1062 .insert(ATTR_TYPE_DECLARATIONS.to_string(), encoded);
1063 Ok(())
1064 }
1065
1066 #[must_use]
1068 pub fn type_declarations(&self) -> Vec<TypeDeclaration> {
1069 self.attrs
1070 .get(ATTR_TYPE_DECLARATIONS)
1071 .and_then(|s| serde_json::from_str::<Vec<TypeDeclaration>>(s).ok())
1072 .unwrap_or_default()
1073 }
1074
1075 pub fn set_effect_interface_declarations(
1077 &mut self,
1078 decls: &[EffectInterfaceDeclaration],
1079 ) -> Result<(), String> {
1080 let encoded =
1081 serde_json::to_string(decls).map_err(|e| format!("encode effect declarations: {e}"))?;
1082 self.attrs
1083 .insert(ATTR_EFFECT_INTERFACE_DECLARATIONS.to_string(), encoded);
1084 Ok(())
1085 }
1086
1087 #[must_use]
1089 pub fn effect_interface_declarations(&self) -> Vec<EffectInterfaceDeclaration> {
1090 self.attrs
1091 .get(ATTR_EFFECT_INTERFACE_DECLARATIONS)
1092 .and_then(|s| serde_json::from_str::<Vec<EffectInterfaceDeclaration>>(s).ok())
1093 .unwrap_or_default()
1094 }
1095
1096 #[must_use]
1099 pub fn effect_contract_declarations(&self) -> Vec<EffectContractDeclaration> {
1100 let used: BTreeSet<String> = self.protocol_uses().into_iter().collect();
1101 self.effect_interface_declarations()
1102 .into_iter()
1103 .flat_map(|effect| {
1104 let is_used = used.contains(effect.name.as_str());
1105 effect.operations.into_iter().map(move |op| {
1106 let admissibility = if is_used {
1107 "declared_use_only"
1108 } else {
1109 "internal_only"
1110 };
1111 let (totality, timeout_policy) = match op.progress.as_str() {
1112 "immediate" => ("immediate", "none"),
1113 "may_block" => ("may_block", "required"),
1114 _ => ("may_block", "required"),
1115 };
1116 let handler_domain = if is_used { "external" } else { "internal" };
1117 EffectContractDeclaration {
1118 interface_name: effect.name.clone(),
1119 operation_name: op.name,
1120 authority_class: op.authority_class,
1121 semantic_class: op.semantic_class.clone(),
1122 progress: op.progress.clone(),
1123 region: op.region.clone(),
1124 agreement_use: op.agreement_use.clone(),
1125 admissibility: admissibility.to_string(),
1126 totality: totality.to_string(),
1127 timeout_policy: timeout_policy.to_string(),
1128 reentrancy_policy: op.reentrancy,
1129 handler_domain: handler_domain.to_string(),
1130 }
1131 })
1132 })
1133 .collect()
1134 }
1135
1136 pub fn set_protocol_uses(&mut self, uses: &[String]) -> Result<(), String> {
1138 let encoded =
1139 serde_json::to_string(uses).map_err(|e| format!("encode protocol uses: {e}"))?;
1140 self.attrs.insert(ATTR_PROTOCOL_USES.to_string(), encoded);
1141 Ok(())
1142 }
1143
1144 #[must_use]
1146 pub fn protocol_uses(&self) -> Vec<String> {
1147 self.attrs
1148 .get(ATTR_PROTOCOL_USES)
1149 .and_then(|s| serde_json::from_str::<Vec<String>>(s).ok())
1150 .unwrap_or_default()
1151 }
1152
1153 pub fn set_region_declarations(&mut self, decls: &[RegionDeclaration]) -> Result<(), String> {
1155 let encoded =
1156 serde_json::to_string(decls).map_err(|e| format!("encode region declarations: {e}"))?;
1157 self.attrs
1158 .insert(ATTR_REGION_DECLARATIONS.to_string(), encoded);
1159 Ok(())
1160 }
1161
1162 #[must_use]
1164 pub fn region_declarations(&self) -> Vec<RegionDeclaration> {
1165 self.attrs
1166 .get(ATTR_REGION_DECLARATIONS)
1167 .and_then(|s| serde_json::from_str::<Vec<RegionDeclaration>>(s).ok())
1168 .unwrap_or_default()
1169 }
1170
1171 pub fn set_operation_declarations(
1173 &mut self,
1174 decls: &[OperationDeclaration],
1175 ) -> Result<(), String> {
1176 let encoded = serde_json::to_string(decls)
1177 .map_err(|e| format!("encode operation declarations: {e}"))?;
1178 self.attrs
1179 .insert(ATTR_OPERATION_DECLARATIONS.to_string(), encoded);
1180 Ok(())
1181 }
1182
1183 #[must_use]
1185 pub fn operation_declarations(&self) -> Vec<OperationDeclaration> {
1186 self.attrs
1187 .get(ATTR_OPERATION_DECLARATIONS)
1188 .and_then(|s| serde_json::from_str::<Vec<OperationDeclaration>>(s).ok())
1189 .unwrap_or_default()
1190 }
1191
1192 pub fn set_guest_runtime_declarations(
1194 &mut self,
1195 decls: &[GuestRuntimeDeclaration],
1196 ) -> Result<(), String> {
1197 let encoded = serde_json::to_string(decls)
1198 .map_err(|e| format!("encode guest runtime declarations: {e}"))?;
1199 self.attrs
1200 .insert(ATTR_GUEST_RUNTIME_DECLARATIONS.to_string(), encoded);
1201 Ok(())
1202 }
1203
1204 #[must_use]
1206 pub fn guest_runtime_declarations(&self) -> Vec<GuestRuntimeDeclaration> {
1207 self.attrs
1208 .get(ATTR_GUEST_RUNTIME_DECLARATIONS)
1209 .and_then(|s| serde_json::from_str::<Vec<GuestRuntimeDeclaration>>(s).ok())
1210 .unwrap_or_default()
1211 }
1212
1213 pub fn set_execution_profile_declarations(
1215 &mut self,
1216 decls: &[ExecutionProfileDeclaration],
1217 ) -> Result<(), String> {
1218 let encoded = serde_json::to_string(decls)
1219 .map_err(|e| format!("encode execution profile declarations: {e}"))?;
1220 self.attrs
1221 .insert(ATTR_EXECUTION_PROFILE_DECLARATIONS.to_string(), encoded);
1222 Ok(())
1223 }
1224
1225 #[must_use]
1227 pub fn execution_profile_declarations(&self) -> Vec<ExecutionProfileDeclaration> {
1228 self.attrs
1229 .get(ATTR_EXECUTION_PROFILE_DECLARATIONS)
1230 .and_then(|s| serde_json::from_str::<Vec<ExecutionProfileDeclaration>>(s).ok())
1231 .unwrap_or_default()
1232 }
1233
1234 pub fn set_protocol_execution_profiles(&mut self, profiles: &[String]) -> Result<(), String> {
1236 let encoded = serde_json::to_string(profiles)
1237 .map_err(|e| format!("encode protocol execution profiles: {e}"))?;
1238 self.attrs
1239 .insert(ATTR_PROTOCOL_EXECUTION_PROFILES.to_string(), encoded);
1240 Ok(())
1241 }
1242
1243 #[must_use]
1245 pub fn protocol_execution_profiles(&self) -> Vec<String> {
1246 self.attrs
1247 .get(ATTR_PROTOCOL_EXECUTION_PROFILES)
1248 .and_then(|s| serde_json::from_str::<Vec<String>>(s).ok())
1249 .unwrap_or_default()
1250 }
1251
1252 pub fn set_agreement_profile_declarations(
1254 &mut self,
1255 decls: &[AgreementProfileDeclaration],
1256 ) -> Result<(), String> {
1257 let encoded = serde_json::to_string(decls)
1258 .map_err(|e| format!("encode agreement profile declarations: {e}"))?;
1259 self.attrs
1260 .insert(ATTR_AGREEMENT_PROFILE_DECLARATIONS.to_string(), encoded);
1261 Ok(())
1262 }
1263
1264 #[must_use]
1266 pub fn agreement_profile_declarations(&self) -> Vec<AgreementProfileDeclaration> {
1267 self.attrs
1268 .get(ATTR_AGREEMENT_PROFILE_DECLARATIONS)
1269 .and_then(|s| serde_json::from_str::<Vec<AgreementProfileDeclaration>>(s).ok())
1270 .unwrap_or_default()
1271 }
1272
1273 fn required_theorem_pack_capabilities(&self) -> BTreeSet<String> {
1274 let required = self.required_theorem_packs();
1275 let required_set: BTreeSet<&str> = required.iter().map(String::as_str).collect();
1276 self.theorem_packs()
1277 .into_iter()
1278 .filter(|bundle| required_set.contains(bundle.name.as_str()))
1279 .flat_map(|bundle| bundle.capabilities.into_iter())
1280 .collect()
1281 }
1282
1283 fn required_protocol_machine_core_capabilities(&self) -> BTreeSet<String> {
1284 fn collect(protocol: &Protocol, out: &mut BTreeSet<String>) {
1285 if let Some(cap) = protocol.get_annotations().custom("required_capability") {
1286 out.insert(cap.to_string());
1287 }
1288 match protocol {
1289 Protocol::Begin { continuation, .. }
1290 | Protocol::Await { continuation, .. }
1291 | Protocol::Resolve { continuation, .. }
1292 | Protocol::Invalidate { continuation, .. }
1293 | Protocol::Send { continuation, .. }
1294 | Protocol::Broadcast { continuation, .. }
1295 | Protocol::Extension { continuation, .. }
1296 | Protocol::Publish { continuation, .. }
1297 | Protocol::PublishAuthority { continuation, .. }
1298 | Protocol::Materialize { continuation, .. }
1299 | Protocol::Handoff { continuation, .. }
1300 | Protocol::DependentWork { continuation, .. } => collect(continuation, out),
1301 Protocol::Choice { branches, .. } => {
1302 for branch in branches {
1303 collect(&branch.protocol, out);
1304 }
1305 }
1306 Protocol::Loop { body, .. } | Protocol::Rec { body, .. } => collect(body, out),
1307 Protocol::Let { continuation, .. } => collect(continuation, out),
1308 Protocol::Case { branches, .. } => {
1309 for branch in branches {
1310 collect(&branch.protocol, out);
1311 }
1312 }
1313 Protocol::Timeout {
1314 body,
1315 on_timeout,
1316 on_cancel,
1317 ..
1318 } => {
1319 collect(body, out);
1320 collect(on_timeout, out);
1321 if let Some(on_cancel) = on_cancel.as_deref() {
1322 collect(on_cancel, out);
1323 }
1324 }
1325 Protocol::Parallel { protocols } => {
1326 for p in protocols {
1327 collect(p, out);
1328 }
1329 }
1330 Protocol::Var(_) | Protocol::End => {}
1331 }
1332 }
1333
1334 let mut out = BTreeSet::new();
1335 collect(&self.protocol, &mut out);
1336 out
1337 }
1338}
1339
1340impl ProgressAttachment {
1341 #[must_use]
1343 pub fn is_explicit(&self) -> bool {
1344 self.requires_profile.is_some()
1345 || self.within_window.is_some()
1346 || self.on_timeout.is_some()
1347 || self.on_stall.is_some()
1348 }
1349}
1350
1351fn find_session_projection_blocker(protocol: &Protocol) -> Option<&'static str> {
1352 match protocol {
1353 Protocol::Begin { .. } => Some("begin requires protocol-machine commitment semantics"),
1354 Protocol::Await { .. } => Some("await requires protocol-machine commitment semantics"),
1355 Protocol::Resolve { .. } => Some("resolve requires protocol-machine commitment semantics"),
1356 Protocol::Invalidate { .. } => {
1357 Some("invalidate requires protocol-machine commitment semantics")
1358 }
1359 Protocol::Case { .. } => Some("authority-local case/of requires protocol-machine lowering"),
1360 Protocol::Timeout { .. } => Some("timeout requires protocol-machine progress semantics"),
1361 Protocol::Publish { .. } => Some("publish requires publication/materialization semantics"),
1362 Protocol::PublishAuthority { .. } => {
1363 Some("publish requires publication/materialization semantics")
1364 }
1365 Protocol::Materialize { .. } => {
1366 Some("materialize requires publication/materialization semantics")
1367 }
1368 Protocol::Handoff { .. } => Some("handoff requires semantic handoff semantics"),
1369 Protocol::DependentWork { .. } => {
1370 Some("dependent work requires protocol-machine commitment semantics")
1371 }
1372 Protocol::Send { continuation, .. }
1373 | Protocol::Broadcast { continuation, .. }
1374 | Protocol::Let { continuation, .. }
1375 | Protocol::Extension { continuation, .. } => find_session_projection_blocker(continuation),
1376 Protocol::Choice { branches, .. } => branches
1377 .iter()
1378 .find_map(|branch| find_session_projection_blocker(&branch.protocol)),
1379 Protocol::Loop { body, .. } | Protocol::Rec { body, .. } => {
1380 find_session_projection_blocker(body)
1381 }
1382 Protocol::Parallel { protocols } => {
1383 protocols.iter().find_map(find_session_projection_blocker)
1384 }
1385 Protocol::Var(_) | Protocol::End => None,
1386 }
1387}
1388
1389#[cfg(test)]
1390mod tests {
1391 use super::*;
1392 use quote::format_ident;
1393
1394 #[test]
1395 fn proof_bundle_metadata_roundtrip() {
1396 let mut choreo = Choreography {
1397 name: format_ident!("RoundTrip"),
1398 namespace: None,
1399 roles: Vec::new(),
1400 protocol: Protocol::End,
1401 attrs: HashMap::new(),
1402 };
1403 let bundles = vec![
1404 TheoremPackDeclaration {
1405 name: "Base".to_string(),
1406 capabilities: vec!["delegation".to_string()],
1407 version: None,
1408 issuer: None,
1409 constraints: Vec::new(),
1410 },
1411 TheoremPackDeclaration {
1412 name: "Guard".to_string(),
1413 capabilities: vec!["guard_tokens".to_string()],
1414 version: None,
1415 issuer: None,
1416 constraints: Vec::new(),
1417 },
1418 ];
1419 let required = vec!["Base".to_string()];
1420
1421 choreo
1422 .set_theorem_packs(&bundles)
1423 .expect("set proof bundles");
1424 choreo
1425 .set_required_theorem_packs(&required)
1426 .expect("set required bundles");
1427
1428 assert_eq!(choreo.theorem_packs(), bundles);
1429 assert_eq!(choreo.required_theorem_packs(), required);
1430 }
1431}