Skip to main content

type_bridge_schema/
resolve.rs

1use std::collections::{BTreeMap, BTreeSet};
2
3use type_bridge_contract::capability::{CapabilityId, CapabilitySet};
4use type_bridge_contract::codec::to_canonical_json;
5use type_bridge_contract::diagnostic::DiagnosticCategory;
6use type_bridge_contract::fingerprint::{
7    CanonicalizationVersion, Fingerprint, FingerprintDomain, SemanticProfileId,
8};
9use type_bridge_contract::id::{AttributeId, FunctionId, RoleId, StructId, TypeId, TypeKind};
10use type_bridge_contract::schema::{
11    AnnotationKindId, AnnotationSubjectId, DeclaredIdentityFingerprint, DeclaredSchema,
12    FunctionFact, InterfaceKind, OwnsFact, OwnsFactId, PlaysFact, PlaysFactId, RelatesFact,
13    SchemaAnnotationValue, SchemaDiagnostics, SchemaFact, SchemaFactId, SemanticProfile,
14    SemanticSchemaFingerprint, StructFact, StructField, SubFactId, ValueFactId,
15};
16use type_bridge_contract::value::{Cardinality, ValueTypeTag};
17
18use crate::semantic_schema_fingerprint;
19
20/// Direct-fact identity plus the inheritance path used to derive an effective entry.
21#[derive(Clone, Debug, Eq, PartialEq)]
22pub struct ResolutionOrigin {
23    declared: SchemaFactId,
24    inheritance_path: Vec<TypeId>,
25}
26
27impl ResolutionOrigin {
28    fn direct(declared: SchemaFactId) -> Self {
29        Self {
30            declared,
31            inheritance_path: Vec::new(),
32        }
33    }
34
35    fn inherited(&self, via: TypeId) -> Self {
36        let mut origin = self.clone();
37        origin.inheritance_path.push(via);
38        origin
39    }
40
41    /// Return the direct fact from which this entry was derived.
42    #[must_use]
43    pub const fn declared(&self) -> &SchemaFactId {
44        &self.declared
45    }
46
47    /// Return the ordered inheritance path; empty means direct.
48    #[must_use]
49    pub fn inheritance_path(&self) -> &[TypeId] {
50        &self.inheritance_path
51    }
52
53    /// Report whether the effective entry is direct on its resolved type.
54    #[must_use]
55    pub fn is_direct(&self) -> bool {
56        self.inheritance_path.is_empty()
57    }
58}
59
60/// Effective ownership, including resolved constraints and direct origin.
61#[derive(Clone, Debug, Eq, PartialEq)]
62pub struct EffectiveOwns {
63    id: OwnsFactId,
64    origin: ResolutionOrigin,
65    annotations: BTreeMap<AnnotationKindId, SchemaAnnotationValue>,
66    cardinality: Cardinality,
67    key: bool,
68    unique: bool,
69}
70
71impl EffectiveOwns {
72    /// Return the effective ownership identity for the resolved owner.
73    #[must_use]
74    pub const fn id(&self) -> &OwnsFactId {
75        &self.id
76    }
77
78    /// Return its direct declaration origin.
79    #[must_use]
80    pub const fn origin(&self) -> &ResolutionOrigin {
81        &self.origin
82    }
83
84    /// Return all direct or inherited annotations keyed by independent kind.
85    #[must_use]
86    pub const fn annotations(&self) -> &BTreeMap<AnnotationKindId, SchemaAnnotationValue> {
87        &self.annotations
88    }
89
90    /// Return explicit or profile-materialized cardinality.
91    #[must_use]
92    pub const fn cardinality(&self) -> Cardinality {
93        self.cardinality
94    }
95
96    /// Report key semantics.
97    #[must_use]
98    pub const fn is_key(&self) -> bool {
99        self.key
100    }
101
102    /// Report uniqueness semantics independent of key.
103    #[must_use]
104    pub const fn is_unique(&self) -> bool {
105        self.unique
106    }
107}
108
109/// Effective role-playing interface and its independent annotations.
110#[derive(Clone, Debug, Eq, PartialEq)]
111pub struct EffectivePlays {
112    id: PlaysFactId,
113    origin: ResolutionOrigin,
114    annotations: BTreeMap<AnnotationKindId, SchemaAnnotationValue>,
115    cardinality: Cardinality,
116}
117
118impl EffectivePlays {
119    /// Return the effective playing identity.
120    #[must_use]
121    pub const fn id(&self) -> &PlaysFactId {
122        &self.id
123    }
124
125    /// Return its direct declaration origin.
126    #[must_use]
127    pub const fn origin(&self) -> &ResolutionOrigin {
128        &self.origin
129    }
130
131    /// Return independent annotations for this exact player-role fact.
132    #[must_use]
133    pub const fn annotations(&self) -> &BTreeMap<AnnotationKindId, SchemaAnnotationValue> {
134        &self.annotations
135    }
136
137    /// Return explicit or profile-materialized cardinality.
138    #[must_use]
139    pub const fn cardinality(&self) -> Cardinality {
140        self.cardinality
141    }
142}
143
144/// Derived relation-role identity that can represent an inherited role interface.
145#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
146pub struct EffectiveRelatesId {
147    relation: TypeId,
148    role: RoleId,
149}
150
151impl EffectiveRelatesId {
152    fn new(relation: TypeId, role: RoleId) -> Self {
153        Self { relation, role }
154    }
155
156    /// Return the relation on which the role is effective.
157    #[must_use]
158    pub const fn relation(&self) -> &TypeId {
159        &self.relation
160    }
161
162    /// Return the role identity, whose declaring relation may be an ancestor.
163    #[must_use]
164    pub const fn role(&self) -> &RoleId {
165        &self.role
166    }
167}
168
169/// Effective related role, including specialization replacement history.
170#[derive(Clone, Debug, Eq, PartialEq)]
171pub struct EffectiveRelates {
172    id: EffectiveRelatesId,
173    origin: ResolutionOrigin,
174    annotations: BTreeMap<AnnotationKindId, SchemaAnnotationValue>,
175    cardinality: Cardinality,
176    replaced_roles: BTreeSet<RoleId>,
177    is_abstract: bool,
178}
179
180impl EffectiveRelates {
181    /// Return the effective related-role identity.
182    #[must_use]
183    pub const fn id(&self) -> &EffectiveRelatesId {
184        &self.id
185    }
186
187    /// Return its direct declaration origin.
188    #[must_use]
189    pub const fn origin(&self) -> &ResolutionOrigin {
190        &self.origin
191    }
192
193    /// Return independent annotations on the exact relates fact.
194    #[must_use]
195    pub const fn annotations(&self) -> &BTreeMap<AnnotationKindId, SchemaAnnotationValue> {
196        &self.annotations
197    }
198
199    /// Return explicit or profile-materialized cardinality.
200    #[must_use]
201    pub const fn cardinality(&self) -> Cardinality {
202        self.cardinality
203    }
204
205    /// Return every inherited role interface replaced by specialization.
206    #[must_use]
207    pub const fn replaced_roles(&self) -> &BTreeSet<RoleId> {
208        &self.replaced_roles
209    }
210
211    /// Report whether this role cannot be instantiated directly.
212    #[must_use]
213    pub const fn is_abstract(&self) -> bool {
214        self.is_abstract
215    }
216
217    /// Report whether players may fill this role at the effective relation scope.
218    ///
219    /// TypeDB forbids players only at the relation that directly declares an
220    /// abstract role. A concrete child relation may use the same role when it is
221    /// inherited without specialization, while the role remains schema-abstract.
222    #[must_use]
223    pub fn accepts_players_at_effective_scope(&self) -> bool {
224        !self.is_abstract || !self.origin.is_direct()
225    }
226}
227
228/// Effective direct or inherited attribute value domain.
229#[derive(Clone, Debug, Eq, PartialEq)]
230pub struct EffectiveValueType {
231    value_type: ValueTypeTag,
232    origin: ResolutionOrigin,
233    annotations: BTreeMap<AnnotationKindId, SchemaAnnotationValue>,
234}
235
236impl EffectiveValueType {
237    /// Return the effective value domain.
238    #[must_use]
239    pub const fn value_type(&self) -> ValueTypeTag {
240        self.value_type
241    }
242
243    /// Return its direct value-fact origin.
244    #[must_use]
245    pub const fn origin(&self) -> &ResolutionOrigin {
246        &self.origin
247    }
248
249    /// Return effective value-domain constraints.
250    #[must_use]
251    pub const fn annotations(&self) -> &BTreeMap<AnnotationKindId, SchemaAnnotationValue> {
252        &self.annotations
253    }
254}
255
256/// One direct subtype edge, including its exact declaration origin and annotations.
257#[derive(Clone, Debug, Eq, PartialEq)]
258pub struct EffectiveSub {
259    id: SubFactId,
260    origin: ResolutionOrigin,
261    annotations: BTreeMap<AnnotationKindId, SchemaAnnotationValue>,
262}
263
264impl EffectiveSub {
265    /// Return the exact direct subtype-edge identity.
266    #[must_use]
267    pub const fn id(&self) -> &SubFactId {
268        &self.id
269    }
270
271    /// Return the direct declaration origin.
272    #[must_use]
273    pub const fn origin(&self) -> &ResolutionOrigin {
274        &self.origin
275    }
276
277    /// Return annotations attached to this exact subtype edge.
278    #[must_use]
279    pub const fn annotations(&self) -> &BTreeMap<AnnotationKindId, SchemaAnnotationValue> {
280        &self.annotations
281    }
282}
283
284/// Fully resolved type semantics, rebuilt only from direct facts.
285#[derive(Clone, Debug, Eq, PartialEq)]
286pub struct ResolvedType {
287    id: TypeId,
288    supertypes: Vec<TypeId>,
289    subtypes: BTreeSet<TypeId>,
290    direct_sub: Option<EffectiveSub>,
291    annotations: BTreeMap<AnnotationKindId, SchemaAnnotationValue>,
292    value_type: Option<EffectiveValueType>,
293    owns: BTreeMap<AttributeId, EffectiveOwns>,
294    plays: BTreeMap<RoleId, EffectivePlays>,
295    relates: BTreeMap<RoleId, EffectiveRelates>,
296    key_attributes: BTreeSet<AttributeId>,
297    unique_attributes: BTreeSet<AttributeId>,
298    owned_attribute_order: Vec<AttributeId>,
299    is_abstract: bool,
300    constructible: bool,
301}
302
303impl ResolvedType {
304    /// Return the stable type identity.
305    #[must_use]
306    pub const fn id(&self) -> &TypeId {
307        &self.id
308    }
309
310    /// Return transitive supertypes from nearest to furthest.
311    #[must_use]
312    pub fn supertypes(&self) -> &[TypeId] {
313        &self.supertypes
314    }
315
316    /// Return all transitive subtypes.
317    #[must_use]
318    pub const fn subtypes(&self) -> &BTreeSet<TypeId> {
319        &self.subtypes
320    }
321
322    /// Return the exact direct subtype edge, if this type declares a parent.
323    #[must_use]
324    pub const fn direct_sub(&self) -> Option<&EffectiveSub> {
325        self.direct_sub.as_ref()
326    }
327
328    /// Return effective type annotations.
329    #[must_use]
330    pub const fn annotations(&self) -> &BTreeMap<AnnotationKindId, SchemaAnnotationValue> {
331        &self.annotations
332    }
333
334    /// Return the effective attribute value domain, if this is an attribute.
335    #[must_use]
336    pub const fn value_type(&self) -> Option<&EffectiveValueType> {
337        self.value_type.as_ref()
338    }
339
340    /// Return effective ownerships keyed by attribute identity.
341    #[must_use]
342    pub const fn owns(&self) -> &BTreeMap<AttributeId, EffectiveOwns> {
343        &self.owns
344    }
345
346    /// Return effective role-playing interfaces keyed by role identity.
347    #[must_use]
348    pub const fn plays(&self) -> &BTreeMap<RoleId, EffectivePlays> {
349        &self.plays
350    }
351
352    /// Return effective related roles for relation types.
353    #[must_use]
354    pub const fn relates(&self) -> &BTreeMap<RoleId, EffectiveRelates> {
355        &self.relates
356    }
357
358    /// Return effective key attributes.
359    #[must_use]
360    pub const fn key_attributes(&self) -> &BTreeSet<AttributeId> {
361        &self.key_attributes
362    }
363
364    /// Return effective unique attributes not implied solely by key.
365    #[must_use]
366    pub const fn unique_attributes(&self) -> &BTreeSet<AttributeId> {
367        &self.unique_attributes
368    }
369
370    /// Return deterministic ownership order metadata.
371    #[must_use]
372    pub fn owned_attribute_order(&self) -> &[AttributeId] {
373        &self.owned_attribute_order
374    }
375
376    /// Report direct abstractness.
377    #[must_use]
378    pub const fn is_abstract(&self) -> bool {
379        self.is_abstract
380    }
381
382    /// Report whether instances can be constructed under effective role constraints.
383    #[must_use]
384    pub const fn is_constructible(&self) -> bool {
385        self.constructible
386    }
387}
388
389/// Resolved role descriptor and accepted concrete player types.
390#[derive(Clone, Debug, Eq, PartialEq)]
391pub struct ResolvedRole {
392    id: RoleId,
393    accepted_players: BTreeSet<TypeId>,
394    replacing_roles: BTreeSet<RoleId>,
395    is_abstract: bool,
396}
397
398impl ResolvedRole {
399    /// Return the declared role identity.
400    #[must_use]
401    pub const fn id(&self) -> &RoleId {
402        &self.id
403    }
404
405    /// Return concrete entity or relation types accepted as players.
406    #[must_use]
407    pub const fn accepted_players(&self) -> &BTreeSet<TypeId> {
408        &self.accepted_players
409    }
410
411    /// Return ancestor role interfaces this role replaces.
412    #[must_use]
413    pub const fn replacing_roles(&self) -> &BTreeSet<RoleId> {
414        &self.replacing_roles
415    }
416
417    /// Report declared role abstractness.
418    #[must_use]
419    pub const fn is_abstract(&self) -> bool {
420        self.is_abstract
421    }
422}
423
424/// Opaque resolved function retained without speculative body typing.
425#[derive(Clone, Debug, Eq, PartialEq)]
426pub struct ResolvedFunction {
427    declaration: FunctionFact,
428    annotations: BTreeMap<AnnotationKindId, SchemaAnnotationValue>,
429}
430
431impl ResolvedFunction {
432    /// Return the stable function identity.
433    #[must_use]
434    pub const fn id(&self) -> &FunctionId {
435        self.declaration.id()
436    }
437
438    /// Return the validated direct declaration without rewriting its body.
439    #[must_use]
440    pub const fn declaration(&self) -> &FunctionFact {
441        &self.declaration
442    }
443
444    /// Return function documentation and metadata.
445    #[must_use]
446    pub const fn annotations(&self) -> &BTreeMap<AnnotationKindId, SchemaAnnotationValue> {
447        &self.annotations
448    }
449}
450
451/// A validated struct in resolved schema output.
452#[derive(Clone, Debug, Eq, PartialEq)]
453pub struct ResolvedStruct {
454    id: StructId,
455    fields: Vec<StructField>,
456}
457
458impl ResolvedStruct {
459    /// Return the stable struct identity.
460    #[must_use]
461    pub const fn id(&self) -> &StructId {
462        &self.id
463    }
464
465    /// Return fields in their semantic declaration order.
466    #[must_use]
467    pub fn fields(&self) -> &[StructField] {
468        &self.fields
469    }
470}
471
472/// Stable content-addressed query descriptor identity.
473#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
474pub struct DescriptorId(String);
475
476impl DescriptorId {
477    /// Return the lowercase content-addressed descriptor string.
478    #[must_use]
479    pub fn as_str(&self) -> &str {
480        &self.0
481    }
482}
483
484/// Stable direct-fact descriptor lookup.
485#[derive(Clone, Debug, Default, Eq, PartialEq)]
486pub struct DescriptorIndex {
487    descriptors: BTreeMap<DescriptorId, SchemaFactId>,
488}
489
490impl DescriptorIndex {
491    /// Return the fact associated with a descriptor.
492    #[must_use]
493    pub fn get(&self, descriptor: &DescriptorId) -> Option<&SchemaFactId> {
494        self.descriptors.get(descriptor)
495    }
496
497    /// Iterate descriptors in stable content order.
498    pub fn iter(&self) -> impl ExactSizeIterator<Item = (&DescriptorId, &SchemaFactId)> {
499        self.descriptors.iter()
500    }
501}
502
503/// Projection dependency edges and deterministic strongly connected components.
504#[derive(Clone, Debug, Default, Eq, PartialEq)]
505pub struct SchemaDependencyGraph {
506    edges: BTreeMap<TypeId, BTreeSet<TypeId>>,
507    strongly_connected_components: Vec<BTreeSet<TypeId>>,
508}
509
510impl SchemaDependencyGraph {
511    /// Return outgoing projection dependencies for one type.
512    #[must_use]
513    pub fn dependencies(&self, id: &TypeId) -> Option<&BTreeSet<TypeId>> {
514        self.edges.get(id)
515    }
516
517    /// Return deterministic projection-dependency SCCs.
518    #[must_use]
519    pub fn strongly_connected_components(&self) -> &[BTreeSet<TypeId>] {
520        &self.strongly_connected_components
521    }
522}
523
524/// Immutable pure resolution result.
525#[derive(Clone, Debug, Eq, PartialEq)]
526pub struct ResolvedSchema {
527    declared_identity: DeclaredIdentityFingerprint,
528    semantic_fingerprint: SemanticSchemaFingerprint,
529    types: BTreeMap<TypeId, ResolvedType>,
530    roles: BTreeMap<RoleId, ResolvedRole>,
531    functions: BTreeMap<FunctionId, ResolvedFunction>,
532    structs: BTreeMap<StructId, ResolvedStruct>,
533    descriptor_index: DescriptorIndex,
534    dependency_graph: SchemaDependencyGraph,
535}
536
537impl ResolvedSchema {
538    /// Return the unchanged direct-declaration identity.
539    #[must_use]
540    pub const fn declared_identity_fingerprint(&self) -> &DeclaredIdentityFingerprint {
541        &self.declared_identity
542    }
543
544    /// Return canonical direct semantics under the selected profile.
545    #[must_use]
546    pub const fn semantic_fingerprint(&self) -> &SemanticSchemaFingerprint {
547        &self.semantic_fingerprint
548    }
549
550    /// Return resolved types by stable identity.
551    #[must_use]
552    pub const fn types(&self) -> &BTreeMap<TypeId, ResolvedType> {
553        &self.types
554    }
555
556    /// Return resolved roles by declaring-role identity.
557    #[must_use]
558    pub const fn roles(&self) -> &BTreeMap<RoleId, ResolvedRole> {
559        &self.roles
560    }
561
562    /// Return opaque resolved functions.
563    #[must_use]
564    pub const fn functions(&self) -> &BTreeMap<FunctionId, ResolvedFunction> {
565        &self.functions
566    }
567
568    /// Return resolved structs in stable identity order.
569    #[must_use]
570    pub const fn structs(&self) -> &BTreeMap<StructId, ResolvedStruct> {
571        &self.structs
572    }
573
574    /// Return stable direct-fact descriptors.
575    #[must_use]
576    pub const fn descriptor_index(&self) -> &DescriptorIndex {
577        &self.descriptor_index
578    }
579
580    /// Return projection dependency SCCs.
581    #[must_use]
582    pub const fn dependency_graph(&self) -> &SchemaDependencyGraph {
583        &self.dependency_graph
584    }
585}
586
587#[derive(Default)]
588struct DirectIndex {
589    types: BTreeSet<TypeId>,
590    parents: BTreeMap<TypeId, Vec<SubFactId>>,
591    values: BTreeMap<AttributeId, (ValueTypeTag, ValueFactId)>,
592    owns: BTreeMap<TypeId, Vec<OwnsFact>>,
593    relates: BTreeMap<TypeId, Vec<RelatesFact>>,
594    plays: BTreeMap<TypeId, Vec<PlaysFact>>,
595    annotations: BTreeMap<AnnotationSubjectId, BTreeMap<AnnotationKindId, SchemaAnnotationValue>>,
596    functions: BTreeMap<FunctionId, FunctionFact>,
597    structs: BTreeMap<StructId, StructFact>,
598}
599
600/// Schema-feature capabilities implemented by the built-in compatibility resolver.
601pub const BUILTIN_SCHEMA_CAPABILITY_IDS: &[&str] =
602    &["schema.annotations", "schema.doc-meta", "schema.roles"];
603
604fn builtin_schema_capabilities() -> CapabilitySet {
605    BUILTIN_SCHEMA_CAPABILITY_IDS
606        .iter()
607        .map(|capability| {
608            CapabilityId::new(*capability).expect("built-in schema capability IDs are canonical")
609        })
610        .collect()
611}
612
613/// Resolve direct schema facts under the built-in semantic-profile compatibility surface.
614pub fn resolve(
615    declared: &DeclaredSchema,
616    profile_id: &SemanticProfileId,
617) -> Result<ResolvedSchema, SchemaDiagnostics> {
618    resolve_schema_with_capabilities(declared, profile_id, &builtin_schema_capabilities())
619}
620
621/// Resolve direct schema facts using an explicitly available schema-feature set.
622pub fn resolve_schema_with_capabilities(
623    declared: &DeclaredSchema,
624    profile_id: &SemanticProfileId,
625    available_capabilities: &CapabilitySet,
626) -> Result<ResolvedSchema, SchemaDiagnostics> {
627    declared
628        .required_capabilities()
629        .ensure_supported_by(available_capabilities)
630        .map_err(|diagnostic| {
631            type_bridge_contract::schema::SchemaDiagnostics::one(
632                type_bridge_contract::schema::SchemaDiagnostic::new(diagnostic, None),
633            )
634        })?;
635    let profile = SemanticProfile::resolve(profile_id).map_err(|diagnostic| {
636        type_bridge_contract::schema::SchemaDiagnostics::one(
637            type_bridge_contract::schema::SchemaDiagnostic::new(diagnostic, None),
638        )
639    })?;
640    let index = DirectIndex::build(declared)?;
641    let parents = validate_parents(declared, &index)?;
642    validate_inheritance_cycles(declared, &index.types, &parents)?;
643
644    let mut order = index.types.iter().cloned().collect::<Vec<_>>();
645    order.sort_by_key(|id| (ancestor_chain(id, &parents).len(), id.clone()));
646    let mut types = BTreeMap::<TypeId, ResolvedType>::new();
647
648    for id in order {
649        let parent = parents.get(&id);
650        let inherited = parent.and_then(|parent| types.get(parent)).cloned();
651        let mut resolved = inherited
652            .as_ref()
653            .map(|parent| inherit_type(parent, &id, &profile))
654            .transpose()?
655            .unwrap_or_else(|| empty_resolved_type(id.clone()));
656        resolved.id = id.clone();
657        resolved.supertypes = ancestor_chain(&id, &parents);
658        if let Some(parent) = parent {
659            let sub_id = SubFactId::new(id.clone(), parent.clone()).map_err(no_source)?;
660            resolved.direct_sub = Some(EffectiveSub {
661                id: sub_id.clone(),
662                origin: ResolutionOrigin::direct(SchemaFactId::Sub(sub_id.clone())),
663                annotations: annotations_for(&index, AnnotationSubjectId::Sub(sub_id)),
664            });
665        }
666
667        let direct_annotations = annotations_for(&index, AnnotationSubjectId::Type(id.clone()));
668        if let Some(independent) = inherited
669            .as_ref()
670            .and_then(|parent| parent.annotations.get(&AnnotationKindId::Independent))
671        {
672            resolved
673                .annotations
674                .insert(AnnotationKindId::Independent, independent.clone());
675        }
676        resolved.annotations.extend(direct_annotations);
677        resolved.is_abstract = resolved
678            .annotations
679            .contains_key(&AnnotationKindId::Abstract);
680
681        if id.kind() == TypeKind::Attribute {
682            let attribute = AttributeId::new(id.label().as_str()).map_err(no_source)?;
683            if let Some((value_type, value_id)) = index.values.get(&attribute) {
684                resolved.value_type = Some(EffectiveValueType {
685                    value_type: *value_type,
686                    origin: ResolutionOrigin::direct(SchemaFactId::Value(value_id.clone())),
687                    annotations: annotations_for(
688                        &index,
689                        AnnotationSubjectId::Value(value_id.clone()),
690                    ),
691                });
692            }
693        }
694
695        for owns in index.owns.get(&id).into_iter().flatten() {
696            let annotations = annotations_for(&index, AnnotationSubjectId::Owns(owns.id().clone()));
697            let effective = EffectiveOwns {
698                id: owns.id().clone(),
699                origin: ResolutionOrigin::direct(SchemaFactId::Owns(owns.id().clone())),
700                cardinality: cardinality(&annotations, &profile, InterfaceKind::Owns),
701                key: annotations.contains_key(&AnnotationKindId::Key),
702                unique: annotations.contains_key(&AnnotationKindId::Unique)
703                    || annotations.contains_key(&AnnotationKindId::Key),
704                annotations,
705            };
706            resolved
707                .owns
708                .insert(owns.id().attribute().clone(), effective);
709        }
710
711        for plays in index.plays.get(&id).into_iter().flatten() {
712            let annotations =
713                annotations_for(&index, AnnotationSubjectId::Plays(plays.id().clone()));
714            let effective = EffectivePlays {
715                id: plays.id().clone(),
716                origin: ResolutionOrigin::direct(SchemaFactId::Plays(plays.id().clone())),
717                cardinality: cardinality(&annotations, &profile, InterfaceKind::Plays),
718                annotations,
719            };
720            resolved.plays.insert(plays.id().role().clone(), effective);
721        }
722
723        for relates in index.relates.get(&id).into_iter().flatten() {
724            let annotations =
725                annotations_for(&index, AnnotationSubjectId::Relates(relates.id().clone()));
726            let mut replaced_roles = BTreeSet::new();
727            if let Some(specialized) = relates.specializes() {
728                let replaced = resolved
729                    .relates
730                    .iter()
731                    .find_map(|(role, effective)| {
732                        (role == specialized || effective.replaced_roles.contains(specialized))
733                            .then(|| role.clone())
734                    })
735                    .ok_or_else(|| {
736                        source_error(
737                            declared,
738                            &SchemaFactId::Relates(relates.id().clone()),
739                            "invalid_role_specialization",
740                            "specialized role is not effective on the child relation",
741                        )
742                    })?;
743                let replaced = resolved
744                    .relates
745                    .remove(&replaced)
746                    .expect("located effective role exists");
747                replaced_roles.extend(replaced.replaced_roles);
748                replaced_roles.insert(specialized.clone());
749            }
750            let effective = EffectiveRelates {
751                id: EffectiveRelatesId::new(id.clone(), relates.id().role().clone()),
752                origin: ResolutionOrigin::direct(SchemaFactId::Relates(relates.id().clone())),
753                cardinality: cardinality(&annotations, &profile, InterfaceKind::Relates),
754                is_abstract: annotations.contains_key(&AnnotationKindId::Abstract),
755                annotations,
756                replaced_roles,
757            };
758            resolved
759                .relates
760                .insert(relates.id().role().clone(), effective);
761        }
762
763        resolved.key_attributes = resolved
764            .owns
765            .values()
766            .filter(|owns| owns.key)
767            .map(|owns| owns.id.attribute().clone())
768            .collect();
769        resolved.unique_attributes = resolved
770            .owns
771            .values()
772            .filter(|owns| owns.unique)
773            .map(|owns| owns.id.attribute().clone())
774            .collect();
775        resolved.owned_attribute_order = resolved.owns.keys().cloned().collect();
776        resolved.constructible = !resolved.is_abstract
777            && resolved
778                .relates
779                .values()
780                .all(EffectiveRelates::accepts_players_at_effective_scope);
781        types.insert(id, resolved);
782    }
783
784    populate_subtypes(&mut types, &parents);
785    let roles = resolve_roles(&types, &index);
786    let functions = index
787        .functions
788        .values()
789        .map(|function| {
790            (
791                function.id().clone(),
792                ResolvedFunction {
793                    declaration: function.clone(),
794                    annotations: annotations_for(
795                        &index,
796                        AnnotationSubjectId::Function(function.id().clone()),
797                    ),
798                },
799            )
800        })
801        .collect();
802    let structs = index
803        .structs
804        .values()
805        .map(|fact| {
806            (
807                fact.id().clone(),
808                ResolvedStruct {
809                    id: fact.id().clone(),
810                    fields: fact.fields().to_vec(),
811                },
812            )
813        })
814        .collect();
815    let descriptor_index = descriptor_index(declared)?;
816    let dependency_graph = dependency_graph(&types, &roles);
817    let semantic_fingerprint = semantic_schema_fingerprint(declared, profile_id)?;
818
819    Ok(ResolvedSchema {
820        declared_identity: declared.declared_identity_fingerprint().clone(),
821        semantic_fingerprint,
822        types,
823        roles,
824        functions,
825        structs,
826        descriptor_index,
827        dependency_graph,
828    })
829}
830
831impl DirectIndex {
832    fn build(declared: &DeclaredSchema) -> Result<Self, SchemaDiagnostics> {
833        let mut index = Self::default();
834        for fact in declared.facts() {
835            match fact {
836                SchemaFact::Type(fact) => {
837                    index.types.insert(fact.id().clone());
838                }
839                SchemaFact::Sub(fact) => index
840                    .parents
841                    .entry(fact.id().subtype().clone())
842                    .or_default()
843                    .push(fact.id().clone()),
844                SchemaFact::Value(fact) => {
845                    index.values.insert(
846                        fact.id().attribute().clone(),
847                        (fact.value_type(), fact.id().clone()),
848                    );
849                }
850                SchemaFact::Owns(fact) => index
851                    .owns
852                    .entry(fact.id().owner().clone())
853                    .or_default()
854                    .push(fact.clone()),
855                SchemaFact::Relates(fact) => index
856                    .relates
857                    .entry(fact.id().relation().clone())
858                    .or_default()
859                    .push(fact.clone()),
860                SchemaFact::Plays(fact) => index
861                    .plays
862                    .entry(fact.id().player().clone())
863                    .or_default()
864                    .push(fact.clone()),
865                SchemaFact::Annotation(fact) => {
866                    index
867                        .annotations
868                        .entry(fact.id().subject().clone())
869                        .or_default()
870                        .insert(fact.id().kind().clone(), fact.value().clone());
871                }
872                SchemaFact::Function(fact) => {
873                    index.functions.insert(fact.id().clone(), fact.clone());
874                }
875                SchemaFact::Struct(fact) => {
876                    index.structs.insert(fact.id().clone(), fact.clone());
877                }
878            }
879        }
880        Ok(index)
881    }
882}
883
884fn validate_parents(
885    declared: &DeclaredSchema,
886    index: &DirectIndex,
887) -> Result<BTreeMap<TypeId, TypeId>, SchemaDiagnostics> {
888    let mut parents = BTreeMap::new();
889    for (subtype, facts) in &index.parents {
890        if facts.len() > 1 {
891            let first = SchemaFactId::Sub(facts[0].clone());
892            let second = SchemaFactId::Sub(facts[1].clone());
893            let primary = declared.source(&second).cloned();
894            let related = declared.source(&first).cloned();
895            if let (Some(primary), Some(related)) = (primary, related) {
896                return Err(crate::yaml::diagnostic_with_related(
897                    DiagnosticCategory::InvalidContract,
898                    "multiple_type_parents",
899                    "a schema type has more than one same-kind parent",
900                    primary,
901                    related,
902                    "first parent declaration is here",
903                ));
904            }
905            return Err(source_error(
906                declared,
907                &second,
908                "multiple_type_parents",
909                "a schema type has more than one same-kind parent",
910            ));
911        }
912        let parent = facts[0].supertype().clone();
913        if subtype.kind() != parent.kind() {
914            return Err(source_error(
915                declared,
916                &SchemaFactId::Sub(facts[0].clone()),
917                "invalid_type_parent_kind",
918                "schema subtype and supertype must have the same kind",
919            ));
920        }
921        parents.insert(subtype.clone(), parent);
922    }
923    Ok(parents)
924}
925
926fn validate_inheritance_cycles(
927    declared: &DeclaredSchema,
928    types: &BTreeSet<TypeId>,
929    parents: &BTreeMap<TypeId, TypeId>,
930) -> Result<(), SchemaDiagnostics> {
931    for start in types {
932        let mut current = start;
933        let mut visited = BTreeSet::new();
934        while let Some(parent) = parents.get(current) {
935            if !visited.insert(current.clone()) {
936                let fact = SubFactId::new(current.clone(), parent.clone()).map_err(no_source)?;
937                return Err(source_error(
938                    declared,
939                    &SchemaFactId::Sub(fact),
940                    "schema_inheritance_cycle",
941                    "schema type inheritance contains a cycle",
942                ));
943            }
944            current = parent;
945        }
946    }
947    Ok(())
948}
949
950fn ancestor_chain(id: &TypeId, parents: &BTreeMap<TypeId, TypeId>) -> Vec<TypeId> {
951    let mut ancestors = Vec::new();
952    let mut current = id;
953    while let Some(parent) = parents.get(current) {
954        ancestors.push(parent.clone());
955        current = parent;
956    }
957    ancestors
958}
959
960fn empty_resolved_type(id: TypeId) -> ResolvedType {
961    ResolvedType {
962        id,
963        supertypes: Vec::new(),
964        subtypes: BTreeSet::new(),
965        direct_sub: None,
966        annotations: BTreeMap::new(),
967        value_type: None,
968        owns: BTreeMap::new(),
969        plays: BTreeMap::new(),
970        relates: BTreeMap::new(),
971        key_attributes: BTreeSet::new(),
972        unique_attributes: BTreeSet::new(),
973        owned_attribute_order: Vec::new(),
974        is_abstract: false,
975        constructible: true,
976    }
977}
978
979fn inherit_type(
980    parent: &ResolvedType,
981    child: &TypeId,
982    profile: &SemanticProfile,
983) -> Result<ResolvedType, SchemaDiagnostics> {
984    let mut resolved = empty_resolved_type(child.clone());
985    resolved.annotations = parent
986        .annotations
987        .iter()
988        .filter(|(kind, _)| **kind == AnnotationKindId::Independent)
989        .map(|(kind, value)| (kind.clone(), value.clone()))
990        .collect();
991    resolved.value_type = parent.value_type.as_ref().map(|value| EffectiveValueType {
992        value_type: value.value_type,
993        origin: value.origin.inherited(parent.id.clone()),
994        annotations: value.annotations.clone(),
995    });
996    for owns in parent.owns.values() {
997        let id = OwnsFactId::new(child.clone(), owns.id.attribute().clone()).map_err(no_source)?;
998        let mut inherited = owns.clone();
999        inherited.id = id;
1000        inherited.origin = inherited.origin.inherited(parent.id.clone());
1001        inherited.cardinality = profile.effective_cardinality(
1002            InterfaceKind::Owns,
1003            inherited
1004                .annotations
1005                .get(&AnnotationKindId::Card)
1006                .and_then(cardinality_value),
1007            inherited.key,
1008        );
1009        resolved
1010            .owns
1011            .insert(inherited.id.attribute().clone(), inherited);
1012    }
1013    for plays in parent.plays.values() {
1014        let id = PlaysFactId::new(child.clone(), plays.id.role().clone()).map_err(no_source)?;
1015        let mut inherited = plays.clone();
1016        inherited.id = id;
1017        inherited.origin = inherited.origin.inherited(parent.id.clone());
1018        resolved
1019            .plays
1020            .insert(inherited.id.role().clone(), inherited);
1021    }
1022    for relates in parent.relates.values() {
1023        let mut inherited = relates.clone();
1024        inherited.id = EffectiveRelatesId::new(child.clone(), relates.id.role().clone());
1025        inherited.origin = inherited.origin.inherited(parent.id.clone());
1026        resolved
1027            .relates
1028            .insert(inherited.id.role().clone(), inherited);
1029    }
1030    Ok(resolved)
1031}
1032
1033fn annotations_for(
1034    index: &DirectIndex,
1035    subject: AnnotationSubjectId,
1036) -> BTreeMap<AnnotationKindId, SchemaAnnotationValue> {
1037    index.annotations.get(&subject).cloned().unwrap_or_default()
1038}
1039
1040fn cardinality(
1041    annotations: &BTreeMap<AnnotationKindId, SchemaAnnotationValue>,
1042    profile: &SemanticProfile,
1043    kind: InterfaceKind,
1044) -> Cardinality {
1045    profile.effective_cardinality(
1046        kind,
1047        annotations
1048            .get(&AnnotationKindId::Card)
1049            .and_then(cardinality_value),
1050        kind == InterfaceKind::Owns && annotations.contains_key(&AnnotationKindId::Key),
1051    )
1052}
1053
1054fn cardinality_value(value: &SchemaAnnotationValue) -> Option<Cardinality> {
1055    match value {
1056        SchemaAnnotationValue::Cardinality(cardinality) => Some(*cardinality),
1057        _ => None,
1058    }
1059}
1060
1061fn populate_subtypes(
1062    types: &mut BTreeMap<TypeId, ResolvedType>,
1063    parents: &BTreeMap<TypeId, TypeId>,
1064) {
1065    let ids = types.keys().cloned().collect::<Vec<_>>();
1066    for id in ids {
1067        for parent in ancestor_chain(&id, parents) {
1068            if let Some(parent) = types.get_mut(&parent) {
1069                parent.subtypes.insert(id.clone());
1070            }
1071        }
1072    }
1073}
1074
1075fn resolve_roles(
1076    types: &BTreeMap<TypeId, ResolvedType>,
1077    index: &DirectIndex,
1078) -> BTreeMap<RoleId, ResolvedRole> {
1079    let mut roles = BTreeMap::new();
1080    for relates in index.relates.values().flatten() {
1081        let annotations =
1082            annotations_for(index, AnnotationSubjectId::Relates(relates.id().clone()));
1083        roles.insert(
1084            relates.id().role().clone(),
1085            ResolvedRole {
1086                id: relates.id().role().clone(),
1087                accepted_players: BTreeSet::new(),
1088                replacing_roles: BTreeSet::new(),
1089                is_abstract: annotations.contains_key(&AnnotationKindId::Abstract),
1090            },
1091        );
1092    }
1093    for resolved in types.values() {
1094        for relates in resolved.relates.values() {
1095            if let Some(role) = roles.get_mut(relates.id.role()) {
1096                role.replacing_roles.extend(relates.replaced_roles.clone());
1097            }
1098        }
1099    }
1100    for resolved in types.values().filter(|resolved| resolved.constructible) {
1101        for plays in resolved.plays.values() {
1102            if let Some(role) = roles.get_mut(plays.id.role()) {
1103                role.accepted_players.insert(resolved.id.clone());
1104            }
1105        }
1106    }
1107    roles
1108}
1109
1110fn descriptor_index(declared: &DeclaredSchema) -> Result<DescriptorIndex, SchemaDiagnostics> {
1111    let domain = FingerprintDomain::new("typebridge.schema.descriptor").map_err(no_source)?;
1112    let canonicalization =
1113        CanonicalizationVersion::new("typebridge.schema-fact-id-json/v1").map_err(no_source)?;
1114    let mut descriptors = BTreeMap::new();
1115    for fact in declared.facts() {
1116        let id = fact.id();
1117        let canonical = to_canonical_json(&id).map_err(no_source)?;
1118        let digest =
1119            Fingerprint::compute(domain.clone(), canonicalization.clone(), None, &canonical)
1120                .digest()
1121                .to_hex();
1122        descriptors.insert(DescriptorId(format!("schema:{digest}")), id);
1123    }
1124    Ok(DescriptorIndex { descriptors })
1125}
1126
1127fn dependency_graph(
1128    types: &BTreeMap<TypeId, ResolvedType>,
1129    roles: &BTreeMap<RoleId, ResolvedRole>,
1130) -> SchemaDependencyGraph {
1131    let mut edges = types
1132        .keys()
1133        .cloned()
1134        .map(|id| (id, BTreeSet::new()))
1135        .collect::<BTreeMap<_, _>>();
1136    for resolved in types.values() {
1137        if let Some(parent) = resolved.supertypes.first() {
1138            edges
1139                .entry(resolved.id.clone())
1140                .or_default()
1141                .insert(parent.clone());
1142        }
1143        for attribute in resolved.owns.keys() {
1144            if let Ok(attribute) = TypeId::new(TypeKind::Attribute, attribute.label().as_str()) {
1145                edges
1146                    .entry(resolved.id.clone())
1147                    .or_default()
1148                    .insert(attribute);
1149            }
1150        }
1151        for role in resolved.plays.keys() {
1152            if let Ok(relation) =
1153                TypeId::new(TypeKind::Relation, role.declaring_relation().as_str())
1154            {
1155                edges
1156                    .entry(resolved.id.clone())
1157                    .or_default()
1158                    .insert(relation);
1159            }
1160        }
1161        for relates in resolved.relates.values() {
1162            if let Some(role) = roles.get(relates.id().role()) {
1163                edges
1164                    .entry(resolved.id.clone())
1165                    .or_default()
1166                    .extend(role.accepted_players().iter().cloned());
1167            }
1168        }
1169    }
1170    let strongly_connected_components = strongly_connected_components(&edges);
1171    SchemaDependencyGraph {
1172        edges,
1173        strongly_connected_components,
1174    }
1175}
1176
1177fn strongly_connected_components(
1178    edges: &BTreeMap<TypeId, BTreeSet<TypeId>>,
1179) -> Vec<BTreeSet<TypeId>> {
1180    struct Tarjan<'a> {
1181        edges: &'a BTreeMap<TypeId, BTreeSet<TypeId>>,
1182        index: usize,
1183        indices: BTreeMap<TypeId, usize>,
1184        lowlinks: BTreeMap<TypeId, usize>,
1185        stack: Vec<TypeId>,
1186        on_stack: BTreeSet<TypeId>,
1187        components: Vec<BTreeSet<TypeId>>,
1188    }
1189    impl Tarjan<'_> {
1190        fn visit(&mut self, node: TypeId) {
1191            let index = self.index;
1192            self.index += 1;
1193            self.indices.insert(node.clone(), index);
1194            self.lowlinks.insert(node.clone(), index);
1195            self.stack.push(node.clone());
1196            self.on_stack.insert(node.clone());
1197
1198            for dependency in self.edges.get(&node).into_iter().flatten() {
1199                if !self.indices.contains_key(dependency) {
1200                    self.visit(dependency.clone());
1201                    let low = self.lowlinks[&node].min(self.lowlinks[dependency]);
1202                    self.lowlinks.insert(node.clone(), low);
1203                } else if self.on_stack.contains(dependency) {
1204                    let low = self.lowlinks[&node].min(self.indices[dependency]);
1205                    self.lowlinks.insert(node.clone(), low);
1206                }
1207            }
1208
1209            if self.lowlinks[&node] == self.indices[&node] {
1210                let mut component = BTreeSet::new();
1211                loop {
1212                    let member = self.stack.pop().expect("SCC root has a stack member");
1213                    self.on_stack.remove(&member);
1214                    component.insert(member.clone());
1215                    if member == node {
1216                        break;
1217                    }
1218                }
1219                self.components.push(component);
1220            }
1221        }
1222    }
1223
1224    let mut tarjan = Tarjan {
1225        edges,
1226        index: 0,
1227        indices: BTreeMap::new(),
1228        lowlinks: BTreeMap::new(),
1229        stack: Vec::new(),
1230        on_stack: BTreeSet::new(),
1231        components: Vec::new(),
1232    };
1233    for node in edges.keys() {
1234        if !tarjan.indices.contains_key(node) {
1235            tarjan.visit(node.clone());
1236        }
1237    }
1238    tarjan
1239        .components
1240        .sort_by(|left, right| left.first().cmp(&right.first()));
1241    tarjan.components
1242}
1243
1244fn source_error(
1245    declared: &DeclaredSchema,
1246    fact: &SchemaFactId,
1247    code: &'static str,
1248    message: &'static str,
1249) -> SchemaDiagnostics {
1250    crate::yaml::diagnostic(
1251        DiagnosticCategory::InvalidContract,
1252        code,
1253        message,
1254        declared.source(fact).cloned(),
1255    )
1256}
1257
1258fn no_source(diagnostic: type_bridge_contract::diagnostic::Diagnostic) -> SchemaDiagnostics {
1259    type_bridge_contract::schema::SchemaDiagnostics::one(
1260        type_bridge_contract::schema::SchemaDiagnostic::new(diagnostic, None),
1261    )
1262}