Skip to main content

presolve_compiler/
form_ownership.rs

1use std::collections::{BTreeMap, BTreeSet};
2
3use crate::{
4    ApplicationSemanticModel, ComponentBuildRoot, ComponentNode, ComponentRootId, FieldBindingId,
5    FieldId, FormEntity, FormFieldBinding, FormFieldEntity, FormId, FormOwnershipGraphId,
6    SemanticId, SemanticOwner, SemanticReference, SemanticReferenceKind, SourceProvenance,
7    TemplateSemanticEntity, TemplateSemanticKind,
8};
9
10/// A typed sum key retaining each existing canonical identity without
11/// re-encoding it into a Form-specific node identity.
12#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
13pub enum FormOwnershipNodeKey {
14    Component(SemanticId),
15    Form(FormId),
16    FormField(FieldId),
17    TemplateControl(SemanticId),
18    FieldBinding(FieldBindingId),
19}
20
21impl FormOwnershipNodeKey {
22    #[must_use]
23    pub fn semantic_id(&self) -> &SemanticId {
24        match self {
25            Self::Component(id) | Self::TemplateControl(id) => id,
26            Self::Form(id) => id.as_semantic_id(),
27            Self::FormField(id) => id.as_semantic_id(),
28            Self::FieldBinding(id) => id.as_semantic_id(),
29        }
30    }
31}
32
33#[derive(Debug, Clone, PartialEq, Eq)]
34pub enum FormOwnershipNode {
35    Component {
36        id: SemanticId,
37        provenance: SourceProvenance,
38    },
39    Form {
40        id: FormId,
41        provenance: SourceProvenance,
42    },
43    FormField {
44        id: FieldId,
45        provenance: SourceProvenance,
46    },
47    TemplateControl {
48        id: SemanticId,
49        provenance: SourceProvenance,
50    },
51    FieldBinding {
52        id: FieldBindingId,
53        provenance: SourceProvenance,
54    },
55}
56
57impl FormOwnershipNode {
58    #[must_use]
59    pub fn key(&self) -> FormOwnershipNodeKey {
60        match self {
61            Self::Component { id, .. } => FormOwnershipNodeKey::Component(id.clone()),
62            Self::Form { id, .. } => FormOwnershipNodeKey::Form(id.clone()),
63            Self::FormField { id, .. } => FormOwnershipNodeKey::FormField(id.clone()),
64            Self::TemplateControl { id, .. } => FormOwnershipNodeKey::TemplateControl(id.clone()),
65            Self::FieldBinding { id, .. } => FormOwnershipNodeKey::FieldBinding(id.clone()),
66        }
67    }
68
69    #[must_use]
70    pub const fn provenance(&self) -> &SourceProvenance {
71        match self {
72            Self::Component { provenance, .. }
73            | Self::Form { provenance, .. }
74            | Self::FormField { provenance, .. }
75            | Self::TemplateControl { provenance, .. }
76            | Self::FieldBinding { provenance, .. } => provenance,
77        }
78    }
79}
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
82pub enum FormOwnershipEdgeKind {
83    ComponentOwnsForm,
84    FormOwnsField,
85    TemplateControlOwnsBinding,
86}
87
88#[derive(Debug, Clone, PartialEq, Eq)]
89pub struct FormOwnershipEdge {
90    pub owner: FormOwnershipNodeKey,
91    pub child: FormOwnershipNodeKey,
92    pub kind: FormOwnershipEdgeKind,
93    pub provenance: SourceProvenance,
94}
95
96#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
97pub enum FormReferenceKind {
98    FieldBindingField,
99    FieldBindingForm,
100}
101
102#[derive(Debug, Clone, PartialEq, Eq)]
103pub struct FormReferenceEdge {
104    pub kind: FormReferenceKind,
105    pub source: FormOwnershipNodeKey,
106    pub target: FormOwnershipNodeKey,
107    pub provenance: SourceProvenance,
108}
109
110#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
111pub enum FormOwnershipIntegrityKind {
112    DuplicateNode,
113    MissingOwner,
114    UnknownOwnershipEndpoint,
115    MultipleOwners,
116    OwnershipCycle,
117    UnreachableNode,
118    UnknownReferenceEndpoint,
119    ComponentOwnershipMismatch,
120    FieldFormMismatch,
121    BindingFieldMismatch,
122    BindingFormMismatch,
123    BindingTemplateMismatch,
124    InvalidCandidatePromoted,
125    InstanceIdentityInDeclarationGraph,
126    MissingProvenance,
127    NonCanonicalOrdering,
128    GraphIdentityMismatch,
129}
130
131impl FormOwnershipIntegrityKind {
132    #[must_use]
133    pub const fn code(self) -> &'static str {
134        match self {
135            Self::DuplicateNode => "PSASM1203",
136            Self::MissingOwner => "PSASM1204",
137            Self::UnknownOwnershipEndpoint => "PSASM1205",
138            Self::MultipleOwners => "PSASM1206",
139            Self::OwnershipCycle => "PSASM1207",
140            Self::UnreachableNode => "PSASM1208",
141            Self::UnknownReferenceEndpoint => "PSASM1209",
142            Self::ComponentOwnershipMismatch => "PSASM1210",
143            Self::FieldFormMismatch => "PSASM1211",
144            Self::BindingFieldMismatch => "PSASM1212",
145            Self::BindingFormMismatch => "PSASM1213",
146            Self::BindingTemplateMismatch => "PSASM1214",
147            Self::InvalidCandidatePromoted => "PSASM1215",
148            Self::InstanceIdentityInDeclarationGraph => "PSASM1216",
149            Self::MissingProvenance => "PSASM1217",
150            Self::NonCanonicalOrdering => "PSASM1218",
151            Self::GraphIdentityMismatch => "PSASM1219",
152        }
153    }
154}
155
156#[derive(Debug, Clone, PartialEq, Eq)]
157pub struct FormOwnershipIntegrityDiagnostic {
158    pub code: String,
159    pub kind: FormOwnershipIntegrityKind,
160    pub message: String,
161}
162
163#[derive(Debug, Clone, PartialEq, Eq)]
164pub struct FormOwnershipValidation {
165    pub diagnostics: Vec<FormOwnershipIntegrityDiagnostic>,
166    pub is_valid: bool,
167}
168
169impl Default for FormOwnershipValidation {
170    fn default() -> Self {
171        Self {
172            diagnostics: Vec::new(),
173            is_valid: true,
174        }
175    }
176}
177
178/// Immutable declaration-level projection of canonical Form ownership and
179/// binding reference facts already present in the ASM.
180#[derive(Debug, Clone, PartialEq, Eq)]
181pub struct FormOwnershipGraph {
182    pub id: FormOwnershipGraphId,
183    pub nodes: BTreeMap<FormOwnershipNodeKey, FormOwnershipNode>,
184    pub ownership_edges: Vec<FormOwnershipEdge>,
185    pub reference_edges: Vec<FormReferenceEdge>,
186    pub roots: Vec<SemanticId>,
187    pub validation: FormOwnershipValidation,
188}
189
190impl FormOwnershipGraph {
191    #[must_use]
192    pub fn node(&self, id: &FormOwnershipNodeKey) -> Option<&FormOwnershipNode> {
193        self.nodes.get(id)
194    }
195
196    #[must_use]
197    pub fn owner_of(&self, id: &FormOwnershipNodeKey) -> Option<&FormOwnershipNodeKey> {
198        self.ownership_edges
199            .iter()
200            .find_map(|edge| (&edge.child == id).then_some(&edge.owner))
201    }
202
203    #[must_use]
204    pub fn children_of(&self, id: &FormOwnershipNodeKey) -> Vec<&FormOwnershipNodeKey> {
205        self.ownership_edges
206            .iter()
207            .filter_map(|edge| (&edge.owner == id).then_some(&edge.child))
208            .collect()
209    }
210
211    #[must_use]
212    pub fn forms_of(&self, component: &SemanticId) -> Vec<&FormId> {
213        self.children_of(&FormOwnershipNodeKey::Component(component.clone()))
214            .into_iter()
215            .filter_map(|child| match child {
216                FormOwnershipNodeKey::Form(id) => Some(id),
217                _ => None,
218            })
219            .collect()
220    }
221
222    #[must_use]
223    pub fn fields_of(&self, form: &FormId) -> Vec<&FieldId> {
224        self.children_of(&FormOwnershipNodeKey::Form(form.clone()))
225            .into_iter()
226            .filter_map(|child| match child {
227                FormOwnershipNodeKey::FormField(id) => Some(id),
228                _ => None,
229            })
230            .collect()
231    }
232
233    #[must_use]
234    pub fn binding_owner(&self, binding: &FieldBindingId) -> Option<&SemanticId> {
235        match self.owner_of(&FormOwnershipNodeKey::FieldBinding(binding.clone()))? {
236            FormOwnershipNodeKey::TemplateControl(id) => Some(id),
237            _ => None,
238        }
239    }
240
241    #[must_use]
242    pub fn bindings_of_field(&self, field: &FieldId) -> Vec<&FieldBindingId> {
243        self.reference_edges
244            .iter()
245            .filter_map(|edge| {
246                if edge.kind == FormReferenceKind::FieldBindingField
247                    && edge.target == FormOwnershipNodeKey::FormField(field.clone())
248                {
249                    match &edge.source {
250                        FormOwnershipNodeKey::FieldBinding(id) => Some(id),
251                        _ => None,
252                    }
253                } else {
254                    None
255                }
256            })
257            .collect()
258    }
259
260    #[must_use]
261    pub fn bindings_of_form(&self, form: &FormId) -> Vec<&FieldBindingId> {
262        self.reference_edges
263            .iter()
264            .filter_map(|edge| {
265                if edge.kind == FormReferenceKind::FieldBindingForm
266                    && edge.target == FormOwnershipNodeKey::Form(form.clone())
267                {
268                    match &edge.source {
269                        FormOwnershipNodeKey::FieldBinding(id) => Some(id),
270                        _ => None,
271                    }
272                } else {
273                    None
274                }
275            })
276            .collect()
277    }
278
279    #[must_use]
280    pub fn field_of_binding(&self, binding: &FieldBindingId) -> Option<&FieldId> {
281        self.reference_edges.iter().find_map(|edge| {
282            if edge.kind == FormReferenceKind::FieldBindingField
283                && edge.source == FormOwnershipNodeKey::FieldBinding(binding.clone())
284            {
285                match &edge.target {
286                    FormOwnershipNodeKey::FormField(id) => Some(id),
287                    _ => None,
288                }
289            } else {
290                None
291            }
292        })
293    }
294
295    #[must_use]
296    pub fn form_of_binding(&self, binding: &FieldBindingId) -> Option<&FormId> {
297        self.reference_edges.iter().find_map(|edge| {
298            if edge.kind == FormReferenceKind::FieldBindingForm
299                && edge.source == FormOwnershipNodeKey::FieldBinding(binding.clone())
300            {
301                match &edge.target {
302                    FormOwnershipNodeKey::Form(id) => Some(id),
303                    _ => None,
304                }
305            } else {
306                None
307            }
308        })
309    }
310
311    #[must_use]
312    pub fn component_of_form(&self, form: &FormId) -> Option<&SemanticId> {
313        match self.owner_of(&FormOwnershipNodeKey::Form(form.clone()))? {
314            FormOwnershipNodeKey::Component(id) => Some(id),
315            _ => None,
316        }
317    }
318
319    #[must_use]
320    pub fn component_of_field(&self, field: &FieldId) -> Option<&SemanticId> {
321        self.component_of_form(
322            match self.owner_of(&FormOwnershipNodeKey::FormField(field.clone()))? {
323                FormOwnershipNodeKey::Form(id) => id,
324                _ => return None,
325            },
326        )
327    }
328
329    #[must_use]
330    pub fn component_of_binding(&self, binding: &FieldBindingId) -> Option<&SemanticId> {
331        self.component_of_form(self.form_of_binding(binding)?)
332    }
333
334    #[must_use]
335    pub fn roots(&self) -> &[SemanticId] {
336        &self.roots
337    }
338}
339
340#[allow(clippy::too_many_arguments, clippy::too_many_lines)]
341#[must_use]
342pub fn collect_form_ownership_graph(
343    build_roots: &BTreeMap<ComponentRootId, ComponentBuildRoot>,
344    components: &[ComponentNode],
345    forms: &BTreeMap<FormId, FormEntity>,
346    fields: &BTreeMap<FieldId, FormFieldEntity>,
347    bindings: &BTreeMap<FieldBindingId, FormFieldBinding>,
348    template_entities: &[TemplateSemanticEntity],
349    ownership: &BTreeMap<SemanticId, SemanticOwner>,
350    references: &[SemanticReference],
351    provenance: &BTreeMap<SemanticId, SourceProvenance>,
352) -> FormOwnershipGraph {
353    let id = FormOwnershipGraphId::for_build_roots(build_roots.keys());
354    let component_ids = components
355        .iter()
356        .map(|component| component.id.clone())
357        .collect::<BTreeSet<_>>();
358    let participating_components = forms
359        .values()
360        .filter_map(|form| form.owner.entity_id().cloned())
361        .chain(bindings.values().map(|binding| binding.component.clone()))
362        .filter(|component| component_ids.contains(component))
363        .collect::<BTreeSet<_>>();
364    let template_entities = template_entities
365        .iter()
366        .map(|entity| (entity.id.clone(), entity))
367        .collect::<BTreeMap<_, _>>();
368
369    let mut nodes = BTreeMap::new();
370    for component in &participating_components {
371        if let Some(provenance) = provenance.get(component) {
372            insert_node(
373                &mut nodes,
374                FormOwnershipNode::Component {
375                    id: component.clone(),
376                    provenance: provenance.clone(),
377                },
378            );
379        }
380    }
381    for form in forms.values() {
382        insert_node(
383            &mut nodes,
384            FormOwnershipNode::Form {
385                id: form.id.clone(),
386                provenance: form.provenance.clone(),
387            },
388        );
389    }
390    for field in fields.values() {
391        insert_node(
392            &mut nodes,
393            FormOwnershipNode::FormField {
394                id: field.id.clone(),
395                provenance: field.provenance.clone(),
396            },
397        );
398    }
399    for binding in bindings.values() {
400        if let Some(control) = template_entities.get(&binding.control_entity) {
401            insert_node(
402                &mut nodes,
403                FormOwnershipNode::TemplateControl {
404                    id: control.id.clone(),
405                    provenance: control.provenance.clone(),
406                },
407            );
408        }
409        insert_node(
410            &mut nodes,
411            FormOwnershipNode::FieldBinding {
412                id: binding.id.clone(),
413                provenance: binding.provenance.clone(),
414            },
415        );
416    }
417
418    let mut ownership_edges = Vec::new();
419    for form in forms.values() {
420        push_ownership_edge(
421            &mut ownership_edges,
422            ownership,
423            &nodes,
424            form.id.as_semantic_id(),
425            FormOwnershipEdgeKind::ComponentOwnsForm,
426            form.provenance.clone(),
427        );
428    }
429    for field in fields.values() {
430        push_ownership_edge(
431            &mut ownership_edges,
432            ownership,
433            &nodes,
434            field.id.as_semantic_id(),
435            FormOwnershipEdgeKind::FormOwnsField,
436            field.provenance.clone(),
437        );
438    }
439    for binding in bindings.values() {
440        push_ownership_edge(
441            &mut ownership_edges,
442            ownership,
443            &nodes,
444            binding.id.as_semantic_id(),
445            FormOwnershipEdgeKind::TemplateControlOwnsBinding,
446            binding.attribute_provenance.clone(),
447        );
448    }
449    sort_ownership_edges(&mut ownership_edges);
450
451    let mut reference_edges = references
452        .iter()
453        .filter_map(|reference| form_reference_edge(reference, &nodes))
454        .collect::<Vec<_>>();
455    sort_reference_edges(&mut reference_edges);
456
457    let roots = participating_components.into_iter().collect::<Vec<_>>();
458    let mut graph = FormOwnershipGraph {
459        id,
460        nodes,
461        ownership_edges,
462        reference_edges,
463        roots,
464        validation: FormOwnershipValidation::default(),
465    };
466    graph.validation = validate_form_ownership_graph_sources(
467        &graph,
468        build_roots,
469        components,
470        forms,
471        fields,
472        bindings,
473        template_entities.values().copied(),
474        ownership,
475        references,
476        provenance,
477        &BTreeSet::new(),
478    );
479    graph
480}
481
482#[must_use]
483pub fn validate_form_ownership_graph(
484    graph: &FormOwnershipGraph,
485    model: &ApplicationSemanticModel,
486) -> FormOwnershipValidation {
487    let instance_ids = model
488        .component_instance_plan
489        .instances
490        .keys()
491        .map(|id| id.as_semantic_id().clone())
492        .chain(
493            model
494                .component_instance_plan
495                .blocked
496                .keys()
497                .map(|id| id.as_semantic_id().clone()),
498        )
499        .collect();
500    validate_form_ownership_graph_sources(
501        graph,
502        &model.component_instance_plan.roots,
503        &model.components,
504        &model.forms,
505        &model.form_fields,
506        &model.form_field_bindings,
507        model.template_entities.iter(),
508        &model.ownership,
509        &model.references,
510        &model.provenance,
511        &instance_ids,
512    )
513}
514
515fn insert_node(
516    nodes: &mut BTreeMap<FormOwnershipNodeKey, FormOwnershipNode>,
517    node: FormOwnershipNode,
518) {
519    nodes.insert(node.key(), node);
520}
521
522fn node_key_for_semantic_id(
523    nodes: &BTreeMap<FormOwnershipNodeKey, FormOwnershipNode>,
524    id: &SemanticId,
525) -> Option<FormOwnershipNodeKey> {
526    nodes.keys().find(|key| key.semantic_id() == id).cloned()
527}
528
529fn push_ownership_edge(
530    edges: &mut Vec<FormOwnershipEdge>,
531    ownership: &BTreeMap<SemanticId, SemanticOwner>,
532    nodes: &BTreeMap<FormOwnershipNodeKey, FormOwnershipNode>,
533    child: &SemanticId,
534    kind: FormOwnershipEdgeKind,
535    provenance: SourceProvenance,
536) {
537    let Some(child) = node_key_for_semantic_id(nodes, child) else {
538        return;
539    };
540    let Some(owner) = ownership
541        .get(child.semantic_id())
542        .and_then(SemanticOwner::entity_id)
543        .and_then(|owner| node_key_for_semantic_id(nodes, owner))
544    else {
545        return;
546    };
547    edges.push(FormOwnershipEdge {
548        owner,
549        child,
550        kind,
551        provenance,
552    });
553}
554
555fn form_reference_edge(
556    reference: &SemanticReference,
557    nodes: &BTreeMap<FormOwnershipNodeKey, FormOwnershipNode>,
558) -> Option<FormReferenceEdge> {
559    let kind = match reference.kind {
560        SemanticReferenceKind::FieldBindingField => FormReferenceKind::FieldBindingField,
561        SemanticReferenceKind::FieldBindingForm => FormReferenceKind::FieldBindingForm,
562        _ => return None,
563    };
564    Some(FormReferenceEdge {
565        kind,
566        source: node_key_for_semantic_id(nodes, &reference.source)?,
567        target: node_key_for_semantic_id(nodes, &reference.target)?,
568        provenance: reference.provenance.clone(),
569    })
570}
571
572fn sort_ownership_edges(edges: &mut [FormOwnershipEdge]) {
573    edges.sort_by(|left, right| {
574        (
575            left.owner.semantic_id().as_str(),
576            left.child.semantic_id().as_str(),
577            left.kind,
578        )
579            .cmp(&(
580                right.owner.semantic_id().as_str(),
581                right.child.semantic_id().as_str(),
582                right.kind,
583            ))
584    });
585}
586
587fn sort_reference_edges(edges: &mut [FormReferenceEdge]) {
588    edges.sort_by(|left, right| {
589        (
590            left.source.semantic_id().as_str(),
591            left.kind,
592            left.target.semantic_id().as_str(),
593        )
594            .cmp(&(
595                right.source.semantic_id().as_str(),
596                right.kind,
597                right.target.semantic_id().as_str(),
598            ))
599    });
600}
601
602#[allow(clippy::too_many_arguments, clippy::too_many_lines)]
603fn validate_form_ownership_graph_sources<'a>(
604    graph: &FormOwnershipGraph,
605    build_roots: &BTreeMap<ComponentRootId, ComponentBuildRoot>,
606    components: &[ComponentNode],
607    forms: &BTreeMap<FormId, FormEntity>,
608    fields: &BTreeMap<FieldId, FormFieldEntity>,
609    bindings: &BTreeMap<FieldBindingId, FormFieldBinding>,
610    template_entities: impl IntoIterator<Item = &'a TemplateSemanticEntity>,
611    ownership: &BTreeMap<SemanticId, SemanticOwner>,
612    references: &[SemanticReference],
613    provenance: &BTreeMap<SemanticId, SourceProvenance>,
614    instance_ids: &BTreeSet<SemanticId>,
615) -> FormOwnershipValidation {
616    let mut diagnostics = Vec::new();
617    let template_entities = template_entities
618        .into_iter()
619        .map(|entity| (entity.id.clone(), entity))
620        .collect::<BTreeMap<_, _>>();
621    let components = components
622        .iter()
623        .map(|component| (component.id.clone(), component))
624        .collect::<BTreeMap<_, _>>();
625
626    if graph.id != FormOwnershipGraphId::for_build_roots(build_roots.keys()) {
627        push_diagnostic(
628            &mut diagnostics,
629            FormOwnershipIntegrityKind::GraphIdentityMismatch,
630            "Form ownership graph identity does not match the canonical build-root set",
631        );
632    }
633
634    let mut semantic_ids = BTreeSet::new();
635    for (key, node) in &graph.nodes {
636        if node.key() != *key || !semantic_ids.insert(key.semantic_id().clone()) {
637            push_diagnostic(
638                &mut diagnostics,
639                FormOwnershipIntegrityKind::DuplicateNode,
640                format!(
641                    "duplicate or mismatched Form ownership node `{}`",
642                    key.semantic_id()
643                ),
644            );
645        }
646        if missing_provenance(node.provenance()) {
647            push_diagnostic(
648                &mut diagnostics,
649                FormOwnershipIntegrityKind::MissingProvenance,
650                format!(
651                    "Form ownership node `{}` has no canonical provenance",
652                    key.semantic_id()
653                ),
654            );
655        }
656        if instance_ids.contains(key.semantic_id()) {
657            push_diagnostic(
658                &mut diagnostics,
659                FormOwnershipIntegrityKind::InstanceIdentityInDeclarationGraph,
660                format!(
661                    "instance identity `{}` leaked into the declaration graph",
662                    key.semantic_id()
663                ),
664            );
665        }
666    }
667
668    validate_node_domain(
669        graph,
670        &components,
671        forms,
672        fields,
673        bindings,
674        &template_entities,
675        provenance,
676        &mut diagnostics,
677    );
678    validate_edge_domains(graph, &mut diagnostics);
679
680    for form in forms.values() {
681        let child = FormOwnershipNodeKey::Form(form.id.clone());
682        let expected_component = form.owner.entity_id();
683        let expected_owner =
684            expected_component.map(|id| FormOwnershipNodeKey::Component(id.clone()));
685        validate_exact_owner(
686            graph,
687            &child,
688            expected_owner.as_ref(),
689            FormOwnershipEdgeKind::ComponentOwnsForm,
690            FormOwnershipIntegrityKind::ComponentOwnershipMismatch,
691            &mut diagnostics,
692        );
693        validate_ownership_provenance(graph, &child, &form.provenance, &mut diagnostics);
694        if ownership.get(form.id.as_semantic_id()) != Some(&form.owner) {
695            push_diagnostic(
696                &mut diagnostics,
697                FormOwnershipIntegrityKind::ComponentOwnershipMismatch,
698                format!("Form `{}` disagrees with canonical ASM ownership", form.id),
699            );
700        }
701    }
702
703    for field in fields.values() {
704        let child = FormOwnershipNodeKey::FormField(field.id.clone());
705        let expected_owner = FormOwnershipNodeKey::Form(field.owner_form.clone());
706        validate_exact_owner(
707            graph,
708            &child,
709            Some(&expected_owner),
710            FormOwnershipEdgeKind::FormOwnsField,
711            FormOwnershipIntegrityKind::FieldFormMismatch,
712            &mut diagnostics,
713        );
714        validate_ownership_provenance(graph, &child, &field.provenance, &mut diagnostics);
715        let form_component = forms
716            .get(&field.owner_form)
717            .and_then(|form| form.owner.entity_id());
718        if form_component != Some(&field.owner_component)
719            || ownership.get(field.id.as_semantic_id())
720                != Some(&SemanticOwner::entity(
721                    field.owner_form.as_semantic_id().clone(),
722                ))
723        {
724            push_diagnostic(
725                &mut diagnostics,
726                FormOwnershipIntegrityKind::FieldFormMismatch,
727                format!(
728                    "Field `{}` disagrees with its canonical Form owner",
729                    field.id
730                ),
731            );
732        }
733    }
734
735    for binding in bindings.values() {
736        let child = FormOwnershipNodeKey::FieldBinding(binding.id.clone());
737        let expected_owner = FormOwnershipNodeKey::TemplateControl(binding.control_entity.clone());
738        validate_exact_owner(
739            graph,
740            &child,
741            Some(&expected_owner),
742            FormOwnershipEdgeKind::TemplateControlOwnsBinding,
743            FormOwnershipIntegrityKind::BindingTemplateMismatch,
744            &mut diagnostics,
745        );
746        validate_ownership_provenance(
747            graph,
748            &child,
749            &binding.attribute_provenance,
750            &mut diagnostics,
751        );
752        if ownership.get(binding.id.as_semantic_id())
753            != Some(&SemanticOwner::entity(binding.control_entity.clone()))
754        {
755            push_diagnostic(
756                &mut diagnostics,
757                FormOwnershipIntegrityKind::BindingTemplateMismatch,
758                format!(
759                    "binding `{}` disagrees with canonical control ownership",
760                    binding.id
761                ),
762            );
763        }
764
765        let expected_field = FormOwnershipNodeKey::FormField(binding.field.clone());
766        let expected_form = FormOwnershipNodeKey::Form(binding.form.clone());
767        validate_exact_reference(
768            graph,
769            &child,
770            &expected_field,
771            FormReferenceKind::FieldBindingField,
772            FormOwnershipIntegrityKind::BindingFieldMismatch,
773            &mut diagnostics,
774        );
775        validate_reference_provenance(
776            graph,
777            &child,
778            FormReferenceKind::FieldBindingField,
779            &binding.expression_provenance,
780            &mut diagnostics,
781        );
782        validate_exact_reference(
783            graph,
784            &child,
785            &expected_form,
786            FormReferenceKind::FieldBindingForm,
787            FormOwnershipIntegrityKind::BindingFormMismatch,
788            &mut diagnostics,
789        );
790        validate_reference_provenance(
791            graph,
792            &child,
793            FormReferenceKind::FieldBindingForm,
794            &binding.expression_provenance,
795            &mut diagnostics,
796        );
797        let field = fields.get(&binding.field);
798        let form = forms.get(&binding.form);
799        let template_component =
800            component_owner_of(&binding.control_entity, &components, ownership);
801        if field.is_none_or(|field| field.owner_form != binding.form) {
802            push_diagnostic(
803                &mut diagnostics,
804                FormOwnershipIntegrityKind::BindingFieldMismatch,
805                format!(
806                    "binding `{}` references a Field with a different Form",
807                    binding.id
808                ),
809            );
810        }
811        if form
812            .and_then(|form| form.owner.entity_id())
813            .is_none_or(|component| component != &binding.component)
814        {
815            push_diagnostic(
816                &mut diagnostics,
817                FormOwnershipIntegrityKind::BindingFormMismatch,
818                format!(
819                    "binding `{}` references a Form from another component",
820                    binding.id
821                ),
822            );
823        }
824        if field.is_none_or(|field| field.owner_component != binding.component)
825            || template_component.as_ref() != Some(&binding.component)
826        {
827            push_diagnostic(
828                &mut diagnostics,
829                FormOwnershipIntegrityKind::ComponentOwnershipMismatch,
830                format!(
831                    "binding `{}` crosses canonical component ownership",
832                    binding.id
833                ),
834            );
835        }
836    }
837
838    for edge in &graph.ownership_edges {
839        if !graph.nodes.contains_key(&edge.owner) || !graph.nodes.contains_key(&edge.child) {
840            push_diagnostic(
841                &mut diagnostics,
842                FormOwnershipIntegrityKind::UnknownOwnershipEndpoint,
843                format!(
844                    "ownership edge `{}` -> `{}` has an unknown endpoint",
845                    edge.owner.semantic_id(),
846                    edge.child.semantic_id()
847                ),
848            );
849        }
850        if missing_provenance(&edge.provenance) {
851            push_diagnostic(
852                &mut diagnostics,
853                FormOwnershipIntegrityKind::MissingProvenance,
854                format!(
855                    "ownership edge to `{}` lacks provenance",
856                    edge.child.semantic_id()
857                ),
858            );
859        }
860    }
861    for edge in &graph.reference_edges {
862        if !graph.nodes.contains_key(&edge.source) || !graph.nodes.contains_key(&edge.target) {
863            push_diagnostic(
864                &mut diagnostics,
865                FormOwnershipIntegrityKind::UnknownReferenceEndpoint,
866                format!(
867                    "reference `{}` -> `{}` has an unknown endpoint",
868                    edge.source.semantic_id(),
869                    edge.target.semantic_id()
870                ),
871            );
872        }
873        if missing_provenance(&edge.provenance) {
874            push_diagnostic(
875                &mut diagnostics,
876                FormOwnershipIntegrityKind::MissingProvenance,
877                format!(
878                    "reference from `{}` lacks provenance",
879                    edge.source.semantic_id()
880                ),
881            );
882        }
883    }
884
885    validate_parents_and_reachability(graph, &mut diagnostics);
886    if ownership_has_cycle(graph) {
887        push_diagnostic(
888            &mut diagnostics,
889            FormOwnershipIntegrityKind::OwnershipCycle,
890            "Form ownership edges contain a cycle",
891        );
892    }
893
894    let expected_roots = forms
895        .values()
896        .filter_map(|form| form.owner.entity_id().cloned())
897        .chain(bindings.values().map(|binding| binding.component.clone()))
898        .collect::<BTreeSet<_>>()
899        .into_iter()
900        .collect::<Vec<_>>();
901    if graph.roots != expected_roots || !is_strictly_sorted(&graph.roots) {
902        push_diagnostic(
903            &mut diagnostics,
904            FormOwnershipIntegrityKind::NonCanonicalOrdering,
905            "Form ownership roots are incomplete or non-canonical",
906        );
907    }
908    let mut ownership_sorted = graph.ownership_edges.clone();
909    sort_ownership_edges(&mut ownership_sorted);
910    let mut references_sorted = graph.reference_edges.clone();
911    sort_reference_edges(&mut references_sorted);
912    if ownership_sorted != graph.ownership_edges
913        || references_sorted != graph.reference_edges
914        || has_duplicate_ownership_edge(&graph.ownership_edges)
915        || has_duplicate_reference_edge(&graph.reference_edges)
916    {
917        push_diagnostic(
918            &mut diagnostics,
919            FormOwnershipIntegrityKind::NonCanonicalOrdering,
920            "Form ownership edges or references are not canonically ordered and unique",
921        );
922    }
923
924    // Prove that the graph references are a projection of the canonical ASM
925    // relations, not an independently invented relation set.
926    for binding in bindings.values() {
927        for (kind, target) in [
928            (
929                SemanticReferenceKind::FieldBindingField,
930                binding.field.as_semantic_id(),
931            ),
932            (
933                SemanticReferenceKind::FieldBindingForm,
934                binding.form.as_semantic_id(),
935            ),
936        ] {
937            if references
938                .iter()
939                .filter(|reference| {
940                    reference.kind == kind
941                        && reference.source == *binding.id.as_semantic_id()
942                        && reference.target == *target
943                })
944                .count()
945                != 1
946            {
947                push_diagnostic(
948                    &mut diagnostics,
949                    if kind == SemanticReferenceKind::FieldBindingField {
950                        FormOwnershipIntegrityKind::BindingFieldMismatch
951                    } else {
952                        FormOwnershipIntegrityKind::BindingFormMismatch
953                    },
954                    format!("binding `{}` lacks one canonical ASM reference", binding.id),
955                );
956            }
957        }
958    }
959
960    diagnostics.sort_by(|left, right| {
961        (&left.code, left.kind, &left.message).cmp(&(&right.code, right.kind, &right.message))
962    });
963    diagnostics.dedup();
964    FormOwnershipValidation {
965        is_valid: diagnostics.is_empty(),
966        diagnostics,
967    }
968}
969
970#[allow(clippy::too_many_arguments)]
971fn validate_node_domain(
972    graph: &FormOwnershipGraph,
973    components: &BTreeMap<SemanticId, &ComponentNode>,
974    forms: &BTreeMap<FormId, FormEntity>,
975    fields: &BTreeMap<FieldId, FormFieldEntity>,
976    bindings: &BTreeMap<FieldBindingId, FormFieldBinding>,
977    templates: &BTreeMap<SemanticId, &TemplateSemanticEntity>,
978    provenance: &BTreeMap<SemanticId, SourceProvenance>,
979    diagnostics: &mut Vec<FormOwnershipIntegrityDiagnostic>,
980) {
981    for (key, node) in &graph.nodes {
982        let valid = match key {
983            FormOwnershipNodeKey::Component(id) => {
984                components.contains_key(id)
985                    && (forms
986                        .values()
987                        .any(|form| form.owner.entity_id() == Some(id))
988                        || bindings.values().any(|binding| binding.component == *id))
989                    && provenance.get(id) == Some(node.provenance())
990            }
991            FormOwnershipNodeKey::Form(id) => forms
992                .get(id)
993                .is_some_and(|form| form.provenance == *node.provenance()),
994            FormOwnershipNodeKey::FormField(id) => fields
995                .get(id)
996                .is_some_and(|field| field.provenance == *node.provenance()),
997            FormOwnershipNodeKey::TemplateControl(id) => templates.get(id).is_some_and(|entity| {
998                entity.kind == TemplateSemanticKind::Element
999                    && entity.provenance == *node.provenance()
1000                    && bindings
1001                        .values()
1002                        .any(|binding| binding.control_entity == *id)
1003            }),
1004            FormOwnershipNodeKey::FieldBinding(id) => bindings
1005                .get(id)
1006                .is_some_and(|binding| binding.provenance == *node.provenance()),
1007        };
1008        if !valid {
1009            push_diagnostic(
1010                diagnostics,
1011                FormOwnershipIntegrityKind::InvalidCandidatePromoted,
1012                format!(
1013                    "node `{}` is not one valid canonical I2-I4 entity",
1014                    key.semantic_id()
1015                ),
1016            );
1017        }
1018    }
1019
1020    for expected in
1021        forms
1022            .keys()
1023            .cloned()
1024            .map(FormOwnershipNodeKey::Form)
1025            .chain(fields.keys().cloned().map(FormOwnershipNodeKey::FormField))
1026            .chain(
1027                bindings
1028                    .keys()
1029                    .cloned()
1030                    .map(FormOwnershipNodeKey::FieldBinding),
1031            )
1032            .chain(bindings.values().map(|binding| {
1033                FormOwnershipNodeKey::TemplateControl(binding.control_entity.clone())
1034            }))
1035    {
1036        if !graph.nodes.contains_key(&expected) {
1037            push_diagnostic(
1038                diagnostics,
1039                FormOwnershipIntegrityKind::UnreachableNode,
1040                format!(
1041                    "canonical entity `{}` is absent from the Form ownership graph",
1042                    expected.semantic_id()
1043                ),
1044            );
1045        }
1046    }
1047}
1048
1049fn validate_edge_domains(
1050    graph: &FormOwnershipGraph,
1051    diagnostics: &mut Vec<FormOwnershipIntegrityDiagnostic>,
1052) {
1053    for edge in &graph.ownership_edges {
1054        let valid = matches!(
1055            (&edge.owner, &edge.child, edge.kind),
1056            (
1057                FormOwnershipNodeKey::Component(_),
1058                FormOwnershipNodeKey::Form(_),
1059                FormOwnershipEdgeKind::ComponentOwnsForm
1060            ) | (
1061                FormOwnershipNodeKey::Form(_),
1062                FormOwnershipNodeKey::FormField(_),
1063                FormOwnershipEdgeKind::FormOwnsField
1064            ) | (
1065                FormOwnershipNodeKey::TemplateControl(_),
1066                FormOwnershipNodeKey::FieldBinding(_),
1067                FormOwnershipEdgeKind::TemplateControlOwnsBinding
1068            )
1069        );
1070        if !valid {
1071            push_diagnostic(
1072                diagnostics,
1073                FormOwnershipIntegrityKind::UnknownOwnershipEndpoint,
1074                format!(
1075                    "ownership edge to `{}` has an invalid typed domain",
1076                    edge.child.semantic_id()
1077                ),
1078            );
1079        }
1080    }
1081    for edge in &graph.reference_edges {
1082        let valid = matches!(
1083            (&edge.source, &edge.target, edge.kind),
1084            (
1085                FormOwnershipNodeKey::FieldBinding(_),
1086                FormOwnershipNodeKey::FormField(_),
1087                FormReferenceKind::FieldBindingField
1088            ) | (
1089                FormOwnershipNodeKey::FieldBinding(_),
1090                FormOwnershipNodeKey::Form(_),
1091                FormReferenceKind::FieldBindingForm
1092            )
1093        );
1094        if !valid {
1095            push_diagnostic(
1096                diagnostics,
1097                FormOwnershipIntegrityKind::UnknownReferenceEndpoint,
1098                format!(
1099                    "reference from `{}` has an invalid typed domain",
1100                    edge.source.semantic_id()
1101                ),
1102            );
1103        }
1104    }
1105}
1106
1107fn validate_exact_owner(
1108    graph: &FormOwnershipGraph,
1109    child: &FormOwnershipNodeKey,
1110    expected: Option<&FormOwnershipNodeKey>,
1111    kind: FormOwnershipEdgeKind,
1112    mismatch_kind: FormOwnershipIntegrityKind,
1113    diagnostics: &mut Vec<FormOwnershipIntegrityDiagnostic>,
1114) {
1115    let parents = graph
1116        .ownership_edges
1117        .iter()
1118        .filter(|edge| &edge.child == child)
1119        .collect::<Vec<_>>();
1120    if parents.is_empty() {
1121        push_diagnostic(
1122            diagnostics,
1123            FormOwnershipIntegrityKind::MissingOwner,
1124            format!("`{}` has no ownership parent", child.semantic_id()),
1125        );
1126        return;
1127    }
1128    if parents.len() > 1 {
1129        push_diagnostic(
1130            diagnostics,
1131            FormOwnershipIntegrityKind::MultipleOwners,
1132            format!("`{}` has multiple ownership parents", child.semantic_id()),
1133        );
1134    }
1135    if expected.is_none_or(|expected| {
1136        parents.len() != 1 || parents[0].owner != *expected || parents[0].kind != kind
1137    }) {
1138        push_diagnostic(
1139            diagnostics,
1140            mismatch_kind,
1141            format!("`{}` has a non-canonical direct owner", child.semantic_id()),
1142        );
1143    }
1144}
1145
1146fn validate_exact_reference(
1147    graph: &FormOwnershipGraph,
1148    source: &FormOwnershipNodeKey,
1149    target: &FormOwnershipNodeKey,
1150    kind: FormReferenceKind,
1151    mismatch_kind: FormOwnershipIntegrityKind,
1152    diagnostics: &mut Vec<FormOwnershipIntegrityDiagnostic>,
1153) {
1154    if graph
1155        .reference_edges
1156        .iter()
1157        .filter(|edge| &edge.source == source && edge.kind == kind)
1158        .count()
1159        != 1
1160        || !graph
1161            .reference_edges
1162            .iter()
1163            .any(|edge| &edge.source == source && &edge.target == target && edge.kind == kind)
1164    {
1165        push_diagnostic(
1166            diagnostics,
1167            mismatch_kind,
1168            format!("`{}` has a non-canonical reference", source.semantic_id()),
1169        );
1170    }
1171}
1172
1173fn validate_ownership_provenance(
1174    graph: &FormOwnershipGraph,
1175    child: &FormOwnershipNodeKey,
1176    expected: &SourceProvenance,
1177    diagnostics: &mut Vec<FormOwnershipIntegrityDiagnostic>,
1178) {
1179    if graph
1180        .ownership_edges
1181        .iter()
1182        .find(|edge| &edge.child == child)
1183        .is_none_or(|edge| &edge.provenance != expected)
1184    {
1185        push_diagnostic(
1186            diagnostics,
1187            FormOwnershipIntegrityKind::MissingProvenance,
1188            format!(
1189                "ownership edge to `{}` lacks canonical child/use-site provenance",
1190                child.semantic_id()
1191            ),
1192        );
1193    }
1194}
1195
1196fn validate_reference_provenance(
1197    graph: &FormOwnershipGraph,
1198    source: &FormOwnershipNodeKey,
1199    kind: FormReferenceKind,
1200    expected: &SourceProvenance,
1201    diagnostics: &mut Vec<FormOwnershipIntegrityDiagnostic>,
1202) {
1203    if graph
1204        .reference_edges
1205        .iter()
1206        .find(|edge| &edge.source == source && edge.kind == kind)
1207        .is_none_or(|edge| &edge.provenance != expected)
1208    {
1209        push_diagnostic(
1210            diagnostics,
1211            FormOwnershipIntegrityKind::MissingProvenance,
1212            format!(
1213                "reference from `{}` lacks canonical binding-expression provenance",
1214                source.semantic_id()
1215            ),
1216        );
1217    }
1218}
1219
1220fn validate_parents_and_reachability(
1221    graph: &FormOwnershipGraph,
1222    diagnostics: &mut Vec<FormOwnershipIntegrityDiagnostic>,
1223) {
1224    let root_keys = graph
1225        .roots
1226        .iter()
1227        .cloned()
1228        .map(FormOwnershipNodeKey::Component)
1229        .collect::<Vec<_>>();
1230    for key in graph.nodes.keys() {
1231        match key {
1232            FormOwnershipNodeKey::Form(_) | FormOwnershipNodeKey::FormField(_) => {
1233                let reachable = root_keys
1234                    .iter()
1235                    .filter(|root| is_reachable(graph, root, key))
1236                    .count();
1237                if reachable != 1 {
1238                    push_diagnostic(
1239                        diagnostics,
1240                        FormOwnershipIntegrityKind::UnreachableNode,
1241                        format!(
1242                            "`{}` is reachable from {reachable} component roots",
1243                            key.semantic_id()
1244                        ),
1245                    );
1246                }
1247            }
1248            FormOwnershipNodeKey::FieldBinding(_) => {
1249                let controls = graph
1250                    .nodes
1251                    .keys()
1252                    .filter(|candidate| {
1253                        matches!(candidate, FormOwnershipNodeKey::TemplateControl(_))
1254                    })
1255                    .filter(|control| is_reachable(graph, control, key))
1256                    .count();
1257                if controls != 1 {
1258                    push_diagnostic(
1259                        diagnostics,
1260                        FormOwnershipIntegrityKind::UnreachableNode,
1261                        format!(
1262                            "binding `{}` is reachable from {controls} controls",
1263                            key.semantic_id()
1264                        ),
1265                    );
1266                }
1267            }
1268            FormOwnershipNodeKey::Component(_) | FormOwnershipNodeKey::TemplateControl(_) => {}
1269        }
1270    }
1271}
1272
1273fn is_reachable(
1274    graph: &FormOwnershipGraph,
1275    start: &FormOwnershipNodeKey,
1276    target: &FormOwnershipNodeKey,
1277) -> bool {
1278    let mut pending = vec![start.clone()];
1279    let mut seen = BTreeSet::new();
1280    while let Some(current) = pending.pop() {
1281        if !seen.insert(current.clone()) {
1282            continue;
1283        }
1284        if current == *target {
1285            return true;
1286        }
1287        pending.extend(graph.children_of(&current).into_iter().cloned());
1288    }
1289    false
1290}
1291
1292fn ownership_has_cycle(graph: &FormOwnershipGraph) -> bool {
1293    graph.nodes.keys().any(|start| {
1294        let mut pending = graph
1295            .children_of(start)
1296            .into_iter()
1297            .cloned()
1298            .collect::<Vec<_>>();
1299        let mut seen = BTreeSet::new();
1300        while let Some(current) = pending.pop() {
1301            if current == *start {
1302                return true;
1303            }
1304            if seen.insert(current.clone()) {
1305                pending.extend(graph.children_of(&current).into_iter().cloned());
1306            }
1307        }
1308        false
1309    })
1310}
1311
1312fn component_owner_of(
1313    id: &SemanticId,
1314    components: &BTreeMap<SemanticId, &ComponentNode>,
1315    ownership: &BTreeMap<SemanticId, SemanticOwner>,
1316) -> Option<SemanticId> {
1317    let mut current = id;
1318    let mut seen = BTreeSet::new();
1319    loop {
1320        if components.contains_key(current) {
1321            return Some(current.clone());
1322        }
1323        if !seen.insert(current.clone()) {
1324            return None;
1325        }
1326        current = ownership.get(current)?.entity_id()?;
1327    }
1328}
1329
1330fn missing_provenance(provenance: &SourceProvenance) -> bool {
1331    provenance.path.as_os_str().is_empty() || provenance.span.start >= provenance.span.end
1332}
1333
1334fn is_strictly_sorted<T: Ord>(items: &[T]) -> bool {
1335    items.windows(2).all(|window| window[0] < window[1])
1336}
1337
1338fn has_duplicate_ownership_edge(edges: &[FormOwnershipEdge]) -> bool {
1339    let mut seen = BTreeSet::new();
1340    edges
1341        .iter()
1342        .any(|edge| !seen.insert((edge.owner.clone(), edge.child.clone(), edge.kind)))
1343}
1344
1345fn has_duplicate_reference_edge(edges: &[FormReferenceEdge]) -> bool {
1346    let mut seen = BTreeSet::new();
1347    edges
1348        .iter()
1349        .any(|edge| !seen.insert((edge.source.clone(), edge.kind, edge.target.clone())))
1350}
1351
1352fn push_diagnostic(
1353    diagnostics: &mut Vec<FormOwnershipIntegrityDiagnostic>,
1354    kind: FormOwnershipIntegrityKind,
1355    message: impl Into<String>,
1356) {
1357    diagnostics.push(FormOwnershipIntegrityDiagnostic {
1358        code: kind.code().to_string(),
1359        kind,
1360        message: message.into(),
1361    });
1362}
1363
1364#[cfg(test)]
1365mod tests {
1366    use std::collections::BTreeSet;
1367
1368    use presolve_parser::SourceSpan;
1369
1370    use super::{
1371        validate_form_ownership_graph, FormOwnershipEdge, FormOwnershipEdgeKind,
1372        FormOwnershipIntegrityKind, FormOwnershipNode, FormOwnershipNodeKey, FormReferenceEdge,
1373        FormReferenceKind,
1374    };
1375    use crate::{
1376        build_application_semantic_model, build_application_semantic_model_for_unit,
1377        build_semantic_graph, validate_application_semantic_model, CompilationUnit, FieldId,
1378        FormId, FormInstanceId, SemanticId, SourceProvenance, SEMANTIC_GRAPH_SCHEMA_VERSION,
1379    };
1380
1381    fn form_source() -> &'static str {
1382        r#"
1383@component("profile-editor")
1384class ProfileEditor {
1385  @form() profileForm!: Form;
1386  @form() passwordForm!: Form;
1387  @field(this.profileForm) displayName = "Austin";
1388  @field(this.profileForm) contactMethod: "email" | "phone" = "email";
1389  @field(this.passwordForm) password = "";
1390  render() {
1391    return <main>
1392      <input field={this.displayName} />
1393      <input type="radio" value="email" field={this.contactMethod} />
1394      <input type="radio" value="phone" field={this.contactMethod} />
1395    </main>;
1396  }
1397}
1398"#
1399    }
1400
1401    #[test]
1402    fn projects_exact_declaration_and_control_ownership_with_read_only_queries() {
1403        let parsed = presolve_parser::parse_file("src/ProfileEditor.tsx", form_source());
1404        let asm = build_application_semantic_model(&parsed);
1405        let graph = asm.form_ownership();
1406        let component = &asm.components[0].id;
1407        let profile = FormId::for_owner(component, "profileForm");
1408        let password = FormId::for_owner(component, "passwordForm");
1409        let display = FieldId::for_form(&profile, "displayName");
1410        let contact = FieldId::for_form(&profile, "contactMethod");
1411
1412        assert!(
1413            graph.validation.is_valid,
1414            "{:?}",
1415            graph.validation.diagnostics
1416        );
1417        assert!(validate_application_semantic_model(&asm).is_empty());
1418        assert_eq!(graph.roots(), std::slice::from_ref(component));
1419        assert_eq!(graph.forms_of(component), vec![&password, &profile]);
1420        assert_eq!(graph.fields_of(&profile), vec![&contact, &display]);
1421        assert_eq!(graph.bindings_of_field(&contact).len(), 2);
1422        assert_eq!(graph.bindings_of_form(&profile).len(), 3);
1423        assert_eq!(graph.ownership_edges.len(), 8);
1424        assert_eq!(graph.reference_edges.len(), 6);
1425
1426        for binding in asm.form_field_bindings.values() {
1427            assert_eq!(
1428                graph.binding_owner(&binding.id),
1429                Some(&binding.control_entity)
1430            );
1431            assert_eq!(graph.field_of_binding(&binding.id), Some(&binding.field));
1432            assert_eq!(graph.form_of_binding(&binding.id), Some(&binding.form));
1433            assert_eq!(graph.component_of_binding(&binding.id), Some(component));
1434            assert_eq!(
1435                asm.parent_of(binding.id.as_semantic_id()),
1436                Some(&binding.control_entity)
1437            );
1438            assert!(!graph
1439                .children_of(&FormOwnershipNodeKey::Form(binding.form.clone()))
1440                .contains(&&FormOwnershipNodeKey::FieldBinding(binding.id.clone())));
1441            assert!(!graph
1442                .children_of(&FormOwnershipNodeKey::FormField(binding.field.clone()))
1443                .contains(&&FormOwnershipNodeKey::FieldBinding(binding.id.clone())));
1444        }
1445
1446        assert!(graph.id.as_str().starts_with("form-ownership-graph:root:"));
1447        assert!(graph.nodes.values().all(|node| {
1448            !node.provenance().path.as_os_str().is_empty()
1449                && node.provenance().span.start < node.provenance().span.end
1450        }));
1451        assert!(graph.nodes.keys().all(|key| {
1452            !asm.component_instance_plan
1453                .instances
1454                .keys()
1455                .any(|instance| instance.as_semantic_id() == key.semantic_id())
1456        }));
1457    }
1458
1459    #[test]
1460    fn excludes_invalid_candidates_without_poisoning_valid_products() {
1461        let parsed = presolve_parser::parse_file(
1462            "src/Mixed.tsx",
1463            r#"
1464@component("mixed-editor")
1465class MixedEditor {
1466  @form("invalid") brokenForm!: Form;
1467  @form() validForm!: Form;
1468  @field(this.brokenForm) broken = "";
1469  @field(this.validForm) valid = "ok";
1470  render() {
1471    return <main>
1472      <input field={this.broken} />
1473      <input field={this.valid} />
1474      <div field={this.valid} />
1475    </main>;
1476  }
1477}
1478"#,
1479        );
1480        let asm = build_application_semantic_model(&parsed);
1481        let graph = asm.form_ownership();
1482        let component = &asm.components[0].id;
1483        let valid_form = FormId::for_owner(component, "validForm");
1484        let valid_field = FieldId::for_form(&valid_form, "valid");
1485
1486        assert!(
1487            graph.validation.is_valid,
1488            "{:?}",
1489            graph.validation.diagnostics
1490        );
1491        assert_eq!(asm.forms.len(), 1);
1492        assert_eq!(asm.form_fields.len(), 1);
1493        assert_eq!(asm.form_field_bindings.len(), 1);
1494        assert!(graph
1495            .node(&FormOwnershipNodeKey::Form(valid_form))
1496            .is_some());
1497        assert!(graph
1498            .node(&FormOwnershipNodeKey::FormField(valid_field))
1499            .is_some());
1500        assert!(asm
1501            .form_declaration_candidates()
1502            .iter()
1503            .any(|candidate| !candidate.violations().is_empty()));
1504        assert!(asm
1505            .form_field_declaration_candidates()
1506            .iter()
1507            .any(|candidate| !candidate.is_valid()));
1508        assert!(asm
1509            .form_field_binding_candidates()
1510            .iter()
1511            .any(|candidate| !candidate.is_valid()));
1512    }
1513
1514    #[test]
1515    fn validates_malformed_graphs_without_repairing_them() {
1516        let parsed = presolve_parser::parse_file("src/ProfileEditor.tsx", form_source());
1517        let asm = build_application_semantic_model(&parsed);
1518        let mut graph = asm.form_ownership.clone();
1519        let form = asm.forms.values().next().expect("Form");
1520        let field = asm.form_fields.values().next().expect("Field");
1521        let binding = asm.form_field_bindings.values().next().expect("binding");
1522
1523        graph
1524            .ownership_edges
1525            .retain(|edge| edge.child != FormOwnershipNodeKey::FieldBinding(binding.id.clone()));
1526        graph.ownership_edges.push(FormOwnershipEdge {
1527            owner: FormOwnershipNodeKey::FormField(field.id.clone()),
1528            child: FormOwnershipNodeKey::Form(form.id.clone()),
1529            kind: FormOwnershipEdgeKind::ComponentOwnsForm,
1530            provenance: form.provenance.clone(),
1531        });
1532        graph.ownership_edges.push(FormOwnershipEdge {
1533            owner: FormOwnershipNodeKey::Component(SemanticId::component_in_module(
1534                "src/Missing.tsx",
1535                Some("missing-owner"),
1536                "MissingOwner",
1537            )),
1538            child: FormOwnershipNodeKey::Form(form.id.clone()),
1539            kind: FormOwnershipEdgeKind::ComponentOwnsForm,
1540            provenance: form.provenance.clone(),
1541        });
1542        graph.ownership_edges.push(FormOwnershipEdge {
1543            owner: FormOwnershipNodeKey::FormField(field.id.clone()),
1544            child: FormOwnershipNodeKey::Form(form.id.clone()),
1545            kind: FormOwnershipEdgeKind::ComponentOwnsForm,
1546            provenance: form.provenance.clone(),
1547        });
1548        let fake_form = FormId::for_owner(&asm.components[0].id, "notCanonical");
1549        let fake_field = FieldId::for_form(&fake_form, "missing");
1550        graph.reference_edges.push(FormReferenceEdge {
1551            kind: FormReferenceKind::FieldBindingField,
1552            source: FormOwnershipNodeKey::FieldBinding(binding.id.clone()),
1553            target: FormOwnershipNodeKey::FormField(fake_field),
1554            provenance: binding.expression_provenance.clone(),
1555        });
1556        let node = graph
1557            .nodes
1558            .get_mut(&FormOwnershipNodeKey::FormField(field.id.clone()))
1559            .expect("field node");
1560        if let FormOwnershipNode::FormField { provenance, .. } = node {
1561            *provenance = SourceProvenance::new(
1562                "",
1563                SourceSpan {
1564                    start: 0,
1565                    end: 0,
1566                    line: 0,
1567                    column: 0,
1568                },
1569            );
1570        }
1571        let instance = asm
1572            .component_instance_plan
1573            .instances
1574            .values()
1575            .next()
1576            .expect("component instance");
1577        graph.nodes.insert(
1578            FormOwnershipNodeKey::Component(instance.id.as_semantic_id().clone()),
1579            FormOwnershipNode::Component {
1580                id: instance.id.as_semantic_id().clone(),
1581                provenance: instance.provenance.clone(),
1582            },
1583        );
1584
1585        let validation = validate_form_ownership_graph(&graph, &asm);
1586        let kinds = validation
1587            .diagnostics
1588            .iter()
1589            .map(|diagnostic| diagnostic.kind)
1590            .collect::<BTreeSet<_>>();
1591        assert!(!validation.is_valid);
1592        assert!(kinds.contains(&FormOwnershipIntegrityKind::MissingOwner));
1593        assert!(kinds.contains(&FormOwnershipIntegrityKind::MultipleOwners));
1594        assert!(kinds.contains(&FormOwnershipIntegrityKind::OwnershipCycle));
1595        assert!(kinds.contains(&FormOwnershipIntegrityKind::UnknownOwnershipEndpoint));
1596        assert!(kinds.contains(&FormOwnershipIntegrityKind::UnknownReferenceEndpoint));
1597        assert!(kinds.contains(&FormOwnershipIntegrityKind::MissingProvenance));
1598        assert!(kinds.contains(&FormOwnershipIntegrityKind::InstanceIdentityInDeclarationGraph));
1599        assert!(kinds.contains(&FormOwnershipIntegrityKind::NonCanonicalOrdering));
1600        assert_eq!(
1601            graph.ownership_edges.len(),
1602            asm.form_ownership.ownership_edges.len() + 2
1603        );
1604    }
1605
1606    #[test]
1607    fn validates_product_reciprocity_and_component_mismatches() {
1608        let parsed = presolve_parser::parse_file("src/ProfileEditor.tsx", form_source());
1609        let asm = build_application_semantic_model(&parsed);
1610        let mut malformed = asm.clone();
1611        let form_ids = malformed.forms.keys().cloned().collect::<Vec<_>>();
1612        let field_id = malformed.form_fields.keys().next().expect("Field").clone();
1613        let binding_id = malformed
1614            .form_field_bindings
1615            .keys()
1616            .next()
1617            .expect("binding")
1618            .clone();
1619        malformed
1620            .form_fields
1621            .get_mut(&field_id)
1622            .expect("Field")
1623            .owner_form = form_ids[1].clone();
1624        let binding = malformed
1625            .form_field_bindings
1626            .get_mut(&binding_id)
1627            .expect("binding");
1628        binding.form = form_ids[1].clone();
1629        binding.component =
1630            SemanticId::component_in_module("src/Other.tsx", Some("other-editor"), "OtherEditor");
1631
1632        let validation = validate_form_ownership_graph(&malformed.form_ownership, &malformed);
1633        let kinds = validation
1634            .diagnostics
1635            .iter()
1636            .map(|diagnostic| diagnostic.kind)
1637            .collect::<BTreeSet<_>>();
1638        assert!(kinds.contains(&FormOwnershipIntegrityKind::FieldFormMismatch));
1639        assert!(kinds.contains(&FormOwnershipIntegrityKind::BindingFormMismatch));
1640        assert!(kinds.contains(&FormOwnershipIntegrityKind::ComponentOwnershipMismatch));
1641    }
1642
1643    #[test]
1644    fn is_byte_stable_under_repeated_and_reversed_multi_file_construction() {
1645        let first = r#"
1646@component("first-editor")
1647class FirstEditor {
1648  @form() editor!: Form;
1649  @field(this.editor) value = "first";
1650  render() { return <input field={this.value} />; }
1651}
1652"#;
1653        let second = r#"
1654@component("second-editor")
1655class SecondEditor {
1656  @form() editor!: Form;
1657  @field(this.editor) value = "second";
1658  render() { return <textarea field={this.value} />; }
1659}
1660"#;
1661        let ordered =
1662            CompilationUnit::parse_sources([("src/First.tsx", first), ("src/Second.tsx", second)]);
1663        let reversed =
1664            CompilationUnit::parse_sources([("src/Second.tsx", second), ("src/First.tsx", first)]);
1665        let ordered = build_application_semantic_model_for_unit(&ordered);
1666        let repeated =
1667            build_application_semantic_model_for_unit(&CompilationUnit::parse_sources([
1668                ("src/First.tsx", first),
1669                ("src/Second.tsx", second),
1670            ]));
1671        let reversed = build_application_semantic_model_for_unit(&reversed);
1672
1673        assert_eq!(ordered.form_ownership, repeated.form_ownership);
1674        assert_eq!(ordered.form_ownership, reversed.form_ownership);
1675        assert_eq!(ordered.form_ownership.roots.len(), 2);
1676        assert_eq!(ordered.form_ownership.nodes.len(), 10);
1677        assert!(ordered.form_ownership.validation.is_valid);
1678    }
1679
1680    #[test]
1681    fn keeps_repeated_component_instances_out_of_the_declaration_graph() {
1682        let parsed = presolve_parser::parse_file(
1683            "src/App.tsx",
1684            r#"
1685@component("profile-editor")
1686class ProfileEditor {
1687  @form() profile!: Form;
1688  @field(this.profile) name = "";
1689  render() { return <input field={this.name} />; }
1690}
1691
1692@component("app-root")
1693class AppRoot {
1694  render() {
1695    return <main><ProfileEditor /><ProfileEditor /></main>;
1696  }
1697}
1698"#,
1699        );
1700        let asm = build_application_semantic_model(&parsed);
1701        let editor = asm
1702            .components
1703            .iter()
1704            .find(|component| component.element_name.as_deref() == Some("profile-editor"))
1705            .expect("ProfileEditor");
1706        let instances = asm
1707            .component_instances_for_definition(&editor.id)
1708            .into_iter()
1709            .collect::<Vec<_>>();
1710        let form = FormId::for_owner(&editor.id, "profile");
1711
1712        assert_eq!(instances.len(), 2);
1713        assert_eq!(
1714            asm.form_ownership
1715                .nodes
1716                .keys()
1717                .filter(|key| matches!(key, FormOwnershipNodeKey::Form(_)))
1718                .count(),
1719            1
1720        );
1721        let first = FormInstanceId::for_component_instance(&instances[0].id, &form);
1722        let second = FormInstanceId::for_component_instance(&instances[1].id, &form);
1723        assert_ne!(first, second);
1724        assert_eq!(
1725            first,
1726            FormInstanceId::for_component_instance(&instances[0].id, &form)
1727        );
1728        assert!(asm.form_ownership.nodes.keys().all(|key| {
1729            key.semantic_id() != first.as_semantic_id()
1730                && key.semantic_id() != second.as_semantic_id()
1731        }));
1732        assert!(asm.form_ownership.validation.is_valid);
1733    }
1734
1735    #[test]
1736    fn projects_form_ownership_products_into_semantic_graph_v6() {
1737        let parsed = presolve_parser::parse_file("src/ProfileEditor.tsx", form_source());
1738        let asm = build_application_semantic_model(&parsed);
1739        let public = build_semantic_graph(&asm);
1740
1741        assert_eq!(public.schema_version, SEMANTIC_GRAPH_SCHEMA_VERSION);
1742        assert_eq!(SEMANTIC_GRAPH_SCHEMA_VERSION, 6);
1743        assert!(public
1744            .nodes
1745            .iter()
1746            .any(|node| matches!(node.kind, crate::SemanticGraphNodeKind::Form)));
1747        assert!(public
1748            .edges
1749            .iter()
1750            .any(|edge| matches!(edge.kind, crate::SemanticGraphEdgeKind::FormOwnsField)));
1751    }
1752}