Skip to main content

type_bridge_contract/
projection.rs

1//! Binding-target configuration and reproducible projection fingerprints.
2
3use std::cmp::Ordering;
4use std::collections::{BTreeMap, BTreeSet};
5use std::fmt;
6
7use serde::{Serialize, Serializer};
8
9use crate::codec::{FormatVersion, to_canonical_json};
10use crate::diagnostic::{Diagnostic, DiagnosticCategory};
11use crate::fingerprint::{CanonicalizationVersion, Fingerprint, FingerprintDomain};
12use crate::id::{AttributeId, FunctionId, Label, RoleId, StructId, TypeId, TypeKind};
13use crate::limits::MAX_CANONICAL_COLLECTION_LEN;
14use crate::schema::{
15    AnnotationFact, AnnotationFactId, AnnotationSubjectId, OwnsFactId, PlaysFactId,
16    SchemaAnnotationValue, SchemaFactId, SubFactId, ValueFactId,
17};
18use crate::schema_fingerprint::SemanticSchemaFingerprint;
19use crate::value::{Cardinality, ValueTypeTag};
20
21const MAX_PROJECTION_COMPONENT_ID_BYTES: usize = 255;
22const PYTHON_GENERATOR_HANDLER_ID: &str = "typebridge.generator.python";
23const TYPESCRIPT_GENERATOR_HANDLER_ID: &str = "typebridge.generator.typescript";
24const RUST_GENERATOR_HANDLER_ID: &str = "typebridge.generator.rust";
25const CODE_RESOURCE_DOMAIN: &str = "typebridge.binding.code-resource";
26const RAW_BYTES_CANONICALIZATION: &str = "typebridge.raw-bytes/v1";
27const BINDING_PROJECTION_DOMAIN: &str = "typebridge.binding.projection";
28const BINDING_PROJECTION_CANONICALIZATION: &str = "typebridge.binding-projection/v1";
29const BINDING_PROJECTION_CONTENT_DOMAIN: &str = "typebridge.binding.projection-content";
30const MAX_TARGET_IDENTIFIER_BYTES: usize = 255;
31
32/// A binding target with a consumed Phase 3 projection contract.
33#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
34#[serde(rename_all = "snake_case")]
35pub enum BindingTarget {
36    /// Generated Python source and typing artifacts.
37    Python,
38    /// Generated TypeScript source and declarations.
39    #[serde(rename = "typescript")]
40    TypeScript,
41    /// Generated native Rust types and schema tokens.
42    Rust,
43}
44
45impl BindingTarget {
46    const fn required_generator_handler_id(self) -> &'static str {
47        match self {
48            Self::Python => PYTHON_GENERATOR_HANDLER_ID,
49            Self::TypeScript => TYPESCRIPT_GENERATOR_HANDLER_ID,
50            Self::Rust => RUST_GENERATOR_HANDLER_ID,
51        }
52    }
53}
54
55/// The exact label-to-Python-name transformation consumed by the emitter.
56#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
57pub enum PythonNamingPolicy {
58    /// The first collision-checked TypeBridge Python naming policy.
59    #[serde(rename = "typebridge.python/v1")]
60    TypeBridgeV1,
61}
62
63/// The exact label-to-TypeScript-name transformation consumed by the emitter.
64#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
65pub enum TypeScriptNamingPolicy {
66    /// The first collision-checked TypeBridge TypeScript naming policy.
67    #[serde(rename = "typebridge.typescript/v1")]
68    TypeBridgeV1,
69}
70
71/// The exact label-to-Rust-name transformation consumed by native projection.
72#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
73pub enum RustNamingPolicy {
74    /// The first collision-checked TypeBridge Rust naming policy.
75    #[serde(rename = "typebridge.rust/v1")]
76    TypeBridgeV1,
77}
78
79/// The generated Rust construction surface committed by the projection fingerprint.
80#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
81pub enum RustCreatePolicy {
82    /// Emit a public `{Model}Create` input with private fields, checked `try_new`,
83    /// and manager insertion that consumes the validated input. Abstract or
84    /// otherwise nonconstructible models expose no create input.
85    #[serde(rename = "typebridge.rust.validated-create-input/v1")]
86    ValidatedInputV1,
87}
88
89/// Target-specific options that are consumed by a shipped emitter.
90#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
91#[serde(tag = "binding")]
92pub enum ProjectionConfig {
93    /// Python projection options.
94    #[serde(rename = "python")]
95    Python {
96        /// Versioned Python naming behavior used for every generated symbol.
97        naming_policy: PythonNamingPolicy,
98    },
99    /// TypeScript projection options.
100    #[serde(rename = "typescript")]
101    TypeScript {
102        /// Versioned TypeScript naming behavior used for every generated symbol.
103        naming_policy: TypeScriptNamingPolicy,
104    },
105    /// Native Rust projection options.
106    #[serde(rename = "rust")]
107    Rust {
108        /// Versioned Rust naming behavior used for every generated symbol.
109        naming_policy: RustNamingPolicy,
110        /// Versioned checked construction surface generated for constructible models.
111        create_policy: RustCreatePolicy,
112    },
113}
114
115impl ProjectionConfig {
116    /// Construct the initial Python projection configuration.
117    #[must_use]
118    pub const fn python() -> Self {
119        Self::Python {
120            naming_policy: PythonNamingPolicy::TypeBridgeV1,
121        }
122    }
123
124    /// Construct the initial TypeScript projection configuration.
125    #[must_use]
126    pub const fn typescript() -> Self {
127        Self::TypeScript {
128            naming_policy: TypeScriptNamingPolicy::TypeBridgeV1,
129        }
130    }
131
132    /// Construct the initial native Rust projection configuration.
133    #[must_use]
134    pub const fn rust() -> Self {
135        Self::Rust {
136            naming_policy: RustNamingPolicy::TypeBridgeV1,
137            create_policy: RustCreatePolicy::ValidatedInputV1,
138        }
139    }
140
141    /// Return the binding target that consumes this configuration.
142    #[must_use]
143    pub const fn target(&self) -> BindingTarget {
144        match self {
145            Self::Python { .. } => BindingTarget::Python,
146            Self::TypeScript { .. } => BindingTarget::TypeScript,
147            Self::Rust { .. } => BindingTarget::Rust,
148        }
149    }
150
151    /// Return the TypeScript naming policy when this is a TypeScript config.
152    #[must_use]
153    pub const fn typescript_naming_policy(&self) -> Option<TypeScriptNamingPolicy> {
154        match self {
155            Self::TypeScript { naming_policy } => Some(*naming_policy),
156            Self::Python { .. } | Self::Rust { .. } => None,
157        }
158    }
159
160    /// Return the Rust naming policy when this is a Rust config.
161    #[must_use]
162    pub const fn rust_naming_policy(&self) -> Option<RustNamingPolicy> {
163        match self {
164            Self::Rust { naming_policy, .. } => Some(*naming_policy),
165            Self::Python { .. } | Self::TypeScript { .. } => None,
166        }
167    }
168
169    /// Return the checked Rust create policy when this is a Rust config.
170    #[must_use]
171    pub const fn rust_create_policy(&self) -> Option<RustCreatePolicy> {
172        match self {
173            Self::Rust { create_policy, .. } => Some(*create_policy),
174            Self::Python { .. } | Self::TypeScript { .. } => None,
175        }
176    }
177}
178
179fn validate_component_id(value: String) -> Result<String, Diagnostic> {
180    let segments = value.split('.').collect::<Vec<_>>();
181    let valid_segment = |segment: &str| {
182        let mut bytes = segment.bytes();
183        bytes.next().is_some_and(|byte| byte.is_ascii_lowercase())
184            && bytes.all(|byte| {
185                byte.is_ascii_lowercase() || byte.is_ascii_digit() || matches!(byte, b'-' | b'_')
186            })
187    };
188    if value.len() <= MAX_PROJECTION_COMPONENT_ID_BYTES
189        && segments.len() >= 2
190        && segments.iter().all(|segment| valid_segment(segment))
191    {
192        Ok(value)
193    } else {
194        Err(Diagnostic::stable(
195            DiagnosticCategory::InvalidContract,
196            "malformed_projection_component_id",
197            "projection component ID must be a bounded lowercase namespaced identifier",
198        ))
199    }
200}
201
202macro_rules! projection_component_id {
203    ($name:ident, $doc:literal) => {
204        #[doc = $doc]
205        #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
206        pub struct $name(String);
207
208        impl $name {
209            /// Validate and construct a namespaced component identity.
210            pub fn new(value: impl Into<String>) -> Result<Self, Diagnostic> {
211                Ok(Self(validate_component_id(value.into())?))
212            }
213
214            /// Return the canonical identity spelling.
215            #[must_use]
216            pub fn as_str(&self) -> &str {
217                &self.0
218            }
219        }
220
221        impl fmt::Display for $name {
222            fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
223                formatter.write_str(self.as_str())
224            }
225        }
226
227        impl Serialize for $name {
228            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
229            where
230                S: Serializer,
231            {
232                serializer.serialize_str(self.as_str())
233            }
234        }
235    };
236}
237
238projection_component_id!(
239    ProjectionHandlerId,
240    "A stable identity for one generator or projection handler."
241);
242projection_component_id!(
243    CodeResourceId,
244    "A stable identity for exact code-resource bytes referenced during emission."
245);
246
247/// A nonzero behavior version for one projection handler.
248#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
249#[serde(transparent)]
250pub struct ProjectionHandlerVersion(u16);
251
252impl ProjectionHandlerVersion {
253    /// The initial handler behavior version.
254    pub const V1: Self = Self(1);
255
256    /// Validate and construct a handler behavior version.
257    pub fn new(value: u16) -> Result<Self, Diagnostic> {
258        if value == 0 {
259            Err(Diagnostic::stable(
260                DiagnosticCategory::InvalidContract,
261                "invalid_projection_handler_version",
262                "projection handler version must be nonzero",
263            ))
264        } else {
265            Ok(Self(value))
266        }
267    }
268
269    /// Return the numeric behavior version.
270    #[must_use]
271    pub const fn get(self) -> u16 {
272        self.0
273    }
274}
275
276/// The identity and behavior version of a handler that participated in emission.
277#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
278pub struct ProjectionHandler {
279    id: ProjectionHandlerId,
280    version: ProjectionHandlerVersion,
281}
282
283impl ProjectionHandler {
284    /// Construct one executed projection-handler identity.
285    pub fn new(id: impl Into<String>, version: u16) -> Result<Self, Diagnostic> {
286        Ok(Self {
287            id: ProjectionHandlerId::new(id)?,
288            version: ProjectionHandlerVersion::new(version)?,
289        })
290    }
291
292    /// Construct the initial built-in Python generator identity.
293    #[must_use]
294    pub fn python_v1() -> Self {
295        Self {
296            id: ProjectionHandlerId::new(PYTHON_GENERATOR_HANDLER_ID)
297                .expect("the built-in Python generator ID is valid"),
298            version: ProjectionHandlerVersion::V1,
299        }
300    }
301
302    /// Construct the initial built-in TypeScript generator identity.
303    #[must_use]
304    pub fn typescript_v1() -> Self {
305        Self {
306            id: ProjectionHandlerId::new(TYPESCRIPT_GENERATOR_HANDLER_ID)
307                .expect("built-in TypeScript projection handler ID is valid"),
308            version: ProjectionHandlerVersion::V1,
309        }
310    }
311
312    /// Construct the initial built-in native Rust generator identity.
313    #[must_use]
314    pub fn rust_v1() -> Self {
315        Self {
316            id: ProjectionHandlerId::new(RUST_GENERATOR_HANDLER_ID)
317                .expect("built-in Rust projection handler ID is valid"),
318            version: ProjectionHandlerVersion::V1,
319        }
320    }
321
322    /// Return the handler identity.
323    #[must_use]
324    pub const fn id(&self) -> &ProjectionHandlerId {
325        &self.id
326    }
327
328    /// Return the handler behavior version.
329    #[must_use]
330    pub const fn version(&self) -> ProjectionHandlerVersion {
331        self.version
332    }
333}
334
335/// A domain-separated digest of exact code-resource bytes used by an emitter.
336#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
337pub struct CodeResourceDigest {
338    id: CodeResourceId,
339    content_fingerprint: Fingerprint,
340}
341
342impl CodeResourceDigest {
343    /// Hash the exact bytes of one referenced code resource.
344    pub fn from_bytes(id: impl Into<String>, bytes: &[u8]) -> Result<Self, Diagnostic> {
345        Ok(Self {
346            id: CodeResourceId::new(id)?,
347            content_fingerprint: Fingerprint::compute(
348                FingerprintDomain::new(CODE_RESOURCE_DOMAIN)?,
349                CanonicalizationVersion::new(RAW_BYTES_CANONICALIZATION)?,
350                None,
351                bytes,
352            ),
353        })
354    }
355
356    /// Return the resource identity.
357    #[must_use]
358    pub const fn id(&self) -> &CodeResourceId {
359        &self.id
360    }
361
362    /// Return the domain-separated exact-content fingerprint.
363    #[must_use]
364    pub const fn content_fingerprint(&self) -> &Fingerprint {
365        &self.content_fingerprint
366    }
367}
368
369#[derive(Serialize)]
370struct BindingProjectionView<'a> {
371    format_version: FormatVersion,
372    target: BindingTarget,
373    semantic_schema_fingerprint: &'a SemanticSchemaFingerprint,
374    config: &'a ProjectionConfig,
375    generator_handlers: Vec<&'a ProjectionHandler>,
376    referenced_code_resources: Vec<&'a CodeResourceDigest>,
377}
378
379fn ordered_handlers(
380    target: BindingTarget,
381    handlers: &[ProjectionHandler],
382) -> Result<Vec<&ProjectionHandler>, Diagnostic> {
383    let mut ordered = handlers.iter().collect::<Vec<_>>();
384    ordered.sort_by(|left, right| match left.id().cmp(right.id()) {
385        Ordering::Equal => left.version().cmp(&right.version()),
386        ordering => ordering,
387    });
388    if ordered.windows(2).any(|pair| pair[0].id() == pair[1].id()) {
389        return Err(Diagnostic::stable(
390            DiagnosticCategory::InvalidContract,
391            "duplicate_projection_handler_id",
392            "a projection handler identity may appear only once",
393        ));
394    }
395    if !ordered
396        .iter()
397        .any(|handler| handler.id().as_str() == target.required_generator_handler_id())
398    {
399        return Err(Diagnostic::stable(
400            DiagnosticCategory::InvalidContract,
401            "missing_target_projection_handler",
402            "projection fingerprint inputs omit the target's generator handler",
403        ));
404    }
405    Ok(ordered)
406}
407
408fn ordered_resources(
409    resources: &[CodeResourceDigest],
410) -> Result<Vec<&CodeResourceDigest>, Diagnostic> {
411    let mut ordered = resources.iter().collect::<Vec<_>>();
412    ordered.sort_by(|left, right| left.id().cmp(right.id()));
413    if ordered.windows(2).any(|pair| pair[0].id() == pair[1].id()) {
414        return Err(Diagnostic::stable(
415            DiagnosticCategory::InvalidContract,
416            "duplicate_projection_resource_id",
417            "a referenced code-resource identity may appear only once",
418        ));
419    }
420    Ok(ordered)
421}
422
423/// Produce the exact canonical preimage for a binding-projection fingerprint.
424pub fn canonical_binding_projection_bytes(
425    target: BindingTarget,
426    semantic_schema: &SemanticSchemaFingerprint,
427    config: &ProjectionConfig,
428    handlers: &[ProjectionHandler],
429    resources: &[CodeResourceDigest],
430) -> Result<Vec<u8>, Diagnostic> {
431    if config.target() != target {
432        return Err(Diagnostic::stable(
433            DiagnosticCategory::InvalidContract,
434            "projection_config_target_mismatch",
435            "projection configuration belongs to a different binding target",
436        ));
437    }
438    let view = BindingProjectionView {
439        format_version: FormatVersion::V1,
440        target,
441        semantic_schema_fingerprint: semantic_schema,
442        config,
443        generator_handlers: ordered_handlers(target, handlers)?,
444        referenced_code_resources: ordered_resources(resources)?,
445    };
446    to_canonical_json(&view)
447}
448
449/// Fingerprint of one binding projection and every input that can alter it.
450#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
451#[serde(transparent)]
452pub struct BindingProjectionFingerprint(Fingerprint);
453
454impl BindingProjectionFingerprint {
455    /// Compute a target-specific projection fingerprint from trusted inputs.
456    pub fn compute(
457        target: BindingTarget,
458        semantic_schema: &SemanticSchemaFingerprint,
459        config: &ProjectionConfig,
460        handlers: &[ProjectionHandler],
461        resources: &[CodeResourceDigest],
462    ) -> Result<Self, Diagnostic> {
463        let canonical = canonical_binding_projection_bytes(
464            target,
465            semantic_schema,
466            config,
467            handlers,
468            resources,
469        )?;
470        let semantic_profile = semantic_schema
471            .as_fingerprint()
472            .semantic_profile()
473            .cloned()
474            .ok_or_else(|| {
475                Diagnostic::stable(
476                    DiagnosticCategory::InvalidContract,
477                    "projection_semantic_profile_missing",
478                    "semantic schema fingerprint does not carry its semantic profile",
479                )
480            })?;
481        Ok(Self(Fingerprint::compute(
482            FingerprintDomain::new(BINDING_PROJECTION_DOMAIN)?,
483            CanonicalizationVersion::new(BINDING_PROJECTION_CANONICALIZATION)?,
484            Some(semantic_profile),
485            &canonical,
486        )))
487    }
488
489    /// Return the generic fingerprint metadata and digest.
490    #[must_use]
491    pub const fn as_fingerprint(&self) -> &Fingerprint {
492        &self.0
493    }
494
495    /// Compute a fingerprint that additionally commits to canonical projection content.
496    pub fn compute_with_projection(
497        target: BindingTarget,
498        semantic_schema: &SemanticSchemaFingerprint,
499        config: &ProjectionConfig,
500        handlers: &[ProjectionHandler],
501        resources: &[CodeResourceDigest],
502        canonical_projection: &[u8],
503    ) -> Result<Self, Diagnostic> {
504        #[derive(Serialize)]
505        struct CompleteProjectionView<'a> {
506            inputs: BindingProjectionView<'a>,
507            projection_content: Fingerprint,
508        }
509
510        if config.target() != target {
511            return Err(Diagnostic::stable(
512                DiagnosticCategory::InvalidContract,
513                "projection_config_target_mismatch",
514                "projection configuration belongs to a different binding target",
515            ));
516        }
517        let inputs = BindingProjectionView {
518            format_version: FormatVersion::V1,
519            target,
520            semantic_schema_fingerprint: semantic_schema,
521            config,
522            generator_handlers: ordered_handlers(target, handlers)?,
523            referenced_code_resources: ordered_resources(resources)?,
524        };
525        let projection_content = Fingerprint::compute(
526            FingerprintDomain::new(BINDING_PROJECTION_CONTENT_DOMAIN)?,
527            CanonicalizationVersion::new(BINDING_PROJECTION_CANONICALIZATION)?,
528            semantic_schema.as_fingerprint().semantic_profile().cloned(),
529            canonical_projection,
530        );
531        let canonical = to_canonical_json(&CompleteProjectionView {
532            inputs,
533            projection_content,
534        })?;
535        let semantic_profile = semantic_schema
536            .as_fingerprint()
537            .semantic_profile()
538            .cloned()
539            .ok_or_else(|| {
540                Diagnostic::stable(
541                    DiagnosticCategory::InvalidContract,
542                    "projection_semantic_profile_missing",
543                    "semantic schema fingerprint does not carry its semantic profile",
544                )
545            })?;
546        Ok(Self(Fingerprint::compute(
547            FingerprintDomain::new(BINDING_PROJECTION_DOMAIN)?,
548            CanonicalizationVersion::new(BINDING_PROJECTION_CANONICALIZATION)?,
549            Some(semantic_profile),
550            &canonical,
551        )))
552    }
553}
554
555fn serialize_map_values<S, K, V>(map: &BTreeMap<K, V>, serializer: S) -> Result<S::Ok, S::Error>
556where
557    S: Serializer,
558    V: Serialize,
559{
560    map.values().collect::<Vec<_>>().serialize(serializer)
561}
562
563#[derive(Serialize)]
564struct RoleUpcastEntry<'a> {
565    role: &'a RoleId,
566    ancestors: &'a [RoleId],
567}
568
569fn serialize_role_upcasts<S>(
570    map: &BTreeMap<RoleId, Vec<RoleId>>,
571    serializer: S,
572) -> Result<S::Ok, S::Error>
573where
574    S: Serializer,
575{
576    map.iter()
577        .map(|(role, ancestors)| RoleUpcastEntry { role, ancestors })
578        .collect::<Vec<_>>()
579        .serialize(serializer)
580}
581
582fn invalid_projection(code: &'static str, message: &'static str) -> Diagnostic {
583    Diagnostic::stable(DiagnosticCategory::InvalidContract, code, message)
584}
585
586fn ensure_collection_limit(length: usize, code: &'static str) -> Result<(), Diagnostic> {
587    if length > MAX_CANONICAL_COLLECTION_LEN {
588        Err(Diagnostic::stable(
589            DiagnosticCategory::ResourceLimit,
590            code,
591            "projection collection exceeds the canonical collection limit",
592        ))
593    } else {
594        Ok(())
595    }
596}
597
598/// A validated target-language identifier emitted verbatim by a generator.
599#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
600#[serde(transparent)]
601pub struct TargetIdentifier(String);
602
603impl TargetIdentifier {
604    /// Validate one ASCII Python identifier under the frozen Python-v1 policy.
605    pub fn python(value: impl Into<String>) -> Result<Self, Diagnostic> {
606        let value = value.into();
607        let mut bytes = value.bytes();
608        let valid = value.len() <= MAX_TARGET_IDENTIFIER_BYTES
609            && bytes
610                .next()
611                .is_some_and(|byte| byte == b'_' || byte.is_ascii_alphabetic())
612            && bytes.all(|byte| byte == b'_' || byte.is_ascii_alphanumeric());
613        if !valid || is_python_keyword(&value) {
614            return Err(invalid_projection(
615                "invalid_python_projection_identifier",
616                "projected Python name is not a bounded non-keyword identifier",
617            ));
618        }
619        Ok(Self(value))
620    }
621
622    /// Validate one ASCII TypeScript identifier under the frozen TypeScript-v1 policy.
623    pub fn typescript(value: impl Into<String>) -> Result<Self, Diagnostic> {
624        let value = value.into();
625        let mut bytes = value.bytes();
626        let valid = value.len() <= MAX_TARGET_IDENTIFIER_BYTES
627            && bytes
628                .next()
629                .is_some_and(|byte| byte == b'_' || byte == b'$' || byte.is_ascii_alphabetic())
630            && bytes.all(|byte| byte == b'_' || byte == b'$' || byte.is_ascii_alphanumeric());
631        if !valid || is_typescript_keyword(&value) {
632            return Err(invalid_projection(
633                "invalid_typescript_projection_identifier",
634                "projected TypeScript name is not a bounded non-keyword identifier",
635            ));
636        }
637        Ok(Self(value))
638    }
639
640    /// Validate one ASCII Rust identifier under the frozen Rust-v1 policy.
641    pub fn rust(value: impl Into<String>) -> Result<Self, Diagnostic> {
642        let value = value.into();
643        let mut bytes = value.bytes();
644        let valid = value != "_"
645            && value.len() <= MAX_TARGET_IDENTIFIER_BYTES
646            && bytes
647                .next()
648                .is_some_and(|byte| byte == b'_' || byte.is_ascii_alphabetic())
649            && bytes.all(|byte| byte == b'_' || byte.is_ascii_alphanumeric());
650        if !valid || is_rust_keyword(&value) {
651            return Err(invalid_projection(
652                "invalid_rust_projection_identifier",
653                "projected Rust name is not a bounded non-keyword ASCII identifier",
654            ));
655        }
656        Ok(Self(value))
657    }
658
659    /// Return the exact target spelling.
660    #[must_use]
661    pub fn as_str(&self) -> &str {
662        &self.0
663    }
664}
665
666fn is_python_keyword(value: &str) -> bool {
667    matches!(
668        value,
669        "False"
670            | "None"
671            | "True"
672            | "and"
673            | "as"
674            | "assert"
675            | "async"
676            | "await"
677            | "break"
678            | "case"
679            | "class"
680            | "continue"
681            | "def"
682            | "del"
683            | "elif"
684            | "else"
685            | "except"
686            | "finally"
687            | "for"
688            | "from"
689            | "global"
690            | "if"
691            | "import"
692            | "in"
693            | "is"
694            | "lambda"
695            | "match"
696            | "nonlocal"
697            | "not"
698            | "or"
699            | "pass"
700            | "raise"
701            | "return"
702            | "try"
703            | "while"
704            | "with"
705            | "yield"
706    )
707}
708
709fn is_typescript_keyword(value: &str) -> bool {
710    matches!(
711        value,
712        "abstract"
713            | "any"
714            | "as"
715            | "asserts"
716            | "async"
717            | "await"
718            | "bigint"
719            | "boolean"
720            | "break"
721            | "case"
722            | "catch"
723            | "class"
724            | "const"
725            | "constructor"
726            | "continue"
727            | "debugger"
728            | "declare"
729            | "default"
730            | "delete"
731            | "do"
732            | "else"
733            | "enum"
734            | "export"
735            | "extends"
736            | "false"
737            | "finally"
738            | "for"
739            | "from"
740            | "function"
741            | "get"
742            | "if"
743            | "implements"
744            | "import"
745            | "in"
746            | "infer"
747            | "instanceof"
748            | "interface"
749            | "is"
750            | "keyof"
751            | "let"
752            | "module"
753            | "namespace"
754            | "never"
755            | "new"
756            | "null"
757            | "number"
758            | "object"
759            | "of"
760            | "out"
761            | "override"
762            | "package"
763            | "private"
764            | "protected"
765            | "public"
766            | "readonly"
767            | "require"
768            | "return"
769            | "satisfies"
770            | "set"
771            | "static"
772            | "string"
773            | "super"
774            | "switch"
775            | "symbol"
776            | "this"
777            | "throw"
778            | "true"
779            | "try"
780            | "type"
781            | "typeof"
782            | "undefined"
783            | "unique"
784            | "unknown"
785            | "using"
786            | "var"
787            | "void"
788            | "while"
789            | "with"
790            | "yield"
791    )
792}
793
794fn is_rust_keyword(value: &str) -> bool {
795    matches!(
796        value,
797        "Self"
798            | "abstract"
799            | "as"
800            | "async"
801            | "await"
802            | "become"
803            | "box"
804            | "break"
805            | "const"
806            | "continue"
807            | "crate"
808            | "do"
809            | "dyn"
810            | "else"
811            | "enum"
812            | "extern"
813            | "false"
814            | "final"
815            | "fn"
816            | "for"
817            | "gen"
818            | "if"
819            | "impl"
820            | "in"
821            | "let"
822            | "loop"
823            | "macro"
824            | "match"
825            | "mod"
826            | "move"
827            | "mut"
828            | "override"
829            | "priv"
830            | "pub"
831            | "ref"
832            | "return"
833            | "self"
834            | "static"
835            | "struct"
836            | "super"
837            | "trait"
838            | "true"
839            | "try"
840            | "type"
841            | "typeof"
842            | "unsafe"
843            | "unsized"
844            | "use"
845            | "virtual"
846            | "where"
847            | "while"
848            | "yield"
849    )
850}
851
852/// Whether a projected model use is complete or a nonrecursive reference.
853#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
854#[serde(rename_all = "snake_case")]
855pub enum ProjectedModelForm {
856    /// A complete materialized model.
857    Complete,
858    /// An identity/reference-only model.
859    Reference,
860}
861
862/// One typed use of a projected model.
863#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
864pub struct ProjectedModelUse {
865    id: TypeId,
866    form: ProjectedModelForm,
867}
868
869impl ProjectedModelUse {
870    /// Construct one typed model use.
871    #[must_use]
872    pub const fn new(id: TypeId, form: ProjectedModelForm) -> Self {
873        Self { id, form }
874    }
875    /// Return the model identity.
876    #[must_use]
877    pub const fn id(&self) -> &TypeId {
878        &self.id
879    }
880    /// Return the requested materialization form.
881    #[must_use]
882    pub const fn form(&self) -> ProjectedModelForm {
883        self.form
884    }
885}
886
887/// A fully resolved type position used by projected signatures.
888#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
889#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
890pub enum ProjectedTypeRef {
891    /// A built-in scalar domain.
892    Scalar(ValueTypeTag),
893    /// A model identity and materialization form.
894    Model(ProjectedModelUse),
895    /// A schema struct value.
896    Struct(StructId),
897}
898
899/// Scalar versus collection container shape derived from cardinality.
900#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
901#[serde(rename_all = "snake_case")]
902pub enum ProjectedContainer {
903    /// At most one value.
904    Scalar,
905    /// Multiple values in a generated sequence container.
906    Sequence,
907}
908
909/// Requiredness and container form derived from one resolved cardinality.
910#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
911pub struct ProjectedMultiplicity {
912    cardinality: Cardinality,
913    required: bool,
914    container: ProjectedContainer,
915}
916
917impl ProjectedMultiplicity {
918    /// Derive an honest input/read shape from resolved cardinality.
919    #[must_use]
920    pub const fn from_cardinality(cardinality: Cardinality) -> Self {
921        let container = match cardinality.max() {
922            Some(0 | 1) => ProjectedContainer::Scalar,
923            Some(_) | None => ProjectedContainer::Sequence,
924        };
925        Self {
926            cardinality,
927            required: cardinality.min() > 0,
928            container,
929        }
930    }
931    /// Return the exact resolved cardinality.
932    #[must_use]
933    pub const fn cardinality(&self) -> Cardinality {
934        self.cardinality
935    }
936    /// Report whether the generated field is required.
937    #[must_use]
938    pub const fn required(&self) -> bool {
939        self.required
940    }
941    /// Return the generated container category.
942    #[must_use]
943    pub const fn container(&self) -> ProjectedContainer {
944        self.container
945    }
946}
947
948/// One effective annotation retained in runtime projection metadata.
949///
950/// The ID names the effective projected subject, not the direct declaration
951/// from which an inherited value originated. Type, ownership, related-role,
952/// and playing annotations therefore use the actual projected owner or player.
953/// For an inherited related role, the annotation-only subject remints the role
954/// label under the effective relation while [`RoleTokenProjection::role`]
955/// retains the canonical declaring-role identity.
956#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
957pub struct ProjectedAnnotation {
958    id: AnnotationFactId,
959    value: SchemaAnnotationValue,
960}
961
962impl ProjectedAnnotation {
963    /// Construct one projected annotation through the authoritative annotation contract.
964    pub fn new(id: AnnotationFactId, value: SchemaAnnotationValue) -> Result<Self, Diagnostic> {
965        AnnotationFact::new(id.clone(), value.clone())?;
966        Ok(Self { id, value })
967    }
968    /// Return the effective-subject annotation identity.
969    #[must_use]
970    pub const fn id(&self) -> &AnnotationFactId {
971        &self.id
972    }
973    /// Return the effective annotation value.
974    #[must_use]
975    pub const fn value(&self) -> &SchemaAnnotationValue {
976        &self.value
977    }
978}
979
980/// One owner-branded owned-attribute query token.
981#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
982pub struct FieldTokenProjection {
983    id: OwnsFactId,
984    declaring_id: OwnsFactId,
985    target_name: TargetIdentifier,
986    multiplicity: ProjectedMultiplicity,
987    key: bool,
988    unique: bool,
989    #[serde(serialize_with = "serialize_map_values")]
990    annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
991}
992
993impl FieldTokenProjection {
994    /// Construct a projected owned-attribute token.
995    pub fn new(
996        id: OwnsFactId,
997        declaring_id: OwnsFactId,
998        target_name: TargetIdentifier,
999        multiplicity: ProjectedMultiplicity,
1000        key: bool,
1001        unique: bool,
1002        annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
1003    ) -> Result<Self, Diagnostic> {
1004        if id.attribute() != declaring_id.attribute() {
1005            return Err(invalid_projection(
1006                "invalid_projection_reference",
1007                "effective owns fact attribute does not match declaring owns fact attribute",
1008            ));
1009        }
1010        if annotations.iter().any(|(key, value)| {
1011            key != value.id()
1012                || !matches!(
1013                    value.id().subject(),
1014                    AnnotationSubjectId::Owns(subject) if subject == &id
1015                )
1016        }) {
1017            return Err(invalid_projection(
1018                "invalid_projected_owns_annotation",
1019                "owns annotations require matching exact effective owns subjects",
1020            ));
1021        }
1022        ensure_collection_limit(annotations.len(), "too_many_projected_annotations")?;
1023        Ok(Self {
1024            id,
1025            declaring_id,
1026            target_name,
1027            multiplicity,
1028            key,
1029            unique,
1030            annotations,
1031        })
1032    }
1033    /// Return the effective ownership identity.
1034    #[must_use]
1035    pub const fn id(&self) -> &OwnsFactId {
1036        &self.id
1037    }
1038    /// Return the declaring ownership identity.
1039    #[must_use]
1040    pub const fn declaring_id(&self) -> &OwnsFactId {
1041        &self.declaring_id
1042    }
1043    /// Return the emitted member name.
1044    #[must_use]
1045    pub const fn target_name(&self) -> &TargetIdentifier {
1046        &self.target_name
1047    }
1048    /// Return resolved requiredness and container shape.
1049    #[must_use]
1050    pub const fn multiplicity(&self) -> ProjectedMultiplicity {
1051        self.multiplicity
1052    }
1053    /// Report key semantics.
1054    #[must_use]
1055    pub const fn is_key(&self) -> bool {
1056        self.key
1057    }
1058    /// Report independent uniqueness semantics.
1059    #[must_use]
1060    pub const fn is_unique(&self) -> bool {
1061        self.unique
1062    }
1063    /// Return effective annotations.
1064    #[must_use]
1065    pub const fn annotations(&self) -> &BTreeMap<AnnotationFactId, ProjectedAnnotation> {
1066        &self.annotations
1067    }
1068}
1069
1070/// One owner-branded related-role query token.
1071#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1072pub struct RoleTokenProjection {
1073    owner: TypeId,
1074    role: RoleId,
1075    target_name: TargetIdentifier,
1076    #[serde(skip_serializing_if = "Option::is_none")]
1077    player_union_target_name: Option<TargetIdentifier>,
1078    accepted_players: BTreeSet<TypeId>,
1079    specializes: Option<RoleId>,
1080    multiplicity: ProjectedMultiplicity,
1081    is_abstract: bool,
1082    #[serde(serialize_with = "serialize_map_values")]
1083    annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
1084}
1085
1086impl RoleTokenProjection {
1087    /// Construct an effective role token for one actual relation owner.
1088    #[allow(clippy::too_many_arguments)]
1089    pub fn new(
1090        owner: TypeId,
1091        role: RoleId,
1092        target_name: TargetIdentifier,
1093        accepted_players: BTreeSet<TypeId>,
1094        specializes: Option<RoleId>,
1095        multiplicity: ProjectedMultiplicity,
1096        is_abstract: bool,
1097        annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
1098    ) -> Result<Self, Diagnostic> {
1099        if owner.kind() != TypeKind::Relation
1100            || accepted_players
1101                .iter()
1102                .any(|id| !matches!(id.kind(), TypeKind::Entity | TypeKind::Relation))
1103        {
1104            return Err(invalid_projection(
1105                "invalid_projected_role_token",
1106                "role tokens require a relation owner and entity/relation players",
1107            ));
1108        }
1109        ensure_collection_limit(accepted_players.len(), "too_many_projected_role_players")?;
1110        ensure_collection_limit(annotations.len(), "too_many_projected_annotations")?;
1111        Ok(Self {
1112            owner,
1113            role,
1114            target_name,
1115            player_union_target_name: None,
1116            accepted_players,
1117            specializes,
1118            multiplicity,
1119            is_abstract,
1120            annotations,
1121        })
1122    }
1123    /// Attach the explicit native player-union type name.
1124    #[must_use]
1125    pub fn with_player_union_target_name(mut self, target_name: TargetIdentifier) -> Self {
1126        self.player_union_target_name = Some(target_name);
1127        self
1128    }
1129    /// Return the actual relation model that owns the token.
1130    #[must_use]
1131    pub const fn owner(&self) -> &TypeId {
1132        &self.owner
1133    }
1134    /// Return the canonical declaring-role identity.
1135    #[must_use]
1136    pub const fn role(&self) -> &RoleId {
1137        &self.role
1138    }
1139    /// Return the emitted member name.
1140    #[must_use]
1141    pub const fn target_name(&self) -> &TargetIdentifier {
1142        &self.target_name
1143    }
1144    /// Return the explicit native player-union type name, when the target uses one.
1145    #[must_use]
1146    pub const fn player_union_target_name(&self) -> Option<&TargetIdentifier> {
1147        self.player_union_target_name.as_ref()
1148    }
1149    /// Return exact logical player identities.
1150    #[must_use]
1151    pub const fn accepted_players(&self) -> &BTreeSet<TypeId> {
1152        &self.accepted_players
1153    }
1154    /// Return the immediate specialized role, if any.
1155    #[must_use]
1156    pub const fn specializes(&self) -> Option<&RoleId> {
1157        self.specializes.as_ref()
1158    }
1159    /// Return resolved role cardinality shape.
1160    #[must_use]
1161    pub const fn multiplicity(&self) -> ProjectedMultiplicity {
1162        self.multiplicity
1163    }
1164    /// Report whether the role is abstract.
1165    #[must_use]
1166    pub const fn is_abstract(&self) -> bool {
1167        self.is_abstract
1168    }
1169    /// Return effective relates annotations.
1170    #[must_use]
1171    pub const fn annotations(&self) -> &BTreeMap<AnnotationFactId, ProjectedAnnotation> {
1172        &self.annotations
1173    }
1174}
1175
1176/// One direct role declaration and its immediate specialization target.
1177#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1178pub struct DeclaredRoleProjection {
1179    role: RoleId,
1180    specializes: Option<RoleId>,
1181}
1182
1183impl DeclaredRoleProjection {
1184    /// Construct one direct projected role declaration.
1185    #[must_use]
1186    pub const fn new(role: RoleId, specializes: Option<RoleId>) -> Self {
1187        Self { role, specializes }
1188    }
1189    /// Return the declared role.
1190    #[must_use]
1191    pub const fn role(&self) -> &RoleId {
1192        &self.role
1193    }
1194    /// Return its immediate specialization target.
1195    #[must_use]
1196    pub const fn specializes(&self) -> Option<&RoleId> {
1197        self.specializes.as_ref()
1198    }
1199}
1200
1201/// The exact direct subtype declaration retained by every runtime projection.
1202#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1203pub struct DirectSubProjection {
1204    id: SubFactId,
1205    origin: SchemaFactId,
1206    #[serde(serialize_with = "serialize_map_values")]
1207    annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
1208}
1209
1210impl DirectSubProjection {
1211    /// Construct one direct subtype declaration from resolved schema values.
1212    pub fn new(
1213        id: SubFactId,
1214        origin: SchemaFactId,
1215        annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
1216    ) -> Result<Self, Diagnostic> {
1217        if origin != SchemaFactId::Sub(id.clone()) {
1218            return Err(invalid_projection(
1219                "invalid_projected_sub_origin",
1220                "projected subtype origin must identify its exact direct edge",
1221            ));
1222        }
1223        if annotations.iter().any(|(key, value)| {
1224            key != value.id()
1225                || !matches!(key.subject(), AnnotationSubjectId::Sub(subject) if subject == &id)
1226        }) {
1227            return Err(invalid_projection(
1228                "invalid_projected_sub_annotation",
1229                "subtype annotations require matching exact edge subjects",
1230            ));
1231        }
1232        ensure_collection_limit(annotations.len(), "too_many_projected_annotations")?;
1233        Ok(Self {
1234            id,
1235            origin,
1236            annotations,
1237        })
1238    }
1239
1240    /// Return the exact direct subtype-edge identity.
1241    #[must_use]
1242    pub const fn id(&self) -> &SubFactId {
1243        &self.id
1244    }
1245
1246    /// Return the direct declaration origin.
1247    #[must_use]
1248    pub const fn origin(&self) -> &SchemaFactId {
1249        &self.origin
1250    }
1251
1252    /// Return annotations attached to this exact subtype edge.
1253    #[must_use]
1254    pub const fn annotations(&self) -> &BTreeMap<AnnotationFactId, ProjectedAnnotation> {
1255        &self.annotations
1256    }
1257}
1258
1259/// The nominal declaration facet of one model.
1260#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1261pub struct DeclarationProjection {
1262    parent: Option<TypeId>,
1263    // Pre-release wire ledger: before any 2.0.0 artifact shipped,
1264    // binding-projection/v1 gained the exact direct subtype declaration.
1265    direct_sub: Option<DirectSubProjection>,
1266    value_type: Option<ValueTypeTag>,
1267    is_abstract: bool,
1268    is_constructible: bool,
1269    #[serde(serialize_with = "serialize_map_values")]
1270    annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
1271    #[serde(serialize_with = "serialize_map_values")]
1272    value_annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
1273    direct_fields: Vec<OwnsFactId>,
1274    #[serde(serialize_with = "serialize_map_values")]
1275    direct_roles: BTreeMap<RoleId, DeclaredRoleProjection>,
1276    direct_plays: BTreeSet<PlaysFactId>,
1277}
1278
1279impl DeclarationProjection {
1280    /// Construct one declaration facet from direct attachment identities.
1281    #[allow(clippy::too_many_arguments)]
1282    pub fn new(
1283        parent: Option<TypeId>,
1284        value_type: Option<ValueTypeTag>,
1285        is_abstract: bool,
1286        is_constructible: bool,
1287        annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
1288        direct_fields: Vec<OwnsFactId>,
1289        direct_roles: BTreeMap<RoleId, DeclaredRoleProjection>,
1290        direct_plays: BTreeSet<PlaysFactId>,
1291    ) -> Result<Self, Diagnostic> {
1292        for length in [
1293            annotations.len(),
1294            direct_fields.len(),
1295            direct_roles.len(),
1296            direct_plays.len(),
1297        ] {
1298            ensure_collection_limit(length, "projection_declaration_limit_exceeded")?;
1299        }
1300        Ok(Self {
1301            parent,
1302            direct_sub: None,
1303            value_type,
1304            is_abstract,
1305            is_constructible,
1306            annotations,
1307            value_annotations: BTreeMap::new(),
1308            direct_fields,
1309            direct_roles,
1310            direct_plays,
1311        })
1312    }
1313    /// Attach the direct subtype identity, origin, and annotations.
1314    pub fn with_direct_sub(
1315        mut self,
1316        direct_sub: Option<DirectSubProjection>,
1317    ) -> Result<Self, Diagnostic> {
1318        if direct_sub
1319            .as_ref()
1320            .is_some_and(|sub| self.parent.as_ref() != Some(sub.id().supertype()))
1321        {
1322            return Err(invalid_projection(
1323                "invalid_projected_sub_parent",
1324                "projected direct subtype edge must match the nominal parent",
1325            ));
1326        }
1327        self.direct_sub = direct_sub;
1328        Ok(self)
1329    }
1330    /// Attach effective attribute-value constraints without changing legacy construction.
1331    pub fn with_value_annotations(
1332        mut self,
1333        annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
1334    ) -> Result<Self, Diagnostic> {
1335        if annotations.iter().any(|(key, value)| {
1336            key != value.id() || !matches!(key.subject(), AnnotationSubjectId::Value(_))
1337        }) {
1338            return Err(invalid_projection(
1339                "invalid_projected_value_annotation",
1340                "attribute value annotations require matching effective value subjects",
1341            ));
1342        }
1343        ensure_collection_limit(annotations.len(), "too_many_projected_annotations")?;
1344        self.value_annotations = annotations;
1345        Ok(self)
1346    }
1347    /// Return the direct nominal parent.
1348    #[must_use]
1349    pub const fn parent(&self) -> Option<&TypeId> {
1350        self.parent.as_ref()
1351    }
1352    /// Return the exact direct subtype declaration, if this model has a parent.
1353    #[must_use]
1354    pub const fn direct_sub(&self) -> Option<&DirectSubProjection> {
1355        self.direct_sub.as_ref()
1356    }
1357    /// Return an attribute's effective scalar domain.
1358    #[must_use]
1359    pub const fn value_type(&self) -> Option<ValueTypeTag> {
1360        self.value_type
1361    }
1362    /// Report abstractness.
1363    #[must_use]
1364    pub const fn is_abstract(&self) -> bool {
1365        self.is_abstract
1366    }
1367    /// Report constructibility.
1368    #[must_use]
1369    pub const fn is_constructible(&self) -> bool {
1370        self.is_constructible
1371    }
1372    /// Return effective type annotations.
1373    #[must_use]
1374    pub const fn annotations(&self) -> &BTreeMap<AnnotationFactId, ProjectedAnnotation> {
1375        &self.annotations
1376    }
1377    /// Return effective attribute-value constraints.
1378    #[must_use]
1379    pub const fn value_annotations(&self) -> &BTreeMap<AnnotationFactId, ProjectedAnnotation> {
1380        &self.value_annotations
1381    }
1382    /// Return direct ownership attachment identities in semantic order.
1383    #[must_use]
1384    pub fn direct_fields(&self) -> &[OwnsFactId] {
1385        &self.direct_fields
1386    }
1387    /// Return direct role declarations.
1388    #[must_use]
1389    pub const fn direct_roles(&self) -> &BTreeMap<RoleId, DeclaredRoleProjection> {
1390        &self.direct_roles
1391    }
1392    /// Return direct role-playing attachments.
1393    #[must_use]
1394    pub const fn direct_plays(&self) -> &BTreeSet<PlaysFactId> {
1395        &self.direct_plays
1396    }
1397}
1398
1399/// One owned-attribute input in a create facet.
1400#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1401pub struct CreateFieldProjection {
1402    token: OwnsFactId,
1403    value: ProjectedTypeRef,
1404    multiplicity: ProjectedMultiplicity,
1405}
1406
1407impl CreateFieldProjection {
1408    /// Construct one create input.
1409    #[must_use]
1410    pub const fn new(
1411        token: OwnsFactId,
1412        value: ProjectedTypeRef,
1413        multiplicity: ProjectedMultiplicity,
1414    ) -> Self {
1415        Self {
1416            token,
1417            value,
1418            multiplicity,
1419        }
1420    }
1421    /// Return the field token identity.
1422    #[must_use]
1423    pub const fn token(&self) -> &OwnsFactId {
1424        &self.token
1425    }
1426    /// Return the accepted input value type.
1427    #[must_use]
1428    pub const fn value(&self) -> &ProjectedTypeRef {
1429        &self.value
1430    }
1431    /// Return input requiredness/container shape.
1432    #[must_use]
1433    pub const fn multiplicity(&self) -> ProjectedMultiplicity {
1434        self.multiplicity
1435    }
1436}
1437
1438/// One active related-role input in a create facet.
1439#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1440pub struct CreateRoleProjection {
1441    role: RoleId,
1442    players: BTreeSet<ProjectedModelUse>,
1443    multiplicity: ProjectedMultiplicity,
1444}
1445
1446impl CreateRoleProjection {
1447    /// Construct one role input from its exact accepted player uses.
1448    pub fn new(
1449        role: RoleId,
1450        players: BTreeSet<ProjectedModelUse>,
1451        multiplicity: ProjectedMultiplicity,
1452    ) -> Result<Self, Diagnostic> {
1453        ensure_collection_limit(players.len(), "too_many_projected_role_players")?;
1454        Ok(Self {
1455            role,
1456            players,
1457            multiplicity,
1458        })
1459    }
1460    /// Return the canonical role identity.
1461    #[must_use]
1462    pub const fn role(&self) -> &RoleId {
1463        &self.role
1464    }
1465    /// Return exact accepted player forms.
1466    #[must_use]
1467    pub const fn players(&self) -> &BTreeSet<ProjectedModelUse> {
1468        &self.players
1469    }
1470    /// Return input requiredness/container shape.
1471    #[must_use]
1472    pub const fn multiplicity(&self) -> ProjectedMultiplicity {
1473        self.multiplicity
1474    }
1475}
1476
1477/// The exact generated constructor facet.
1478#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1479pub struct CreateProjection {
1480    #[serde(skip_serializing_if = "Option::is_none")]
1481    target_name: Option<TargetIdentifier>,
1482    enabled: bool,
1483    fields: Vec<CreateFieldProjection>,
1484    #[serde(serialize_with = "serialize_map_values")]
1485    roles: BTreeMap<RoleId, CreateRoleProjection>,
1486}
1487
1488impl CreateProjection {
1489    /// Construct one exact constructor shape.
1490    pub fn new(
1491        enabled: bool,
1492        fields: Vec<CreateFieldProjection>,
1493        roles: BTreeMap<RoleId, CreateRoleProjection>,
1494    ) -> Result<Self, Diagnostic> {
1495        ensure_collection_limit(fields.len(), "too_many_projected_create_fields")?;
1496        ensure_collection_limit(roles.len(), "too_many_projected_create_roles")?;
1497        Ok(Self {
1498            target_name: None,
1499            enabled,
1500            fields,
1501            roles,
1502        })
1503    }
1504    /// Attach the explicit generated create-input type name.
1505    #[must_use]
1506    pub fn with_target_name(mut self, target_name: TargetIdentifier) -> Self {
1507        self.target_name = Some(target_name);
1508        self
1509    }
1510    /// Return the generated create-input type name, when construction is exposed.
1511    #[must_use]
1512    pub const fn target_name(&self) -> Option<&TargetIdentifier> {
1513        self.target_name.as_ref()
1514    }
1515    /// Report whether generated construction is available.
1516    #[must_use]
1517    pub const fn enabled(&self) -> bool {
1518        self.enabled
1519    }
1520    /// Return owned-attribute inputs in semantic order.
1521    #[must_use]
1522    pub fn fields(&self) -> &[CreateFieldProjection] {
1523        &self.fields
1524    }
1525    /// Return active role inputs.
1526    #[must_use]
1527    pub const fn roles(&self) -> &BTreeMap<RoleId, CreateRoleProjection> {
1528        &self.roles
1529    }
1530}
1531
1532/// One owned-attribute field in a complete read facet.
1533#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1534pub struct ReadFieldProjection {
1535    token: OwnsFactId,
1536    value: ProjectedTypeRef,
1537    multiplicity: ProjectedMultiplicity,
1538}
1539
1540impl ReadFieldProjection {
1541    /// Construct one complete-read field.
1542    #[must_use]
1543    pub const fn new(
1544        token: OwnsFactId,
1545        value: ProjectedTypeRef,
1546        multiplicity: ProjectedMultiplicity,
1547    ) -> Self {
1548        Self {
1549            token,
1550            value,
1551            multiplicity,
1552        }
1553    }
1554    /// Return the field token identity.
1555    #[must_use]
1556    pub const fn token(&self) -> &OwnsFactId {
1557        &self.token
1558    }
1559    /// Return the read value type.
1560    #[must_use]
1561    pub const fn value(&self) -> &ProjectedTypeRef {
1562        &self.value
1563    }
1564    /// Return read container shape.
1565    #[must_use]
1566    pub const fn multiplicity(&self) -> ProjectedMultiplicity {
1567        self.multiplicity
1568    }
1569}
1570
1571/// One active role field in a complete read facet.
1572#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1573pub struct ReadRoleProjection {
1574    role: RoleId,
1575    players: BTreeSet<ProjectedModelUse>,
1576    multiplicity: ProjectedMultiplicity,
1577}
1578
1579impl ReadRoleProjection {
1580    /// Construct one complete-read role field.
1581    pub fn new(
1582        role: RoleId,
1583        players: BTreeSet<ProjectedModelUse>,
1584        multiplicity: ProjectedMultiplicity,
1585    ) -> Result<Self, Diagnostic> {
1586        ensure_collection_limit(players.len(), "too_many_projected_role_players")?;
1587        Ok(Self {
1588            role,
1589            players,
1590            multiplicity,
1591        })
1592    }
1593    /// Return the canonical role identity.
1594    #[must_use]
1595    pub const fn role(&self) -> &RoleId {
1596        &self.role
1597    }
1598    /// Return exact read player forms.
1599    #[must_use]
1600    pub const fn players(&self) -> &BTreeSet<ProjectedModelUse> {
1601        &self.players
1602    }
1603    /// Return read container shape.
1604    #[must_use]
1605    pub const fn multiplicity(&self) -> ProjectedMultiplicity {
1606        self.multiplicity
1607    }
1608}
1609
1610/// The complete materialized read facet.
1611#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1612pub struct CompleteReadProjection {
1613    fields: Vec<ReadFieldProjection>,
1614    #[serde(serialize_with = "serialize_map_values")]
1615    roles: BTreeMap<RoleId, ReadRoleProjection>,
1616    nominal_upcasts: Vec<TypeId>,
1617    #[serde(serialize_with = "serialize_role_upcasts")]
1618    role_upcasts: BTreeMap<RoleId, Vec<RoleId>>,
1619}
1620
1621impl CompleteReadProjection {
1622    /// Construct one complete-read shape.
1623    pub fn new(
1624        fields: Vec<ReadFieldProjection>,
1625        roles: BTreeMap<RoleId, ReadRoleProjection>,
1626        nominal_upcasts: Vec<TypeId>,
1627    ) -> Result<Self, Diagnostic> {
1628        for length in [fields.len(), roles.len(), nominal_upcasts.len()] {
1629            ensure_collection_limit(length, "projection_read_limit_exceeded")?;
1630        }
1631        Ok(Self {
1632            fields,
1633            roles,
1634            nominal_upcasts,
1635            role_upcasts: BTreeMap::new(),
1636        })
1637    }
1638    /// Attach active-child-role to ordered ancestor-role read mappings.
1639    pub fn with_role_upcasts(
1640        mut self,
1641        role_upcasts: BTreeMap<RoleId, Vec<RoleId>>,
1642    ) -> Result<Self, Diagnostic> {
1643        if role_upcasts.iter().any(|(role, ancestors)| {
1644            !self.roles.contains_key(role)
1645                || ancestors.is_empty()
1646                || ancestors.iter().collect::<BTreeSet<_>>().len() != ancestors.len()
1647        }) {
1648            return Err(invalid_projection(
1649                "invalid_projected_role_upcast",
1650                "role upcasts require an active role and unique non-empty ancestor roles",
1651            ));
1652        }
1653        ensure_collection_limit(role_upcasts.len(), "projection_read_limit_exceeded")?;
1654        self.role_upcasts = role_upcasts;
1655        Ok(self)
1656    }
1657    /// Return owned-attribute fields in semantic order.
1658    #[must_use]
1659    pub fn fields(&self) -> &[ReadFieldProjection] {
1660        &self.fields
1661    }
1662    /// Return active role fields.
1663    #[must_use]
1664    pub const fn roles(&self) -> &BTreeMap<RoleId, ReadRoleProjection> {
1665        &self.roles
1666    }
1667    /// Return legal nominal upcast model identities, nearest first.
1668    #[must_use]
1669    pub fn nominal_upcasts(&self) -> &[TypeId] {
1670        &self.nominal_upcasts
1671    }
1672    /// Return specialized child-role to nearest-first ancestor-role mappings.
1673    #[must_use]
1674    pub const fn role_upcasts(&self) -> &BTreeMap<RoleId, Vec<RoleId>> {
1675        &self.role_upcasts
1676    }
1677}
1678
1679/// How a binding may construct a nonrecursive reference value.
1680#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
1681#[serde(rename_all = "snake_case")]
1682pub enum ReferenceConstructionPolicy {
1683    /// Only an engine IID may construct a reference in the initial runtime contract.
1684    IidOnly,
1685    /// Reference construction admits typed key fallback.
1686    KeyFallback,
1687}
1688
1689/// The nonrecursive identity/reference read facet.
1690#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1691pub struct ReferenceReadProjection {
1692    target_name: Option<TargetIdentifier>,
1693    key_fields: Vec<OwnsFactId>,
1694    construction_policy: ReferenceConstructionPolicy,
1695}
1696
1697impl ReferenceReadProjection {
1698    /// Construct a reference shape; attributes deliberately have no reference class.
1699    pub fn new(
1700        target_name: Option<TargetIdentifier>,
1701        key_fields: Vec<OwnsFactId>,
1702    ) -> Result<Self, Diagnostic> {
1703        ensure_collection_limit(key_fields.len(), "too_many_projected_reference_keys")?;
1704        let mut unique = BTreeSet::new();
1705        if key_fields.iter().any(|id| !unique.insert(id)) {
1706            return Err(invalid_projection(
1707                "duplicate_projected_reference_key",
1708                "reference key identities must be unique",
1709            ));
1710        }
1711        let construction_policy = if key_fields.is_empty() {
1712            ReferenceConstructionPolicy::IidOnly
1713        } else {
1714            ReferenceConstructionPolicy::KeyFallback
1715        };
1716        Ok(Self {
1717            target_name,
1718            key_fields,
1719            construction_policy,
1720        })
1721    }
1722    /// Return the generated reference type name, if supported.
1723    #[must_use]
1724    pub const fn target_name(&self) -> Option<&TargetIdentifier> {
1725        self.target_name.as_ref()
1726    }
1727    /// Return effective key fields in semantic order.
1728    #[must_use]
1729    pub fn key_fields(&self) -> &[OwnsFactId] {
1730        &self.key_fields
1731    }
1732    /// Return the explicit checked reference-construction policy.
1733    #[must_use]
1734    pub const fn construction_policy(&self) -> ReferenceConstructionPolicy {
1735        self.construction_policy
1736    }
1737}
1738
1739/// Schema-only query tokens for one projected model.
1740#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1741pub struct QueryTokenProjection {
1742    type_id: TypeId,
1743    #[serde(skip_serializing_if = "Option::is_none")]
1744    target_name: Option<TargetIdentifier>,
1745    #[serde(serialize_with = "serialize_map_values")]
1746    fields: BTreeMap<OwnsFactId, FieldTokenProjection>,
1747    #[serde(serialize_with = "serialize_map_values")]
1748    roles: BTreeMap<RoleId, RoleTokenProjection>,
1749}
1750
1751impl QueryTokenProjection {
1752    /// Construct schema-only tokens without query-plan or invocation state.
1753    pub fn new(
1754        type_id: TypeId,
1755        fields: BTreeMap<OwnsFactId, FieldTokenProjection>,
1756        roles: BTreeMap<RoleId, RoleTokenProjection>,
1757    ) -> Result<Self, Diagnostic> {
1758        ensure_collection_limit(fields.len(), "too_many_projected_field_tokens")?;
1759        ensure_collection_limit(roles.len(), "too_many_projected_role_tokens")?;
1760        Ok(Self {
1761            type_id,
1762            target_name: None,
1763            fields,
1764            roles,
1765        })
1766    }
1767    /// Attach the explicit nominal type/query-token name.
1768    #[must_use]
1769    pub fn with_target_name(mut self, target_name: TargetIdentifier) -> Self {
1770        self.target_name = Some(target_name);
1771        self
1772    }
1773    /// Return the model type token.
1774    #[must_use]
1775    pub const fn type_id(&self) -> &TypeId {
1776        &self.type_id
1777    }
1778    /// Return the explicit nominal type/query-token name, when the target uses one.
1779    #[must_use]
1780    pub const fn target_name(&self) -> Option<&TargetIdentifier> {
1781        self.target_name.as_ref()
1782    }
1783    /// Return owner-branded owned-attribute tokens.
1784    #[must_use]
1785    pub const fn fields(&self) -> &BTreeMap<OwnsFactId, FieldTokenProjection> {
1786        &self.fields
1787    }
1788    /// Return owner-branded role tokens.
1789    #[must_use]
1790    pub const fn roles(&self) -> &BTreeMap<RoleId, RoleTokenProjection> {
1791        &self.roles
1792    }
1793}
1794
1795/// All five runtime facets for one resolved schema type.
1796#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1797pub struct ModelProjection {
1798    id: TypeId,
1799    target_name: TargetIdentifier,
1800    declaration: DeclarationProjection,
1801    create: CreateProjection,
1802    complete_read: CompleteReadProjection,
1803    reference_read: ReferenceReadProjection,
1804    query_tokens: QueryTokenProjection,
1805}
1806
1807impl ModelProjection {
1808    /// Construct and cross-check all five facets for one model.
1809    #[allow(clippy::too_many_arguments)]
1810    pub fn new(
1811        id: TypeId,
1812        target_name: TargetIdentifier,
1813        declaration: DeclarationProjection,
1814        create: CreateProjection,
1815        complete_read: CompleteReadProjection,
1816        reference_read: ReferenceReadProjection,
1817        query_tokens: QueryTokenProjection,
1818    ) -> Result<Self, Diagnostic> {
1819        let exact_required_scalar = |multiplicity: ProjectedMultiplicity| {
1820            multiplicity.required()
1821                && multiplicity.container() == ProjectedContainer::Scalar
1822                && multiplicity.cardinality().min() == 1
1823                && multiplicity.cardinality().max() == Some(1)
1824        };
1825        let reference_keys_valid = reference_read.key_fields().iter().all(|key| {
1826            let Some(token) = query_tokens.fields().get(key) else {
1827                return false;
1828            };
1829            if !token.is_key() || !exact_required_scalar(token.multiplicity()) {
1830                return false;
1831            }
1832            let mut complete = complete_read
1833                .fields()
1834                .iter()
1835                .filter(|field| field.token() == key);
1836            let Some(field) = complete.next() else {
1837                return false;
1838            };
1839            if complete.next().is_some() || !exact_required_scalar(field.multiplicity()) {
1840                return false;
1841            }
1842            matches!(
1843                field.value(),
1844                ProjectedTypeRef::Model(value)
1845                    if value.form() == ProjectedModelForm::Complete
1846                        && value.id().kind() == TypeKind::Attribute
1847                        && value.id().label() == key.attribute().label()
1848            )
1849        });
1850        if (!reference_read.key_fields().is_empty() && reference_read.target_name().is_none())
1851            || !reference_keys_valid
1852        {
1853            return Err(invalid_projection(
1854                "invalid_projected_reference_key",
1855                "reference keys require exact required-scalar complete/query key facets",
1856            ));
1857        }
1858        if query_tokens.type_id() != &id
1859            || match (declaration.parent(), declaration.direct_sub()) {
1860                (None, None) => false,
1861                (Some(parent), Some(sub)) => {
1862                    sub.id().subtype() != &id || sub.id().supertype() != parent
1863                }
1864                (None, Some(_)) | (Some(_), None) => true,
1865            }
1866            || query_tokens
1867                .fields()
1868                .values()
1869                .any(|field| field.id().owner() != &id)
1870            || query_tokens
1871                .roles()
1872                .values()
1873                .any(|role| role.owner() != &id)
1874            || create
1875                .fields()
1876                .iter()
1877                .any(|field| !query_tokens.fields().contains_key(field.token()))
1878            || complete_read
1879                .fields()
1880                .iter()
1881                .any(|field| !query_tokens.fields().contains_key(field.token()))
1882            || create
1883                .roles()
1884                .iter()
1885                .any(|(id, role)| id != role.role() || !query_tokens.roles().contains_key(id))
1886            || complete_read
1887                .roles()
1888                .iter()
1889                .any(|(id, role)| id != role.role() || !query_tokens.roles().contains_key(id))
1890        {
1891            return Err(invalid_projection(
1892                "invalid_model_projection_reference",
1893                "model facets contain a mismatched owner or token reference",
1894            ));
1895        }
1896        Ok(Self {
1897            id,
1898            target_name,
1899            declaration,
1900            create,
1901            complete_read,
1902            reference_read,
1903            query_tokens,
1904        })
1905    }
1906    /// Return the schema model identity.
1907    #[must_use]
1908    pub const fn id(&self) -> &TypeId {
1909        &self.id
1910    }
1911    /// Return the emitted nominal name.
1912    #[must_use]
1913    pub const fn target_name(&self) -> &TargetIdentifier {
1914        &self.target_name
1915    }
1916    /// Return the nominal declaration facet.
1917    #[must_use]
1918    pub const fn declaration(&self) -> &DeclarationProjection {
1919        &self.declaration
1920    }
1921    /// Return the create facet.
1922    #[must_use]
1923    pub const fn create(&self) -> &CreateProjection {
1924        &self.create
1925    }
1926    /// Return the complete-read facet.
1927    #[must_use]
1928    pub const fn complete_read(&self) -> &CompleteReadProjection {
1929        &self.complete_read
1930    }
1931    /// Return the reference-read facet.
1932    #[must_use]
1933    pub const fn reference_read(&self) -> &ReferenceReadProjection {
1934        &self.reference_read
1935    }
1936    /// Return schema-only query tokens.
1937    #[must_use]
1938    pub const fn query_tokens(&self) -> &QueryTokenProjection {
1939        &self.query_tokens
1940    }
1941}
1942
1943/// One ordered struct field and its emitted identifier.
1944#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1945pub struct StructFieldProjection {
1946    name: Label,
1947    target_name: TargetIdentifier,
1948    value_type: ValueTypeTag,
1949    optional: bool,
1950}
1951
1952impl StructFieldProjection {
1953    /// Construct one struct value field.
1954    #[must_use]
1955    pub const fn new(
1956        name: Label,
1957        target_name: TargetIdentifier,
1958        value_type: ValueTypeTag,
1959        optional: bool,
1960    ) -> Self {
1961        Self {
1962            name,
1963            target_name,
1964            value_type,
1965            optional,
1966        }
1967    }
1968    /// Return the schema field name.
1969    #[must_use]
1970    pub const fn name(&self) -> &Label {
1971        &self.name
1972    }
1973    /// Return the emitted field name.
1974    #[must_use]
1975    pub const fn target_name(&self) -> &TargetIdentifier {
1976        &self.target_name
1977    }
1978    /// Return the scalar value domain.
1979    #[must_use]
1980    pub const fn value_type(&self) -> ValueTypeTag {
1981        self.value_type
1982    }
1983    /// Report optionality.
1984    #[must_use]
1985    pub const fn optional(&self) -> bool {
1986        self.optional
1987    }
1988}
1989
1990/// One projected schema struct value type.
1991#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1992pub struct StructProjection {
1993    id: StructId,
1994    target_name: TargetIdentifier,
1995    fields: Vec<StructFieldProjection>,
1996}
1997
1998impl StructProjection {
1999    /// Construct one ordered struct projection.
2000    pub fn new(
2001        id: StructId,
2002        target_name: TargetIdentifier,
2003        fields: Vec<StructFieldProjection>,
2004    ) -> Result<Self, Diagnostic> {
2005        ensure_collection_limit(fields.len(), "too_many_projected_struct_fields")?;
2006        Ok(Self {
2007            id,
2008            target_name,
2009            fields,
2010        })
2011    }
2012    /// Return the struct identity.
2013    #[must_use]
2014    pub const fn id(&self) -> &StructId {
2015        &self.id
2016    }
2017    /// Return the emitted value-type name.
2018    #[must_use]
2019    pub const fn target_name(&self) -> &TargetIdentifier {
2020        &self.target_name
2021    }
2022    /// Return fields in semantic declaration order.
2023    #[must_use]
2024    pub fn fields(&self) -> &[StructFieldProjection] {
2025        &self.fields
2026    }
2027}
2028
2029/// One ordered projected function parameter.
2030#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
2031pub struct FunctionParameterProjection {
2032    name: Label,
2033    target_name: TargetIdentifier,
2034    type_ref: ProjectedTypeRef,
2035}
2036
2037impl FunctionParameterProjection {
2038    /// Construct one typed function parameter.
2039    #[must_use]
2040    pub const fn new(
2041        name: Label,
2042        target_name: TargetIdentifier,
2043        type_ref: ProjectedTypeRef,
2044    ) -> Self {
2045        Self {
2046            name,
2047            target_name,
2048            type_ref,
2049        }
2050    }
2051    /// Return the schema parameter name.
2052    #[must_use]
2053    pub const fn name(&self) -> &Label {
2054        &self.name
2055    }
2056    /// Return the emitted parameter name.
2057    #[must_use]
2058    pub const fn target_name(&self) -> &TargetIdentifier {
2059        &self.target_name
2060    }
2061    /// Return the exact resolved parameter type.
2062    #[must_use]
2063    pub const fn type_ref(&self) -> &ProjectedTypeRef {
2064        &self.type_ref
2065    }
2066}
2067
2068/// One projected function return element.
2069#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
2070pub struct FunctionReturnElementProjection {
2071    type_ref: ProjectedTypeRef,
2072    optional: bool,
2073}
2074
2075impl FunctionReturnElementProjection {
2076    /// Construct one return element.
2077    #[must_use]
2078    pub const fn new(type_ref: ProjectedTypeRef, optional: bool) -> Self {
2079        Self { type_ref, optional }
2080    }
2081    /// Return the exact resolved result type.
2082    #[must_use]
2083    pub const fn type_ref(&self) -> &ProjectedTypeRef {
2084        &self.type_ref
2085    }
2086    /// Report optionality.
2087    #[must_use]
2088    pub const fn optional(&self) -> bool {
2089        self.optional
2090    }
2091}
2092
2093/// Projected scalar, tuple, or stream function return shape.
2094#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
2095#[serde(tag = "kind", content = "elements", rename_all = "snake_case")]
2096pub enum FunctionReturnProjection {
2097    /// One scalar result element.
2098    Scalar(FunctionReturnElementProjection),
2099    /// Two or more ordered tuple elements.
2100    Tuple(Vec<FunctionReturnElementProjection>),
2101    /// One or more ordered stream-row elements.
2102    Stream(Vec<FunctionReturnElementProjection>),
2103}
2104
2105/// A schema-only typed function token/reference.
2106#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
2107pub struct FunctionProjection {
2108    id: FunctionId,
2109    target_name: TargetIdentifier,
2110    parameters: Vec<FunctionParameterProjection>,
2111    returns: FunctionReturnProjection,
2112    #[serde(serialize_with = "serialize_map_values")]
2113    annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
2114}
2115
2116impl FunctionProjection {
2117    /// Construct a function token without translating its body.
2118    pub fn new(
2119        id: FunctionId,
2120        target_name: TargetIdentifier,
2121        parameters: Vec<FunctionParameterProjection>,
2122        returns: FunctionReturnProjection,
2123    ) -> Result<Self, Diagnostic> {
2124        ensure_collection_limit(parameters.len(), "too_many_projected_function_parameters")?;
2125        Ok(Self {
2126            id,
2127            target_name,
2128            parameters,
2129            returns,
2130            annotations: BTreeMap::new(),
2131        })
2132    }
2133    /// Attach effective function documentation and metadata.
2134    pub fn with_annotations(
2135        mut self,
2136        annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
2137    ) -> Result<Self, Diagnostic> {
2138        if annotations.iter().any(|(key, value)| {
2139            key != value.id() || key.subject() != &AnnotationSubjectId::Function(self.id.clone())
2140        }) {
2141            return Err(invalid_projection(
2142                "invalid_projected_function_annotation",
2143                "function annotations require the projected function subject",
2144            ));
2145        }
2146        ensure_collection_limit(annotations.len(), "too_many_projected_annotations")?;
2147        self.annotations = annotations;
2148        Ok(self)
2149    }
2150    /// Return the function identity.
2151    #[must_use]
2152    pub const fn id(&self) -> &FunctionId {
2153        &self.id
2154    }
2155    /// Return the emitted function-token name.
2156    #[must_use]
2157    pub const fn target_name(&self) -> &TargetIdentifier {
2158        &self.target_name
2159    }
2160    /// Return parameters in signature order.
2161    #[must_use]
2162    pub fn parameters(&self) -> &[FunctionParameterProjection] {
2163        &self.parameters
2164    }
2165    /// Return the native return shape.
2166    #[must_use]
2167    pub const fn returns(&self) -> &FunctionReturnProjection {
2168        &self.returns
2169    }
2170    /// Return function documentation and metadata.
2171    #[must_use]
2172    pub const fn annotations(&self) -> &BTreeMap<AnnotationFactId, ProjectedAnnotation> {
2173        &self.annotations
2174    }
2175}
2176
2177/// Effective per-player metadata keyed independently from shared role tokens.
2178#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
2179pub struct PlayingProjection {
2180    id: PlaysFactId,
2181    role: RoleId,
2182    target_name: Option<TargetIdentifier>,
2183    multiplicity: ProjectedMultiplicity,
2184    #[serde(serialize_with = "serialize_map_values")]
2185    annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
2186}
2187
2188impl PlayingProjection {
2189    /// Construct metadata for one exact effective playing edge.
2190    pub fn new(
2191        id: PlaysFactId,
2192        role: RoleId,
2193        multiplicity: ProjectedMultiplicity,
2194        annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
2195    ) -> Result<Self, Diagnostic> {
2196        if id.role() != &role
2197            || annotations.iter().any(|(key, value)| {
2198                key != value.id() || key.subject() != &AnnotationSubjectId::Plays(id.clone())
2199            })
2200        {
2201            return Err(invalid_projection(
2202                "invalid_playing_projection_reference",
2203                "playing metadata has a mismatched role or annotation subject",
2204            ));
2205        }
2206        ensure_collection_limit(annotations.len(), "too_many_projected_annotations")?;
2207        Ok(Self {
2208            id,
2209            role,
2210            target_name: None,
2211            multiplicity,
2212            annotations,
2213        })
2214    }
2215    /// Attach the owner-branded emitted plays-token name.
2216    #[must_use]
2217    pub fn with_target_name(mut self, target_name: TargetIdentifier) -> Self {
2218        self.target_name = Some(target_name);
2219        self
2220    }
2221    /// Return the exact playing identity.
2222    #[must_use]
2223    pub const fn id(&self) -> &PlaysFactId {
2224        &self.id
2225    }
2226    /// Return the shared canonical role identity.
2227    #[must_use]
2228    pub const fn role(&self) -> &RoleId {
2229        &self.role
2230    }
2231    /// Return the emitted owner-branded plays-token name when projected.
2232    #[must_use]
2233    pub const fn target_name(&self) -> Option<&TargetIdentifier> {
2234        self.target_name.as_ref()
2235    }
2236    /// Return the player-edge cardinality metadata.
2237    #[must_use]
2238    pub const fn multiplicity(&self) -> ProjectedMultiplicity {
2239        self.multiplicity
2240    }
2241    /// Return effective per-edge annotations.
2242    #[must_use]
2243    pub const fn annotations(&self) -> &BTreeMap<AnnotationFactId, ProjectedAnnotation> {
2244        &self.annotations
2245    }
2246}
2247
2248/// Deterministic shells-first and SCC-link emission schedule.
2249#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
2250pub struct EmissionPlan {
2251    model_shells: Vec<TypeId>,
2252    model_link_components: Vec<BTreeSet<TypeId>>,
2253    structs: Vec<StructId>,
2254    functions: Vec<FunctionId>,
2255}
2256
2257impl EmissionPlan {
2258    /// Construct a deterministic two-phase emission schedule.
2259    pub fn new(
2260        model_shells: Vec<TypeId>,
2261        model_link_components: Vec<BTreeSet<TypeId>>,
2262        structs: Vec<StructId>,
2263        functions: Vec<FunctionId>,
2264    ) -> Result<Self, Diagnostic> {
2265        for length in [
2266            model_shells.len(),
2267            model_link_components.len(),
2268            structs.len(),
2269            functions.len(),
2270        ] {
2271            ensure_collection_limit(length, "projection_emission_limit_exceeded")?;
2272        }
2273        Ok(Self {
2274            model_shells,
2275            model_link_components,
2276            structs,
2277            functions,
2278        })
2279    }
2280    /// Return parent-first nominal model shell order.
2281    #[must_use]
2282    pub fn model_shells(&self) -> &[TypeId] {
2283        &self.model_shells
2284    }
2285    /// Return dependency-first SCC link components.
2286    #[must_use]
2287    pub fn model_link_components(&self) -> &[BTreeSet<TypeId>] {
2288        &self.model_link_components
2289    }
2290    /// Return stable struct emission order.
2291    #[must_use]
2292    pub fn structs(&self) -> &[StructId] {
2293        &self.structs
2294    }
2295    /// Return stable function-token emission order.
2296    #[must_use]
2297    pub fn functions(&self) -> &[FunctionId] {
2298        &self.functions
2299    }
2300}
2301
2302/// A validated target-specific runtime projection derived from resolved semantics.
2303#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
2304pub struct RuntimeProjection {
2305    target: BindingTarget,
2306    config: ProjectionConfig,
2307    semantic_fingerprint: SemanticSchemaFingerprint,
2308    projection_fingerprint: BindingProjectionFingerprint,
2309    generator_handlers: Vec<ProjectionHandler>,
2310    code_resources: Vec<CodeResourceDigest>,
2311    #[serde(serialize_with = "serialize_map_values")]
2312    models: BTreeMap<TypeId, ModelProjection>,
2313    #[serde(serialize_with = "serialize_map_values")]
2314    structs: BTreeMap<StructId, StructProjection>,
2315    #[serde(serialize_with = "serialize_map_values")]
2316    functions: BTreeMap<FunctionId, FunctionProjection>,
2317    #[serde(serialize_with = "serialize_map_values")]
2318    playing_facts: BTreeMap<PlaysFactId, PlayingProjection>,
2319    emission: EmissionPlan,
2320}
2321
2322#[derive(Serialize)]
2323struct RuntimeProjectionContentView<'a> {
2324    #[serde(serialize_with = "serialize_map_values")]
2325    models: &'a BTreeMap<TypeId, ModelProjection>,
2326    #[serde(serialize_with = "serialize_map_values")]
2327    structs: &'a BTreeMap<StructId, StructProjection>,
2328    #[serde(serialize_with = "serialize_map_values")]
2329    functions: &'a BTreeMap<FunctionId, FunctionProjection>,
2330    #[serde(serialize_with = "serialize_map_values")]
2331    playing_facts: &'a BTreeMap<PlaysFactId, PlayingProjection>,
2332    emission: &'a EmissionPlan,
2333}
2334
2335impl RuntimeProjection {
2336    /// Validate a complete projection graph and compute its content-bound fingerprint.
2337    #[allow(clippy::too_many_arguments)]
2338    pub fn try_new(
2339        target: BindingTarget,
2340        config: ProjectionConfig,
2341        semantic_fingerprint: SemanticSchemaFingerprint,
2342        handlers: &[ProjectionHandler],
2343        resources: &[CodeResourceDigest],
2344        models: BTreeMap<TypeId, ModelProjection>,
2345        structs: BTreeMap<StructId, StructProjection>,
2346        functions: BTreeMap<FunctionId, FunctionProjection>,
2347        playing_facts: BTreeMap<PlaysFactId, PlayingProjection>,
2348        emission: EmissionPlan,
2349    ) -> Result<Self, Diagnostic> {
2350        if config.target() != target
2351            || models.iter().any(|(key, value)| key != value.id())
2352            || structs.iter().any(|(key, value)| key != value.id())
2353            || functions.iter().any(|(key, value)| key != value.id())
2354            || playing_facts.iter().any(|(key, value)| key != value.id())
2355        {
2356            return Err(invalid_projection(
2357                "invalid_runtime_projection_map",
2358                "runtime projection map keys or target configuration are inconsistent",
2359            ));
2360        }
2361        for length in [
2362            models.len(),
2363            structs.len(),
2364            functions.len(),
2365            playing_facts.len(),
2366        ] {
2367            ensure_collection_limit(length, "runtime_projection_limit_exceeded")?;
2368        }
2369        if target == BindingTarget::Rust {
2370            let rust_names_complete = models.values().all(|model| {
2371                model.create().enabled() == model.create().target_name().is_some()
2372                    && model.query_tokens().target_name().is_some()
2373                    && model
2374                        .query_tokens()
2375                        .roles()
2376                        .values()
2377                        .all(|role| role.player_union_target_name().is_some())
2378                    && matches!(model.id().kind(), TypeKind::Entity | TypeKind::Relation)
2379                        == model.reference_read().target_name().is_some()
2380            }) && playing_facts
2381                .values()
2382                .all(|playing| playing.target_name().is_some());
2383            if !rust_names_complete {
2384                return Err(invalid_projection(
2385                    "missing_rust_projection_identifier",
2386                    "Rust projection omits a required create, reference, query-token, player-union, or plays identifier",
2387                ));
2388            }
2389        }
2390        let model_ids = models.keys().cloned().collect::<BTreeSet<_>>();
2391        if emission
2392            .model_shells()
2393            .iter()
2394            .cloned()
2395            .collect::<BTreeSet<_>>()
2396            != model_ids
2397            || emission.model_shells().len() != model_ids.len()
2398            || emission
2399                .model_link_components()
2400                .iter()
2401                .flat_map(BTreeSet::iter)
2402                .cloned()
2403                .collect::<BTreeSet<_>>()
2404                != model_ids
2405            || emission
2406                .model_link_components()
2407                .iter()
2408                .map(BTreeSet::len)
2409                .sum::<usize>()
2410                != model_ids.len()
2411            || emission.structs() != structs.keys().cloned().collect::<Vec<_>>()
2412            || emission.functions() != functions.keys().cloned().collect::<Vec<_>>()
2413        {
2414            return Err(invalid_projection(
2415                "invalid_projection_emission_plan",
2416                "emission plan does not cover each projected value exactly once",
2417            ));
2418        }
2419        let all_model_refs_valid = models.values().all(|model| {
2420            model.query_tokens().roles().values().all(|role| {
2421                role.accepted_players()
2422                    .iter()
2423                    .all(|id| models.contains_key(id))
2424            }) && model.create().roles().values().all(|role| {
2425                role.players()
2426                    .iter()
2427                    .all(|value| models.contains_key(value.id()))
2428            }) && model.complete_read().roles().values().all(|role| {
2429                role.players()
2430                    .iter()
2431                    .all(|value| models.contains_key(value.id()))
2432            })
2433        });
2434        if !all_model_refs_valid {
2435            return Err(invalid_projection(
2436                "invalid_projection_reference",
2437                "projection references a model that is not present",
2438            ));
2439        }
2440        let declaring_owners_valid = models.values().all(|model| {
2441            model.query_tokens().fields().values().all(|token| {
2442                let declaring_owner = token.declaring_id().owner();
2443                let mut curr = Some(model.id());
2444                let mut found = false;
2445                let mut visited = BTreeSet::new();
2446                while let Some(curr_id) = curr {
2447                    if !visited.insert(curr_id) {
2448                        return false;
2449                    }
2450                    if curr_id == declaring_owner {
2451                        found = true;
2452                        break;
2453                    }
2454                    curr = models.get(curr_id).and_then(|m| m.declaration().parent());
2455                }
2456                found
2457            })
2458        });
2459        if !declaring_owners_valid {
2460            return Err(invalid_projection(
2461                "invalid_projection_reference",
2462                "field token declaring owner is not the effective owner or a valid ancestor",
2463            ));
2464        }
2465        let model_use_is_valid = |value: &ProjectedModelUse| {
2466            models.get(value.id()).is_some_and(|model| {
2467                value.form() != ProjectedModelForm::Reference
2468                    || model.reference_read().target_name().is_some()
2469            })
2470        };
2471        let type_ref_is_valid = |value: &ProjectedTypeRef| match value {
2472            ProjectedTypeRef::Scalar(_) => true,
2473            ProjectedTypeRef::Model(value) => model_use_is_valid(value),
2474            ProjectedTypeRef::Struct(id) => structs.contains_key(id),
2475        };
2476        let role_exists = |role: &RoleId| {
2477            models.values().any(|model| {
2478                model.id().kind() == TypeKind::Relation
2479                    && model.id().label() == role.declaring_relation()
2480                    && model.query_tokens().roles().contains_key(role)
2481            })
2482        };
2483        let shell_positions = emission
2484            .model_shells()
2485            .iter()
2486            .enumerate()
2487            .map(|(index, id)| (id.clone(), index))
2488            .collect::<BTreeMap<_, _>>();
2489        let closed_models = models.values().all(|model| {
2490            let declaration = model.declaration();
2491            let parent_is_valid = declaration.parent().is_none_or(|parent| {
2492                models.contains_key(parent) && shell_positions[parent] < shell_positions[model.id()]
2493            });
2494            let direct_sub_is_valid = match (declaration.parent(), declaration.direct_sub()) {
2495                (None, None) => true,
2496                (Some(parent), Some(sub)) => {
2497                    sub.id().subtype() == model.id() && sub.id().supertype() == parent
2498                }
2499                (None, Some(_)) | (Some(_), None) => false,
2500            };
2501            let direct_fields_are_valid = declaration
2502                .direct_fields()
2503                .iter()
2504                .all(|id| model.query_tokens().fields().contains_key(id));
2505            let direct_roles_are_valid = declaration.direct_roles().iter().all(|(id, role)| {
2506                id == role.role()
2507                    && model.query_tokens().roles().contains_key(id)
2508                    && role.specializes().is_none_or(&role_exists)
2509            });
2510            let direct_plays_are_valid = declaration
2511                .direct_plays()
2512                .iter()
2513                .all(|id| id.player() == model.id() && playing_facts.contains_key(id));
2514            let fields_are_valid = model.query_tokens().fields().values().all(|field| {
2515                models.keys().any(|id| {
2516                    id.kind() == TypeKind::Attribute && id.label() == field.id().attribute().label()
2517                })
2518            }) && model
2519                .create()
2520                .fields()
2521                .iter()
2522                .all(|field| type_ref_is_valid(field.value()))
2523                && model
2524                    .complete_read()
2525                    .fields()
2526                    .iter()
2527                    .all(|field| type_ref_is_valid(field.value()));
2528            let roles_are_valid = model
2529                .query_tokens()
2530                .roles()
2531                .values()
2532                .all(|role| role.specializes().is_none_or(&role_exists))
2533                && model
2534                    .create()
2535                    .roles()
2536                    .values()
2537                    .all(|role| role.players().iter().all(&model_use_is_valid))
2538                && model
2539                    .complete_read()
2540                    .roles()
2541                    .values()
2542                    .all(|role| role.players().iter().all(&model_use_is_valid));
2543            let role_upcasts_are_valid =
2544                model
2545                    .complete_read()
2546                    .role_upcasts()
2547                    .iter()
2548                    .all(|(active, ancestors)| {
2549                        model.complete_read().roles().contains_key(active)
2550                            && ancestors.iter().all(&role_exists)
2551                    });
2552            let references_are_valid = model.reference_read().key_fields().iter().all(|id| {
2553                model
2554                    .query_tokens()
2555                    .fields()
2556                    .get(id)
2557                    .is_some_and(FieldTokenProjection::is_key)
2558            });
2559            let value_subject = model.id().kind() != TypeKind::Attribute
2560                || model.declaration().value_annotations().keys().all(|id| {
2561                    id.subject()
2562                        == &AnnotationSubjectId::Value(ValueFactId::new(
2563                            AttributeId::new(model.id().label().as_str())
2564                                .expect("projected attribute label is valid"),
2565                        ))
2566                });
2567            parent_is_valid
2568                && direct_sub_is_valid
2569                && direct_fields_are_valid
2570                && direct_roles_are_valid
2571                && direct_plays_are_valid
2572                && fields_are_valid
2573                && roles_are_valid
2574                && role_upcasts_are_valid
2575                && references_are_valid
2576                && value_subject
2577        });
2578        let closed_playing = playing_facts.values().all(|playing| {
2579            models.contains_key(playing.id().player())
2580                && role_exists(playing.role())
2581                && (!matches!(target, BindingTarget::TypeScript | BindingTarget::Rust)
2582                    || playing.target_name().is_some())
2583        });
2584        let closed_functions = functions.values().all(|function| {
2585            function
2586                .parameters()
2587                .iter()
2588                .all(|parameter| type_ref_is_valid(parameter.type_ref()))
2589                && match function.returns() {
2590                    FunctionReturnProjection::Scalar(element) => {
2591                        type_ref_is_valid(element.type_ref())
2592                    }
2593                    FunctionReturnProjection::Tuple(elements)
2594                    | FunctionReturnProjection::Stream(elements) => elements
2595                        .iter()
2596                        .all(|element| type_ref_is_valid(element.type_ref())),
2597                }
2598        });
2599        if !closed_models || !closed_playing || !closed_functions {
2600            return Err(invalid_projection(
2601                "invalid_projection_reference",
2602                "projection graph contains an unavailable type, field, role, specialization, reference, or function dependency",
2603            ));
2604        }
2605        let content = to_canonical_json(&RuntimeProjectionContentView {
2606            models: &models,
2607            structs: &structs,
2608            functions: &functions,
2609            playing_facts: &playing_facts,
2610            emission: &emission,
2611        })?;
2612        let projection_fingerprint = BindingProjectionFingerprint::compute_with_projection(
2613            target,
2614            &semantic_fingerprint,
2615            &config,
2616            handlers,
2617            resources,
2618            &content,
2619        )?;
2620        let mut generator_handlers = handlers.to_vec();
2621        generator_handlers.sort_by(|left, right| left.id().cmp(right.id()));
2622        let mut code_resources = resources.to_vec();
2623        code_resources.sort_by(|left, right| left.id().cmp(right.id()));
2624        Ok(Self {
2625            target,
2626            config,
2627            semantic_fingerprint,
2628            projection_fingerprint,
2629            generator_handlers,
2630            code_resources,
2631            models,
2632            structs,
2633            functions,
2634            playing_facts,
2635            emission,
2636        })
2637    }
2638    /// Return the binding target.
2639    #[must_use]
2640    pub const fn target(&self) -> BindingTarget {
2641        self.target
2642    }
2643    /// Return the exact projection configuration.
2644    #[must_use]
2645    pub const fn config(&self) -> &ProjectionConfig {
2646        &self.config
2647    }
2648    /// Return the source semantic schema fingerprint.
2649    #[must_use]
2650    pub const fn semantic_fingerprint(&self) -> &SemanticSchemaFingerprint {
2651        &self.semantic_fingerprint
2652    }
2653    /// Return the content-bound target projection fingerprint.
2654    #[must_use]
2655    pub const fn projection_fingerprint(&self) -> &BindingProjectionFingerprint {
2656        &self.projection_fingerprint
2657    }
2658    /// Return the ordered handler evidence committed by the projection fingerprint.
2659    #[must_use]
2660    pub fn generator_handlers(&self) -> &[ProjectionHandler] {
2661        &self.generator_handlers
2662    }
2663    /// Return the ordered code-resource evidence committed by the projection fingerprint.
2664    #[must_use]
2665    pub fn code_resources(&self) -> &[CodeResourceDigest] {
2666        &self.code_resources
2667    }
2668    /// Return projected models in canonical identity order.
2669    #[must_use]
2670    pub const fn models(&self) -> &BTreeMap<TypeId, ModelProjection> {
2671        &self.models
2672    }
2673    /// Return projected structs in canonical identity order.
2674    #[must_use]
2675    pub const fn structs(&self) -> &BTreeMap<StructId, StructProjection> {
2676        &self.structs
2677    }
2678    /// Return projected schema functions in canonical identity order.
2679    #[must_use]
2680    pub const fn functions(&self) -> &BTreeMap<FunctionId, FunctionProjection> {
2681        &self.functions
2682    }
2683    /// Return effective per-player metadata keyed by exact playing identity.
2684    #[must_use]
2685    pub const fn playing_facts(&self) -> &BTreeMap<PlaysFactId, PlayingProjection> {
2686        &self.playing_facts
2687    }
2688    /// Return the shells-first generation schedule.
2689    #[must_use]
2690    pub const fn emission(&self) -> &EmissionPlan {
2691        &self.emission
2692    }
2693}
2694
2695impl CodeResourceDigest {
2696    /// Adopt decoded resource evidence only after checking its exact fingerprint domain.
2697    pub(crate) fn from_wire(
2698        id: impl Into<String>,
2699        content_fingerprint: Fingerprint,
2700    ) -> Result<Self, Diagnostic> {
2701        if content_fingerprint.domain().as_str() != CODE_RESOURCE_DOMAIN
2702            || content_fingerprint.canonicalization().as_str() != RAW_BYTES_CANONICALIZATION
2703            || content_fingerprint.semantic_profile().is_some()
2704        {
2705            return Err(Diagnostic::stable(
2706                DiagnosticCategory::Integrity,
2707                "invalid_code_resource_fingerprint",
2708                "code resource fingerprint wire metadata is inconsistent",
2709            ));
2710        }
2711        Ok(Self {
2712            id: CodeResourceId::new(id)?,
2713            content_fingerprint,
2714        })
2715    }
2716}