Skip to main content

type_bridge_schema/
project.rs

1use std::collections::{BTreeMap, BTreeSet};
2
3use type_bridge_contract::diagnostic::{Diagnostic, DiagnosticCategory};
4use type_bridge_contract::fingerprint::{CanonicalizationVersion, Fingerprint, FingerprintDomain};
5use type_bridge_contract::id::{AttributeId, RoleId, TypeId, TypeKind};
6use type_bridge_contract::projection::{
7    BindingTarget, CNamingPolicy, CodeResourceDigest, CompleteReadProjection,
8    CreateFieldProjection, CreateProjection, CreateRoleProjection, DeclarationProjection,
9    DeclaredRoleProjection, DirectSubProjection, EmissionPlan, FieldTokenProjection,
10    FunctionParameterProjection, FunctionProjection, FunctionReturnElementProjection,
11    FunctionReturnProjection, ModelProjection, PlayingProjection, ProjectedAnnotation,
12    ProjectedModelForm, ProjectedModelUse, ProjectedMultiplicity, ProjectedTypeRef,
13    ProjectionConfig, ProjectionHandler, QueryTokenProjection, ReadFieldProjection,
14    ReadRoleProjection, ReferenceReadProjection, RoleTokenProjection, RuntimeProjection,
15    RustCreatePolicy, StructFieldProjection, StructProjection, TYPESCRIPT_MODEL_RESERVED_NAMES,
16    TargetIdentifier,
17};
18use type_bridge_contract::schema::{
19    AnnotationFactId, AnnotationKindId, AnnotationSubjectId, FunctionReturnMode, OwnsFactId,
20    RelatesFactId, SchemaAnnotationValue, SchemaDiagnostic, SchemaDiagnostics, TypeReference,
21    ValueFactId,
22};
23
24use crate::resolve::{EffectiveRelates, ResolvedSchema, ResolvedType};
25
26const C_MAX_TARGET_IDENTIFIER_BYTES: usize = 255;
27const C_IDENTIFIER_DIGEST_DOMAIN: &str = "typebridge.binding.c-identifier";
28const C_IDENTIFIER_CANONICALIZATION: &str = "typebridge.c-identifier/v1";
29
30fn no_source(error: Diagnostic) -> SchemaDiagnostics {
31    SchemaDiagnostics::one(SchemaDiagnostic::new(error, None))
32}
33
34fn projection_error(code: &'static str, message: impl Into<String>) -> SchemaDiagnostics {
35    crate::diagnostic::diagnostic(DiagnosticCategory::InvalidContract, code, message, None)
36}
37
38fn python_class_name(label: &str) -> String {
39    label
40        .replace('_', "-")
41        .split('-')
42        .map(|part| {
43            let mut chars = part.chars();
44            match chars.next() {
45                Some(first) => {
46                    let mut output = first.to_uppercase().collect::<String>();
47                    let rest = chars.as_str();
48                    let uniform = part.chars().all(char::is_uppercase)
49                        || part.chars().all(char::is_lowercase);
50                    if uniform {
51                        output.push_str(&rest.to_lowercase());
52                    } else {
53                        output.push_str(rest);
54                    }
55                    output
56                }
57                None => String::new(),
58            }
59        })
60        .collect()
61}
62
63fn python_member_name(label: &str) -> String {
64    label.replace('-', "_")
65}
66
67fn typescript_member_name(label: &str) -> String {
68    let class_name = python_class_name(label);
69    let mut chars = class_name.chars();
70    chars.next().map_or_else(String::new, |first| {
71        first.to_lowercase().chain(chars).collect()
72    })
73}
74
75fn rust_member_name(label: &str) -> String {
76    label.replace('-', "_").to_ascii_lowercase()
77}
78
79fn c_v1_escape(label: &str) -> String {
80    let mut escaped = String::with_capacity(label.len());
81    for byte in label.bytes() {
82        match byte {
83            b'a'..=b'y' | b'A'..=b'Z' | b'0'..=b'9' => escaped.push(char::from(byte)),
84            b'z' => escaped.push_str("zz"),
85            b'-' => escaped.push_str("zh"),
86            b'_' => escaped.push_str("zu"),
87            _ => escaped.push_str(&format!("zx{byte:02x}")),
88        }
89    }
90    escaped
91}
92
93fn c_identifier(value: String) -> Result<TargetIdentifier, SchemaDiagnostics> {
94    let bounded = if value.len() <= C_MAX_TARGET_IDENTIFIER_BYTES {
95        value
96    } else {
97        let digest = Fingerprint::compute(
98            FingerprintDomain::new(C_IDENTIFIER_DIGEST_DOMAIN)
99                .expect("the built-in C identifier fingerprint domain is valid"),
100            CanonicalizationVersion::new(C_IDENTIFIER_CANONICALIZATION)
101                .expect("the built-in C identifier canonicalization is valid"),
102            None,
103            value.as_bytes(),
104        )
105        .digest()
106        .to_hex();
107        let mut stem_bytes = C_MAX_TARGET_IDENTIFIER_BYTES - digest.len() - 1;
108        while value.as_bytes().get(stem_bytes - 1) == Some(&b'_') {
109            stem_bytes -= 1;
110        }
111        format!("{}_{}", &value[..stem_bytes], digest)
112    };
113    TargetIdentifier::c(bounded).map_err(no_source)
114}
115
116fn python_identifier(value: String) -> Result<TargetIdentifier, SchemaDiagnostics> {
117    if matches!(value.as_str(), "iid" | "model_config" | "model_fields")
118        || value.starts_with("__") && value.ends_with("__")
119    {
120        return Err(projection_error(
121            "reserved_python_projection_identifier",
122            "projected Python name collides with generated runtime state",
123        ));
124    }
125    TargetIdentifier::python(value).map_err(no_source)
126}
127
128fn typescript_identifier(value: String) -> Result<TargetIdentifier, SchemaDiagnostics> {
129    if matches!(
130        value.as_str(),
131        "iid"
132            | "typeToken"
133            | "fields"
134            | "roles"
135            | "plays"
136            | "create"
137            | "reference"
138            | "prototype"
139            | "__proto__"
140    ) {
141        return Err(projection_error(
142            "reserved_typescript_projection_identifier",
143            "projected TypeScript name collides with generated runtime state",
144        ));
145    }
146    TargetIdentifier::typescript(value).map_err(no_source)
147}
148
149fn rust_identifier(value: String) -> Result<TargetIdentifier, SchemaDiagnostics> {
150    TargetIdentifier::rust(value).map_err(no_source)
151}
152
153struct ProjectionNamer<'a> {
154    target: BindingTarget,
155    c_symbol_prefix: Option<&'a str>,
156}
157
158impl<'a> ProjectionNamer<'a> {
159    fn new(target: BindingTarget, config: &'a ProjectionConfig) -> Result<Self, SchemaDiagnostics> {
160        let c_symbol_prefix = if target == BindingTarget::C {
161            if config.c_naming_policy() != Some(CNamingPolicy::TypeBridgeV1) {
162                return Err(projection_error(
163                    "unsupported_c_naming_policy",
164                    "C projection does not recognize the configured naming policy",
165                ));
166            }
167            Some(
168                config
169                    .c_symbol_prefix()
170                    .ok_or_else(|| {
171                        projection_error(
172                            "missing_c_symbol_prefix",
173                            "C projection configuration omits its symbol prefix",
174                        )
175                    })?
176                    .as_str(),
177            )
178        } else {
179            None
180        };
181        Ok(Self {
182            target,
183            c_symbol_prefix,
184        })
185    }
186
187    fn c_symbol_prefix(&self) -> &str {
188        self.c_symbol_prefix
189            .expect("C naming is only requested for a validated C configuration")
190    }
191
192    fn c_label_identifier(&self, label: &str) -> Result<TargetIdentifier, SchemaDiagnostics> {
193        c_identifier(format!("{}_{}", self.c_symbol_prefix(), c_v1_escape(label)))
194    }
195
196    fn class_identifier(&self, label: &str) -> Result<TargetIdentifier, SchemaDiagnostics> {
197        let value = python_class_name(label);
198        if self.target == BindingTarget::Python {
199            python_identifier(value)
200        } else if self.target == BindingTarget::TypeScript {
201            typescript_identifier(value)
202        } else if self.target == BindingTarget::Rust {
203            rust_identifier(value)
204        } else if self.target == BindingTarget::C {
205            self.c_label_identifier(label)
206        } else {
207            Err(projection_error(
208                "unsupported_binding_projection_target",
209                "schema projection does not support this binding target",
210            ))
211        }
212    }
213
214    fn member_identifier(&self, label: &str) -> Result<TargetIdentifier, SchemaDiagnostics> {
215        if self.target == BindingTarget::Python {
216            python_identifier(python_member_name(label))
217        } else if self.target == BindingTarget::TypeScript {
218            typescript_identifier(typescript_member_name(label))
219        } else if self.target == BindingTarget::Rust {
220            rust_identifier(rust_member_name(label))
221        } else if self.target == BindingTarget::C {
222            self.c_label_identifier(label)
223        } else {
224            Err(projection_error(
225                "unsupported_binding_projection_target",
226                "schema projection does not support this binding target",
227            ))
228        }
229    }
230
231    fn model_member_identifier(&self, label: &str) -> Result<TargetIdentifier, SchemaDiagnostics> {
232        if self.target == BindingTarget::TypeScript {
233            let mut name = typescript_member_name(label);
234            if TYPESCRIPT_MODEL_RESERVED_NAMES.contains(&name.as_str()) {
235                // Camel-case projection removes underscores, so this suffix cannot
236                // collide with an otherwise valid, unchanged projected member.
237                name.push('_');
238            }
239            TargetIdentifier::typescript(name).map_err(no_source)
240        } else {
241            self.member_identifier(label)
242        }
243    }
244
245    fn reference_identifier(
246        &self,
247        name: &TargetIdentifier,
248    ) -> Result<TargetIdentifier, SchemaDiagnostics> {
249        if self.target == BindingTarget::Python {
250            python_identifier(format!("{}Ref", name.as_str()))
251        } else if self.target == BindingTarget::TypeScript {
252            typescript_identifier(format!("{}Ref", name.as_str()))
253        } else if self.target == BindingTarget::Rust {
254            rust_identifier(format!("{}Ref", name.as_str()))
255        } else if self.target == BindingTarget::C {
256            c_identifier(format!("{}_ref", name.as_str()))
257        } else {
258            Err(projection_error(
259                "unsupported_binding_projection_target",
260                "schema projection does not support this binding target",
261            ))
262        }
263    }
264
265    fn query_token_identifier(
266        &self,
267        model_name: &TargetIdentifier,
268    ) -> Result<Option<TargetIdentifier>, SchemaDiagnostics> {
269        if self.target == BindingTarget::Rust {
270            rust_identifier(format!("{}Type", model_name.as_str())).map(Some)
271        } else if self.target == BindingTarget::C {
272            c_identifier(format!("{}_type", model_name.as_str())).map(Some)
273        } else {
274            Ok(None)
275        }
276    }
277
278    fn player_union_identifier(
279        &self,
280        model_name: &TargetIdentifier,
281        role_label: &str,
282    ) -> Result<Option<TargetIdentifier>, SchemaDiagnostics> {
283        if self.target == BindingTarget::Rust {
284            rust_identifier(format!(
285                "{}{}Player",
286                model_name.as_str(),
287                python_class_name(role_label),
288            ))
289            .map(Some)
290        } else if self.target == BindingTarget::C {
291            c_identifier(format!(
292                "{}_{}_player",
293                model_name.as_str(),
294                c_v1_escape(role_label),
295            ))
296            .map(Some)
297        } else {
298            Ok(None)
299        }
300    }
301
302    fn create_identifier(
303        &self,
304        model_name: &TargetIdentifier,
305    ) -> Result<Option<TargetIdentifier>, SchemaDiagnostics> {
306        if self.target == BindingTarget::Rust {
307            rust_identifier(format!("{}Create", model_name.as_str())).map(Some)
308        } else if self.target == BindingTarget::C {
309            c_identifier(format!("{}_create", model_name.as_str())).map(Some)
310        } else {
311            Ok(None)
312        }
313    }
314
315    fn playing_identifier(
316        &self,
317        player_label: &str,
318        relation_label: &str,
319        role_label: &str,
320    ) -> Result<TargetIdentifier, SchemaDiagnostics> {
321        if self.target == BindingTarget::C {
322            c_identifier(format!(
323                "{}_plays_{}_relation_{}_role_{}",
324                self.c_symbol_prefix(),
325                c_v1_escape(player_label),
326                c_v1_escape(relation_label),
327                c_v1_escape(role_label),
328            ))
329        } else {
330            self.member_identifier(&format!(
331                "plays-{player_label}-{relation_label}-{role_label}"
332            ))
333        }
334    }
335
336    fn c_schema_package_identifier(&self) -> Result<Option<TargetIdentifier>, SchemaDiagnostics> {
337        if self.target == BindingTarget::C {
338            c_identifier(format!("{}_schema_package_open", self.c_symbol_prefix())).map(Some)
339        } else {
340            Ok(None)
341        }
342    }
343}
344
345#[derive(Default)]
346struct NameRegistry {
347    names: BTreeMap<(String, String), String>,
348}
349
350impl NameRegistry {
351    fn insert(
352        &mut self,
353        namespace: impl Into<String>,
354        name: &TargetIdentifier,
355        identity: impl Into<String>,
356    ) -> Result<(), SchemaDiagnostics> {
357        let key = (namespace.into(), name.as_str().to_owned());
358        let identity = identity.into();
359        if let Some(previous) = self.names.get(&key) {
360            if previous != &identity {
361                return Err(projection_error(
362                    "projection_name_collision",
363                    format!(
364                        "projected name `{}` in namespace `{}` collides between `{}` and `{}`",
365                        key.1, key.0, previous, identity
366                    ),
367                ));
368            }
369        } else {
370            self.names.insert(key, identity);
371        }
372        Ok(())
373    }
374}
375
376fn annotations(
377    subject: AnnotationSubjectId,
378    values: &BTreeMap<AnnotationKindId, SchemaAnnotationValue>,
379) -> Result<BTreeMap<AnnotationFactId, ProjectedAnnotation>, SchemaDiagnostics> {
380    values
381        .iter()
382        .map(|(kind, value)| {
383            let id = AnnotationFactId::new(subject.clone(), kind.clone());
384            ProjectedAnnotation::new(id.clone(), value.clone())
385                .map(|annotation| (id, annotation))
386                .map_err(no_source)
387        })
388        .collect()
389}
390
391fn effective_relates_annotation_subject(
392    relates: &EffectiveRelates,
393) -> Result<AnnotationSubjectId, SchemaDiagnostics> {
394    let role = RoleId::new(
395        relates.id().relation().label().as_str(),
396        relates.id().role().label().as_str(),
397    )
398    .map_err(no_source)?;
399    RelatesFactId::new(relates.id().relation().clone(), role)
400        .map(AnnotationSubjectId::Relates)
401        .map_err(no_source)
402}
403
404fn ordered_owns(resolved: &ResolvedType) -> Vec<OwnsFactId> {
405    let mut ordered = Vec::new();
406    let mut seen = BTreeSet::new();
407    for attribute in resolved.owned_attribute_order() {
408        if let Some(owns) = resolved.owns().get(attribute) {
409            seen.insert(attribute.clone());
410            ordered.push(owns.id().clone());
411        }
412    }
413    ordered.extend(
414        resolved
415            .owns()
416            .iter()
417            .filter(|(attribute, _)| !seen.contains(*attribute))
418            .map(|(_, owns)| owns.id().clone()),
419    );
420    ordered
421}
422
423fn read_model_use(id: &TypeId) -> Result<ProjectedModelUse, SchemaDiagnostics> {
424    let form = match id.kind() {
425        TypeKind::Entity => ProjectedModelForm::Complete,
426        TypeKind::Relation => ProjectedModelForm::Reference,
427        TypeKind::Attribute | TypeKind::Struct => {
428            return Err(projection_error(
429                "invalid_projection_reference",
430                "only entity and relation types can be projected as role players",
431            ));
432        }
433    };
434    Ok(ProjectedModelUse::new(id.clone(), form))
435}
436
437fn create_model_uses(id: &TypeId) -> Result<BTreeSet<ProjectedModelUse>, SchemaDiagnostics> {
438    match id.kind() {
439        TypeKind::Entity => Ok(BTreeSet::from([ProjectedModelUse::new(
440            id.clone(),
441            ProjectedModelForm::Complete,
442        )])),
443        TypeKind::Relation => Ok(BTreeSet::from([
444            ProjectedModelUse::new(id.clone(), ProjectedModelForm::Complete),
445            ProjectedModelUse::new(id.clone(), ProjectedModelForm::Reference),
446        ])),
447        TypeKind::Attribute | TypeKind::Struct => Err(projection_error(
448            "invalid_projection_reference",
449            "only entity and relation types can be projected as role players",
450        )),
451    }
452}
453
454fn immediate_specialization(
455    resolved: &ResolvedType,
456    relates: &EffectiveRelates,
457) -> Result<Option<RoleId>, SchemaDiagnostics> {
458    if relates.replaced_roles().is_empty() {
459        return Ok(None);
460    }
461    let mut ranked = relates
462        .replaced_roles()
463        .iter()
464        .filter_map(|role| {
465            resolved
466                .supertypes()
467                .iter()
468                .position(|parent| parent.label() == role.declaring_relation())
469                .map(|distance| (distance, role.clone()))
470        })
471        .collect::<Vec<_>>();
472    ranked.sort();
473    let Some((distance, role)) = ranked.first().cloned() else {
474        return Err(projection_error(
475            "invalid_projection_reference",
476            "specialized role has no declaring relation in the resolved ancestor chain",
477        ));
478    };
479    if ranked.get(1).is_some_and(|next| next.0 == distance) {
480        return Err(projection_error(
481            "ambiguous_role_specialization_projection",
482            "more than one replaced role is nearest to the specializing relation",
483        ));
484    }
485    Ok(Some(role))
486}
487
488fn resolve_type_reference(
489    reference: &TypeReference,
490    resolved: &ResolvedSchema,
491) -> Result<ProjectedTypeRef, SchemaDiagnostics> {
492    match reference {
493        TypeReference::Value(value) => Ok(ProjectedTypeRef::Scalar(*value)),
494        TypeReference::Schema(label) => {
495            let models = resolved
496                .types()
497                .keys()
498                .filter(|id| id.label() == label)
499                .cloned()
500                .collect::<Vec<_>>();
501            let structures = resolved
502                .structs()
503                .keys()
504                .filter(|id| id.label() == label)
505                .cloned()
506                .collect::<Vec<_>>();
507            match (models.as_slice(), structures.as_slice()) {
508                ([model], []) => Ok(ProjectedTypeRef::Model(ProjectedModelUse::new(
509                    model.clone(),
510                    ProjectedModelForm::Complete,
511                ))),
512                ([], [structure]) => Ok(ProjectedTypeRef::Struct(structure.clone())),
513                ([], []) => Err(projection_error(
514                    "unknown_projection_type_reference",
515                    "function signature references an unknown projected type",
516                )),
517                _ => Err(projection_error(
518                    "ambiguous_projection_type_reference",
519                    "function signature label resolves to more than one projected type",
520                )),
521            }
522        }
523    }
524}
525
526fn function_return(
527    returns: &FunctionReturnMode,
528    resolved: &ResolvedSchema,
529) -> Result<FunctionReturnProjection, SchemaDiagnostics> {
530    let project = |element: &type_bridge_contract::schema::FunctionReturnElement| {
531        Ok(FunctionReturnElementProjection::new(
532            resolve_type_reference(element.type_ref(), resolved)?,
533            element.optional(),
534        ))
535    };
536    match returns {
537        FunctionReturnMode::Scalar(element) => {
538            Ok(FunctionReturnProjection::Scalar(project(element)?))
539        }
540        FunctionReturnMode::Tuple(elements) => elements
541            .iter()
542            .map(project)
543            .collect::<Result<Vec<_>, _>>()
544            .map(FunctionReturnProjection::Tuple),
545        FunctionReturnMode::Stream(elements) => elements
546            .iter()
547            .map(project)
548            .collect::<Result<Vec<_>, _>>()
549            .map(FunctionReturnProjection::Stream),
550    }
551}
552
553fn link_components(resolved: &ResolvedSchema) -> Result<Vec<BTreeSet<TypeId>>, SchemaDiagnostics> {
554    let components = resolved.dependency_graph().strongly_connected_components();
555    let mut membership = BTreeMap::new();
556    for (index, component) in components.iter().enumerate() {
557        for member in component {
558            if membership.insert(member.clone(), index).is_some() {
559                return Err(projection_error(
560                    "invalid_projection_emission_plan",
561                    "resolver SCC partition repeats a model identity",
562                ));
563            }
564        }
565    }
566    if membership.len() != resolved.types().len()
567        || resolved
568            .types()
569            .keys()
570            .any(|id| !membership.contains_key(id))
571    {
572        return Err(projection_error(
573            "invalid_projection_emission_plan",
574            "resolver SCC partition does not cover every model",
575        ));
576    }
577    let mut dependents = vec![BTreeSet::<usize>::new(); components.len()];
578    let mut indegree = vec![0usize; components.len()];
579    for id in resolved.types().keys() {
580        let source = membership[id];
581        for dependency in resolved
582            .dependency_graph()
583            .dependencies(id)
584            .into_iter()
585            .flatten()
586        {
587            let target = membership[dependency];
588            if source != target && dependents[target].insert(source) {
589                indegree[source] += 1;
590            }
591        }
592    }
593    let minimum = components
594        .iter()
595        .map(|component| {
596            component
597                .first()
598                .cloned()
599                .expect("resolver SCCs are non-empty")
600        })
601        .collect::<Vec<_>>();
602    let mut ready = indegree
603        .iter()
604        .enumerate()
605        .filter(|(_, degree)| **degree == 0)
606        .map(|(index, _)| (minimum[index].clone(), index))
607        .collect::<BTreeSet<_>>();
608    let mut ordered = Vec::with_capacity(components.len());
609    while let Some((_, index)) = ready.pop_first() {
610        ordered.push(components[index].clone());
611        for dependent in &dependents[index] {
612            indegree[*dependent] -= 1;
613            if indegree[*dependent] == 0 {
614                ready.insert((minimum[*dependent].clone(), *dependent));
615            }
616        }
617    }
618    if ordered.len() != components.len() {
619        return Err(projection_error(
620            "invalid_projection_emission_plan",
621            "resolver SCC condensation graph is cyclic",
622        ));
623    }
624    Ok(ordered)
625}
626
627/// Derive a target-specific runtime projection without provider I/O or mutation.
628pub fn project(
629    resolved: &ResolvedSchema,
630    target: BindingTarget,
631    config: &ProjectionConfig,
632    handlers: &[ProjectionHandler],
633    resources: &[CodeResourceDigest],
634) -> Result<RuntimeProjection, SchemaDiagnostics> {
635    if config.target() != target {
636        return Err(projection_error(
637            "projection_config_target_mismatch",
638            "projection configuration belongs to a different target",
639        ));
640    }
641    config.validate_type_name_overrides().map_err(no_source)?;
642    for item in config.type_name_overrides() {
643        if !resolved.types().contains_key(item.type_id()) {
644            return Err(projection_error(
645                "unknown_type_name_override",
646                format!(
647                    "type-name override refers to unknown type {:?}",
648                    item.type_id()
649                ),
650            ));
651        }
652    }
653    let namer = ProjectionNamer::new(target, config)?;
654    let mut names = NameRegistry::default();
655    if let Some(descriptor_name) = namer.c_schema_package_identifier()? {
656        names.insert("root", &descriptor_name, "runtime:c-schema-package-open")?;
657    }
658    let mut models = BTreeMap::new();
659    let mut playing_facts = BTreeMap::new();
660
661    for (id, resolved_type) in resolved.types() {
662        let target_name = match config
663            .type_name_overrides()
664            .iter()
665            .find(|item| item.type_id() == id)
666        {
667            Some(item) => item.name().clone(),
668            None => namer.class_identifier(id.label().as_str())?,
669        };
670        names.insert("root", &target_name, format!("model:{id:?}"))?;
671        let reference_name = if matches!(id.kind(), TypeKind::Entity | TypeKind::Relation) {
672            let reference = namer.reference_identifier(&target_name)?;
673            names.insert("root", &reference, format!("reference:{id:?}"))?;
674            Some(reference)
675        } else {
676            None
677        };
678        let query_token_name = if let Some(name) = namer.query_token_identifier(&target_name)? {
679            names.insert("root", &name, format!("query-token:{id:?}"))?;
680            Some(name)
681        } else {
682            None
683        };
684        let type_annotations = annotations(
685            AnnotationSubjectId::Type(id.clone()),
686            resolved_type.annotations(),
687        )?;
688        let ordered_field_ids = ordered_owns(resolved_type);
689        let mut field_tokens = BTreeMap::new();
690        let mut direct_fields = Vec::new();
691        let mut create_fields = Vec::new();
692        let mut read_fields = Vec::new();
693        for owns_id in &ordered_field_ids {
694            let owns = resolved_type
695                .owns()
696                .get(owns_id.attribute())
697                .ok_or_else(|| {
698                    projection_error(
699                        "invalid_projection_reference",
700                        "ordered ownership is absent from the effective ownership map",
701                    )
702                })?;
703            let name = namer.model_member_identifier(owns.id().attribute().label().as_str())?;
704            names.insert(
705                format!("model:{id:?}"),
706                &name,
707                format!("field:{:?}", owns.id()),
708            )?;
709            let declaring_id = match owns.origin().declared() {
710                type_bridge_contract::schema::SchemaFactId::Owns(fact_id) => fact_id.clone(),
711                _ => {
712                    return Err(projection_error(
713                        "invalid_projection_reference",
714                        "declared resolution origin for owns fact is not an Owns fact ID",
715                    ));
716                }
717            };
718            let multiplicity =
719                ProjectedMultiplicity::new(owns.cardinality(), owns.collection_mode());
720            let token = FieldTokenProjection::new(
721                owns.id().clone(),
722                declaring_id,
723                name,
724                multiplicity,
725                owns.is_key(),
726                owns.is_unique(),
727                annotations(
728                    AnnotationSubjectId::Owns(owns.id().clone()),
729                    owns.annotations(),
730                )?,
731            )
732            .map_err(no_source)?;
733            let attribute_model =
734                TypeId::new(TypeKind::Attribute, owns.id().attribute().label().as_str())
735                    .map_err(no_source)?;
736            let value = ProjectedTypeRef::Model(ProjectedModelUse::new(
737                attribute_model,
738                ProjectedModelForm::Complete,
739            ));
740            if owns.origin().is_direct() {
741                direct_fields.push(owns.id().clone());
742            }
743            create_fields.push(CreateFieldProjection::new(
744                owns.id().clone(),
745                value.clone(),
746                multiplicity,
747            ));
748            read_fields.push(ReadFieldProjection::new(
749                owns.id().clone(),
750                value,
751                multiplicity,
752            ));
753            field_tokens.insert(owns.id().clone(), token);
754        }
755
756        let replaced = resolved_type
757            .relates()
758            .values()
759            .flat_map(|relates| relates.replaced_roles().iter().cloned())
760            .collect::<BTreeSet<_>>();
761        let mut role_tokens = BTreeMap::new();
762        let mut direct_roles = BTreeMap::new();
763        let mut create_roles = BTreeMap::new();
764        let mut read_roles = BTreeMap::new();
765        let mut create_enabled = resolved_type.is_constructible() && !resolved_type.is_abstract();
766        for (role_id, relates) in resolved_type.relates() {
767            if replaced.contains(role_id) {
768                continue;
769            }
770            let name = namer.model_member_identifier(role_id.label().as_str())?;
771            names.insert(format!("model:{id:?}"), &name, format!("role:{role_id:?}"))?;
772            let resolved_role = resolved.roles().get(role_id).ok_or_else(|| {
773                projection_error(
774                    "invalid_projection_reference",
775                    "effective relates role is absent from the resolved role index",
776                )
777            })?;
778            let specializes = immediate_specialization(resolved_type, relates)?;
779            let multiplicity =
780                ProjectedMultiplicity::new(relates.cardinality(), relates.collection_mode());
781            let player_union_name = if let Some(name) =
782                namer.player_union_identifier(&target_name, role_id.label().as_str())?
783            {
784                names.insert("root", &name, format!("player-union:{id:?}:{role_id:?}"))?;
785                Some(name)
786            } else {
787                None
788            };
789            let mut token = RoleTokenProjection::new(
790                id.clone(),
791                role_id.clone(),
792                name,
793                resolved_role.accepted_players().clone(),
794                specializes.clone(),
795                multiplicity,
796                relates.is_abstract(),
797                annotations(
798                    effective_relates_annotation_subject(relates)?,
799                    relates.annotations(),
800                )?,
801            )
802            .map_err(no_source)?;
803            if let Some(name) = player_union_name {
804                token = token.with_player_union_target_name(name);
805            }
806            let read_players = resolved_role
807                .accepted_players()
808                .iter()
809                .map(read_model_use)
810                .collect::<Result<BTreeSet<_>, _>>()?;
811            let mut create_players = BTreeSet::new();
812            for player in resolved_role.accepted_players() {
813                create_players.extend(create_model_uses(player)?);
814            }
815            if relates.origin().is_direct() {
816                direct_roles.insert(
817                    role_id.clone(),
818                    DeclaredRoleProjection::new(role_id.clone(), specializes),
819                );
820            }
821            if relates.accepts_players_at_effective_scope() {
822                if create_players.is_empty() && multiplicity.required() {
823                    create_enabled = false;
824                } else if !create_players.is_empty() {
825                    create_roles.insert(
826                        role_id.clone(),
827                        CreateRoleProjection::new(role_id.clone(), create_players, multiplicity)
828                            .map_err(no_source)?,
829                    );
830                }
831            }
832            read_roles.insert(
833                role_id.clone(),
834                ReadRoleProjection::new(role_id.clone(), read_players, multiplicity)
835                    .map_err(no_source)?,
836            );
837            role_tokens.insert(role_id.clone(), token);
838        }
839
840        let mut direct_plays = BTreeSet::new();
841        for plays in resolved_type.plays().values() {
842            if plays.origin().is_direct() {
843                direct_plays.insert(plays.id().clone());
844            }
845            let plays_name = namer.playing_identifier(
846                id.label().as_str(),
847                plays.id().role().declaring_relation().as_str(),
848                plays.id().role().label().as_str(),
849            )?;
850            names.insert("root", &plays_name, format!("plays:{:?}", plays.id()))?;
851            let projected = PlayingProjection::new(
852                plays.id().clone(),
853                plays.id().role().clone(),
854                ProjectedMultiplicity::from_cardinality(plays.cardinality()),
855                annotations(
856                    AnnotationSubjectId::Plays(plays.id().clone()),
857                    plays.annotations(),
858                )?,
859            )
860            .map_err(no_source)?
861            .with_target_name(plays_name);
862            if playing_facts
863                .insert(plays.id().clone(), projected)
864                .is_some()
865            {
866                return Err(projection_error(
867                    "invalid_runtime_projection_map",
868                    "an effective playing identity appears more than once",
869                ));
870            }
871        }
872
873        let key_fields = ordered_field_ids
874            .iter()
875            .filter(|owns| resolved_type.key_attributes().contains(owns.attribute()))
876            .cloned()
877            .collect();
878        let value_annotations = if let Some(value) = resolved_type.value_type() {
879            let attribute = AttributeId::new(id.label().as_str()).map_err(no_source)?;
880            annotations(
881                AnnotationSubjectId::Value(ValueFactId::new(attribute)),
882                value.annotations(),
883            )?
884        } else {
885            BTreeMap::new()
886        };
887        let direct_sub = resolved_type
888            .direct_sub()
889            .map(|sub| {
890                let annotations = annotations(
891                    AnnotationSubjectId::Sub(sub.id().clone()),
892                    sub.annotations(),
893                )?;
894                DirectSubProjection::new(
895                    sub.id().clone(),
896                    sub.origin().declared().clone(),
897                    annotations,
898                )
899                .map_err(no_source)
900            })
901            .transpose()?;
902        let declaration = DeclarationProjection::new(
903            resolved_type.supertypes().first().cloned(),
904            resolved_type.value_type().map(|value| value.value_type()),
905            resolved_type.is_abstract(),
906            resolved_type.is_constructible(),
907            type_annotations,
908            direct_fields,
909            direct_roles,
910            direct_plays,
911        )
912        .map_err(no_source)?
913        .with_direct_sub(direct_sub)
914        .map_err(no_source)?
915        .with_value_annotations(value_annotations)
916        .map_err(no_source)?;
917        let create_target_name = if create_enabled {
918            if target == BindingTarget::Rust
919                && config.rust_create_policy() != Some(RustCreatePolicy::ValidatedInputV1)
920            {
921                return Err(projection_error(
922                    "unsupported_rust_create_policy",
923                    "Rust projection does not recognize the configured create policy",
924                ));
925            }
926            if let Some(name) = namer.create_identifier(&target_name)? {
927                names.insert("root", &name, format!("create:{id:?}"))?;
928                Some(name)
929            } else {
930                None
931            }
932        } else {
933            None
934        };
935        let mut create = CreateProjection::new(create_enabled, create_fields, create_roles)
936            .map_err(no_source)?;
937        if let Some(name) = create_target_name {
938            create = create.with_target_name(name);
939        }
940        let role_upcasts = resolved_type
941            .relates()
942            .iter()
943            .filter_map(|(role, relates)| {
944                if relates.replaced_roles().is_empty() {
945                    return None;
946                }
947                let mut ancestors = relates.replaced_roles().iter().cloned().collect::<Vec<_>>();
948                ancestors.sort_by_key(|ancestor| {
949                    resolved_type
950                        .supertypes()
951                        .iter()
952                        .position(|parent| parent.label() == ancestor.declaring_relation())
953                        .unwrap_or(usize::MAX)
954                });
955                Some((role.clone(), ancestors))
956            })
957            .collect();
958        let complete_read = CompleteReadProjection::new(
959            read_fields,
960            read_roles,
961            resolved_type.supertypes().to_vec(),
962        )
963        .map_err(no_source)?
964        .with_role_upcasts(role_upcasts)
965        .map_err(no_source)?;
966        let reference_read =
967            ReferenceReadProjection::new(reference_name, key_fields).map_err(no_source)?;
968        let mut query_tokens =
969            QueryTokenProjection::new(id.clone(), field_tokens, role_tokens).map_err(no_source)?;
970        if let Some(name) = query_token_name {
971            query_tokens = query_tokens.with_target_name(name);
972        }
973        let model = ModelProjection::new(
974            id.clone(),
975            target_name,
976            declaration,
977            create,
978            complete_read,
979            reference_read,
980            query_tokens,
981        )
982        .map_err(no_source)?;
983        models.insert(id.clone(), model);
984    }
985
986    let mut structs = BTreeMap::new();
987    for (id, structure) in resolved.structs() {
988        let target_name = namer.class_identifier(id.label().as_str())?;
989        names.insert("root", &target_name, format!("struct:{id:?}"))?;
990        let mut fields = Vec::new();
991        for field in structure.fields() {
992            let target_field = namer.member_identifier(field.name().as_str())?;
993            names.insert(
994                format!("struct:{id:?}"),
995                &target_field,
996                format!("field:{:?}", field.name()),
997            )?;
998            fields.push(StructFieldProjection::new(
999                field.name().clone(),
1000                target_field,
1001                field.value_type(),
1002                field.optional(),
1003            ));
1004        }
1005        structs.insert(
1006            id.clone(),
1007            StructProjection::new(id.clone(), target_name, fields).map_err(no_source)?,
1008        );
1009    }
1010
1011    let mut functions = BTreeMap::new();
1012    for (id, function) in resolved.functions() {
1013        let target_name = namer.member_identifier(id.label().as_str())?;
1014        names.insert("root", &target_name, format!("function:{id:?}"))?;
1015        let mut parameters = Vec::new();
1016        for parameter in function.declaration().signature().parameters() {
1017            let target_parameter = namer.member_identifier(parameter.name().as_str())?;
1018            names.insert(
1019                format!("function:{id:?}"),
1020                &target_parameter,
1021                format!("parameter:{:?}", parameter.name()),
1022            )?;
1023            parameters.push(FunctionParameterProjection::new(
1024                parameter.name().clone(),
1025                target_parameter,
1026                resolve_type_reference(parameter.type_ref(), resolved)?,
1027            ));
1028        }
1029        functions.insert(
1030            id.clone(),
1031            FunctionProjection::new(
1032                id.clone(),
1033                target_name,
1034                parameters,
1035                function_return(function.declaration().signature().returns(), resolved)?,
1036            )
1037            .map_err(no_source)?
1038            .with_annotations(annotations(
1039                AnnotationSubjectId::Function(id.clone()),
1040                function.annotations(),
1041            )?)
1042            .map_err(no_source)?,
1043        );
1044    }
1045
1046    let mut shells = resolved.types().values().collect::<Vec<_>>();
1047    shells.sort_by_key(|model| (model.supertypes().len(), model.id().clone()));
1048    let emission = EmissionPlan::new(
1049        shells.into_iter().map(|model| model.id().clone()).collect(),
1050        link_components(resolved)?,
1051        structs.keys().cloned().collect(),
1052        functions.keys().cloned().collect(),
1053    )
1054    .map_err(no_source)?;
1055    RuntimeProjection::try_new(
1056        target,
1057        config.clone(),
1058        resolved.semantic_fingerprint().clone(),
1059        handlers,
1060        resources,
1061        models,
1062        structs,
1063        functions,
1064        playing_facts,
1065        emission,
1066    )
1067    .map_err(no_source)
1068}