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 theory_convertible: bool,
353 pub proof_only: bool,
354 pub diagnostic: String,
355}
356
357#[derive(Debug)]
359pub struct Choreography {
360 pub name: Ident,
362 pub namespace: Option<String>,
364 pub roles: Vec<Role>,
366 pub protocol: Protocol,
368 pub attrs: HashMap<String, String>,
370}
371
372impl Choreography {
373 #[must_use]
375 pub fn qualified_name(&self) -> String {
376 match &self.namespace {
377 Some(ns) => format!("{}::{}", ns, self.name),
378 None => self.name.to_string(),
379 }
380 }
381
382 pub fn validate(&self) -> Result<(), ValidationError> {
389 for role in &self.roles {
391 if !self.protocol.mentions_role(role) {
392 return Err(ValidationError::UnusedRole(role.name().to_string()));
393 }
394 }
395
396 self.protocol.validate(&self.roles)?;
398 self.validate_proof_bundles()?;
399 self.validate_effect_surface()?;
400 self.validate_execution_profile_surface()?;
401 self.validate_operation_surface()?;
402
403 Ok(())
404 }
405
406 fn validate_proof_bundles(&self) -> Result<(), ValidationError> {
407 let bundles = self.theorem_packs();
408 let mut declared: BTreeSet<String> = BTreeSet::new();
409 for bundle in &bundles {
410 if !declared.insert(bundle.name.clone()) {
411 return Err(ValidationError::DuplicateProofBundle(bundle.name.clone()));
412 }
413 }
414
415 for required in self.required_theorem_packs() {
416 if !declared.contains(&required) {
417 return Err(ValidationError::MissingProofBundle(required));
418 }
419 }
420
421 let required_caps = self.required_theorem_pack_capabilities();
422 for capability in self.required_protocol_machine_core_capabilities() {
423 if !required_caps.contains(&capability) {
424 return Err(ValidationError::MissingCapability(capability));
425 }
426 }
427
428 Ok(())
429 }
430
431 fn validate_effect_surface(&self) -> Result<(), ValidationError> {
432 let mut effect_names = BTreeSet::new();
433 let mut effect_ops: HashMap<String, HashMap<String, EffectOperationDeclaration>> =
434 HashMap::new();
435 for effect in self.effect_interface_declarations() {
436 if !effect_names.insert(effect.name.clone()) {
437 return Err(ValidationError::ExtensionError(format!(
438 "duplicate effect interface declaration `{}`",
439 effect.name
440 )));
441 }
442 let mut ops = HashMap::new();
443 for op in effect.operations {
444 let semantic_class_ok = matches!(
445 op.semantic_class.as_str(),
446 "authoritative" | "observational" | "best_effort"
447 );
448 if !semantic_class_ok {
449 return Err(ValidationError::ExtensionError(format!(
450 "effect operation `{}.{}` has unsupported `class {}`",
451 effect.name, op.name, op.semantic_class
452 )));
453 }
454 let progress_ok = matches!(op.progress.as_str(), "immediate" | "may_block");
455 if !progress_ok {
456 return Err(ValidationError::ExtensionError(format!(
457 "effect operation `{}.{}` has unsupported `progress {}`",
458 effect.name, op.name, op.progress
459 )));
460 }
461 if !matches!(op.region.as_str(), "session" | "fragment" | "global") {
462 return Err(ValidationError::ExtensionError(format!(
463 "effect operation `{}.{}` has unsupported `region {}`",
464 effect.name, op.name, op.region
465 )));
466 }
467 if !matches!(op.agreement_use.as_str(), "required" | "none" | "forbidden") {
468 return Err(ValidationError::ExtensionError(format!(
469 "effect operation `{}.{}` has unsupported `agreement_use {}`",
470 effect.name, op.name, op.agreement_use
471 )));
472 }
473 if !matches!(
474 op.reentrancy.as_str(),
475 "allow" | "reject_same_operation" | "reject_same_fragment"
476 ) {
477 return Err(ValidationError::ExtensionError(format!(
478 "effect operation `{}.{}` has unsupported `reentrancy {}`",
479 effect.name, op.name, op.reentrancy
480 )));
481 }
482 if matches!(op.authority_class, EffectAuthorityClass::Observe)
483 && op.semantic_class != "observational"
484 {
485 return Err(ValidationError::ExtensionError(format!(
486 "effect operation `{}.{}` is observational and must declare `class : observational`",
487 effect.name, op.name
488 )));
489 }
490 if matches!(op.authority_class, EffectAuthorityClass::Authoritative)
491 && op.semantic_class != "authoritative"
492 {
493 return Err(ValidationError::ExtensionError(format!(
494 "effect operation `{}.{}` is authoritative and must declare `class : authoritative`",
495 effect.name, op.name
496 )));
497 }
498 if op.progress == "immediate"
499 && matches!(op.authority_class, EffectAuthorityClass::Authoritative)
500 {
501 return Err(ValidationError::ExtensionError(format!(
502 "effect operation `{}.{}` may not be both `authoritative` and `progress immediate`",
503 effect.name, op.name
504 )));
505 }
506 if op.agreement_use == "required"
507 && matches!(op.authority_class, EffectAuthorityClass::Observe)
508 {
509 return Err(ValidationError::ExtensionError(format!(
510 "effect operation `{}.{}` may not require agreement use on an observational surface",
511 effect.name, op.name
512 )));
513 }
514 if ops.insert(op.name.clone(), op.clone()).is_some() {
515 return Err(ValidationError::ExtensionError(format!(
516 "duplicate effect operation `{}.{}`",
517 effect.name, op.name
518 )));
519 }
520 }
521 effect_ops.insert(effect.name, ops);
522 }
523
524 let declared = effect_names;
525 let used: BTreeSet<String> = self.protocol_uses().into_iter().collect();
526 for effect in &used {
527 if !declared.contains(effect) {
528 return Err(ValidationError::ExtensionError(format!(
529 "protocol uses undeclared effect interface `{effect}`"
530 )));
531 }
532 }
533
534 fn validate_expr(
535 expr: &super::AuthorityExpr,
536 effect_ops: &HashMap<String, HashMap<String, EffectOperationDeclaration>>,
537 used: &BTreeSet<String>,
538 ) -> Result<(), ValidationError> {
539 match expr {
540 super::AuthorityExpr::Check {
541 effect, operation, ..
542 } => {
543 if !used.contains(effect) {
544 return Err(ValidationError::ExtensionError(format!(
545 "effect invocation `{effect}.{operation}` is not allowed without `uses {effect}`"
546 )));
547 }
548 let Some(ops) = effect_ops.get(effect) else {
549 return Err(ValidationError::ExtensionError(format!(
550 "effect invocation references undeclared interface `{effect}`"
551 )));
552 };
553 let Some(op_decl) = ops.get(operation) else {
554 return Err(ValidationError::ExtensionError(format!(
555 "effect invocation references undeclared operation `{effect}.{operation}`"
556 )));
557 };
558 if matches!(op_decl.authority_class, EffectAuthorityClass::Observe) {
559 return Err(ValidationError::ExtensionError(format!(
560 "effect invocation `{effect}.{operation}` is observational and may not be invoked with `check`"
561 )));
562 }
563 Ok(())
564 }
565 super::AuthorityExpr::Observe {
566 effect, operation, ..
567 } => {
568 if !used.contains(effect) {
569 return Err(ValidationError::ExtensionError(format!(
570 "effect invocation `{effect}.{operation}` is not allowed without `uses {effect}`"
571 )));
572 }
573 let Some(ops) = effect_ops.get(effect) else {
574 return Err(ValidationError::ExtensionError(format!(
575 "effect invocation references undeclared interface `{effect}`"
576 )));
577 };
578 let Some(op_decl) = ops.get(operation) else {
579 return Err(ValidationError::ExtensionError(format!(
580 "effect invocation references undeclared operation `{effect}.{operation}`"
581 )));
582 };
583 if !matches!(op_decl.authority_class, EffectAuthorityClass::Observe) {
584 return Err(ValidationError::ExtensionError(format!(
585 "effect invocation `{effect}.{operation}` is not observational and may not be invoked with `observe`"
586 )));
587 }
588 Ok(())
589 }
590 super::AuthorityExpr::Var(_)
591 | super::AuthorityExpr::Transfer { .. }
592 | super::AuthorityExpr::Constructor { .. }
593 | super::AuthorityExpr::Call { .. } => Ok(()),
594 }
595 }
596
597 fn validate_protocol_effects(
598 protocol: &Protocol,
599 effect_ops: &HashMap<String, HashMap<String, EffectOperationDeclaration>>,
600 used: &BTreeSet<String>,
601 ) -> Result<(), ValidationError> {
602 match protocol {
603 Protocol::Begin { continuation, .. }
604 | Protocol::Await { continuation, .. }
605 | Protocol::Resolve { continuation, .. }
606 | Protocol::Invalidate { continuation, .. }
607 | Protocol::Send { continuation, .. }
608 | Protocol::Broadcast { continuation, .. }
609 | Protocol::Extension { continuation, .. }
610 | Protocol::Let { continuation, .. }
611 | Protocol::Publish { continuation, .. }
612 | Protocol::PublishAuthority { continuation, .. }
613 | Protocol::Materialize { continuation, .. }
614 | Protocol::Handoff { continuation, .. }
615 | Protocol::DependentWork { continuation, .. } => {
616 if let Protocol::Let { mode, expr, .. } = protocol {
617 validate_expr(expr, effect_ops, used)?;
618 match (mode, expr) {
619 (
620 super::AuthorityBindingMode::Plain,
621 super::AuthorityExpr::Check {
622 effect, operation, ..
623 },
624 ) => {
625 let op_decl = effect_ops
626 .get(effect)
627 .and_then(|ops| ops.get(operation))
628 .expect("validated effect operation should exist");
629 if matches!(
630 op_decl.authority_class,
631 EffectAuthorityClass::Authoritative
632 ) {
633 return Err(ValidationError::ExtensionError(format!(
634 "authoritative effect invocation `{effect}.{operation}` must bind through `authoritative let`"
635 )));
636 }
637 }
638 (
639 super::AuthorityBindingMode::Plain,
640 super::AuthorityExpr::Observe { .. },
641 ) => {
642 return Err(ValidationError::ExtensionError(
643 "`observe` expressions must bind through `observe let`"
644 .to_string(),
645 ));
646 }
647 (
648 super::AuthorityBindingMode::Authoritative,
649 super::AuthorityExpr::Check {
650 effect, operation, ..
651 },
652 ) => {
653 let op_decl = effect_ops
654 .get(effect)
655 .and_then(|ops| ops.get(operation))
656 .expect("validated effect operation should exist");
657 if !matches!(
658 op_decl.authority_class,
659 EffectAuthorityClass::Authoritative
660 ) {
661 return Err(ValidationError::ExtensionError(format!(
662 "`authoritative let` may only bind authoritative effect invocations; `{effect}.{operation}` is {:?}",
663 op_decl.authority_class
664 )));
665 }
666 }
667 (
668 super::AuthorityBindingMode::Observe,
669 super::AuthorityExpr::Observe { .. },
670 ) => {}
671 (super::AuthorityBindingMode::Plain, _) => {}
672 (super::AuthorityBindingMode::Authoritative, _) => {
673 return Err(ValidationError::ExtensionError(
674 "`authoritative let` must bind a `check` expression"
675 .to_string(),
676 ));
677 }
678 (super::AuthorityBindingMode::Observe, _) => {
679 return Err(ValidationError::ExtensionError(
680 "`observe let` must bind an `observe` expression".to_string(),
681 ));
682 }
683 }
684 }
685 validate_protocol_effects(continuation, effect_ops, used)
686 }
687 Protocol::Choice { branches, .. } => {
688 for branch in branches {
689 if let Some(ChoiceGuard::Evidence {
690 effect, operation, ..
691 }) = &branch.guard
692 {
693 if !used.contains(effect) {
694 return Err(ValidationError::ExtensionError(format!(
695 "effect guard `{effect}.{operation}` is not allowed without `uses {effect}`"
696 )));
697 }
698 let Some(ops) = effect_ops.get(effect) else {
699 return Err(ValidationError::ExtensionError(format!(
700 "effect guard references undeclared interface `{effect}`"
701 )));
702 };
703 let Some(op_decl) = ops.get(operation) else {
704 return Err(ValidationError::ExtensionError(format!(
705 "effect guard references undeclared operation `{effect}.{operation}`"
706 )));
707 };
708 if matches!(op_decl.authority_class, EffectAuthorityClass::Observe) {
709 return Err(ValidationError::ExtensionError(format!(
710 "effect guard `{effect}.{operation}` is observational and may not be invoked with `check`"
711 )));
712 }
713 }
714 validate_protocol_effects(&branch.protocol, effect_ops, used)?;
715 }
716 Ok(())
717 }
718 Protocol::Case { expr, branches } => {
719 validate_expr(expr, effect_ops, used)?;
720 for branch in branches {
721 validate_protocol_effects(&branch.protocol, effect_ops, used)?;
722 }
723 Ok(())
724 }
725 Protocol::Timeout {
726 body,
727 on_timeout,
728 on_cancel,
729 ..
730 } => {
731 validate_protocol_effects(body, effect_ops, used)?;
732 validate_protocol_effects(on_timeout, effect_ops, used)?;
733 if let Some(on_cancel) = on_cancel.as_deref() {
734 validate_protocol_effects(on_cancel, effect_ops, used)?;
735 }
736 Ok(())
737 }
738 Protocol::Loop { body, .. } | Protocol::Rec { body, .. } => {
739 validate_protocol_effects(body, effect_ops, used)
740 }
741 Protocol::Parallel { protocols } => {
742 for protocol in protocols {
743 validate_protocol_effects(protocol, effect_ops, used)?;
744 }
745 Ok(())
746 }
747 Protocol::Var(_) | Protocol::End => Ok(()),
748 }
749 }
750
751 validate_protocol_effects(&self.protocol, &effect_ops, &used)
752 }
753
754 fn validate_execution_profile_surface(&self) -> Result<(), ValidationError> {
755 let mut declared = BTreeSet::new();
756 for profile in self.execution_profile_declarations() {
757 if !declared.insert(profile.name.clone()) {
758 return Err(ValidationError::ExtensionError(format!(
759 "duplicate execution profile declaration `{}`",
760 profile.name
761 )));
762 }
763 }
764
765 for profile in self.protocol_execution_profiles() {
766 if !declared.contains(&profile) {
767 return Err(ValidationError::ExtensionError(format!(
768 "protocol references undeclared execution profile `{profile}`"
769 )));
770 }
771 }
772
773 Ok(())
774 }
775
776 fn validate_operation_surface(&self) -> Result<(), ValidationError> {
777 let declared_agreement_profiles = self
778 .agreement_profile_declarations()
779 .into_iter()
780 .map(|profile| profile.name)
781 .collect::<BTreeSet<_>>();
782
783 for operation in self.operation_declarations() {
784 if operation.progress_contract.is_none() {
785 return Err(ValidationError::ExtensionError(format!(
786 "operation `{}` is parity-critical and must declare a progress contract",
787 operation.name
788 )));
789 }
790 if !operation
791 .progress_contract
792 .as_ref()
793 .is_some_and(|progress| progress.is_explicit())
794 {
795 return Err(ValidationError::ExtensionError(format!(
796 "operation `{}` must declare explicit progress metadata using `requires`, `within`, `on timeout`, or `on stall`",
797 operation.name
798 )));
799 }
800 let Some(agreement) = operation.agreement.as_ref() else {
801 return Err(ValidationError::ExtensionError(format!(
802 "operation `{}` must attach a named agreement profile",
803 operation.name
804 )));
805 };
806 if !declared_agreement_profiles.contains(&agreement.profile_name) {
807 return Err(ValidationError::ExtensionError(format!(
808 "operation `{}` references undeclared agreement profile `{}`",
809 operation.name, agreement.profile_name
810 )));
811 }
812 }
813 Ok(())
814 }
815
816 #[must_use]
817 pub fn language_tier_status(&self) -> LanguageTierStatus {
818 let theory_convertible = super::convert::protocol_to_global(&self.protocol).is_ok();
819 let session_blocker = find_session_projection_blocker(&self.protocol);
820 let missing_progress = self
821 .operation_declarations()
822 .iter()
823 .find(|operation| {
824 operation.progress_contract.is_none()
825 || !operation
826 .progress_contract
827 .as_ref()
828 .is_some_and(|progress| progress.is_explicit())
829 })
830 .map(|operation| {
831 format!(
832 "operation `{}` is missing the required progress contract",
833 operation.name
834 )
835 });
836
837 let protocol_machine_executable = missing_progress.is_none();
838 let session_projectable = session_blocker.is_none();
839 let strongest_tier = match (session_projectable, protocol_machine_executable) {
840 (true, true) => LanguageTier::SessionProjectable,
841 (false, true) => LanguageTier::ProtocolMachineExecutable,
842 (_, false) => LanguageTier::ProofOnly,
843 };
844 let diagnostic = if let Some(blocker) = session_blocker {
845 format!(
846 "full spec is valid, protocol-machine execution is available, but session projection is blocked: {blocker}"
847 )
848 } else if let Some(missing_progress) = missing_progress {
849 format!(
850 "full spec is valid for proof analysis only; protocol-machine execution is blocked: {missing_progress}"
851 )
852 } else if !theory_convertible {
853 "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()
854 } else {
855 "full spec is valid, protocol-machine execution is available, the protocol is session-projectable, and theory conversion is available".to_string()
856 };
857
858 LanguageTierStatus {
859 strongest_tier,
860 session_projectable,
861 protocol_machine_executable,
862 theory_convertible,
863 proof_only: matches!(strongest_tier, LanguageTier::ProofOnly),
864 diagnostic,
865 }
866 }
867
868 #[must_use]
870 pub fn get_attributes(&self) -> &HashMap<String, String> {
871 &self.attrs
872 }
873
874 pub fn get_attributes_mut(&mut self) -> &mut HashMap<String, String> {
876 &mut self.attrs
877 }
878
879 #[must_use]
881 pub fn get_attribute(&self, key: &str) -> Option<&String> {
882 self.attrs.get(key)
883 }
884
885 pub fn set_attribute(&mut self, key: String, value: String) {
887 self.attrs.insert(key, value);
888 }
889
890 pub fn remove_attribute(&mut self, key: &str) -> Option<String> {
892 self.attrs.remove(key)
893 }
894
895 #[must_use]
897 pub fn has_attribute(&self, key: &str) -> bool {
898 self.attrs.contains_key(key)
899 }
900
901 pub fn get_attribute_as<T>(&self, key: &str) -> Option<T>
903 where
904 T: std::str::FromStr,
905 {
906 self.get_attribute(key)?.parse().ok()
907 }
908
909 pub fn get_attribute_as_bool(&self, key: &str) -> Option<bool> {
911 let value = self.get_attribute(key)?;
912 match value.to_lowercase().as_str() {
913 "true" | "1" | "yes" | "on" => Some(true),
914 "false" | "0" | "no" | "off" => Some(false),
915 _ => None,
916 }
917 }
918
919 pub fn clear_attributes(&mut self) {
921 self.attrs.clear();
922 }
923
924 pub fn attribute_count(&self) -> usize {
926 self.attrs.len()
927 }
928
929 pub fn attribute_keys(&self) -> Vec<&String> {
931 self.attrs.keys().collect()
932 }
933
934 pub fn validate_required_attributes(&self, required_keys: &[&str]) -> Result<(), Vec<String>> {
936 let missing: Vec<String> = required_keys
937 .iter()
938 .filter(|&key| !self.has_attribute(key))
939 .map(|&key| key.to_string())
940 .collect();
941
942 if missing.is_empty() {
943 Ok(())
944 } else {
945 Err(missing)
946 }
947 }
948
949 pub fn find_nodes_with_annotation(&self, key: &str) -> Vec<&Protocol> {
951 let mut nodes = Vec::new();
952 self.protocol.collect_nodes_with_annotation(key, &mut nodes);
953 nodes
954 }
955
956 pub fn find_nodes_with_annotation_value(&self, key: &str, value: &str) -> Vec<&Protocol> {
958 let mut nodes = Vec::new();
959 self.protocol
960 .collect_nodes_with_annotation_value(key, value, &mut nodes);
961 nodes
962 }
963
964 pub fn total_annotation_count(&self) -> usize {
966 self.attribute_count() + self.protocol.deep_annotation_count()
967 }
968
969 pub fn set_theorem_packs(
971 &mut self,
972 theorem_packs: &[TheoremPackDeclaration],
973 ) -> Result<(), String> {
974 let encoded = serde_json::to_string(theorem_packs)
975 .map_err(|e| format!("encode theorem packs: {e}"))?;
976 self.attrs.insert(ATTR_THEOREM_PACKS.to_string(), encoded);
977 Ok(())
978 }
979
980 #[must_use]
982 pub fn theorem_packs(&self) -> Vec<TheoremPackDeclaration> {
983 self.attrs
984 .get(ATTR_THEOREM_PACKS)
985 .and_then(|s| serde_json::from_str::<Vec<TheoremPackDeclaration>>(s).ok())
986 .unwrap_or_default()
987 }
988
989 pub fn set_required_theorem_packs(&mut self, required: &[String]) -> Result<(), String> {
991 let encoded = serde_json::to_string(required)
992 .map_err(|e| format!("encode required theorem packs: {e}"))?;
993 self.attrs
994 .insert(ATTR_REQUIRED_THEOREM_PACKS.to_string(), encoded);
995 Ok(())
996 }
997
998 #[must_use]
1000 pub fn required_theorem_packs(&self) -> Vec<String> {
1001 self.attrs
1002 .get(ATTR_REQUIRED_THEOREM_PACKS)
1003 .and_then(|s| serde_json::from_str::<Vec<String>>(s).ok())
1004 .unwrap_or_default()
1005 }
1006
1007 pub fn set_inferred_required_theorem_packs(
1009 &mut self,
1010 required: &[String],
1011 ) -> Result<(), String> {
1012 let encoded = serde_json::to_string(required)
1013 .map_err(|e| format!("encode inferred theorem packs: {e}"))?;
1014 self.attrs
1015 .insert(ATTR_INFERRED_REQUIRED_THEOREM_PACKS.to_string(), encoded);
1016 Ok(())
1017 }
1018
1019 #[must_use]
1021 pub fn inferred_required_theorem_packs(&self) -> Vec<String> {
1022 self.attrs
1023 .get(ATTR_INFERRED_REQUIRED_THEOREM_PACKS)
1024 .and_then(|s| serde_json::from_str::<Vec<String>>(s).ok())
1025 .unwrap_or_default()
1026 }
1027
1028 pub fn set_role_sets(&mut self, role_sets: &[RoleSetDeclaration]) -> Result<(), String> {
1030 let encoded =
1031 serde_json::to_string(role_sets).map_err(|e| format!("encode role sets: {e}"))?;
1032 self.attrs.insert(ATTR_ROLE_SETS.to_string(), encoded);
1033 Ok(())
1034 }
1035
1036 #[must_use]
1038 pub fn role_sets(&self) -> Vec<RoleSetDeclaration> {
1039 self.attrs
1040 .get(ATTR_ROLE_SETS)
1041 .and_then(|s| serde_json::from_str::<Vec<RoleSetDeclaration>>(s).ok())
1042 .unwrap_or_default()
1043 }
1044
1045 pub fn set_topologies(&mut self, topologies: &[TopologyDeclaration]) -> Result<(), String> {
1047 let encoded =
1048 serde_json::to_string(topologies).map_err(|e| format!("encode topologies: {e}"))?;
1049 self.attrs.insert(ATTR_TOPOLOGIES.to_string(), encoded);
1050 Ok(())
1051 }
1052
1053 #[must_use]
1055 pub fn topologies(&self) -> Vec<TopologyDeclaration> {
1056 self.attrs
1057 .get(ATTR_TOPOLOGIES)
1058 .and_then(|s| serde_json::from_str::<Vec<TopologyDeclaration>>(s).ok())
1059 .unwrap_or_default()
1060 }
1061
1062 pub fn set_type_declarations(&mut self, decls: &[TypeDeclaration]) -> Result<(), String> {
1064 let encoded =
1065 serde_json::to_string(decls).map_err(|e| format!("encode type declarations: {e}"))?;
1066 self.attrs
1067 .insert(ATTR_TYPE_DECLARATIONS.to_string(), encoded);
1068 Ok(())
1069 }
1070
1071 #[must_use]
1073 pub fn type_declarations(&self) -> Vec<TypeDeclaration> {
1074 self.attrs
1075 .get(ATTR_TYPE_DECLARATIONS)
1076 .and_then(|s| serde_json::from_str::<Vec<TypeDeclaration>>(s).ok())
1077 .unwrap_or_default()
1078 }
1079
1080 pub fn set_effect_interface_declarations(
1082 &mut self,
1083 decls: &[EffectInterfaceDeclaration],
1084 ) -> Result<(), String> {
1085 let encoded =
1086 serde_json::to_string(decls).map_err(|e| format!("encode effect declarations: {e}"))?;
1087 self.attrs
1088 .insert(ATTR_EFFECT_INTERFACE_DECLARATIONS.to_string(), encoded);
1089 Ok(())
1090 }
1091
1092 #[must_use]
1094 pub fn effect_interface_declarations(&self) -> Vec<EffectInterfaceDeclaration> {
1095 self.attrs
1096 .get(ATTR_EFFECT_INTERFACE_DECLARATIONS)
1097 .and_then(|s| serde_json::from_str::<Vec<EffectInterfaceDeclaration>>(s).ok())
1098 .unwrap_or_default()
1099 }
1100
1101 #[must_use]
1104 pub fn effect_contract_declarations(&self) -> Vec<EffectContractDeclaration> {
1105 let used: BTreeSet<String> = self.protocol_uses().into_iter().collect();
1106 self.effect_interface_declarations()
1107 .into_iter()
1108 .flat_map(|effect| {
1109 let is_used = used.contains(effect.name.as_str());
1110 effect.operations.into_iter().map(move |op| {
1111 let admissibility = if is_used {
1112 "declared_use_only"
1113 } else {
1114 "internal_only"
1115 };
1116 let (totality, timeout_policy) = match op.progress.as_str() {
1117 "immediate" => ("immediate", "none"),
1118 "may_block" => ("may_block", "required"),
1119 _ => ("may_block", "required"),
1120 };
1121 let handler_domain = if is_used { "external" } else { "internal" };
1122 EffectContractDeclaration {
1123 interface_name: effect.name.clone(),
1124 operation_name: op.name,
1125 authority_class: op.authority_class,
1126 semantic_class: op.semantic_class.clone(),
1127 progress: op.progress.clone(),
1128 region: op.region.clone(),
1129 agreement_use: op.agreement_use.clone(),
1130 admissibility: admissibility.to_string(),
1131 totality: totality.to_string(),
1132 timeout_policy: timeout_policy.to_string(),
1133 reentrancy_policy: op.reentrancy,
1134 handler_domain: handler_domain.to_string(),
1135 }
1136 })
1137 })
1138 .collect()
1139 }
1140
1141 pub fn set_protocol_uses(&mut self, uses: &[String]) -> Result<(), String> {
1143 let encoded =
1144 serde_json::to_string(uses).map_err(|e| format!("encode protocol uses: {e}"))?;
1145 self.attrs.insert(ATTR_PROTOCOL_USES.to_string(), encoded);
1146 Ok(())
1147 }
1148
1149 #[must_use]
1151 pub fn protocol_uses(&self) -> Vec<String> {
1152 self.attrs
1153 .get(ATTR_PROTOCOL_USES)
1154 .and_then(|s| serde_json::from_str::<Vec<String>>(s).ok())
1155 .unwrap_or_default()
1156 }
1157
1158 pub fn set_region_declarations(&mut self, decls: &[RegionDeclaration]) -> Result<(), String> {
1160 let encoded =
1161 serde_json::to_string(decls).map_err(|e| format!("encode region declarations: {e}"))?;
1162 self.attrs
1163 .insert(ATTR_REGION_DECLARATIONS.to_string(), encoded);
1164 Ok(())
1165 }
1166
1167 #[must_use]
1169 pub fn region_declarations(&self) -> Vec<RegionDeclaration> {
1170 self.attrs
1171 .get(ATTR_REGION_DECLARATIONS)
1172 .and_then(|s| serde_json::from_str::<Vec<RegionDeclaration>>(s).ok())
1173 .unwrap_or_default()
1174 }
1175
1176 pub fn set_operation_declarations(
1178 &mut self,
1179 decls: &[OperationDeclaration],
1180 ) -> Result<(), String> {
1181 let encoded = serde_json::to_string(decls)
1182 .map_err(|e| format!("encode operation declarations: {e}"))?;
1183 self.attrs
1184 .insert(ATTR_OPERATION_DECLARATIONS.to_string(), encoded);
1185 Ok(())
1186 }
1187
1188 #[must_use]
1190 pub fn operation_declarations(&self) -> Vec<OperationDeclaration> {
1191 self.attrs
1192 .get(ATTR_OPERATION_DECLARATIONS)
1193 .and_then(|s| serde_json::from_str::<Vec<OperationDeclaration>>(s).ok())
1194 .unwrap_or_default()
1195 }
1196
1197 pub fn set_guest_runtime_declarations(
1199 &mut self,
1200 decls: &[GuestRuntimeDeclaration],
1201 ) -> Result<(), String> {
1202 let encoded = serde_json::to_string(decls)
1203 .map_err(|e| format!("encode guest runtime declarations: {e}"))?;
1204 self.attrs
1205 .insert(ATTR_GUEST_RUNTIME_DECLARATIONS.to_string(), encoded);
1206 Ok(())
1207 }
1208
1209 #[must_use]
1211 pub fn guest_runtime_declarations(&self) -> Vec<GuestRuntimeDeclaration> {
1212 self.attrs
1213 .get(ATTR_GUEST_RUNTIME_DECLARATIONS)
1214 .and_then(|s| serde_json::from_str::<Vec<GuestRuntimeDeclaration>>(s).ok())
1215 .unwrap_or_default()
1216 }
1217
1218 pub fn set_execution_profile_declarations(
1220 &mut self,
1221 decls: &[ExecutionProfileDeclaration],
1222 ) -> Result<(), String> {
1223 let encoded = serde_json::to_string(decls)
1224 .map_err(|e| format!("encode execution profile declarations: {e}"))?;
1225 self.attrs
1226 .insert(ATTR_EXECUTION_PROFILE_DECLARATIONS.to_string(), encoded);
1227 Ok(())
1228 }
1229
1230 #[must_use]
1232 pub fn execution_profile_declarations(&self) -> Vec<ExecutionProfileDeclaration> {
1233 self.attrs
1234 .get(ATTR_EXECUTION_PROFILE_DECLARATIONS)
1235 .and_then(|s| serde_json::from_str::<Vec<ExecutionProfileDeclaration>>(s).ok())
1236 .unwrap_or_default()
1237 }
1238
1239 pub fn set_protocol_execution_profiles(&mut self, profiles: &[String]) -> Result<(), String> {
1241 let encoded = serde_json::to_string(profiles)
1242 .map_err(|e| format!("encode protocol execution profiles: {e}"))?;
1243 self.attrs
1244 .insert(ATTR_PROTOCOL_EXECUTION_PROFILES.to_string(), encoded);
1245 Ok(())
1246 }
1247
1248 #[must_use]
1250 pub fn protocol_execution_profiles(&self) -> Vec<String> {
1251 self.attrs
1252 .get(ATTR_PROTOCOL_EXECUTION_PROFILES)
1253 .and_then(|s| serde_json::from_str::<Vec<String>>(s).ok())
1254 .unwrap_or_default()
1255 }
1256
1257 pub fn set_agreement_profile_declarations(
1259 &mut self,
1260 decls: &[AgreementProfileDeclaration],
1261 ) -> Result<(), String> {
1262 let encoded = serde_json::to_string(decls)
1263 .map_err(|e| format!("encode agreement profile declarations: {e}"))?;
1264 self.attrs
1265 .insert(ATTR_AGREEMENT_PROFILE_DECLARATIONS.to_string(), encoded);
1266 Ok(())
1267 }
1268
1269 #[must_use]
1271 pub fn agreement_profile_declarations(&self) -> Vec<AgreementProfileDeclaration> {
1272 self.attrs
1273 .get(ATTR_AGREEMENT_PROFILE_DECLARATIONS)
1274 .and_then(|s| serde_json::from_str::<Vec<AgreementProfileDeclaration>>(s).ok())
1275 .unwrap_or_default()
1276 }
1277
1278 fn required_theorem_pack_capabilities(&self) -> BTreeSet<String> {
1279 let required = self.required_theorem_packs();
1280 let required_set: BTreeSet<&str> = required.iter().map(String::as_str).collect();
1281 self.theorem_packs()
1282 .into_iter()
1283 .filter(|bundle| required_set.contains(bundle.name.as_str()))
1284 .flat_map(|bundle| bundle.capabilities.into_iter())
1285 .collect()
1286 }
1287
1288 fn required_protocol_machine_core_capabilities(&self) -> BTreeSet<String> {
1289 fn collect(protocol: &Protocol, out: &mut BTreeSet<String>) {
1290 if let Some(cap) = protocol.get_annotations().custom("required_capability") {
1291 out.insert(cap.to_string());
1292 }
1293 match protocol {
1294 Protocol::Begin { continuation, .. }
1295 | Protocol::Await { continuation, .. }
1296 | Protocol::Resolve { continuation, .. }
1297 | Protocol::Invalidate { continuation, .. }
1298 | Protocol::Send { continuation, .. }
1299 | Protocol::Broadcast { continuation, .. }
1300 | Protocol::Extension { continuation, .. }
1301 | Protocol::Publish { continuation, .. }
1302 | Protocol::PublishAuthority { continuation, .. }
1303 | Protocol::Materialize { continuation, .. }
1304 | Protocol::Handoff { continuation, .. }
1305 | Protocol::DependentWork { continuation, .. } => collect(continuation, out),
1306 Protocol::Choice { branches, .. } => {
1307 for branch in branches {
1308 collect(&branch.protocol, out);
1309 }
1310 }
1311 Protocol::Loop { body, .. } | Protocol::Rec { body, .. } => collect(body, out),
1312 Protocol::Let { continuation, .. } => collect(continuation, out),
1313 Protocol::Case { branches, .. } => {
1314 for branch in branches {
1315 collect(&branch.protocol, out);
1316 }
1317 }
1318 Protocol::Timeout {
1319 body,
1320 on_timeout,
1321 on_cancel,
1322 ..
1323 } => {
1324 collect(body, out);
1325 collect(on_timeout, out);
1326 if let Some(on_cancel) = on_cancel.as_deref() {
1327 collect(on_cancel, out);
1328 }
1329 }
1330 Protocol::Parallel { protocols } => {
1331 for p in protocols {
1332 collect(p, out);
1333 }
1334 }
1335 Protocol::Var(_) | Protocol::End => {}
1336 }
1337 }
1338
1339 let mut out = BTreeSet::new();
1340 collect(&self.protocol, &mut out);
1341 out
1342 }
1343}
1344
1345impl ProgressAttachment {
1346 #[must_use]
1348 pub fn is_explicit(&self) -> bool {
1349 self.requires_profile.is_some()
1350 || self.within_window.is_some()
1351 || self.on_timeout.is_some()
1352 || self.on_stall.is_some()
1353 }
1354}
1355
1356fn find_session_projection_blocker(protocol: &Protocol) -> Option<&'static str> {
1357 match protocol {
1358 Protocol::Begin { .. } => Some("begin requires protocol-machine commitment semantics"),
1359 Protocol::Await { .. } => Some("await requires protocol-machine commitment semantics"),
1360 Protocol::Resolve { .. } => Some("resolve requires protocol-machine commitment semantics"),
1361 Protocol::Invalidate { .. } => {
1362 Some("invalidate requires protocol-machine commitment semantics")
1363 }
1364 Protocol::Case { .. } => Some("authority-local case/of requires protocol-machine lowering"),
1365 Protocol::Timeout { .. } => Some("timeout requires protocol-machine progress semantics"),
1366 Protocol::Publish { .. } => Some("publish requires publication/materialization semantics"),
1367 Protocol::PublishAuthority { .. } => {
1368 Some("publish requires publication/materialization semantics")
1369 }
1370 Protocol::Materialize { .. } => {
1371 Some("materialize requires publication/materialization semantics")
1372 }
1373 Protocol::Handoff { .. } => Some("handoff requires semantic handoff semantics"),
1374 Protocol::DependentWork { .. } => {
1375 Some("dependent work requires protocol-machine commitment semantics")
1376 }
1377 Protocol::Send { continuation, .. }
1378 | Protocol::Broadcast { continuation, .. }
1379 | Protocol::Let { continuation, .. }
1380 | Protocol::Extension { continuation, .. } => find_session_projection_blocker(continuation),
1381 Protocol::Choice { branches, .. } => branches
1382 .iter()
1383 .find_map(|branch| find_session_projection_blocker(&branch.protocol)),
1384 Protocol::Loop { body, .. } | Protocol::Rec { body, .. } => {
1385 find_session_projection_blocker(body)
1386 }
1387 Protocol::Parallel { protocols } => {
1388 protocols.iter().find_map(find_session_projection_blocker)
1389 }
1390 Protocol::Var(_) | Protocol::End => None,
1391 }
1392}
1393
1394#[cfg(test)]
1395mod tests {
1396 use super::*;
1397 use quote::format_ident;
1398
1399 #[test]
1400 fn proof_bundle_metadata_roundtrip() {
1401 let mut choreo = Choreography {
1402 name: format_ident!("RoundTrip"),
1403 namespace: None,
1404 roles: Vec::new(),
1405 protocol: Protocol::End,
1406 attrs: HashMap::new(),
1407 };
1408 let bundles = vec![
1409 TheoremPackDeclaration {
1410 name: "Base".to_string(),
1411 capabilities: vec!["delegation".to_string()],
1412 version: None,
1413 issuer: None,
1414 constraints: Vec::new(),
1415 },
1416 TheoremPackDeclaration {
1417 name: "Guard".to_string(),
1418 capabilities: vec!["guard_tokens".to_string()],
1419 version: None,
1420 issuer: None,
1421 constraints: Vec::new(),
1422 },
1423 ];
1424 let required = vec!["Base".to_string()];
1425
1426 choreo
1427 .set_theorem_packs(&bundles)
1428 .expect("set proof bundles");
1429 choreo
1430 .set_required_theorem_packs(&required)
1431 .expect("set required bundles");
1432
1433 assert_eq!(choreo.theorem_packs(), bundles);
1434 assert_eq!(choreo.required_theorem_packs(), required);
1435 }
1436}