Skip to main content

telltale_language/ast/
choreography.rs

1// Choreography struct definition and validation
2
3use 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/// Typed proof-bundle declaration metadata from DSL.
24#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
25pub struct TheoremPackDeclaration {
26    /// Stable bundle name.
27    pub name: String,
28    /// Capabilities provided by this bundle.
29    #[serde(default)]
30    pub capabilities: Vec<String>,
31    /// Optional bundle version.
32    #[serde(default)]
33    pub version: Option<String>,
34    /// Optional bundle issuer.
35    #[serde(default)]
36    pub issuer: Option<String>,
37    /// Optional constraints attached to the bundle.
38    #[serde(default)]
39    pub constraints: Vec<String>,
40}
41
42/// Typed role-set declaration metadata from DSL.
43#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
44pub struct RoleSetDeclaration {
45    /// Stable role-set name.
46    pub name: String,
47    /// Explicit members for this role-set.
48    #[serde(default)]
49    pub members: Vec<String>,
50    /// Optional subset selector source role-set or family.
51    #[serde(default)]
52    pub subset_of: Option<String>,
53    /// Optional subset selector start index (inclusive).
54    #[serde(default)]
55    pub subset_start: Option<u32>,
56    /// Optional subset selector end index (exclusive).
57    #[serde(default)]
58    pub subset_end: Option<u32>,
59}
60
61/// Typed topology declaration metadata from DSL.
62#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
63pub struct TopologyDeclaration {
64    /// Topology kind (`cluster`, `ring`, `mesh`).
65    pub kind: String,
66    /// Topology name.
67    pub name: String,
68    /// Referenced members.
69    #[serde(default)]
70    pub members: Vec<String>,
71}
72
73/// DSL type declaration metadata.
74#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
75pub struct TypeDeclaration {
76    /// Declared type name.
77    pub name: String,
78    /// Whether this is a `type alias`.
79    pub is_alias: bool,
80    /// Right-hand side for aliases.
81    #[serde(default)]
82    pub alias_of: Option<String>,
83    /// Union constructors for nominal sum types.
84    #[serde(default)]
85    pub constructors: Vec<TypeConstructorDeclaration>,
86}
87
88/// Constructor declaration for one nominal union type.
89#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
90pub struct TypeConstructorDeclaration {
91    /// Constructor name.
92    pub name: String,
93    /// Optional payload type rendered from source syntax.
94    #[serde(default)]
95    pub payload_type: Option<String>,
96}
97
98/// Nominal effect interface declaration metadata.
99#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
100pub struct EffectInterfaceDeclaration {
101    /// Effect interface name.
102    pub name: String,
103    /// Declared operations for this interface.
104    #[serde(default)]
105    pub operations: Vec<EffectOperationDeclaration>,
106}
107
108/// Authority class for one nominal effect operation.
109#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
110#[serde(rename_all = "snake_case")]
111pub enum EffectAuthorityClass {
112    /// Operation may produce authoritative semantic evidence.
113    Authoritative,
114    /// Operation performs command work without itself proving semantic truth.
115    #[default]
116    Command,
117    /// Operation is observational only and must not be consumed via `check`.
118    Observe,
119}
120
121/// One operation in a nominal effect interface.
122#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
123pub struct EffectOperationDeclaration {
124    /// Operation name.
125    pub name: String,
126    /// Authority class attached to this operation.
127    #[serde(default)]
128    pub authority_class: EffectAuthorityClass,
129    /// Semantic class declared for this operation.
130    pub semantic_class: String,
131    /// Progress class declared for this operation.
132    pub progress: String,
133    /// Region scope declared for this operation.
134    pub region: String,
135    /// Agreement-use discipline declared for this operation.
136    pub agreement_use: String,
137    /// Reentrancy policy declared for this operation.
138    pub reentrancy: String,
139    /// Input type as declared in DSL surface syntax.
140    pub input_type: String,
141    /// Output type as declared in DSL surface syntax.
142    pub output_type: String,
143}
144
145/// Runtime-facing effect metadata derived from one nominal effect declaration.
146#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
147pub struct EffectContractDeclaration {
148    /// Nominal effect interface name.
149    pub interface_name: String,
150    /// Nominal effect operation name.
151    pub operation_name: String,
152    /// Authority class attached to this operation.
153    pub authority_class: EffectAuthorityClass,
154    /// Semantic class attached to this operation.
155    pub semantic_class: String,
156    /// Progress class attached to this operation.
157    pub progress: String,
158    /// Region scope attached to this operation.
159    pub region: String,
160    /// Agreement-use discipline attached to this operation.
161    pub agreement_use: String,
162    /// Runtime admissibility policy name.
163    pub admissibility: String,
164    /// Runtime totality policy name.
165    pub totality: String,
166    /// Runtime timeout policy name.
167    pub timeout_policy: String,
168    /// Runtime reentrancy policy name.
169    pub reentrancy_policy: String,
170    /// Runtime handler-domain name.
171    pub handler_domain: String,
172}
173
174/// Fragment declaration metadata from DSL.
175#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
176pub struct RegionDeclaration {
177    /// Fragment name.
178    pub name: String,
179    /// Named fragment parameters.
180    #[serde(default)]
181    pub params: Vec<String>,
182}
183
184/// Operation parameter declaration metadata.
185#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
186pub struct OperationParameterDeclaration {
187    /// Parameter name.
188    pub name: String,
189    /// Parameter type rendered from source syntax.
190    pub type_name: String,
191}
192
193/// Structured progress-contract attachment metadata from DSL.
194#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
195pub struct ProgressAttachment {
196    /// Stable progress-contract name.
197    pub contract_name: String,
198    /// Required execution/admission profile.
199    #[serde(default)]
200    pub requires_profile: Option<String>,
201    /// Required escalation window class.
202    #[serde(default)]
203    pub within_window: Option<String>,
204    /// Named timeout branch or escalation action.
205    #[serde(default)]
206    pub on_timeout: Option<String>,
207    /// Named stall branch or escalation action.
208    #[serde(default)]
209    pub on_stall: Option<String>,
210}
211
212/// Named reusable agreement-profile declaration metadata from DSL.
213#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
214pub struct AgreementProfileDeclaration {
215    /// Stable profile name.
216    pub name: String,
217    /// Visibility timing fixed by this profile.
218    pub visibility: String,
219    /// Agreement/decision rule name.
220    pub rule: String,
221    /// Minimum agreement level required for provisional usability.
222    pub usable_at: String,
223    /// Minimum agreement level required for finalization.
224    pub finalized_at: String,
225    /// Required evidence kind for this profile.
226    pub evidence: String,
227}
228
229/// Operation-level attachment of one named agreement profile.
230#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
231pub struct OperationAgreementAttachment {
232    /// Referenced agreement-profile name.
233    pub profile_name: String,
234    /// Optional named prestate binding required by the operation.
235    #[serde(default)]
236    pub prestate: Option<String>,
237}
238
239/// Operation declaration metadata from DSL.
240#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
241pub struct OperationDeclaration {
242    /// Operation name.
243    pub name: String,
244    /// Operation parameters.
245    #[serde(default)]
246    pub params: Vec<OperationParameterDeclaration>,
247    /// Semantic owner role.
248    pub owner_role: String,
249    /// Optional fragment scope rendered from source syntax.
250    #[serde(default)]
251    pub within: Option<String>,
252    /// Required progress contract for parity-critical execution.
253    #[serde(default)]
254    pub progress_contract: Option<ProgressAttachment>,
255    /// Named agreement/finality attachment for the operation.
256    #[serde(default)]
257    pub agreement: Option<OperationAgreementAttachment>,
258    /// Declared child-effect aggregation for the operation.
259    #[serde(default)]
260    pub child_effect_aggregation: Option<ChildEffectAggregation>,
261    /// Raw operation body source.
262    pub body_source: String,
263}
264
265/// Canonical child-effect aggregation policy attached below one agreement boundary.
266#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
267pub enum ChildEffectAggregationPolicy {
268    /// All child effects must succeed.
269    All,
270    /// The first successful child effect resolves the aggregation.
271    First,
272    /// A fixed number of successful child effects is required.
273    Threshold {
274        /// Required success count.
275        required_successes: u64,
276    },
277}
278
279/// One declared child-effect aggregation attached to an operation.
280#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
281pub struct ChildEffectAggregation {
282    /// Canonical aggregation policy.
283    pub policy: ChildEffectAggregationPolicy,
284}
285
286impl ChildEffectAggregationPolicy {
287    /// Canonical DSL spelling for this child-effect aggregation policy.
288    #[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    /// Canonical DSL spelling for this child-effect aggregation declaration.
302    #[must_use]
303    pub fn dsl_name(&self) -> String {
304        self.policy.dsl_name()
305    }
306}
307
308/// Guest-runtime declaration metadata from DSL.
309#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
310pub struct GuestRuntimeDeclaration {
311    /// Guest-runtime name.
312    pub name: String,
313    /// Declared effect interface uses.
314    #[serde(default)]
315    pub uses: Vec<String>,
316    /// Entry protocol name.
317    pub entry: String,
318}
319
320/// Execution-profile declaration metadata from DSL.
321#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
322pub struct ExecutionProfileDeclaration {
323    /// Stable profile name.
324    pub name: String,
325    /// Fairness class fixed by the profile.
326    #[serde(default)]
327    pub fairness: Option<String>,
328    /// Admissibility class fixed by the profile.
329    #[serde(default)]
330    pub admissibility: Option<String>,
331    /// Escalation-window class fixed by the profile.
332    #[serde(default)]
333    pub escalation_window: Option<String>,
334}
335
336/// Strongest artifact tier justified by the parsed specification.
337#[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/// Strongest theorem story currently justified for authority-bearing constructs.
347#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
348#[serde(rename_all = "snake_case")]
349pub enum AuthorityMetatheoryTier {
350    /// No authority-specific semantic obligations beyond ordinary session coordination.
351    SessionTypedCoordination,
352    /// The supported authority slice lives in the protocol-machine semantic-object layer:
353    /// evidence-bearing reads plus canonical publication/materialization.
354    EvidencePublicationSemanticObjects,
355    /// The protocol uses authority/runtime features that currently remain outside the
356    /// supported authority theorem slice.
357    RuntimeSemanticOnly,
358}
359
360/// Explicit authority-metatheory status for one parsed specification.
361#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
362pub struct AuthorityMetatheoryStatus {
363    pub strongest_tier: AuthorityMetatheoryTier,
364    pub diagnostic: String,
365}
366
367/// Explicit language-tier status for one parsed specification.
368#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
369pub struct LanguageTierStatus {
370    pub strongest_tier: LanguageTier,
371    pub session_projectable: bool,
372    pub protocol_machine_executable: bool,
373    pub theory_convertible: bool,
374    pub proof_only: bool,
375    pub diagnostic: String,
376}
377
378/// A complete choreographic protocol specification
379#[derive(Debug)]
380pub struct Choreography {
381    /// Protocol name
382    pub name: Ident,
383    /// Optional namespace for the protocol
384    pub namespace: Option<String>,
385    /// Participating roles
386    pub roles: Vec<Role>,
387    /// The protocol specification
388    pub protocol: Protocol,
389    /// Metadata and attributes
390    pub attrs: HashMap<String, String>,
391}
392
393impl Choreography {
394    /// Get the qualified name of the choreography (namespace::name or just name)
395    #[must_use]
396    pub fn qualified_name(&self) -> String {
397        match &self.namespace {
398            Some(ns) => format!("{}::{}", ns, self.name),
399            None => self.name.to_string(),
400        }
401    }
402
403    /// Validate the choreography for correctness
404    ///
405    /// # Errors
406    ///
407    /// Returns [`ValidationError`] if the choreography is invalid (unused roles,
408    /// malformed protocol, duplicate/missing proof bundles, or missing capabilities).
409    pub fn validate(&self) -> Result<(), ValidationError> {
410        // Check all roles are used
411        for role in &self.roles {
412            if !self.protocol.mentions_role(role) {
413                return Err(ValidationError::UnusedRole(role.name().to_string()));
414            }
415        }
416
417        // Check protocol is well-formed
418        self.protocol.validate(&self.roles)?;
419        self.validate_proof_bundles()?;
420        self.validate_effect_surface()?;
421        self.validate_execution_profile_surface()?;
422        self.validate_operation_surface()?;
423
424        Ok(())
425    }
426
427    fn validate_proof_bundles(&self) -> Result<(), ValidationError> {
428        let bundles = self.theorem_packs();
429        let mut declared: BTreeSet<String> = BTreeSet::new();
430        for bundle in &bundles {
431            if !declared.insert(bundle.name.clone()) {
432                return Err(ValidationError::DuplicateProofBundle(bundle.name.clone()));
433            }
434        }
435
436        for required in self.required_theorem_packs() {
437            if !declared.contains(&required) {
438                return Err(ValidationError::MissingProofBundle(required));
439            }
440        }
441
442        let required_caps = self.required_theorem_pack_capabilities();
443        for capability in self.required_protocol_machine_core_capabilities() {
444            if !required_caps.contains(&capability) {
445                return Err(ValidationError::MissingCapability(capability));
446            }
447        }
448
449        Ok(())
450    }
451
452    fn validate_effect_surface(&self) -> Result<(), ValidationError> {
453        let mut effect_names = BTreeSet::new();
454        let mut effect_ops: HashMap<String, HashMap<String, EffectOperationDeclaration>> =
455            HashMap::new();
456        for effect in self.effect_interface_declarations() {
457            if !effect_names.insert(effect.name.clone()) {
458                return Err(ValidationError::ExtensionError(format!(
459                    "duplicate effect interface declaration `{}`",
460                    effect.name
461                )));
462            }
463            let mut ops = HashMap::new();
464            for op in effect.operations {
465                let semantic_class_ok = matches!(
466                    op.semantic_class.as_str(),
467                    "authoritative" | "observational" | "best_effort"
468                );
469                if !semantic_class_ok {
470                    return Err(ValidationError::ExtensionError(format!(
471                        "effect operation `{}.{}` has unsupported `class {}`",
472                        effect.name, op.name, op.semantic_class
473                    )));
474                }
475                let progress_ok = matches!(op.progress.as_str(), "immediate" | "may_block");
476                if !progress_ok {
477                    return Err(ValidationError::ExtensionError(format!(
478                        "effect operation `{}.{}` has unsupported `progress {}`",
479                        effect.name, op.name, op.progress
480                    )));
481                }
482                if !matches!(op.region.as_str(), "session" | "fragment" | "global") {
483                    return Err(ValidationError::ExtensionError(format!(
484                        "effect operation `{}.{}` has unsupported `region {}`",
485                        effect.name, op.name, op.region
486                    )));
487                }
488                if !matches!(op.agreement_use.as_str(), "required" | "none" | "forbidden") {
489                    return Err(ValidationError::ExtensionError(format!(
490                        "effect operation `{}.{}` has unsupported `agreement_use {}`",
491                        effect.name, op.name, op.agreement_use
492                    )));
493                }
494                if !matches!(
495                    op.reentrancy.as_str(),
496                    "allow" | "reject_same_operation" | "reject_same_fragment"
497                ) {
498                    return Err(ValidationError::ExtensionError(format!(
499                        "effect operation `{}.{}` has unsupported `reentrancy {}`",
500                        effect.name, op.name, op.reentrancy
501                    )));
502                }
503                if matches!(op.authority_class, EffectAuthorityClass::Observe)
504                    && op.semantic_class != "observational"
505                {
506                    return Err(ValidationError::ExtensionError(format!(
507                        "effect operation `{}.{}` is observational and must declare `class : observational`",
508                        effect.name, op.name
509                    )));
510                }
511                if matches!(op.authority_class, EffectAuthorityClass::Authoritative)
512                    && op.semantic_class != "authoritative"
513                {
514                    return Err(ValidationError::ExtensionError(format!(
515                        "effect operation `{}.{}` is authoritative and must declare `class : authoritative`",
516                        effect.name, op.name
517                    )));
518                }
519                if op.progress == "immediate"
520                    && matches!(op.authority_class, EffectAuthorityClass::Authoritative)
521                {
522                    return Err(ValidationError::ExtensionError(format!(
523                        "effect operation `{}.{}` may not be both `authoritative` and `progress immediate`",
524                        effect.name, op.name
525                    )));
526                }
527                if op.agreement_use == "required"
528                    && matches!(op.authority_class, EffectAuthorityClass::Observe)
529                {
530                    return Err(ValidationError::ExtensionError(format!(
531                        "effect operation `{}.{}` may not require agreement use on an observational surface",
532                        effect.name, op.name
533                    )));
534                }
535                if ops.insert(op.name.clone(), op.clone()).is_some() {
536                    return Err(ValidationError::ExtensionError(format!(
537                        "duplicate effect operation `{}.{}`",
538                        effect.name, op.name
539                    )));
540                }
541            }
542            effect_ops.insert(effect.name, ops);
543        }
544
545        let declared = effect_names;
546        let used: BTreeSet<String> = self.protocol_uses().into_iter().collect();
547        for effect in &used {
548            if !declared.contains(effect) {
549                return Err(ValidationError::ExtensionError(format!(
550                    "protocol uses undeclared effect interface `{effect}`"
551                )));
552            }
553        }
554
555        fn validate_expr(
556            expr: &super::AuthorityExpr,
557            effect_ops: &HashMap<String, HashMap<String, EffectOperationDeclaration>>,
558            used: &BTreeSet<String>,
559        ) -> Result<(), ValidationError> {
560            match expr {
561                super::AuthorityExpr::Check {
562                    effect, operation, ..
563                } => {
564                    if !used.contains(effect) {
565                        return Err(ValidationError::ExtensionError(format!(
566                            "effect invocation `{effect}.{operation}` is not allowed without `uses {effect}`"
567                        )));
568                    }
569                    let Some(ops) = effect_ops.get(effect) else {
570                        return Err(ValidationError::ExtensionError(format!(
571                            "effect invocation references undeclared interface `{effect}`"
572                        )));
573                    };
574                    let Some(op_decl) = ops.get(operation) else {
575                        return Err(ValidationError::ExtensionError(format!(
576                            "effect invocation references undeclared operation `{effect}.{operation}`"
577                        )));
578                    };
579                    if matches!(op_decl.authority_class, EffectAuthorityClass::Observe) {
580                        return Err(ValidationError::ExtensionError(format!(
581                            "effect invocation `{effect}.{operation}` is observational and may not be invoked with `check`"
582                        )));
583                    }
584                    Ok(())
585                }
586                super::AuthorityExpr::Observe {
587                    effect, operation, ..
588                } => {
589                    if !used.contains(effect) {
590                        return Err(ValidationError::ExtensionError(format!(
591                            "effect invocation `{effect}.{operation}` is not allowed without `uses {effect}`"
592                        )));
593                    }
594                    let Some(ops) = effect_ops.get(effect) else {
595                        return Err(ValidationError::ExtensionError(format!(
596                            "effect invocation references undeclared interface `{effect}`"
597                        )));
598                    };
599                    let Some(op_decl) = ops.get(operation) else {
600                        return Err(ValidationError::ExtensionError(format!(
601                            "effect invocation references undeclared operation `{effect}.{operation}`"
602                        )));
603                    };
604                    if !matches!(op_decl.authority_class, EffectAuthorityClass::Observe) {
605                        return Err(ValidationError::ExtensionError(format!(
606                            "effect invocation `{effect}.{operation}` is not observational and may not be invoked with `observe`"
607                        )));
608                    }
609                    Ok(())
610                }
611                super::AuthorityExpr::Var(_)
612                | super::AuthorityExpr::Transfer { .. }
613                | super::AuthorityExpr::Constructor { .. }
614                | super::AuthorityExpr::Call { .. } => Ok(()),
615            }
616        }
617
618        fn validate_protocol_effects(
619            protocol: &Protocol,
620            effect_ops: &HashMap<String, HashMap<String, EffectOperationDeclaration>>,
621            used: &BTreeSet<String>,
622        ) -> Result<(), ValidationError> {
623            match protocol {
624                Protocol::Begin { continuation, .. }
625                | Protocol::Await { continuation, .. }
626                | Protocol::Resolve { continuation, .. }
627                | Protocol::Invalidate { continuation, .. }
628                | Protocol::Send { continuation, .. }
629                | Protocol::Broadcast { continuation, .. }
630                | Protocol::Extension { continuation, .. }
631                | Protocol::Let { continuation, .. }
632                | Protocol::Publish { continuation, .. }
633                | Protocol::PublishAuthority { continuation, .. }
634                | Protocol::Materialize { continuation, .. }
635                | Protocol::Handoff { continuation, .. }
636                | Protocol::DependentWork { continuation, .. } => {
637                    if let Protocol::Let { mode, expr, .. } = protocol {
638                        validate_expr(expr, effect_ops, used)?;
639                        match (mode, expr) {
640                            (
641                                super::AuthorityBindingMode::Plain,
642                                super::AuthorityExpr::Check {
643                                    effect, operation, ..
644                                },
645                            ) => {
646                                let op_decl = effect_ops
647                                    .get(effect)
648                                    .and_then(|ops| ops.get(operation))
649                                    .ok_or_else(|| {
650                                        ValidationError::ExtensionError(format!(
651                                            "effect invocation `{effect}.{operation}` was not declared"
652                                        ))
653                                    })?;
654                                if matches!(
655                                    op_decl.authority_class,
656                                    EffectAuthorityClass::Authoritative
657                                ) {
658                                    return Err(ValidationError::ExtensionError(format!(
659                                        "authoritative effect invocation `{effect}.{operation}` must bind through `authoritative let`"
660                                    )));
661                                }
662                            }
663                            (
664                                super::AuthorityBindingMode::Plain,
665                                super::AuthorityExpr::Observe { .. },
666                            ) => {
667                                return Err(ValidationError::ExtensionError(
668                                    "`observe` expressions must bind through `observe let`"
669                                        .to_string(),
670                                ));
671                            }
672                            (
673                                super::AuthorityBindingMode::Authoritative,
674                                super::AuthorityExpr::Check {
675                                    effect, operation, ..
676                                },
677                            ) => {
678                                let op_decl = effect_ops
679                                    .get(effect)
680                                    .and_then(|ops| ops.get(operation))
681                                    .ok_or_else(|| {
682                                        ValidationError::ExtensionError(format!(
683                                            "effect invocation `{effect}.{operation}` was not declared"
684                                        ))
685                                    })?;
686                                if !matches!(
687                                    op_decl.authority_class,
688                                    EffectAuthorityClass::Authoritative
689                                ) {
690                                    return Err(ValidationError::ExtensionError(format!(
691                                        "`authoritative let` may only bind authoritative effect invocations; `{effect}.{operation}` is {:?}",
692                                        op_decl.authority_class
693                                    )));
694                                }
695                            }
696                            (
697                                super::AuthorityBindingMode::Observe,
698                                super::AuthorityExpr::Observe { .. },
699                            ) => {}
700                            (super::AuthorityBindingMode::Plain, _) => {}
701                            (super::AuthorityBindingMode::Authoritative, _) => {
702                                return Err(ValidationError::ExtensionError(
703                                    "`authoritative let` must bind a `check` expression"
704                                        .to_string(),
705                                ));
706                            }
707                            (super::AuthorityBindingMode::Observe, _) => {
708                                return Err(ValidationError::ExtensionError(
709                                    "`observe let` must bind an `observe` expression".to_string(),
710                                ));
711                            }
712                        }
713                    }
714                    validate_protocol_effects(continuation, effect_ops, used)
715                }
716                Protocol::Choice { branches, .. } => {
717                    for branch in branches {
718                        if let Some(ChoiceGuard::Evidence {
719                            effect, operation, ..
720                        }) = &branch.guard
721                        {
722                            if !used.contains(effect) {
723                                return Err(ValidationError::ExtensionError(format!(
724                                    "effect guard `{effect}.{operation}` is not allowed without `uses {effect}`"
725                                )));
726                            }
727                            let Some(ops) = effect_ops.get(effect) else {
728                                return Err(ValidationError::ExtensionError(format!(
729                                    "effect guard references undeclared interface `{effect}`"
730                                )));
731                            };
732                            let Some(op_decl) = ops.get(operation) else {
733                                return Err(ValidationError::ExtensionError(format!(
734                                    "effect guard references undeclared operation `{effect}.{operation}`"
735                                )));
736                            };
737                            if matches!(op_decl.authority_class, EffectAuthorityClass::Observe) {
738                                return Err(ValidationError::ExtensionError(format!(
739                                    "effect guard `{effect}.{operation}` is observational and may not be invoked with `check`"
740                                )));
741                            }
742                        }
743                        validate_protocol_effects(&branch.protocol, effect_ops, used)?;
744                    }
745                    Ok(())
746                }
747                Protocol::Case { expr, branches } => {
748                    validate_expr(expr, effect_ops, used)?;
749                    for branch in branches {
750                        validate_protocol_effects(&branch.protocol, effect_ops, used)?;
751                    }
752                    Ok(())
753                }
754                Protocol::Timeout {
755                    body,
756                    on_timeout,
757                    on_cancel,
758                    ..
759                } => {
760                    validate_protocol_effects(body, effect_ops, used)?;
761                    validate_protocol_effects(on_timeout, effect_ops, used)?;
762                    if let Some(on_cancel) = on_cancel.as_deref() {
763                        validate_protocol_effects(on_cancel, effect_ops, used)?;
764                    }
765                    Ok(())
766                }
767                Protocol::Loop { body, .. } | Protocol::Rec { body, .. } => {
768                    validate_protocol_effects(body, effect_ops, used)
769                }
770                Protocol::Parallel { protocols } => {
771                    for protocol in protocols {
772                        validate_protocol_effects(protocol, effect_ops, used)?;
773                    }
774                    Ok(())
775                }
776                Protocol::Var(_) | Protocol::End => Ok(()),
777            }
778        }
779
780        validate_protocol_effects(&self.protocol, &effect_ops, &used)
781    }
782
783    fn validate_execution_profile_surface(&self) -> Result<(), ValidationError> {
784        let mut declared = BTreeSet::new();
785        for profile in self.execution_profile_declarations() {
786            if !declared.insert(profile.name.clone()) {
787                return Err(ValidationError::ExtensionError(format!(
788                    "duplicate execution profile declaration `{}`",
789                    profile.name
790                )));
791            }
792        }
793
794        for profile in self.protocol_execution_profiles() {
795            if !declared.contains(&profile) {
796                return Err(ValidationError::ExtensionError(format!(
797                    "protocol references undeclared execution profile `{profile}`"
798                )));
799            }
800        }
801
802        Ok(())
803    }
804
805    fn validate_operation_surface(&self) -> Result<(), ValidationError> {
806        let declared_agreement_profiles = self
807            .agreement_profile_declarations()
808            .into_iter()
809            .map(|profile| profile.name)
810            .collect::<BTreeSet<_>>();
811
812        for operation in self.operation_declarations() {
813            if operation.progress_contract.is_none() {
814                return Err(ValidationError::ExtensionError(format!(
815                    "operation `{}` is parity-critical and must declare a progress contract",
816                    operation.name
817                )));
818            }
819            if !operation
820                .progress_contract
821                .as_ref()
822                .is_some_and(|progress| progress.is_explicit())
823            {
824                return Err(ValidationError::ExtensionError(format!(
825                    "operation `{}` must declare explicit progress metadata using `requires`, `within`, `on timeout`, or `on stall`",
826                    operation.name
827                )));
828            }
829            let Some(agreement) = operation.agreement.as_ref() else {
830                return Err(ValidationError::ExtensionError(format!(
831                    "operation `{}` must attach a named agreement profile",
832                    operation.name
833                )));
834            };
835            if !declared_agreement_profiles.contains(&agreement.profile_name) {
836                return Err(ValidationError::ExtensionError(format!(
837                    "operation `{}` references undeclared agreement profile `{}`",
838                    operation.name, agreement.profile_name
839                )));
840            }
841        }
842        Ok(())
843    }
844
845    #[must_use]
846    pub fn language_tier_status(&self) -> LanguageTierStatus {
847        let theory_convertible = super::convert::protocol_to_global(&self.protocol).is_ok();
848        let session_blocker = find_session_projection_blocker(&self.protocol);
849        let missing_progress = self
850            .operation_declarations()
851            .iter()
852            .find(|operation| {
853                operation.progress_contract.is_none()
854                    || !operation
855                        .progress_contract
856                        .as_ref()
857                        .is_some_and(|progress| progress.is_explicit())
858            })
859            .map(|operation| {
860                format!(
861                    "operation `{}` is missing the required progress contract",
862                    operation.name
863                )
864            });
865
866        let protocol_machine_executable = missing_progress.is_none();
867        let session_projectable = session_blocker.is_none();
868        let strongest_tier = match (session_projectable, protocol_machine_executable) {
869            (true, true) => LanguageTier::SessionProjectable,
870            (false, true) => LanguageTier::ProtocolMachineExecutable,
871            (_, false) => LanguageTier::ProofOnly,
872        };
873        let diagnostic = if let Some(blocker) = session_blocker {
874            format!(
875                "full spec is valid, protocol-machine execution is available, but session projection is blocked: {blocker}"
876            )
877        } else if let Some(missing_progress) = missing_progress {
878            format!(
879                "full spec is valid for proof analysis only; protocol-machine execution is blocked: {missing_progress}"
880            )
881        } else if !theory_convertible {
882            "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()
883        } else {
884            "full spec is valid, protocol-machine execution is available, the protocol is session-projectable, and theory conversion is available".to_string()
885        };
886
887        LanguageTierStatus {
888            strongest_tier,
889            session_projectable,
890            protocol_machine_executable,
891            theory_convertible,
892            proof_only: matches!(strongest_tier, LanguageTier::ProofOnly),
893            diagnostic,
894        }
895    }
896
897    #[must_use]
898    pub fn authority_metatheory_status(&self) -> AuthorityMetatheoryStatus {
899        let strongest_tier = authority_metatheory_tier(&self.protocol);
900        let diagnostic = match authority_metatheory_blocker(&self.protocol) {
901            Some(blocker) => format!(
902                "authority-bearing constructs remain executable, but the supported theorem slice stops before this runtime semantic surface: {blocker}"
903            ),
904            None => match strongest_tier {
905                AuthorityMetatheoryTier::SessionTypedCoordination =>
906                    "the protocol stays within ordinary session-typed coordination; no authority-specific semantic proof obligations are introduced".to_string(),
907                AuthorityMetatheoryTier::EvidencePublicationSemanticObjects =>
908                    "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(),
909                AuthorityMetatheoryTier::RuntimeSemanticOnly =>
910                    "authority-bearing runtime semantics are present, but they currently sit outside the supported theorem slice".to_string(),
911            },
912        };
913
914        AuthorityMetatheoryStatus {
915            strongest_tier,
916            diagnostic,
917        }
918    }
919
920    /// Get choreography-level attributes/annotations
921    #[must_use]
922    pub fn get_attributes(&self) -> &HashMap<String, String> {
923        &self.attrs
924    }
925
926    /// Get mutable reference to choreography-level attributes
927    pub fn get_attributes_mut(&mut self) -> &mut HashMap<String, String> {
928        &mut self.attrs
929    }
930
931    /// Get a specific choreography attribute
932    #[must_use]
933    pub fn get_attribute(&self, key: &str) -> Option<&String> {
934        self.attrs.get(key)
935    }
936
937    /// Set a choreography attribute
938    pub fn set_attribute(&mut self, key: String, value: String) {
939        self.attrs.insert(key, value);
940    }
941
942    /// Remove a choreography attribute
943    pub fn remove_attribute(&mut self, key: &str) -> Option<String> {
944        self.attrs.remove(key)
945    }
946
947    /// Check if choreography has a specific attribute
948    #[must_use]
949    pub fn has_attribute(&self, key: &str) -> bool {
950        self.attrs.contains_key(key)
951    }
952
953    /// Get attribute as a specific type
954    pub fn get_attribute_as<T>(&self, key: &str) -> Option<T>
955    where
956        T: std::str::FromStr,
957    {
958        self.get_attribute(key)?.parse().ok()
959    }
960
961    /// Get attribute as boolean
962    pub fn get_attribute_as_bool(&self, key: &str) -> Option<bool> {
963        let value = self.get_attribute(key)?;
964        match value.to_lowercase().as_str() {
965            "true" | "1" | "yes" | "on" => Some(true),
966            "false" | "0" | "no" | "off" => Some(false),
967            _ => None,
968        }
969    }
970
971    /// Clear all choreography attributes
972    pub fn clear_attributes(&mut self) {
973        self.attrs.clear();
974    }
975
976    /// Count of choreography attributes
977    pub fn attribute_count(&self) -> usize {
978        self.attrs.len()
979    }
980
981    /// Get all attribute keys
982    pub fn attribute_keys(&self) -> Vec<&String> {
983        self.attrs.keys().collect()
984    }
985
986    /// Validate that required attributes are present
987    pub fn validate_required_attributes(&self, required_keys: &[&str]) -> Result<(), Vec<String>> {
988        let missing: Vec<String> = required_keys
989            .iter()
990            .filter(|&key| !self.has_attribute(key))
991            .map(|&key| key.to_string())
992            .collect();
993
994        if missing.is_empty() {
995            Ok(())
996        } else {
997            Err(missing)
998        }
999    }
1000
1001    /// Find all protocol nodes with a specific annotation
1002    pub fn find_nodes_with_annotation(&self, key: &str) -> Vec<&Protocol> {
1003        let mut nodes = Vec::new();
1004        self.protocol.collect_nodes_with_annotation(key, &mut nodes);
1005        nodes
1006    }
1007
1008    /// Find all protocol nodes with a specific annotation value
1009    pub fn find_nodes_with_annotation_value(&self, key: &str, value: &str) -> Vec<&Protocol> {
1010        let mut nodes = Vec::new();
1011        self.protocol
1012            .collect_nodes_with_annotation_value(key, value, &mut nodes);
1013        nodes
1014    }
1015
1016    /// Count total annotations across the entire choreography
1017    pub fn total_annotation_count(&self) -> usize {
1018        self.attribute_count() + self.protocol.deep_annotation_count()
1019    }
1020
1021    /// Set theorem-pack declarations for this choreography.
1022    pub fn set_theorem_packs(
1023        &mut self,
1024        theorem_packs: &[TheoremPackDeclaration],
1025    ) -> Result<(), String> {
1026        let encoded = serde_json::to_string(theorem_packs)
1027            .map_err(|e| format!("encode theorem packs: {e}"))?;
1028        self.attrs.insert(ATTR_THEOREM_PACKS.to_string(), encoded);
1029        Ok(())
1030    }
1031
1032    /// Get typed theorem-pack declarations.
1033    #[must_use]
1034    pub fn theorem_packs(&self) -> Vec<TheoremPackDeclaration> {
1035        self.attrs
1036            .get(ATTR_THEOREM_PACKS)
1037            .and_then(|s| serde_json::from_str::<Vec<TheoremPackDeclaration>>(s).ok())
1038            .unwrap_or_default()
1039    }
1040
1041    /// Set protocol-required theorem packs.
1042    pub fn set_required_theorem_packs(&mut self, required: &[String]) -> Result<(), String> {
1043        let encoded = serde_json::to_string(required)
1044            .map_err(|e| format!("encode required theorem packs: {e}"))?;
1045        self.attrs
1046            .insert(ATTR_REQUIRED_THEOREM_PACKS.to_string(), encoded);
1047        Ok(())
1048    }
1049
1050    /// Get protocol-required theorem packs.
1051    #[must_use]
1052    pub fn required_theorem_packs(&self) -> Vec<String> {
1053        self.attrs
1054            .get(ATTR_REQUIRED_THEOREM_PACKS)
1055            .and_then(|s| serde_json::from_str::<Vec<String>>(s).ok())
1056            .unwrap_or_default()
1057    }
1058
1059    /// Set inferred protocol-required theorem packs.
1060    pub fn set_inferred_required_theorem_packs(
1061        &mut self,
1062        required: &[String],
1063    ) -> Result<(), String> {
1064        let encoded = serde_json::to_string(required)
1065            .map_err(|e| format!("encode inferred theorem packs: {e}"))?;
1066        self.attrs
1067            .insert(ATTR_INFERRED_REQUIRED_THEOREM_PACKS.to_string(), encoded);
1068        Ok(())
1069    }
1070
1071    /// Get inferred protocol-required theorem packs.
1072    #[must_use]
1073    pub fn inferred_required_theorem_packs(&self) -> Vec<String> {
1074        self.attrs
1075            .get(ATTR_INFERRED_REQUIRED_THEOREM_PACKS)
1076            .and_then(|s| serde_json::from_str::<Vec<String>>(s).ok())
1077            .unwrap_or_default()
1078    }
1079
1080    /// Set role-set declarations for this choreography.
1081    pub fn set_role_sets(&mut self, role_sets: &[RoleSetDeclaration]) -> Result<(), String> {
1082        let encoded =
1083            serde_json::to_string(role_sets).map_err(|e| format!("encode role sets: {e}"))?;
1084        self.attrs.insert(ATTR_ROLE_SETS.to_string(), encoded);
1085        Ok(())
1086    }
1087
1088    /// Get typed role-set declarations.
1089    #[must_use]
1090    pub fn role_sets(&self) -> Vec<RoleSetDeclaration> {
1091        self.attrs
1092            .get(ATTR_ROLE_SETS)
1093            .and_then(|s| serde_json::from_str::<Vec<RoleSetDeclaration>>(s).ok())
1094            .unwrap_or_default()
1095    }
1096
1097    /// Set topology declarations for this choreography.
1098    pub fn set_topologies(&mut self, topologies: &[TopologyDeclaration]) -> Result<(), String> {
1099        let encoded =
1100            serde_json::to_string(topologies).map_err(|e| format!("encode topologies: {e}"))?;
1101        self.attrs.insert(ATTR_TOPOLOGIES.to_string(), encoded);
1102        Ok(())
1103    }
1104
1105    /// Get typed topology declarations.
1106    #[must_use]
1107    pub fn topologies(&self) -> Vec<TopologyDeclaration> {
1108        self.attrs
1109            .get(ATTR_TOPOLOGIES)
1110            .and_then(|s| serde_json::from_str::<Vec<TopologyDeclaration>>(s).ok())
1111            .unwrap_or_default()
1112    }
1113
1114    /// Set nominal type declarations for this choreography.
1115    pub fn set_type_declarations(&mut self, decls: &[TypeDeclaration]) -> Result<(), String> {
1116        let encoded =
1117            serde_json::to_string(decls).map_err(|e| format!("encode type declarations: {e}"))?;
1118        self.attrs
1119            .insert(ATTR_TYPE_DECLARATIONS.to_string(), encoded);
1120        Ok(())
1121    }
1122
1123    /// Get nominal type declarations.
1124    #[must_use]
1125    pub fn type_declarations(&self) -> Vec<TypeDeclaration> {
1126        self.attrs
1127            .get(ATTR_TYPE_DECLARATIONS)
1128            .and_then(|s| serde_json::from_str::<Vec<TypeDeclaration>>(s).ok())
1129            .unwrap_or_default()
1130    }
1131
1132    /// Set nominal effect interface declarations for this choreography.
1133    pub fn set_effect_interface_declarations(
1134        &mut self,
1135        decls: &[EffectInterfaceDeclaration],
1136    ) -> Result<(), String> {
1137        let encoded =
1138            serde_json::to_string(decls).map_err(|e| format!("encode effect declarations: {e}"))?;
1139        self.attrs
1140            .insert(ATTR_EFFECT_INTERFACE_DECLARATIONS.to_string(), encoded);
1141        Ok(())
1142    }
1143
1144    /// Get nominal effect interface declarations.
1145    #[must_use]
1146    pub fn effect_interface_declarations(&self) -> Vec<EffectInterfaceDeclaration> {
1147        self.attrs
1148            .get(ATTR_EFFECT_INTERFACE_DECLARATIONS)
1149            .and_then(|s| serde_json::from_str::<Vec<EffectInterfaceDeclaration>>(s).ok())
1150            .unwrap_or_default()
1151    }
1152
1153    /// Derive runtime-facing effect metadata from nominal effect declarations
1154    /// and protocol `uses` dependencies.
1155    #[must_use]
1156    pub fn effect_contract_declarations(&self) -> Vec<EffectContractDeclaration> {
1157        let used: BTreeSet<String> = self.protocol_uses().into_iter().collect();
1158        self.effect_interface_declarations()
1159            .into_iter()
1160            .flat_map(|effect| {
1161                let is_used = used.contains(effect.name.as_str());
1162                effect.operations.into_iter().map(move |op| {
1163                    let admissibility = if is_used {
1164                        "declared_use_only"
1165                    } else {
1166                        "internal_only"
1167                    };
1168                    let (totality, timeout_policy) = match op.progress.as_str() {
1169                        "immediate" => ("immediate", "none"),
1170                        "may_block" => ("may_block", "required"),
1171                        _ => ("may_block", "required"),
1172                    };
1173                    let handler_domain = if is_used { "external" } else { "internal" };
1174                    EffectContractDeclaration {
1175                        interface_name: effect.name.clone(),
1176                        operation_name: op.name,
1177                        authority_class: op.authority_class,
1178                        semantic_class: op.semantic_class.clone(),
1179                        progress: op.progress.clone(),
1180                        region: op.region.clone(),
1181                        agreement_use: op.agreement_use.clone(),
1182                        admissibility: admissibility.to_string(),
1183                        totality: totality.to_string(),
1184                        timeout_policy: timeout_policy.to_string(),
1185                        reentrancy_policy: op.reentrancy,
1186                        handler_domain: handler_domain.to_string(),
1187                    }
1188                })
1189            })
1190            .collect()
1191    }
1192
1193    /// Set explicit protocol effect dependencies.
1194    pub fn set_protocol_uses(&mut self, uses: &[String]) -> Result<(), String> {
1195        let encoded =
1196            serde_json::to_string(uses).map_err(|e| format!("encode protocol uses: {e}"))?;
1197        self.attrs.insert(ATTR_PROTOCOL_USES.to_string(), encoded);
1198        Ok(())
1199    }
1200
1201    /// Get explicit protocol effect dependencies.
1202    #[must_use]
1203    pub fn protocol_uses(&self) -> Vec<String> {
1204        self.attrs
1205            .get(ATTR_PROTOCOL_USES)
1206            .and_then(|s| serde_json::from_str::<Vec<String>>(s).ok())
1207            .unwrap_or_default()
1208    }
1209
1210    /// Set region declarations for this choreography.
1211    pub fn set_region_declarations(&mut self, decls: &[RegionDeclaration]) -> Result<(), String> {
1212        let encoded =
1213            serde_json::to_string(decls).map_err(|e| format!("encode region declarations: {e}"))?;
1214        self.attrs
1215            .insert(ATTR_REGION_DECLARATIONS.to_string(), encoded);
1216        Ok(())
1217    }
1218
1219    /// Get region declarations.
1220    #[must_use]
1221    pub fn region_declarations(&self) -> Vec<RegionDeclaration> {
1222        self.attrs
1223            .get(ATTR_REGION_DECLARATIONS)
1224            .and_then(|s| serde_json::from_str::<Vec<RegionDeclaration>>(s).ok())
1225            .unwrap_or_default()
1226    }
1227
1228    /// Set operation declarations for this choreography.
1229    pub fn set_operation_declarations(
1230        &mut self,
1231        decls: &[OperationDeclaration],
1232    ) -> Result<(), String> {
1233        let encoded = serde_json::to_string(decls)
1234            .map_err(|e| format!("encode operation declarations: {e}"))?;
1235        self.attrs
1236            .insert(ATTR_OPERATION_DECLARATIONS.to_string(), encoded);
1237        Ok(())
1238    }
1239
1240    /// Get operation declarations.
1241    #[must_use]
1242    pub fn operation_declarations(&self) -> Vec<OperationDeclaration> {
1243        self.attrs
1244            .get(ATTR_OPERATION_DECLARATIONS)
1245            .and_then(|s| serde_json::from_str::<Vec<OperationDeclaration>>(s).ok())
1246            .unwrap_or_default()
1247    }
1248
1249    /// Set guest-runtime declarations for this choreography.
1250    pub fn set_guest_runtime_declarations(
1251        &mut self,
1252        decls: &[GuestRuntimeDeclaration],
1253    ) -> Result<(), String> {
1254        let encoded = serde_json::to_string(decls)
1255            .map_err(|e| format!("encode guest runtime declarations: {e}"))?;
1256        self.attrs
1257            .insert(ATTR_GUEST_RUNTIME_DECLARATIONS.to_string(), encoded);
1258        Ok(())
1259    }
1260
1261    /// Get guest-runtime declarations.
1262    #[must_use]
1263    pub fn guest_runtime_declarations(&self) -> Vec<GuestRuntimeDeclaration> {
1264        self.attrs
1265            .get(ATTR_GUEST_RUNTIME_DECLARATIONS)
1266            .and_then(|s| serde_json::from_str::<Vec<GuestRuntimeDeclaration>>(s).ok())
1267            .unwrap_or_default()
1268    }
1269
1270    /// Set execution-profile declarations for this choreography.
1271    pub fn set_execution_profile_declarations(
1272        &mut self,
1273        decls: &[ExecutionProfileDeclaration],
1274    ) -> Result<(), String> {
1275        let encoded = serde_json::to_string(decls)
1276            .map_err(|e| format!("encode execution profile declarations: {e}"))?;
1277        self.attrs
1278            .insert(ATTR_EXECUTION_PROFILE_DECLARATIONS.to_string(), encoded);
1279        Ok(())
1280    }
1281
1282    /// Get execution-profile declarations.
1283    #[must_use]
1284    pub fn execution_profile_declarations(&self) -> Vec<ExecutionProfileDeclaration> {
1285        self.attrs
1286            .get(ATTR_EXECUTION_PROFILE_DECLARATIONS)
1287            .and_then(|s| serde_json::from_str::<Vec<ExecutionProfileDeclaration>>(s).ok())
1288            .unwrap_or_default()
1289    }
1290
1291    /// Set protocol-selected execution profiles.
1292    pub fn set_protocol_execution_profiles(&mut self, profiles: &[String]) -> Result<(), String> {
1293        let encoded = serde_json::to_string(profiles)
1294            .map_err(|e| format!("encode protocol execution profiles: {e}"))?;
1295        self.attrs
1296            .insert(ATTR_PROTOCOL_EXECUTION_PROFILES.to_string(), encoded);
1297        Ok(())
1298    }
1299
1300    /// Get protocol-selected execution profiles.
1301    #[must_use]
1302    pub fn protocol_execution_profiles(&self) -> Vec<String> {
1303        self.attrs
1304            .get(ATTR_PROTOCOL_EXECUTION_PROFILES)
1305            .and_then(|s| serde_json::from_str::<Vec<String>>(s).ok())
1306            .unwrap_or_default()
1307    }
1308
1309    /// Set reusable agreement-profile declarations for this choreography.
1310    pub fn set_agreement_profile_declarations(
1311        &mut self,
1312        decls: &[AgreementProfileDeclaration],
1313    ) -> Result<(), String> {
1314        let encoded = serde_json::to_string(decls)
1315            .map_err(|e| format!("encode agreement profile declarations: {e}"))?;
1316        self.attrs
1317            .insert(ATTR_AGREEMENT_PROFILE_DECLARATIONS.to_string(), encoded);
1318        Ok(())
1319    }
1320
1321    /// Get reusable agreement-profile declarations.
1322    #[must_use]
1323    pub fn agreement_profile_declarations(&self) -> Vec<AgreementProfileDeclaration> {
1324        self.attrs
1325            .get(ATTR_AGREEMENT_PROFILE_DECLARATIONS)
1326            .and_then(|s| serde_json::from_str::<Vec<AgreementProfileDeclaration>>(s).ok())
1327            .unwrap_or_default()
1328    }
1329
1330    fn required_theorem_pack_capabilities(&self) -> BTreeSet<String> {
1331        let required = self.required_theorem_packs();
1332        let required_set: BTreeSet<&str> = required.iter().map(String::as_str).collect();
1333        self.theorem_packs()
1334            .into_iter()
1335            .filter(|bundle| required_set.contains(bundle.name.as_str()))
1336            .flat_map(|bundle| bundle.capabilities.into_iter())
1337            .collect()
1338    }
1339
1340    fn required_protocol_machine_core_capabilities(&self) -> BTreeSet<String> {
1341        fn collect(protocol: &Protocol, out: &mut BTreeSet<String>) {
1342            if let Some(cap) = protocol.get_annotations().custom("required_capability") {
1343                out.insert(cap.to_string());
1344            }
1345            match protocol {
1346                Protocol::Begin { continuation, .. }
1347                | Protocol::Await { continuation, .. }
1348                | Protocol::Resolve { continuation, .. }
1349                | Protocol::Invalidate { continuation, .. }
1350                | Protocol::Send { continuation, .. }
1351                | Protocol::Broadcast { continuation, .. }
1352                | Protocol::Extension { continuation, .. }
1353                | Protocol::Publish { continuation, .. }
1354                | Protocol::PublishAuthority { continuation, .. }
1355                | Protocol::Materialize { continuation, .. }
1356                | Protocol::Handoff { continuation, .. }
1357                | Protocol::DependentWork { continuation, .. } => collect(continuation, out),
1358                Protocol::Choice { branches, .. } => {
1359                    for branch in branches {
1360                        collect(&branch.protocol, out);
1361                    }
1362                }
1363                Protocol::Loop { body, .. } | Protocol::Rec { body, .. } => collect(body, out),
1364                Protocol::Let { continuation, .. } => collect(continuation, out),
1365                Protocol::Case { branches, .. } => {
1366                    for branch in branches {
1367                        collect(&branch.protocol, out);
1368                    }
1369                }
1370                Protocol::Timeout {
1371                    body,
1372                    on_timeout,
1373                    on_cancel,
1374                    ..
1375                } => {
1376                    collect(body, out);
1377                    collect(on_timeout, out);
1378                    if let Some(on_cancel) = on_cancel.as_deref() {
1379                        collect(on_cancel, out);
1380                    }
1381                }
1382                Protocol::Parallel { protocols } => {
1383                    for p in protocols {
1384                        collect(p, out);
1385                    }
1386                }
1387                Protocol::Var(_) | Protocol::End => {}
1388            }
1389        }
1390
1391        let mut out = BTreeSet::new();
1392        collect(&self.protocol, &mut out);
1393        out
1394    }
1395}
1396
1397impl ProgressAttachment {
1398    /// Whether this attachment carries explicit progress policy rather than a bare name.
1399    #[must_use]
1400    pub fn is_explicit(&self) -> bool {
1401        self.requires_profile.is_some()
1402            || self.within_window.is_some()
1403            || self.on_timeout.is_some()
1404            || self.on_stall.is_some()
1405    }
1406}
1407
1408fn find_session_projection_blocker(protocol: &Protocol) -> Option<&'static str> {
1409    match protocol {
1410        Protocol::Begin { .. } => Some("begin requires protocol-machine commitment semantics"),
1411        Protocol::Await { .. } => Some("await requires protocol-machine commitment semantics"),
1412        Protocol::Resolve { .. } => Some("resolve requires protocol-machine commitment semantics"),
1413        Protocol::Invalidate { .. } => {
1414            Some("invalidate requires protocol-machine commitment semantics")
1415        }
1416        Protocol::Send { continuation, .. }
1417        | Protocol::Broadcast { continuation, .. }
1418        | Protocol::Let { continuation, .. }
1419        | Protocol::Publish { continuation, .. }
1420        | Protocol::PublishAuthority { continuation, .. }
1421        | Protocol::Materialize { continuation, .. }
1422        | Protocol::Handoff { continuation, .. }
1423        | Protocol::DependentWork { continuation, .. }
1424        | Protocol::Extension { continuation, .. } => find_session_projection_blocker(continuation),
1425        Protocol::Case { branches, .. } => branches
1426            .iter()
1427            .find_map(|branch| find_session_projection_blocker(&branch.protocol)),
1428        Protocol::Timeout {
1429            body,
1430            on_timeout,
1431            on_cancel,
1432            ..
1433        } => find_session_projection_blocker(body)
1434            .or_else(|| find_session_projection_blocker(on_timeout))
1435            .or_else(|| {
1436                on_cancel
1437                    .as_deref()
1438                    .and_then(find_session_projection_blocker)
1439            }),
1440        Protocol::Choice { branches, .. } => branches
1441            .iter()
1442            .find_map(|branch| find_session_projection_blocker(&branch.protocol)),
1443        Protocol::Loop { body, .. } | Protocol::Rec { body, .. } => {
1444            find_session_projection_blocker(body)
1445        }
1446        Protocol::Parallel { protocols } => {
1447            protocols.iter().find_map(find_session_projection_blocker)
1448        }
1449        Protocol::Var(_) | Protocol::End => None,
1450    }
1451}
1452
1453fn authority_metatheory_tier(protocol: &Protocol) -> AuthorityMetatheoryTier {
1454    match protocol {
1455        Protocol::Begin { .. }
1456        | Protocol::Await { .. }
1457        | Protocol::Resolve { .. }
1458        | Protocol::Invalidate { .. }
1459        | Protocol::Case { .. }
1460        | Protocol::Timeout { .. }
1461        | Protocol::Handoff { .. }
1462        | Protocol::DependentWork { .. }
1463        | Protocol::Parallel { .. }
1464        | Protocol::Extension { .. } => AuthorityMetatheoryTier::RuntimeSemanticOnly,
1465        Protocol::Publish { continuation, .. }
1466        | Protocol::PublishAuthority { continuation, .. }
1467        | Protocol::Materialize { continuation, .. } => authority_metatheory_tier(continuation)
1468            .max(AuthorityMetatheoryTier::EvidencePublicationSemanticObjects),
1469        Protocol::Let {
1470            expr, continuation, ..
1471        } => {
1472            let expr_tier = match expr {
1473                super::AuthorityExpr::Check { .. } | super::AuthorityExpr::Observe { .. } => {
1474                    AuthorityMetatheoryTier::EvidencePublicationSemanticObjects
1475                }
1476                super::AuthorityExpr::Transfer { .. } => {
1477                    AuthorityMetatheoryTier::RuntimeSemanticOnly
1478                }
1479                super::AuthorityExpr::Var(_)
1480                | super::AuthorityExpr::Constructor { .. }
1481                | super::AuthorityExpr::Call { .. } => {
1482                    AuthorityMetatheoryTier::SessionTypedCoordination
1483                }
1484            };
1485            expr_tier.max(authority_metatheory_tier(continuation))
1486        }
1487        Protocol::Choice { branches, .. } => branches
1488            .iter()
1489            .map(|branch| {
1490                let guard_tier = match branch.guard.as_ref() {
1491                    Some(ChoiceGuard::Evidence { .. }) => {
1492                        AuthorityMetatheoryTier::EvidencePublicationSemanticObjects
1493                    }
1494                    Some(ChoiceGuard::Predicate(_)) | None => {
1495                        AuthorityMetatheoryTier::SessionTypedCoordination
1496                    }
1497                };
1498                guard_tier.max(authority_metatheory_tier(&branch.protocol))
1499            })
1500            .max()
1501            .unwrap_or(AuthorityMetatheoryTier::SessionTypedCoordination),
1502        Protocol::Send { continuation, .. } | Protocol::Broadcast { continuation, .. } => {
1503            authority_metatheory_tier(continuation)
1504        }
1505        Protocol::Loop { body, .. } | Protocol::Rec { body, .. } => authority_metatheory_tier(body),
1506        Protocol::Var(_) | Protocol::End => AuthorityMetatheoryTier::SessionTypedCoordination,
1507    }
1508}
1509
1510fn authority_metatheory_blocker(protocol: &Protocol) -> Option<&'static str> {
1511    match protocol {
1512        Protocol::Begin { .. }
1513        | Protocol::Await { .. }
1514        | Protocol::Resolve { .. }
1515        | Protocol::Invalidate { .. } => {
1516            Some("explicit commitment lifecycle still relies on protocol-machine semantic obligations")
1517        }
1518        Protocol::Case { .. } => Some("authority-local case/of still relies on runtime semantic obligations"),
1519        Protocol::Timeout { .. } => Some("timeout/cancellation still relies on runtime progress semantics"),
1520        Protocol::Handoff { .. } => Some("semantic handoff is still justified through protocol-machine conservation rather than the small authority theorem slice"),
1521        Protocol::DependentWork { .. } => Some("dependent work remains a protocol-machine semantic obligation"),
1522        Protocol::Parallel { .. } => Some("parallel authority composition remains outside the supported authority theorem slice"),
1523        Protocol::Extension { .. } => Some("extension dispatch remains outside the authority theorem slice"),
1524        Protocol::Let { expr, continuation, .. } => match expr {
1525            super::AuthorityExpr::Transfer { .. } => {
1526                Some(
1527                    "transfer receipts are first-class transition artifacts but still rely on the wider runtime authority lifecycle",
1528                )
1529            }
1530            _ => authority_metatheory_blocker(continuation),
1531        },
1532        Protocol::Choice { branches, .. } => branches
1533            .iter()
1534            .find_map(|branch| authority_metatheory_blocker(&branch.protocol)),
1535        Protocol::Send { continuation, .. }
1536        | Protocol::Broadcast { continuation, .. }
1537        | Protocol::Publish { continuation, .. }
1538        | Protocol::PublishAuthority { continuation, .. }
1539        | Protocol::Materialize { continuation, .. } => authority_metatheory_blocker(continuation),
1540        Protocol::Loop { body, .. } | Protocol::Rec { body, .. } => authority_metatheory_blocker(body),
1541        Protocol::Var(_) | Protocol::End => None,
1542    }
1543}
1544
1545#[cfg(test)]
1546mod tests {
1547    use super::*;
1548    use quote::format_ident;
1549
1550    #[test]
1551    fn proof_bundle_metadata_roundtrip() {
1552        let mut choreo = Choreography {
1553            name: format_ident!("RoundTrip"),
1554            namespace: None,
1555            roles: Vec::new(),
1556            protocol: Protocol::End,
1557            attrs: HashMap::new(),
1558        };
1559        let bundles = vec![
1560            TheoremPackDeclaration {
1561                name: "Base".to_string(),
1562                capabilities: vec!["delegation".to_string()],
1563                version: None,
1564                issuer: None,
1565                constraints: Vec::new(),
1566            },
1567            TheoremPackDeclaration {
1568                name: "Guard".to_string(),
1569                capabilities: vec!["guard_tokens".to_string()],
1570                version: None,
1571                issuer: None,
1572                constraints: Vec::new(),
1573            },
1574        ];
1575        let required = vec!["Base".to_string()];
1576
1577        choreo
1578            .set_theorem_packs(&bundles)
1579            .expect("set proof bundles");
1580        choreo
1581            .set_required_theorem_packs(&required)
1582            .expect("set required bundles");
1583
1584        assert_eq!(choreo.theorem_packs(), bundles);
1585        assert_eq!(choreo.required_theorem_packs(), required);
1586    }
1587}