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