Skip to main content

uptrakit_surfaces/
protocol.rs

1use serde::{Deserialize, Serialize};
2use serde_json::{Map, Value};
3use std::collections::HashSet;
4use thiserror::Error;
5use uuid::Uuid;
6
7use crate::{
8    Capability, CapabilitySet, DataSourceDescriptor, DataSourceKind, FrameworkGeneration,
9    FrameworkGenerationRange, InteractionDescriptor, InteractionId, InteractionKind,
10    InteractionTransport, ProviderKind, Scope, SlotValidationError, SurfaceDescriptor, SurfaceId,
11    SurfaceNode, SurfaceSlotDef, Targeting, validate_slot_id, validate_surface_identifier,
12};
13
14#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
15pub struct SurfaceRegistration {
16    pub provider: ProviderIdentity,
17    pub framework_generation: FrameworkGeneration,
18    pub capabilities: CapabilitySet,
19    pub effective_tenant_binding: EffectiveTenantBinding,
20    #[serde(default, skip_serializing_if = "Vec::is_empty")]
21    pub surfaces: Vec<RegisteredSurface>,
22    #[serde(default, skip_serializing_if = "Option::is_none")]
23    pub encryption_metadata: Option<ProviderEncryptionMetadata>,
24}
25
26impl SurfaceRegistration {
27    /// Validates a registration payload against policy and contract rules.
28    ///
29    /// # Errors
30    /// Returns [`SurfaceRegistrationError`] when any validation check fails,
31    /// including unsupported framework generation, missing required
32    /// capabilities, invalid or duplicate identifiers, slot violations,
33    /// provider-kind mismatches, invalid interaction/data-source declarations,
34    /// or broken cross-reference links in the surface graph.
35    pub fn validate_against(
36        &self,
37        policy: &SurfaceRegistrationPolicy,
38    ) -> Result<(), SurfaceRegistrationError> {
39        validate_supported_generation(self, policy)?;
40        validate_required_registration_capabilities(self, policy)?;
41        validate_registered_surfaces(self)?;
42        Ok(())
43    }
44}
45
46fn validate_supported_generation(
47    registration: &SurfaceRegistration,
48    policy: &SurfaceRegistrationPolicy,
49) -> Result<(), SurfaceRegistrationError> {
50    if policy
51        .supported_generation
52        .includes(registration.framework_generation)
53    {
54        return Ok(());
55    }
56
57    Err(registration_error(
58        SurfaceRegistrationErrorCode::UnsupportedGeneration,
59        format!(
60            "framework generation {}.{} is outside supported range {}.{}..={}.{}",
61            registration.framework_generation.major,
62            registration.framework_generation.minor,
63            policy.supported_generation.min.major,
64            policy.supported_generation.min.minor,
65            policy.supported_generation.max.major,
66            policy.supported_generation.max.minor,
67        ),
68    ))
69}
70
71fn validate_required_registration_capabilities(
72    registration: &SurfaceRegistration,
73    policy: &SurfaceRegistrationPolicy,
74) -> Result<(), SurfaceRegistrationError> {
75    if registration
76        .capabilities
77        .contains_all(&policy.required_capabilities)
78    {
79        return Ok(());
80    }
81
82    Err(missing_capability(
83        "registration is missing one or more required capabilities",
84    ))
85}
86
87fn validate_registered_surfaces(
88    registration: &SurfaceRegistration,
89) -> Result<(), SurfaceRegistrationError> {
90    RegisteredSurfacesValidator::new(registration).validate()
91}
92
93struct RegisteredSurfacesValidator<'a> {
94    provider_kind: ProviderKind,
95    capabilities: &'a CapabilitySet,
96    surfaces: &'a [RegisteredSurface],
97    surface_ids: HashSet<&'a str>,
98    single_entry_slots: HashSet<&'static str>,
99}
100
101impl<'a> RegisteredSurfacesValidator<'a> {
102    fn new(registration: &'a SurfaceRegistration) -> Self {
103        Self {
104            provider_kind: registration.provider.provider_kind,
105            capabilities: &registration.capabilities,
106            surfaces: &registration.surfaces,
107            surface_ids: HashSet::new(),
108            single_entry_slots: HashSet::new(),
109        }
110    }
111
112    fn validate(mut self) -> Result<(), SurfaceRegistrationError> {
113        for surface in self.surfaces {
114            self.validate_surface(surface)?;
115        }
116
117        Ok(())
118    }
119
120    fn validate_surface(
121        &mut self,
122        surface: &'a RegisteredSurface,
123    ) -> Result<(), SurfaceRegistrationError> {
124        validate_surface_descriptor_rules(
125            surface,
126            self.provider_kind,
127            self.capabilities,
128            &mut self.surface_ids,
129            &mut self.single_entry_slots,
130        )?;
131
132        let interaction_ids = validate_surface_interaction_rules(surface)?;
133        validate_workflow_step_references(
134            &surface.descriptor.surface_id,
135            &surface.interactions,
136            &interaction_ids,
137        )?;
138
139        let data_source_ids = validate_surface_data_source_rules(surface)?;
140        validate_root_node_references(
141            &surface.descriptor.surface_id,
142            &surface.descriptor.root_node,
143            &interaction_ids,
144            &data_source_ids,
145        )
146    }
147}
148
149fn validate_surface_descriptor_rules<'a>(
150    surface: &'a RegisteredSurface,
151    registration_provider_kind: ProviderKind,
152    capabilities: &CapabilitySet,
153    surface_ids: &mut HashSet<&'a str>,
154    single_entry_slots: &mut HashSet<&'static str>,
155) -> Result<(), SurfaceRegistrationError> {
156    validate_unique_surface_id(surface, surface_ids)?;
157    validate_surface_provider_kind(surface, registration_provider_kind)?;
158    let slot_def = validate_surface_slot(surface)?;
159    validate_single_entry_slot_occupancy(slot_def, single_entry_slots)?;
160    validate_surface_priority_range(surface, slot_def)?;
161    validate_surface_required_capabilities(surface, capabilities)?;
162    validate_surface_usage_capabilities(
163        &surface.descriptor.surface_id,
164        capabilities,
165        &surface.descriptor.root_node,
166        &surface.descriptor.targeting,
167        &surface.interactions,
168        &surface.data_sources,
169    )
170}
171
172fn validate_unique_surface_id<'a>(
173    surface: &'a RegisteredSurface,
174    surface_ids: &mut HashSet<&'a str>,
175) -> Result<(), SurfaceRegistrationError> {
176    if surface_ids.insert(surface.descriptor.surface_id.as_str()) {
177        return Ok(());
178    }
179
180    Err(invalid_contract(format!(
181        "duplicate surface_id `{}` within registration batch",
182        surface.descriptor.surface_id
183    )))
184}
185
186fn validate_surface_provider_kind(
187    surface: &RegisteredSurface,
188    registration_provider_kind: ProviderKind,
189) -> Result<(), SurfaceRegistrationError> {
190    if surface.descriptor.provider_kind == registration_provider_kind {
191        return Ok(());
192    }
193
194    Err(invalid_contract(format!(
195        "surface `{}` provider_kind does not match registration provider_kind",
196        surface.descriptor.surface_id
197    )))
198}
199
200fn validate_surface_slot(
201    surface: &RegisteredSurface,
202) -> Result<&'static SurfaceSlotDef, SurfaceRegistrationError> {
203    validate_slot_id(&surface.descriptor.slot).map_err(map_slot_validation_error)
204}
205
206fn validate_single_entry_slot_occupancy(
207    slot_def: &'static SurfaceSlotDef,
208    single_entry_slots: &mut HashSet<&'static str>,
209) -> Result<(), SurfaceRegistrationError> {
210    if slot_def.multi_entry || single_entry_slots.insert(slot_def.id) {
211        return Ok(());
212    }
213
214    Err(invalid_contract(format!(
215        "slot `{}` is single-entry and cannot accept multiple surfaces in one registration batch",
216        slot_def.id
217    )))
218}
219
220fn validate_surface_priority_range(
221    surface: &RegisteredSurface,
222    slot_def: &'static SurfaceSlotDef,
223) -> Result<(), SurfaceRegistrationError> {
224    if surface.descriptor.provider_kind == ProviderKind::BuiltIn {
225        return Ok(());
226    }
227
228    if surface.descriptor.priority >= slot_def.provider_priority_min
229        && surface.descriptor.priority <= slot_def.provider_priority_max
230    {
231        return Ok(());
232    }
233
234    Err(invalid_contract(format!(
235        "surface `{}` priority {} is outside slot `{}` provider range {}..={}",
236        surface.descriptor.surface_id,
237        surface.descriptor.priority,
238        slot_def.id,
239        slot_def.provider_priority_min,
240        slot_def.provider_priority_max
241    )))
242}
243
244fn validate_surface_required_capabilities(
245    surface: &RegisteredSurface,
246    capabilities: &CapabilitySet,
247) -> Result<(), SurfaceRegistrationError> {
248    if capabilities.contains_all(&surface.descriptor.required_capabilities) {
249        return Ok(());
250    }
251
252    Err(missing_capability(format!(
253        "surface `{}` requires capabilities not advertised by registration",
254        surface.descriptor.surface_id
255    )))
256}
257
258fn validate_surface_interaction_rules(
259    surface: &RegisteredSurface,
260) -> Result<HashSet<&str>, SurfaceRegistrationError> {
261    let mut interaction_ids: HashSet<&str> = HashSet::new();
262    for interaction in &surface.interactions {
263        validate_unique_interaction_id(surface, interaction, &mut interaction_ids)?;
264        validate_interaction_provider_rules(surface, interaction)?;
265    }
266
267    Ok(interaction_ids)
268}
269
270fn validate_unique_interaction_id<'a>(
271    surface: &'a RegisteredSurface,
272    interaction: &'a InteractionDescriptor,
273    interaction_ids: &mut HashSet<&'a str>,
274) -> Result<(), SurfaceRegistrationError> {
275    if interaction_ids.insert(interaction.interaction_id.as_str()) {
276        return Ok(());
277    }
278
279    Err(invalid_contract(format!(
280        "duplicate interaction_id `{}` within surface `{}`",
281        interaction.interaction_id, surface.descriptor.surface_id
282    )))
283}
284
285fn validate_interaction_provider_rules(
286    surface: &RegisteredSurface,
287    interaction: &InteractionDescriptor,
288) -> Result<(), SurfaceRegistrationError> {
289    interaction
290        .validate_for_provider(surface.descriptor.provider_kind)
291        .map_err(|err| invalid_contract(err.to_string()))
292}
293
294fn validate_surface_data_source_rules(
295    surface: &RegisteredSurface,
296) -> Result<HashSet<&str>, SurfaceRegistrationError> {
297    let mut data_source_ids: HashSet<&str> = HashSet::new();
298    for data_source in &surface.data_sources {
299        validate_unique_data_source_id(surface, data_source, &mut data_source_ids)?;
300        validate_data_source_provider_rules(surface, data_source)?;
301    }
302
303    Ok(data_source_ids)
304}
305
306fn validate_unique_data_source_id<'a>(
307    surface: &'a RegisteredSurface,
308    data_source: &'a DataSourceDescriptor,
309    data_source_ids: &mut HashSet<&'a str>,
310) -> Result<(), SurfaceRegistrationError> {
311    if data_source_ids.insert(data_source.data_source_id.as_str()) {
312        return Ok(());
313    }
314
315    Err(invalid_contract(format!(
316        "duplicate data_source_id `{}` within surface `{}`",
317        data_source.data_source_id, surface.descriptor.surface_id
318    )))
319}
320
321fn validate_data_source_provider_rules(
322    surface: &RegisteredSurface,
323    data_source: &DataSourceDescriptor,
324) -> Result<(), SurfaceRegistrationError> {
325    data_source
326        .validate_for_provider(surface.descriptor.provider_kind)
327        .map_err(|err| invalid_contract(err.to_string()))
328}
329
330fn map_slot_validation_error(err: SlotValidationError) -> SurfaceRegistrationError {
331    let code = match err {
332        SlotValidationError::UnknownSlot(_) => SurfaceRegistrationErrorCode::InvalidSlot,
333        SlotValidationError::InvalidIdentifier(_) => SurfaceRegistrationErrorCode::InvalidContract,
334    };
335
336    registration_error(code, err.to_string())
337}
338
339fn registration_error(
340    code: SurfaceRegistrationErrorCode,
341    message: impl Into<String>,
342) -> SurfaceRegistrationError {
343    SurfaceRegistrationError::new(code, message.into())
344}
345
346fn invalid_contract(message: impl Into<String>) -> SurfaceRegistrationError {
347    registration_error(SurfaceRegistrationErrorCode::InvalidContract, message)
348}
349
350fn missing_capability(message: impl Into<String>) -> SurfaceRegistrationError {
351    registration_error(SurfaceRegistrationErrorCode::MissingCapability, message)
352}
353
354fn validate_surface_usage_capabilities(
355    surface_id: &SurfaceId,
356    capabilities: &CapabilitySet,
357    root_node: &SurfaceNode,
358    targeting: &Targeting,
359    interactions: &[InteractionDescriptor],
360    data_sources: &[DataSourceDescriptor],
361) -> Result<(), SurfaceRegistrationError> {
362    validate_node_capabilities(surface_id, capabilities, root_node)?;
363
364    let targeting_capability = match targeting {
365        Targeting::Universal => Capability::UniversalTargeting,
366        Targeting::Targeted => Capability::TargetedTargeting,
367    };
368    require_capability(
369        capabilities,
370        targeting_capability,
371        surface_id,
372        "targeting mode",
373    )?;
374
375    for interaction in interactions {
376        let kind_capability = match interaction.kind {
377            InteractionKind::MutationAction => Capability::MutationAction,
378            InteractionKind::FormSubmit => Capability::FormSubmit,
379            InteractionKind::Workflow => Capability::Workflow,
380            InteractionKind::Navigate => Capability::Navigate,
381            InteractionKind::DataLoad => Capability::DataLoad,
382            InteractionKind::ConfirmableAction => Capability::ConfirmableAction,
383        };
384        require_capability(
385            capabilities,
386            kind_capability,
387            surface_id,
388            "interaction kind",
389        )?;
390
391        if matches!(
392            &interaction.transport,
393            InteractionTransport::ProviderProxied
394        ) {
395            require_capability(
396                capabilities,
397                Capability::ProviderInitiatedActions,
398                surface_id,
399                "interaction transport",
400            )?;
401        }
402
403        if !interaction.sensitive_fields.is_empty() {
404            require_capability(
405                capabilities,
406                Capability::SensitiveFields,
407                surface_id,
408                "sensitive fields",
409            )?;
410        }
411    }
412
413    for data_source in data_sources {
414        let kind_capability = match &data_source.kind {
415            DataSourceKind::Static { .. } => Capability::StaticDataSource,
416            DataSourceKind::ControllerQuery { .. } => Capability::ControllerQueryDataSource,
417            DataSourceKind::ProviderQuery { .. } => Capability::ProviderQueryDataSource,
418        };
419        require_capability(
420            capabilities,
421            kind_capability,
422            surface_id,
423            "data source kind",
424        )?;
425    }
426
427    Ok(())
428}
429
430fn validate_node_capabilities(
431    surface_id: &SurfaceId,
432    capabilities: &CapabilitySet,
433    node: &SurfaceNode,
434) -> Result<(), SurfaceRegistrationError> {
435    let node_capability = match node {
436        SurfaceNode::Section { children, .. } => {
437            for child in children {
438                validate_node_capabilities(surface_id, capabilities, child)?;
439            }
440            Capability::SectionNode
441        }
442        SurfaceNode::TextBlock { .. } => Capability::TextBlockNode,
443        SurfaceNode::KeyValue { .. } => Capability::KeyValueNode,
444        SurfaceNode::Table { .. } => Capability::TableNode,
445        SurfaceNode::Form { .. } => Capability::FormNode,
446        SurfaceNode::ActionBar { .. } => Capability::ActionBarNode,
447        SurfaceNode::Tabs { tabs } => {
448            for tab in tabs {
449                validate_node_capabilities(surface_id, capabilities, &tab.root)?;
450            }
451            Capability::TabsNode
452        }
453        SurfaceNode::Callout { .. } => Capability::CalloutNode,
454        SurfaceNode::EmptyState { .. } => Capability::EmptyStateNode,
455        SurfaceNode::ModalTrigger { modal_nodes, .. } => {
456            for child in modal_nodes {
457                validate_node_capabilities(surface_id, capabilities, child)?;
458            }
459            Capability::ModalTriggerNode
460        }
461        SurfaceNode::WorkflowTrigger { step_nodes, .. } => {
462            for child in step_nodes {
463                validate_node_capabilities(surface_id, capabilities, child)?;
464            }
465            Capability::WorkflowTriggerNode
466        }
467    };
468
469    require_capability(capabilities, node_capability, surface_id, "root_node kind")
470}
471
472fn require_capability(
473    capabilities: &CapabilitySet,
474    required: Capability,
475    surface_id: &SurfaceId,
476    usage: &str,
477) -> Result<(), SurfaceRegistrationError> {
478    if capabilities.0.contains(&required) {
479        return Ok(());
480    }
481
482    Err(missing_capability(format!(
483        "surface `{}` uses {} that requires capability `{}`",
484        surface_id,
485        usage,
486        serde_json::to_string(&required)
487            .unwrap_or_else(|_| "\"unknown\"".to_owned())
488            .trim_matches('"')
489    )))
490}
491
492fn validate_root_node_references(
493    surface_id: &SurfaceId,
494    node: &SurfaceNode,
495    interaction_ids: &HashSet<&str>,
496    data_source_ids: &HashSet<&str>,
497) -> Result<(), SurfaceRegistrationError> {
498    RootNodeReferenceValidator::new(surface_id, interaction_ids, data_source_ids).validate(node)
499}
500
501struct RootNodeReferenceValidator<'a> {
502    surface_id: &'a SurfaceId,
503    interaction_ids: &'a HashSet<&'a str>,
504    data_source_ids: &'a HashSet<&'a str>,
505}
506
507impl<'a> RootNodeReferenceValidator<'a> {
508    fn new(
509        surface_id: &'a SurfaceId,
510        interaction_ids: &'a HashSet<&'a str>,
511        data_source_ids: &'a HashSet<&'a str>,
512    ) -> Self {
513        Self {
514            surface_id,
515            interaction_ids,
516            data_source_ids,
517        }
518    }
519
520    fn validate(&self, node: &SurfaceNode) -> Result<(), SurfaceRegistrationError> {
521        match node {
522            SurfaceNode::Section { children, .. } => self.validate_children(children),
523            SurfaceNode::TextBlock { .. } => Ok(()),
524            SurfaceNode::KeyValue { data_source_id } => {
525                self.require_data_source_reference(data_source_id.as_str())
526            }
527            SurfaceNode::Table {
528                data_source_id,
529                row_actions,
530                ..
531            } => {
532                self.require_data_source_reference(data_source_id.as_str())?;
533                self.validate_table_row_actions(row_actions)
534            }
535            SurfaceNode::Form { interaction_id } => {
536                self.require_root_interaction_reference(interaction_id.as_str())
537            }
538            SurfaceNode::ActionBar { action_ids } => self.validate_action_bar(action_ids),
539            SurfaceNode::Tabs { tabs } => self.validate_tabs(tabs),
540            SurfaceNode::Callout { .. } | SurfaceNode::EmptyState { .. } => Ok(()),
541            SurfaceNode::ModalTrigger {
542                interaction_id,
543                modal_nodes,
544            } => {
545                self.require_root_interaction_reference(interaction_id.as_str())?;
546                self.validate_children(modal_nodes)
547            }
548            SurfaceNode::WorkflowTrigger {
549                interaction_id,
550                step_nodes,
551            } => {
552                self.require_root_interaction_reference(interaction_id.as_str())?;
553                self.validate_children(step_nodes)
554            }
555        }
556    }
557
558    fn validate_children(&self, nodes: &[SurfaceNode]) -> Result<(), SurfaceRegistrationError> {
559        for child in nodes {
560            self.validate(child)?;
561        }
562        Ok(())
563    }
564
565    fn validate_table_row_actions(
566        &self,
567        row_actions: &[crate::SurfaceTableRowAction],
568    ) -> Result<(), SurfaceRegistrationError> {
569        for row_action in row_actions {
570            self.require_interaction_reference(row_action.interaction_id.as_str(), || {
571                format!(
572                    "surface `{}` table references unknown row-action interaction_id `{}`",
573                    self.surface_id, row_action.interaction_id
574                )
575            })?;
576        }
577        Ok(())
578    }
579
580    fn validate_action_bar(
581        &self,
582        action_ids: &[InteractionId],
583    ) -> Result<(), SurfaceRegistrationError> {
584        for action_id in action_ids {
585            self.require_root_interaction_reference(action_id.as_str())?;
586        }
587        Ok(())
588    }
589
590    fn validate_tabs(&self, tabs: &[crate::SurfaceTab]) -> Result<(), SurfaceRegistrationError> {
591        let mut tab_ids: HashSet<&str> = HashSet::new();
592
593        for tab in tabs {
594            validate_surface_identifier(tab.id.as_str()).map_err(|err| {
595                invalid_contract(format!(
596                    "surface `{}` root_node contains invalid tab id `{}`: {}",
597                    self.surface_id, tab.id, err
598                ))
599            })?;
600
601            if !tab_ids.insert(tab.id.as_str()) {
602                return Err(invalid_contract(format!(
603                    "surface `{}` root_node contains duplicate tab id `{}` within one tabs node",
604                    self.surface_id, tab.id
605                )));
606            }
607
608            self.validate(&tab.root)?;
609        }
610
611        Ok(())
612    }
613
614    fn require_root_interaction_reference(
615        &self,
616        interaction_id: &str,
617    ) -> Result<(), SurfaceRegistrationError> {
618        self.require_interaction_reference(interaction_id, || {
619            format!(
620                "surface `{}` root_node references unknown interaction_id `{}`",
621                self.surface_id, interaction_id
622            )
623        })
624    }
625
626    fn require_data_source_reference(
627        &self,
628        data_source_id: &str,
629    ) -> Result<(), SurfaceRegistrationError> {
630        ensure_known_reference(self.data_source_ids, data_source_id, || {
631            format!(
632                "surface `{}` root_node references unknown data_source_id `{}`",
633                self.surface_id, data_source_id
634            )
635        })
636    }
637
638    fn require_interaction_reference(
639        &self,
640        interaction_id: &str,
641        error_message: impl FnOnce() -> String,
642    ) -> Result<(), SurfaceRegistrationError> {
643        ensure_known_reference(self.interaction_ids, interaction_id, error_message)
644    }
645}
646
647fn validate_workflow_step_references(
648    surface_id: &SurfaceId,
649    interactions: &[InteractionDescriptor],
650    interaction_ids: &HashSet<&str>,
651) -> Result<(), SurfaceRegistrationError> {
652    WorkflowStepReferenceValidator::new(surface_id, interaction_ids).validate(interactions)
653}
654
655struct WorkflowStepReferenceValidator<'a> {
656    surface_id: &'a SurfaceId,
657    interaction_ids: &'a HashSet<&'a str>,
658}
659
660impl<'a> WorkflowStepReferenceValidator<'a> {
661    fn new(surface_id: &'a SurfaceId, interaction_ids: &'a HashSet<&'a str>) -> Self {
662        Self {
663            surface_id,
664            interaction_ids,
665        }
666    }
667
668    fn validate(
669        &self,
670        interactions: &[InteractionDescriptor],
671    ) -> Result<(), SurfaceRegistrationError> {
672        for interaction in interactions {
673            if interaction.kind != InteractionKind::Workflow {
674                continue;
675            }
676            self.validate_workflow_interaction_steps(interaction)?;
677        }
678
679        Ok(())
680    }
681
682    fn validate_workflow_interaction_steps(
683        &self,
684        interaction: &InteractionDescriptor,
685    ) -> Result<(), SurfaceRegistrationError> {
686        for step in &interaction.workflow_steps {
687            if let Some(submit_interaction_id) = &step.submit_interaction_id {
688                ensure_known_reference(
689                    self.interaction_ids,
690                    submit_interaction_id.as_str(),
691                    || {
692                        format!(
693                            "surface `{}` workflow interaction `{}` references unknown submit_interaction_id `{}` in step `{}`",
694                            self.surface_id,
695                            interaction.interaction_id,
696                            submit_interaction_id,
697                            step.step_id
698                        )
699                    },
700                )?;
701            }
702
703            if let Some(form_ui) = &step.form_ui
704                && let Some(pre_load_interaction_id) = &form_ui.pre_load_interaction_id
705            {
706                ensure_known_reference(
707                    self.interaction_ids,
708                    pre_load_interaction_id.as_str(),
709                    || {
710                        format!(
711                            "surface `{}` workflow interaction `{}` references unknown pre_load_interaction_id `{}` in step `{}`",
712                            self.surface_id,
713                            interaction.interaction_id,
714                            pre_load_interaction_id,
715                            step.step_id
716                        )
717                    },
718                )?;
719            }
720        }
721
722        Ok(())
723    }
724}
725
726fn ensure_known_reference(
727    known_ids: &HashSet<&str>,
728    reference_id: &str,
729    error_message: impl FnOnce() -> String,
730) -> Result<(), SurfaceRegistrationError> {
731    if known_ids.contains(reference_id) {
732        return Ok(());
733    }
734
735    Err(invalid_contract(error_message()))
736}
737
738#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
739pub struct ProviderIdentity {
740    pub provider_id: String,
741    pub provider_kind: ProviderKind,
742    pub provider_namespace: String,
743}
744
745#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
746pub struct EffectiveTenantBinding {
747    pub scope: Scope,
748    #[serde(default, skip_serializing_if = "Option::is_none")]
749    pub tenant_id: Option<String>,
750}
751
752#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
753pub struct ProviderEncryptionMetadata {
754    pub key_id: String,
755    pub algorithm: ProviderEncryptionAlgorithm,
756    pub public_key: String,
757}
758
759#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
760#[serde(rename_all = "snake_case")]
761pub enum ProviderEncryptionAlgorithm {
762    EciesP256,
763}
764
765#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
766pub struct RegisteredSurface {
767    pub descriptor: SurfaceDescriptor,
768    #[serde(default, skip_serializing_if = "Vec::is_empty")]
769    pub interactions: Vec<InteractionDescriptor>,
770    #[serde(default, skip_serializing_if = "Vec::is_empty")]
771    pub data_sources: Vec<DataSourceDescriptor>,
772}
773
774#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
775pub struct SurfaceRegistrationPolicy {
776    pub supported_generation: FrameworkGenerationRange,
777    pub required_capabilities: CapabilitySet,
778}
779
780#[derive(Debug, Clone, PartialEq, Eq, Error)]
781#[error("{code:?}: {message}")]
782pub struct SurfaceRegistrationError {
783    pub code: SurfaceRegistrationErrorCode,
784    pub message: String,
785}
786
787impl SurfaceRegistrationError {
788    pub fn new(code: SurfaceRegistrationErrorCode, message: String) -> Self {
789        Self { code, message }
790    }
791}
792
793#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
794#[serde(rename_all = "snake_case")]
795pub enum SurfaceRegistrationErrorCode {
796    UnsupportedGeneration,
797    MissingCapability,
798    InvalidSlot,
799    InvalidContract,
800}
801
802#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
803pub struct SurfaceActionRequest {
804    pub request_id: Uuid,
805    pub tenant_id: String,
806    pub surface_id: SurfaceId,
807    pub interaction_id: InteractionId,
808    pub idempotency_key: String,
809    #[serde(default, skip_serializing_if = "Option::is_none")]
810    pub target_provider_id: Option<String>,
811    pub caller_origin: CallerOrigin,
812    #[serde(default, skip_serializing_if = "Map::is_empty")]
813    pub params: Map<String, Value>,
814    #[serde(default, skip_serializing_if = "Option::is_none")]
815    pub encrypted_sensitive_params: Option<EncryptedSensitiveParams>,
816}
817
818#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
819#[serde(rename_all = "snake_case", tag = "kind")]
820pub enum CallerOrigin {
821    UserSession { user_id: String, session_id: String },
822    BuiltInSystem { principal: String },
823    Provider { provider_id: String },
824}
825
826#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
827pub struct EncryptedSensitiveParams {
828    pub key_id: String,
829    pub algorithm: ProviderEncryptionAlgorithm,
830    pub ciphertext_b64: String,
831}
832
833#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
834pub struct SurfaceActionCancel {
835    pub request_id: Uuid,
836    pub target_provider_id: String,
837    pub reason: SurfaceActionCancelReason,
838}
839
840#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
841#[serde(rename_all = "snake_case")]
842pub enum SurfaceActionCancelReason {
843    Timeout,
844    RequestCancelled,
845    ProviderDisconnected,
846}
847
848#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
849pub struct SurfaceActionResponse {
850    pub request_id: Uuid,
851    pub success: bool,
852    #[serde(default, skip_serializing_if = "Option::is_none")]
853    pub result: Option<Value>,
854    #[serde(default, skip_serializing_if = "Option::is_none")]
855    pub error: Option<SurfaceActionError>,
856}
857
858#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
859pub struct SurfaceActionError {
860    pub code: SurfaceActionErrorCode,
861    pub message: String,
862    #[serde(default, skip_serializing_if = "Option::is_none")]
863    pub details: Option<Value>,
864}
865
866#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
867#[serde(rename_all = "snake_case")]
868pub enum SurfaceActionErrorCode {
869    PermissionDenied,
870    InvalidRequest,
871    SchemaValidationFailed,
872    UnsupportedCapability,
873    ProviderUnavailable,
874    Timeout,
875    DuplicateRequest,
876    InternalError,
877}