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, AnnotationKindId, AnnotationSubjectId, CollectionMode,
16    OwnsFactId, PlaysFactId, RelatesFactId, SchemaAnnotationValue, SchemaFactId, SubFactId,
17    ValueFactId,
18};
19use crate::schema_fingerprint::SemanticSchemaFingerprint;
20use crate::value::{Cardinality, ValueTypeTag};
21
22const MAX_PROJECTION_COMPONENT_ID_BYTES: usize = 255;
23const PYTHON_GENERATOR_HANDLER_ID: &str = "typebridge.generator.python";
24const TYPESCRIPT_GENERATOR_HANDLER_ID: &str = "typebridge.generator.typescript";
25const RUST_GENERATOR_HANDLER_ID: &str = "typebridge.generator.rust";
26const C_GENERATOR_HANDLER_ID: &str = "typebridge.generator.c";
27const CODE_RESOURCE_DOMAIN: &str = "typebridge.binding.code-resource";
28const RAW_BYTES_CANONICALIZATION: &str = "typebridge.raw-bytes/v1";
29const BINDING_PROJECTION_DOMAIN: &str = "typebridge.binding.projection";
30const BINDING_PROJECTION_CANONICALIZATION: &str = "typebridge.binding-projection/v1";
31const BINDING_PROJECTION_CONTENT_DOMAIN: &str = "typebridge.binding.projection-content";
32const MAX_TARGET_IDENTIFIER_BYTES: usize = 255;
33const MAX_C_SYMBOL_PREFIX_BYTES: usize = 63;
34
35/// Properties installed on generated TypeScript model tokens or hydrated facets.
36///
37/// Canonical model-member projection appends `_` when a camel-case field or
38/// role name is in this set. The emitter uses the same set to reject invalid
39/// descriptors without changing physical schema identities.
40pub const TYPESCRIPT_MODEL_RESERVED_NAMES: &[&str] = &[
41    "__proto__",
42    "completeRead",
43    "constructor",
44    "create",
45    "declaration",
46    "fields",
47    "id",
48    "iid",
49    "metadata",
50    "manager",
51    "name",
52    "plays",
53    "prototype",
54    "reference",
55    "roles",
56    "typeKey",
57    "typeToken",
58    "valueType",
59];
60
61/// ABI major required by C packages emitted under the C-v1 projection contract.
62pub const TYPE_BRIDGE_C_ABI_MAJOR: u32 = 1;
63/// Current aggregate ABI minor supported by the native C runtime.
64pub const TYPE_BRIDGE_C_ABI_MINOR: u32 = 6;
65/// Generated-only projection-token layout version consumed by native SDK facades.
66pub const TYPE_BRIDGE_PROJECTED_TOKEN_VERSION: u32 = 1;
67/// Maximum generated C create fields on one projected model.
68///
69/// C11 guarantees 1,023 members in one structure. The versioned generated
70/// args structure reserves three members for `struct_size`, `version`, and
71/// `reserved`, leaving 1,020 semantic members.
72pub const TYPE_BRIDGE_C_CREATE_FIELD_MAX: usize = 1_020;
73/// Maximum generated C create roles on one projected model under the same
74/// 1,023-member translation minimum.
75pub const TYPE_BRIDGE_C_CREATE_ROLE_MAX: usize = 1_020;
76/// Maximum combined generated C create fields and roles on one model after
77/// reserving the three version/layout members.
78pub const TYPE_BRIDGE_C_CREATE_MEMBER_MAX: usize = 1_020;
79
80/// The closed semantic kind carried by one generated projection token.
81///
82/// Numeric spellings are frozen independently from Rust enum layout and are
83/// copied explicitly into each native ABI. New native facades must reject an
84/// unknown kind before attempting ordinal resolution.
85#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
86#[non_exhaustive]
87pub enum ProjectedTokenKind {
88    /// One projected model identity.
89    Model,
90    /// One owner-branded projected owned-field identity.
91    Field,
92    /// One owner-branded projected relation-role identity.
93    Role,
94    /// One exact projected schema-function identity.
95    Function,
96    /// One exact projected generated-struct identity.
97    Struct,
98    /// One exact projected attribute-value identity.
99    Attribute,
100}
101
102impl ProjectedTokenKind {
103    /// Return the frozen positive native-ABI spelling.
104    #[must_use]
105    pub const fn as_u32(self) -> u32 {
106        match self {
107            Self::Model => 1,
108            Self::Field => 2,
109            Self::Role => 3,
110            Self::Function => 4,
111            Self::Struct => 5,
112            Self::Attribute => 6,
113        }
114    }
115
116    /// Decode one frozen native-ABI spelling, rejecting future kinds.
117    #[must_use]
118    pub const fn from_u32(value: u32) -> Option<Self> {
119        match value {
120            1 => Some(Self::Model),
121            2 => Some(Self::Field),
122            3 => Some(Self::Role),
123            4 => Some(Self::Function),
124            5 => Some(Self::Struct),
125            6 => Some(Self::Attribute),
126            _ => None,
127        }
128    }
129}
130
131/// One binding-neutral semantic identity resolved from a generated token.
132///
133/// Role and field values retain the effective model owner explicitly because
134/// inherited roles and ownership declarations may share a declaring identity
135/// while remaining distinct generated members on different concrete models.
136#[derive(Clone, Debug, Eq, PartialEq)]
137#[non_exhaustive]
138pub enum ProjectedTokenIdentity {
139    /// One exact projected model.
140    Model(TypeId),
141    /// One field as exposed by an exact projected model.
142    Field {
143        /// Effective projected model owner.
144        owner: TypeId,
145        /// Canonical ownership-fact identity.
146        field: OwnsFactId,
147    },
148    /// One role as exposed by an exact projected relation model.
149    Role {
150        /// Effective projected relation owner.
151        owner: TypeId,
152        /// Canonical relation-qualified role identity.
153        role: RoleId,
154    },
155    /// One exact projected schema function.
156    Function(FunctionId),
157    /// One exact projected generated struct.
158    Struct(StructId),
159    /// One exact projected attribute value.
160    Attribute(AttributeId),
161}
162
163impl ProjectedTokenIdentity {
164    /// Return this identity's frozen token kind.
165    #[must_use]
166    pub const fn kind(&self) -> ProjectedTokenKind {
167        match self {
168            Self::Model(_) => ProjectedTokenKind::Model,
169            Self::Field { .. } => ProjectedTokenKind::Field,
170            Self::Role { .. } => ProjectedTokenKind::Role,
171            Self::Function(_) => ProjectedTokenKind::Function,
172            Self::Struct(_) => ProjectedTokenKind::Struct,
173            Self::Attribute(_) => ProjectedTokenKind::Attribute,
174        }
175    }
176}
177
178/// A binding target with a consumed Phase 3 projection contract.
179#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
180#[serde(rename_all = "snake_case")]
181#[non_exhaustive]
182pub enum BindingTarget {
183    /// Generated Python source and typing artifacts.
184    Python,
185    /// Generated TypeScript source and declarations.
186    #[serde(rename = "typescript")]
187    TypeScript,
188    /// Generated native Rust types and schema tokens.
189    Rust,
190    /// Generated C source, headers, and schema-package metadata.
191    C,
192}
193
194impl BindingTarget {
195    /// Return the stable canonical wire spelling for this target.
196    #[must_use]
197    pub const fn as_str(self) -> &'static str {
198        match self {
199            Self::Python => "python",
200            Self::TypeScript => "typescript",
201            Self::Rust => "rust",
202            Self::C => "c",
203        }
204    }
205
206    const fn required_generator_handler_id(self) -> &'static str {
207        match self {
208            Self::Python => PYTHON_GENERATOR_HANDLER_ID,
209            Self::TypeScript => TYPESCRIPT_GENERATOR_HANDLER_ID,
210            Self::Rust => RUST_GENERATOR_HANDLER_ID,
211            Self::C => C_GENERATOR_HANDLER_ID,
212        }
213    }
214}
215
216/// The exact label-to-Python-name transformation consumed by the emitter.
217#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
218pub enum PythonNamingPolicy {
219    /// The first collision-checked TypeBridge Python naming policy.
220    #[serde(rename = "typebridge.python/v1")]
221    TypeBridgeV1,
222}
223
224/// The exact label-to-TypeScript-name transformation consumed by the emitter.
225#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
226pub enum TypeScriptNamingPolicy {
227    /// The first collision-checked TypeBridge TypeScript naming policy.
228    #[serde(rename = "typebridge.typescript/v1")]
229    TypeBridgeV1,
230}
231
232/// The exact label-to-Rust-name transformation consumed by native projection.
233#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
234pub enum RustNamingPolicy {
235    /// The first collision-checked TypeBridge Rust naming policy.
236    #[serde(rename = "typebridge.rust/v1")]
237    TypeBridgeV1,
238}
239
240/// The generated Rust construction surface committed by the projection fingerprint.
241#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
242pub enum RustCreatePolicy {
243    /// Emit a public `{Model}Create` input with private fields, checked `try_new`,
244    /// and manager insertion that consumes the validated input. Abstract or
245    /// otherwise nonconstructible models expose no create input.
246    #[serde(rename = "typebridge.rust.validated-create-input/v1")]
247    ValidatedInputV1,
248}
249
250/// The exact label-to-C-name transformation consumed by the C emitter.
251#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
252pub enum CNamingPolicy {
253    /// The first collision-checked TypeBridge C naming policy.
254    #[serde(rename = "typebridge.c/v1")]
255    TypeBridgeV1,
256}
257
258/// A validated prefix for generated symbols in C's global link namespace.
259#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
260#[serde(transparent)]
261pub struct CSymbolPrefix(String);
262
263impl CSymbolPrefix {
264    /// Validate and construct a bounded, non-reserved ASCII C symbol prefix.
265    pub fn new(value: impl Into<String>) -> Result<Self, Diagnostic> {
266        let value = value.into();
267        let uses_runtime_namespace = value == "type_bridge" || value.starts_with("type_bridge_");
268        let portable_path_component = value
269            .bytes()
270            .all(|byte| byte == b'_' || byte.is_ascii_lowercase() || byte.is_ascii_digit())
271            && !value.ends_with('_')
272            && !is_windows_device_name(&value);
273        if !is_valid_c_identifier(&value, MAX_C_SYMBOL_PREFIX_BYTES)
274            || !portable_path_component
275            || uses_runtime_namespace
276        {
277            return Err(Diagnostic::stable(
278                DiagnosticCategory::InvalidContract,
279                "invalid_c_symbol_prefix",
280                "C symbol prefix must be a bounded lowercase portable path outside the reserved TypeBridge runtime namespace",
281            ));
282        }
283        Ok(Self(value))
284    }
285
286    /// Return the exact prefix spelling committed by the projection config.
287    #[must_use]
288    pub fn as_str(&self) -> &str {
289        &self.0
290    }
291}
292
293impl fmt::Display for CSymbolPrefix {
294    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
295        formatter.write_str(self.as_str())
296    }
297}
298
299/// Target-specific options that are consumed by a shipped emitter.
300#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
301#[serde(tag = "binding")]
302#[non_exhaustive]
303pub enum ProjectionConfig {
304    /// Python projection options.
305    #[serde(rename = "python")]
306    #[non_exhaustive]
307    Python {
308        /// Versioned Python naming behavior used for every generated symbol.
309        naming_policy: PythonNamingPolicy,
310    },
311    /// TypeScript projection options.
312    #[serde(rename = "typescript")]
313    #[non_exhaustive]
314    TypeScript {
315        /// Versioned TypeScript naming behavior used for every generated symbol.
316        naming_policy: TypeScriptNamingPolicy,
317    },
318    /// Native Rust projection options.
319    #[serde(rename = "rust")]
320    #[non_exhaustive]
321    Rust {
322        /// Versioned Rust naming behavior used for every generated symbol.
323        naming_policy: RustNamingPolicy,
324        /// Versioned checked construction surface generated for constructible models.
325        create_policy: RustCreatePolicy,
326    },
327    /// C projection options.
328    #[serde(rename = "c")]
329    #[non_exhaustive]
330    C {
331        /// Versioned C naming behavior used for every generated symbol.
332        naming_policy: CNamingPolicy,
333        /// Application-specific prefix for C's global link namespace.
334        symbol_prefix: CSymbolPrefix,
335    },
336}
337
338impl ProjectionConfig {
339    /// Construct the initial Python projection configuration.
340    #[must_use]
341    pub const fn python() -> Self {
342        Self::Python {
343            naming_policy: PythonNamingPolicy::TypeBridgeV1,
344        }
345    }
346
347    /// Construct the initial TypeScript projection configuration.
348    #[must_use]
349    pub const fn typescript() -> Self {
350        Self::TypeScript {
351            naming_policy: TypeScriptNamingPolicy::TypeBridgeV1,
352        }
353    }
354
355    /// Construct the initial native Rust projection configuration.
356    #[must_use]
357    pub const fn rust() -> Self {
358        Self::Rust {
359            naming_policy: RustNamingPolicy::TypeBridgeV1,
360            create_policy: RustCreatePolicy::ValidatedInputV1,
361        }
362    }
363
364    /// Construct the initial C projection configuration.
365    #[must_use]
366    pub fn c(symbol_prefix: CSymbolPrefix) -> Self {
367        Self::C {
368            naming_policy: CNamingPolicy::TypeBridgeV1,
369            symbol_prefix,
370        }
371    }
372
373    /// Return the binding target that consumes this configuration.
374    #[must_use]
375    pub const fn target(&self) -> BindingTarget {
376        match self {
377            Self::Python { .. } => BindingTarget::Python,
378            Self::TypeScript { .. } => BindingTarget::TypeScript,
379            Self::Rust { .. } => BindingTarget::Rust,
380            Self::C { .. } => BindingTarget::C,
381        }
382    }
383
384    /// Return the Python naming policy when this is a Python config.
385    #[must_use]
386    pub const fn python_naming_policy(&self) -> Option<PythonNamingPolicy> {
387        match self {
388            Self::Python { naming_policy } => Some(*naming_policy),
389            Self::TypeScript { .. } | Self::Rust { .. } | Self::C { .. } => None,
390        }
391    }
392
393    /// Return the TypeScript naming policy when this is a TypeScript config.
394    #[must_use]
395    pub const fn typescript_naming_policy(&self) -> Option<TypeScriptNamingPolicy> {
396        match self {
397            Self::TypeScript { naming_policy } => Some(*naming_policy),
398            Self::Python { .. } | Self::Rust { .. } | Self::C { .. } => None,
399        }
400    }
401
402    /// Return the Rust naming policy when this is a Rust config.
403    #[must_use]
404    pub const fn rust_naming_policy(&self) -> Option<RustNamingPolicy> {
405        match self {
406            Self::Rust { naming_policy, .. } => Some(*naming_policy),
407            Self::Python { .. } | Self::TypeScript { .. } | Self::C { .. } => None,
408        }
409    }
410
411    /// Return the checked Rust create policy when this is a Rust config.
412    #[must_use]
413    pub const fn rust_create_policy(&self) -> Option<RustCreatePolicy> {
414        match self {
415            Self::Rust { create_policy, .. } => Some(*create_policy),
416            Self::Python { .. } | Self::TypeScript { .. } | Self::C { .. } => None,
417        }
418    }
419
420    /// Return the C naming policy when this is a C config.
421    #[must_use]
422    pub const fn c_naming_policy(&self) -> Option<CNamingPolicy> {
423        match self {
424            Self::C { naming_policy, .. } => Some(*naming_policy),
425            Self::Python { .. } | Self::TypeScript { .. } | Self::Rust { .. } => None,
426        }
427    }
428
429    /// Return the symbol prefix when this is a C config.
430    #[must_use]
431    pub const fn c_symbol_prefix(&self) -> Option<&CSymbolPrefix> {
432        match self {
433            Self::C { symbol_prefix, .. } => Some(symbol_prefix),
434            Self::Python { .. } | Self::TypeScript { .. } | Self::Rust { .. } => None,
435        }
436    }
437}
438
439fn validate_component_id(value: String) -> Result<String, Diagnostic> {
440    let segments = value.split('.').collect::<Vec<_>>();
441    let valid_segment = |segment: &str| {
442        let mut bytes = segment.bytes();
443        bytes.next().is_some_and(|byte| byte.is_ascii_lowercase())
444            && bytes.all(|byte| {
445                byte.is_ascii_lowercase() || byte.is_ascii_digit() || matches!(byte, b'-' | b'_')
446            })
447    };
448    if value.len() <= MAX_PROJECTION_COMPONENT_ID_BYTES
449        && segments.len() >= 2
450        && segments.iter().all(|segment| valid_segment(segment))
451    {
452        Ok(value)
453    } else {
454        Err(Diagnostic::stable(
455            DiagnosticCategory::InvalidContract,
456            "malformed_projection_component_id",
457            "projection component ID must be a bounded lowercase namespaced identifier",
458        ))
459    }
460}
461
462macro_rules! projection_component_id {
463    ($name:ident, $doc:literal) => {
464        #[doc = $doc]
465        #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
466        pub struct $name(String);
467
468        impl $name {
469            /// Validate and construct a namespaced component identity.
470            pub fn new(value: impl Into<String>) -> Result<Self, Diagnostic> {
471                Ok(Self(validate_component_id(value.into())?))
472            }
473
474            /// Return the canonical identity spelling.
475            #[must_use]
476            pub fn as_str(&self) -> &str {
477                &self.0
478            }
479        }
480
481        impl fmt::Display for $name {
482            fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
483                formatter.write_str(self.as_str())
484            }
485        }
486
487        impl Serialize for $name {
488            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
489            where
490                S: Serializer,
491            {
492                serializer.serialize_str(self.as_str())
493            }
494        }
495    };
496}
497
498projection_component_id!(
499    ProjectionHandlerId,
500    "A stable identity for one generator or projection handler."
501);
502projection_component_id!(
503    CodeResourceId,
504    "A stable identity for exact code-resource bytes referenced during emission."
505);
506
507/// A nonzero behavior version for one projection handler.
508#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
509#[serde(transparent)]
510pub struct ProjectionHandlerVersion(u16);
511
512impl ProjectionHandlerVersion {
513    /// The initial handler behavior version.
514    pub const V1: Self = Self(1);
515    /// The second handler behavior version.
516    pub const V2: Self = Self(2);
517    /// The third handler behavior version.
518    pub const V3: Self = Self(3);
519
520    /// Validate and construct a handler behavior version.
521    pub fn new(value: u16) -> Result<Self, Diagnostic> {
522        if value == 0 {
523            Err(Diagnostic::stable(
524                DiagnosticCategory::InvalidContract,
525                "invalid_projection_handler_version",
526                "projection handler version must be nonzero",
527            ))
528        } else {
529            Ok(Self(value))
530        }
531    }
532
533    /// Return the numeric behavior version.
534    #[must_use]
535    pub const fn get(self) -> u16 {
536        self.0
537    }
538}
539
540/// The identity and behavior version of a handler that participated in emission.
541#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
542pub struct ProjectionHandler {
543    id: ProjectionHandlerId,
544    version: ProjectionHandlerVersion,
545}
546
547impl ProjectionHandler {
548    /// Construct one executed projection-handler identity.
549    pub fn new(id: impl Into<String>, version: u16) -> Result<Self, Diagnostic> {
550        Ok(Self {
551            id: ProjectionHandlerId::new(id)?,
552            version: ProjectionHandlerVersion::new(version)?,
553        })
554    }
555
556    /// Construct the initial built-in Python generator identity.
557    #[must_use]
558    pub fn python_v1() -> Self {
559        Self {
560            id: ProjectionHandlerId::new(PYTHON_GENERATOR_HANDLER_ID)
561                .expect("the built-in Python generator ID is valid"),
562            version: ProjectionHandlerVersion::V1,
563        }
564    }
565
566    /// Construct the ordered-collection-aware built-in Python generator identity.
567    #[must_use]
568    pub fn python_v2() -> Self {
569        Self {
570            id: ProjectionHandlerId::new(PYTHON_GENERATOR_HANDLER_ID)
571                .expect("the built-in Python generator ID is valid"),
572            version: ProjectionHandlerVersion::V2,
573        }
574    }
575
576    /// Construct the initial built-in TypeScript generator identity.
577    #[must_use]
578    pub fn typescript_v1() -> Self {
579        Self {
580            id: ProjectionHandlerId::new(TYPESCRIPT_GENERATOR_HANDLER_ID)
581                .expect("built-in TypeScript projection handler ID is valid"),
582            version: ProjectionHandlerVersion::V1,
583        }
584    }
585
586    /// Construct the ordered-collection-aware built-in TypeScript generator identity.
587    #[must_use]
588    pub fn typescript_v2() -> Self {
589        Self {
590            id: ProjectionHandlerId::new(TYPESCRIPT_GENERATOR_HANDLER_ID)
591                .expect("built-in TypeScript projection handler ID is valid"),
592            version: ProjectionHandlerVersion::V2,
593        }
594    }
595
596    /// Construct the initial built-in native Rust generator identity.
597    #[must_use]
598    pub fn rust_v1() -> Self {
599        Self {
600            id: ProjectionHandlerId::new(RUST_GENERATOR_HANDLER_ID)
601                .expect("built-in Rust projection handler ID is valid"),
602            version: ProjectionHandlerVersion::V1,
603        }
604    }
605
606    /// Construct the ordered-collection-aware built-in native Rust generator identity.
607    #[must_use]
608    pub fn rust_v2() -> Self {
609        Self {
610            id: ProjectionHandlerId::new(RUST_GENERATOR_HANDLER_ID)
611                .expect("built-in Rust projection handler ID is valid"),
612            version: ProjectionHandlerVersion::V2,
613        }
614    }
615
616    /// Construct the initial built-in C generator identity.
617    #[must_use]
618    pub fn c_v1() -> Self {
619        Self {
620            id: ProjectionHandlerId::new(C_GENERATOR_HANDLER_ID)
621                .expect("the built-in C generator ID is valid"),
622            version: ProjectionHandlerVersion::V1,
623        }
624    }
625
626    /// Construct the projected-token-aware built-in C generator identity.
627    #[must_use]
628    pub fn c_v2() -> Self {
629        Self {
630            id: ProjectionHandlerId::new(C_GENERATOR_HANDLER_ID)
631                .expect("the built-in C generator ID is valid"),
632            version: ProjectionHandlerVersion::V2,
633        }
634    }
635
636    /// Construct the ordered-collection-aware built-in C generator identity.
637    #[must_use]
638    pub fn c_v3() -> Self {
639        Self {
640            id: ProjectionHandlerId::new(C_GENERATOR_HANDLER_ID)
641                .expect("the built-in C generator ID is valid"),
642            version: ProjectionHandlerVersion::V3,
643        }
644    }
645
646    /// Return the handler identity.
647    #[must_use]
648    pub const fn id(&self) -> &ProjectionHandlerId {
649        &self.id
650    }
651
652    /// Return the handler behavior version.
653    #[must_use]
654    pub const fn version(&self) -> ProjectionHandlerVersion {
655        self.version
656    }
657}
658
659/// A domain-separated digest of exact code-resource bytes used by an emitter.
660#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
661pub struct CodeResourceDigest {
662    id: CodeResourceId,
663    content_fingerprint: Fingerprint,
664}
665
666impl CodeResourceDigest {
667    /// Hash the exact bytes of one referenced code resource.
668    pub fn from_bytes(id: impl Into<String>, bytes: &[u8]) -> Result<Self, Diagnostic> {
669        Ok(Self {
670            id: CodeResourceId::new(id)?,
671            content_fingerprint: Fingerprint::compute(
672                FingerprintDomain::new(CODE_RESOURCE_DOMAIN)?,
673                CanonicalizationVersion::new(RAW_BYTES_CANONICALIZATION)?,
674                None,
675                bytes,
676            ),
677        })
678    }
679
680    /// Return the resource identity.
681    #[must_use]
682    pub const fn id(&self) -> &CodeResourceId {
683        &self.id
684    }
685
686    /// Return the domain-separated exact-content fingerprint.
687    #[must_use]
688    pub const fn content_fingerprint(&self) -> &Fingerprint {
689        &self.content_fingerprint
690    }
691}
692
693#[derive(Serialize)]
694struct BindingProjectionView<'a> {
695    format_version: FormatVersion,
696    target: BindingTarget,
697    semantic_schema_fingerprint: &'a SemanticSchemaFingerprint,
698    config: &'a ProjectionConfig,
699    generator_handlers: Vec<&'a ProjectionHandler>,
700    referenced_code_resources: Vec<&'a CodeResourceDigest>,
701}
702
703fn ordered_handlers(
704    target: BindingTarget,
705    handlers: &[ProjectionHandler],
706) -> Result<Vec<&ProjectionHandler>, Diagnostic> {
707    let mut ordered = handlers.iter().collect::<Vec<_>>();
708    ordered.sort_by(|left, right| match left.id().cmp(right.id()) {
709        Ordering::Equal => left.version().cmp(&right.version()),
710        ordering => ordering,
711    });
712    if ordered.windows(2).any(|pair| pair[0].id() == pair[1].id()) {
713        return Err(Diagnostic::stable(
714            DiagnosticCategory::InvalidContract,
715            "duplicate_projection_handler_id",
716            "a projection handler identity may appear only once",
717        ));
718    }
719    if !ordered
720        .iter()
721        .any(|handler| handler.id().as_str() == target.required_generator_handler_id())
722    {
723        return Err(Diagnostic::stable(
724            DiagnosticCategory::InvalidContract,
725            "missing_target_projection_handler",
726            "projection fingerprint inputs omit the target's generator handler",
727        ));
728    }
729    Ok(ordered)
730}
731
732fn ordered_resources(
733    resources: &[CodeResourceDigest],
734) -> Result<Vec<&CodeResourceDigest>, Diagnostic> {
735    let mut ordered = resources.iter().collect::<Vec<_>>();
736    ordered.sort_by(|left, right| left.id().cmp(right.id()));
737    if ordered.windows(2).any(|pair| pair[0].id() == pair[1].id()) {
738        return Err(Diagnostic::stable(
739            DiagnosticCategory::InvalidContract,
740            "duplicate_projection_resource_id",
741            "a referenced code-resource identity may appear only once",
742        ));
743    }
744    Ok(ordered)
745}
746
747/// Produce the exact canonical preimage for a binding-projection fingerprint.
748pub fn canonical_binding_projection_bytes(
749    target: BindingTarget,
750    semantic_schema: &SemanticSchemaFingerprint,
751    config: &ProjectionConfig,
752    handlers: &[ProjectionHandler],
753    resources: &[CodeResourceDigest],
754) -> Result<Vec<u8>, Diagnostic> {
755    if config.target() != target {
756        return Err(Diagnostic::stable(
757            DiagnosticCategory::InvalidContract,
758            "projection_config_target_mismatch",
759            "projection configuration belongs to a different binding target",
760        ));
761    }
762    let view = BindingProjectionView {
763        format_version: FormatVersion::V1,
764        target,
765        semantic_schema_fingerprint: semantic_schema,
766        config,
767        generator_handlers: ordered_handlers(target, handlers)?,
768        referenced_code_resources: ordered_resources(resources)?,
769    };
770    to_canonical_json(&view)
771}
772
773/// Fingerprint of one binding projection and every input that can alter it.
774#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
775#[serde(transparent)]
776pub struct BindingProjectionFingerprint(Fingerprint);
777
778impl BindingProjectionFingerprint {
779    /// Compute a target-specific projection fingerprint from trusted inputs.
780    pub fn compute(
781        target: BindingTarget,
782        semantic_schema: &SemanticSchemaFingerprint,
783        config: &ProjectionConfig,
784        handlers: &[ProjectionHandler],
785        resources: &[CodeResourceDigest],
786    ) -> Result<Self, Diagnostic> {
787        let canonical = canonical_binding_projection_bytes(
788            target,
789            semantic_schema,
790            config,
791            handlers,
792            resources,
793        )?;
794        let semantic_profile = semantic_schema
795            .as_fingerprint()
796            .semantic_profile()
797            .cloned()
798            .ok_or_else(|| {
799                Diagnostic::stable(
800                    DiagnosticCategory::InvalidContract,
801                    "projection_semantic_profile_missing",
802                    "semantic schema fingerprint does not carry its semantic profile",
803                )
804            })?;
805        Ok(Self(Fingerprint::compute(
806            FingerprintDomain::new(BINDING_PROJECTION_DOMAIN)?,
807            CanonicalizationVersion::new(BINDING_PROJECTION_CANONICALIZATION)?,
808            Some(semantic_profile),
809            &canonical,
810        )))
811    }
812
813    /// Return the generic fingerprint metadata and digest.
814    #[must_use]
815    pub const fn as_fingerprint(&self) -> &Fingerprint {
816        &self.0
817    }
818
819    /// Compute a fingerprint that additionally commits to canonical projection content.
820    pub fn compute_with_projection(
821        target: BindingTarget,
822        semantic_schema: &SemanticSchemaFingerprint,
823        config: &ProjectionConfig,
824        handlers: &[ProjectionHandler],
825        resources: &[CodeResourceDigest],
826        canonical_projection: &[u8],
827    ) -> Result<Self, Diagnostic> {
828        #[derive(Serialize)]
829        struct CompleteProjectionView<'a> {
830            inputs: BindingProjectionView<'a>,
831            projection_content: Fingerprint,
832        }
833
834        if config.target() != target {
835            return Err(Diagnostic::stable(
836                DiagnosticCategory::InvalidContract,
837                "projection_config_target_mismatch",
838                "projection configuration belongs to a different binding target",
839            ));
840        }
841        let inputs = BindingProjectionView {
842            format_version: FormatVersion::V1,
843            target,
844            semantic_schema_fingerprint: semantic_schema,
845            config,
846            generator_handlers: ordered_handlers(target, handlers)?,
847            referenced_code_resources: ordered_resources(resources)?,
848        };
849        let projection_content = Fingerprint::compute(
850            FingerprintDomain::new(BINDING_PROJECTION_CONTENT_DOMAIN)?,
851            CanonicalizationVersion::new(BINDING_PROJECTION_CANONICALIZATION)?,
852            semantic_schema.as_fingerprint().semantic_profile().cloned(),
853            canonical_projection,
854        );
855        let canonical = to_canonical_json(&CompleteProjectionView {
856            inputs,
857            projection_content,
858        })?;
859        let semantic_profile = semantic_schema
860            .as_fingerprint()
861            .semantic_profile()
862            .cloned()
863            .ok_or_else(|| {
864                Diagnostic::stable(
865                    DiagnosticCategory::InvalidContract,
866                    "projection_semantic_profile_missing",
867                    "semantic schema fingerprint does not carry its semantic profile",
868                )
869            })?;
870        Ok(Self(Fingerprint::compute(
871            FingerprintDomain::new(BINDING_PROJECTION_DOMAIN)?,
872            CanonicalizationVersion::new(BINDING_PROJECTION_CANONICALIZATION)?,
873            Some(semantic_profile),
874            &canonical,
875        )))
876    }
877}
878
879fn serialize_map_values<S, K, V>(map: &BTreeMap<K, V>, serializer: S) -> Result<S::Ok, S::Error>
880where
881    S: Serializer,
882    V: Serialize,
883{
884    map.values().collect::<Vec<_>>().serialize(serializer)
885}
886
887#[derive(Serialize)]
888struct RoleUpcastEntry<'a> {
889    role: &'a RoleId,
890    ancestors: &'a [RoleId],
891}
892
893fn serialize_role_upcasts<S>(
894    map: &BTreeMap<RoleId, Vec<RoleId>>,
895    serializer: S,
896) -> Result<S::Ok, S::Error>
897where
898    S: Serializer,
899{
900    map.iter()
901        .map(|(role, ancestors)| RoleUpcastEntry { role, ancestors })
902        .collect::<Vec<_>>()
903        .serialize(serializer)
904}
905
906fn invalid_projection(code: &'static str, message: &'static str) -> Diagnostic {
907    Diagnostic::stable(DiagnosticCategory::InvalidContract, code, message)
908}
909
910fn ensure_collection_limit(length: usize, code: &'static str) -> Result<(), Diagnostic> {
911    if length > MAX_CANONICAL_COLLECTION_LEN {
912        Err(Diagnostic::stable(
913            DiagnosticCategory::ResourceLimit,
914            code,
915            "projection collection exceeds the canonical collection limit",
916        ))
917    } else {
918        Ok(())
919    }
920}
921
922fn validate_c_create_limits(models: &BTreeMap<TypeId, ModelProjection>) -> Result<(), Diagnostic> {
923    for model in models.values() {
924        if model.create().target_name().is_none() {
925            continue;
926        }
927        let field_count = model.create().fields().len();
928        let role_count = model.create().roles().len();
929        if field_count > TYPE_BRIDGE_C_CREATE_FIELD_MAX {
930            return Err(Diagnostic::stable(
931                DiagnosticCategory::ResourceLimit,
932                "c_projection_create_field_limit_exceeded",
933                "C create fields exceed the 1020-member translation ceiling",
934            ));
935        }
936        if role_count > TYPE_BRIDGE_C_CREATE_ROLE_MAX {
937            return Err(Diagnostic::stable(
938                DiagnosticCategory::ResourceLimit,
939                "c_projection_create_role_limit_exceeded",
940                "C create roles exceed the 1020-member translation ceiling",
941            ));
942        }
943        if field_count
944            .checked_add(role_count)
945            .is_none_or(|count| count > TYPE_BRIDGE_C_CREATE_MEMBER_MAX)
946        {
947            return Err(Diagnostic::stable(
948                DiagnosticCategory::ResourceLimit,
949                "c_projection_create_member_limit_exceeded",
950                "combined C create fields and roles exceed the 1020-member translation ceiling",
951            ));
952        }
953    }
954    Ok(())
955}
956
957/// A validated target-language identifier emitted verbatim by a generator.
958#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
959#[serde(transparent)]
960pub struct TargetIdentifier(String);
961
962impl TargetIdentifier {
963    /// Validate one ASCII Python identifier under the frozen Python-v1 policy.
964    pub fn python(value: impl Into<String>) -> Result<Self, Diagnostic> {
965        let value = value.into();
966        let mut bytes = value.bytes();
967        let valid = value.len() <= MAX_TARGET_IDENTIFIER_BYTES
968            && bytes
969                .next()
970                .is_some_and(|byte| byte == b'_' || byte.is_ascii_alphabetic())
971            && bytes.all(|byte| byte == b'_' || byte.is_ascii_alphanumeric());
972        if !valid || is_python_keyword(&value) {
973            return Err(invalid_projection(
974                "invalid_python_projection_identifier",
975                "projected Python name is not a bounded non-keyword identifier",
976            ));
977        }
978        Ok(Self(value))
979    }
980
981    /// Validate one ASCII TypeScript identifier under the frozen TypeScript-v1 policy.
982    pub fn typescript(value: impl Into<String>) -> Result<Self, Diagnostic> {
983        let value = value.into();
984        let mut bytes = value.bytes();
985        let valid = value.len() <= MAX_TARGET_IDENTIFIER_BYTES
986            && bytes
987                .next()
988                .is_some_and(|byte| byte == b'_' || byte == b'$' || byte.is_ascii_alphabetic())
989            && bytes.all(|byte| byte == b'_' || byte == b'$' || byte.is_ascii_alphanumeric());
990        if !valid || is_typescript_keyword(&value) {
991            return Err(invalid_projection(
992                "invalid_typescript_projection_identifier",
993                "projected TypeScript name is not a bounded non-keyword identifier",
994            ));
995        }
996        Ok(Self(value))
997    }
998
999    /// Validate one ASCII Rust identifier under the frozen Rust-v1 policy.
1000    pub fn rust(value: impl Into<String>) -> Result<Self, Diagnostic> {
1001        let value = value.into();
1002        let mut bytes = value.bytes();
1003        let valid = value != "_"
1004            && value.len() <= MAX_TARGET_IDENTIFIER_BYTES
1005            && bytes
1006                .next()
1007                .is_some_and(|byte| byte == b'_' || byte.is_ascii_alphabetic())
1008            && bytes.all(|byte| byte == b'_' || byte.is_ascii_alphanumeric());
1009        if !valid || is_rust_keyword(&value) {
1010            return Err(invalid_projection(
1011                "invalid_rust_projection_identifier",
1012                "projected Rust name is not a bounded non-keyword ASCII identifier",
1013            ));
1014        }
1015        Ok(Self(value))
1016    }
1017
1018    /// Validate one ASCII C identifier under the frozen C-v1 policy.
1019    pub fn c(value: impl Into<String>) -> Result<Self, Diagnostic> {
1020        let value = value.into();
1021        if !is_valid_c_identifier(&value, MAX_TARGET_IDENTIFIER_BYTES) {
1022            return Err(invalid_projection(
1023                "invalid_c_projection_identifier",
1024                "projected C name is not a bounded, non-keyword, non-reserved ASCII identifier",
1025            ));
1026        }
1027        Ok(Self(value))
1028    }
1029
1030    /// Return the exact target spelling.
1031    #[must_use]
1032    pub fn as_str(&self) -> &str {
1033        &self.0
1034    }
1035}
1036
1037fn is_python_keyword(value: &str) -> bool {
1038    matches!(
1039        value,
1040        "False"
1041            | "None"
1042            | "True"
1043            | "and"
1044            | "as"
1045            | "assert"
1046            | "async"
1047            | "await"
1048            | "break"
1049            | "case"
1050            | "class"
1051            | "continue"
1052            | "def"
1053            | "del"
1054            | "elif"
1055            | "else"
1056            | "except"
1057            | "finally"
1058            | "for"
1059            | "from"
1060            | "global"
1061            | "if"
1062            | "import"
1063            | "in"
1064            | "is"
1065            | "lambda"
1066            | "match"
1067            | "nonlocal"
1068            | "not"
1069            | "or"
1070            | "pass"
1071            | "raise"
1072            | "return"
1073            | "try"
1074            | "while"
1075            | "with"
1076            | "yield"
1077    )
1078}
1079
1080fn is_typescript_keyword(value: &str) -> bool {
1081    matches!(
1082        value,
1083        "abstract"
1084            | "any"
1085            | "as"
1086            | "asserts"
1087            | "async"
1088            | "await"
1089            | "bigint"
1090            | "boolean"
1091            | "break"
1092            | "case"
1093            | "catch"
1094            | "class"
1095            | "const"
1096            | "constructor"
1097            | "continue"
1098            | "debugger"
1099            | "declare"
1100            | "default"
1101            | "delete"
1102            | "do"
1103            | "else"
1104            | "enum"
1105            | "export"
1106            | "extends"
1107            | "false"
1108            | "finally"
1109            | "for"
1110            | "from"
1111            | "function"
1112            | "get"
1113            | "if"
1114            | "implements"
1115            | "import"
1116            | "in"
1117            | "infer"
1118            | "instanceof"
1119            | "interface"
1120            | "is"
1121            | "keyof"
1122            | "let"
1123            | "module"
1124            | "namespace"
1125            | "never"
1126            | "new"
1127            | "null"
1128            | "number"
1129            | "object"
1130            | "of"
1131            | "out"
1132            | "override"
1133            | "package"
1134            | "private"
1135            | "protected"
1136            | "public"
1137            | "readonly"
1138            | "require"
1139            | "return"
1140            | "satisfies"
1141            | "set"
1142            | "static"
1143            | "string"
1144            | "super"
1145            | "switch"
1146            | "symbol"
1147            | "this"
1148            | "throw"
1149            | "true"
1150            | "try"
1151            | "type"
1152            | "typeof"
1153            | "undefined"
1154            | "unique"
1155            | "unknown"
1156            | "using"
1157            | "var"
1158            | "void"
1159            | "while"
1160            | "with"
1161            | "yield"
1162    )
1163}
1164
1165fn is_rust_keyword(value: &str) -> bool {
1166    matches!(
1167        value,
1168        "Self"
1169            | "abstract"
1170            | "as"
1171            | "async"
1172            | "await"
1173            | "become"
1174            | "box"
1175            | "break"
1176            | "const"
1177            | "continue"
1178            | "crate"
1179            | "do"
1180            | "dyn"
1181            | "else"
1182            | "enum"
1183            | "extern"
1184            | "false"
1185            | "final"
1186            | "fn"
1187            | "for"
1188            | "gen"
1189            | "if"
1190            | "impl"
1191            | "in"
1192            | "let"
1193            | "loop"
1194            | "macro"
1195            | "match"
1196            | "mod"
1197            | "move"
1198            | "mut"
1199            | "override"
1200            | "priv"
1201            | "pub"
1202            | "ref"
1203            | "return"
1204            | "self"
1205            | "static"
1206            | "struct"
1207            | "super"
1208            | "trait"
1209            | "true"
1210            | "try"
1211            | "type"
1212            | "typeof"
1213            | "unsafe"
1214            | "unsized"
1215            | "use"
1216            | "virtual"
1217            | "where"
1218            | "while"
1219            | "yield"
1220    )
1221}
1222
1223fn is_valid_c_identifier(value: &str, max_bytes: usize) -> bool {
1224    let mut bytes = value.bytes();
1225    value.len() <= max_bytes
1226        && bytes.next().is_some_and(|byte| byte.is_ascii_alphabetic())
1227        && bytes.all(|byte| byte == b'_' || byte.is_ascii_alphanumeric())
1228        && !value.contains("__")
1229        && !is_c_keyword(value)
1230}
1231
1232fn is_windows_device_name(value: &str) -> bool {
1233    matches!(
1234        value,
1235        "aux"
1236            | "clock$"
1237            | "con"
1238            | "nul"
1239            | "prn"
1240            | "com1"
1241            | "com2"
1242            | "com3"
1243            | "com4"
1244            | "com5"
1245            | "com6"
1246            | "com7"
1247            | "com8"
1248            | "com9"
1249            | "lpt1"
1250            | "lpt2"
1251            | "lpt3"
1252            | "lpt4"
1253            | "lpt5"
1254            | "lpt6"
1255            | "lpt7"
1256            | "lpt8"
1257            | "lpt9"
1258    )
1259}
1260
1261fn is_c_keyword(value: &str) -> bool {
1262    matches!(
1263        value,
1264        "_Alignas"
1265            | "_Alignof"
1266            | "_Atomic"
1267            | "_BitInt"
1268            | "_Bool"
1269            | "_Complex"
1270            | "_Decimal128"
1271            | "_Decimal32"
1272            | "_Decimal64"
1273            | "_Generic"
1274            | "_Imaginary"
1275            | "_Noreturn"
1276            | "_Static_assert"
1277            | "_Thread_local"
1278            | "alignas"
1279            | "alignof"
1280            | "auto"
1281            | "bool"
1282            | "break"
1283            | "case"
1284            | "char"
1285            | "const"
1286            | "constexpr"
1287            | "continue"
1288            | "default"
1289            | "do"
1290            | "double"
1291            | "else"
1292            | "enum"
1293            | "extern"
1294            | "false"
1295            | "float"
1296            | "for"
1297            | "goto"
1298            | "if"
1299            | "inline"
1300            | "int"
1301            | "long"
1302            | "nullptr"
1303            | "register"
1304            | "restrict"
1305            | "return"
1306            | "short"
1307            | "signed"
1308            | "sizeof"
1309            | "static"
1310            | "static_assert"
1311            | "struct"
1312            | "switch"
1313            | "thread_local"
1314            | "true"
1315            | "typedef"
1316            | "typeof"
1317            | "typeof_unqual"
1318            | "union"
1319            | "unsigned"
1320            | "void"
1321            | "volatile"
1322            | "while"
1323    )
1324}
1325
1326/// Whether a projected model use is complete or a nonrecursive reference.
1327#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
1328#[serde(rename_all = "snake_case")]
1329pub enum ProjectedModelForm {
1330    /// A complete materialized model.
1331    Complete,
1332    /// An identity/reference-only model.
1333    Reference,
1334}
1335
1336/// One typed use of a projected model.
1337#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
1338pub struct ProjectedModelUse {
1339    id: TypeId,
1340    form: ProjectedModelForm,
1341}
1342
1343impl ProjectedModelUse {
1344    /// Construct one typed model use.
1345    #[must_use]
1346    pub const fn new(id: TypeId, form: ProjectedModelForm) -> Self {
1347        Self { id, form }
1348    }
1349    /// Return the model identity.
1350    #[must_use]
1351    pub const fn id(&self) -> &TypeId {
1352        &self.id
1353    }
1354    /// Return the requested materialization form.
1355    #[must_use]
1356    pub const fn form(&self) -> ProjectedModelForm {
1357        self.form
1358    }
1359}
1360
1361/// A fully resolved type position used by projected signatures.
1362#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
1363#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
1364pub enum ProjectedTypeRef {
1365    /// A built-in scalar domain.
1366    Scalar(ValueTypeTag),
1367    /// A model identity and materialization form.
1368    Model(ProjectedModelUse),
1369    /// A schema struct value.
1370    Struct(StructId),
1371}
1372
1373/// Scalar versus collection container shape derived from cardinality.
1374#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
1375#[serde(rename_all = "snake_case")]
1376pub enum ProjectedContainer {
1377    /// At most one value.
1378    Scalar,
1379    /// Multiple values in a generated sequence container.
1380    Sequence,
1381}
1382
1383/// Requiredness and container form derived from one resolved cardinality.
1384#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
1385pub struct ProjectedMultiplicity {
1386    cardinality: Cardinality,
1387    required: bool,
1388    container: ProjectedContainer,
1389    #[serde(skip_serializing_if = "CollectionMode::is_unordered")]
1390    collection_mode: CollectionMode,
1391}
1392
1393impl ProjectedMultiplicity {
1394    /// Derive the compatibility-default unordered shape from resolved cardinality.
1395    #[must_use]
1396    pub const fn from_cardinality(cardinality: Cardinality) -> Self {
1397        Self::new(cardinality, CollectionMode::Unordered)
1398    }
1399    /// Derive an honest input/read shape from cardinality and collection semantics.
1400    #[must_use]
1401    pub const fn new(cardinality: Cardinality, collection_mode: CollectionMode) -> Self {
1402        let container = match collection_mode {
1403            CollectionMode::OrderedList => ProjectedContainer::Sequence,
1404            CollectionMode::Unordered => match cardinality.max() {
1405                Some(0 | 1) => ProjectedContainer::Scalar,
1406                Some(_) | None => ProjectedContainer::Sequence,
1407            },
1408        };
1409        Self {
1410            cardinality,
1411            required: cardinality.min() > 0,
1412            container,
1413            collection_mode,
1414        }
1415    }
1416    /// Return the exact resolved cardinality.
1417    #[must_use]
1418    pub const fn cardinality(&self) -> Cardinality {
1419        self.cardinality
1420    }
1421    /// Report whether the generated field is required.
1422    #[must_use]
1423    pub const fn required(&self) -> bool {
1424        self.required
1425    }
1426    /// Return the generated container category.
1427    #[must_use]
1428    pub const fn container(&self) -> ProjectedContainer {
1429        self.container
1430    }
1431    /// Return the canonical collection semantics.
1432    #[must_use]
1433    pub const fn collection_mode(&self) -> CollectionMode {
1434        self.collection_mode
1435    }
1436}
1437
1438/// One effective annotation retained in runtime projection metadata.
1439///
1440/// The ID names the effective projected subject, not the direct declaration
1441/// from which an inherited value originated. Type, ownership, related-role,
1442/// and playing annotations therefore use the actual projected owner or player.
1443/// For an inherited related role, the annotation-only subject remints the role
1444/// label under the effective relation while [`RoleTokenProjection::role`]
1445/// retains the canonical declaring-role identity.
1446#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1447pub struct ProjectedAnnotation {
1448    id: AnnotationFactId,
1449    value: SchemaAnnotationValue,
1450}
1451
1452impl ProjectedAnnotation {
1453    /// Construct one projected annotation through the authoritative annotation contract.
1454    pub fn new(id: AnnotationFactId, value: SchemaAnnotationValue) -> Result<Self, Diagnostic> {
1455        AnnotationFact::new(id.clone(), value.clone())?;
1456        Ok(Self { id, value })
1457    }
1458    /// Return the effective-subject annotation identity.
1459    #[must_use]
1460    pub const fn id(&self) -> &AnnotationFactId {
1461        &self.id
1462    }
1463    /// Return the effective annotation value.
1464    #[must_use]
1465    pub const fn value(&self) -> &SchemaAnnotationValue {
1466        &self.value
1467    }
1468}
1469
1470/// One owner-branded owned-attribute query token.
1471#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1472pub struct FieldTokenProjection {
1473    id: OwnsFactId,
1474    declaring_id: OwnsFactId,
1475    target_name: TargetIdentifier,
1476    multiplicity: ProjectedMultiplicity,
1477    key: bool,
1478    unique: bool,
1479    #[serde(serialize_with = "serialize_map_values")]
1480    annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
1481}
1482
1483impl FieldTokenProjection {
1484    /// Construct a projected owned-attribute token.
1485    pub fn new(
1486        id: OwnsFactId,
1487        declaring_id: OwnsFactId,
1488        target_name: TargetIdentifier,
1489        multiplicity: ProjectedMultiplicity,
1490        key: bool,
1491        unique: bool,
1492        annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
1493    ) -> Result<Self, Diagnostic> {
1494        if id.attribute() != declaring_id.attribute() {
1495            return Err(invalid_projection(
1496                "invalid_projection_reference",
1497                "effective owns fact attribute does not match declaring owns fact attribute",
1498            ));
1499        }
1500        if annotations.iter().any(|(key, value)| {
1501            key != value.id()
1502                || !matches!(
1503                    value.id().subject(),
1504                    AnnotationSubjectId::Owns(subject) if subject == &id
1505                )
1506        }) {
1507            return Err(invalid_projection(
1508                "invalid_projected_owns_annotation",
1509                "owns annotations require matching exact effective owns subjects",
1510            ));
1511        }
1512        if multiplicity.collection_mode().is_unordered()
1513            && annotations
1514                .keys()
1515                .any(|id| id.kind() == &AnnotationKindId::Distinct)
1516        {
1517            return Err(invalid_projection(
1518                "distinct_requires_ordered_collection",
1519                "distinct applies only to an ordered ownership collection",
1520            ));
1521        }
1522        ensure_collection_limit(annotations.len(), "too_many_projected_annotations")?;
1523        Ok(Self {
1524            id,
1525            declaring_id,
1526            target_name,
1527            multiplicity,
1528            key,
1529            unique,
1530            annotations,
1531        })
1532    }
1533    /// Return the effective ownership identity.
1534    #[must_use]
1535    pub const fn id(&self) -> &OwnsFactId {
1536        &self.id
1537    }
1538    /// Return the declaring ownership identity.
1539    #[must_use]
1540    pub const fn declaring_id(&self) -> &OwnsFactId {
1541        &self.declaring_id
1542    }
1543    /// Return the emitted member name.
1544    #[must_use]
1545    pub const fn target_name(&self) -> &TargetIdentifier {
1546        &self.target_name
1547    }
1548    /// Return resolved requiredness and container shape.
1549    #[must_use]
1550    pub const fn multiplicity(&self) -> ProjectedMultiplicity {
1551        self.multiplicity
1552    }
1553    /// Report key semantics.
1554    #[must_use]
1555    pub const fn is_key(&self) -> bool {
1556        self.key
1557    }
1558    /// Report independent uniqueness semantics.
1559    #[must_use]
1560    pub const fn is_unique(&self) -> bool {
1561        self.unique
1562    }
1563    /// Return effective annotations.
1564    #[must_use]
1565    pub const fn annotations(&self) -> &BTreeMap<AnnotationFactId, ProjectedAnnotation> {
1566        &self.annotations
1567    }
1568}
1569
1570/// One owner-branded related-role query token.
1571#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1572pub struct RoleTokenProjection {
1573    owner: TypeId,
1574    role: RoleId,
1575    target_name: TargetIdentifier,
1576    #[serde(skip_serializing_if = "Option::is_none")]
1577    player_union_target_name: Option<TargetIdentifier>,
1578    accepted_players: BTreeSet<TypeId>,
1579    specializes: Option<RoleId>,
1580    multiplicity: ProjectedMultiplicity,
1581    is_abstract: bool,
1582    #[serde(serialize_with = "serialize_map_values")]
1583    annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
1584}
1585
1586impl RoleTokenProjection {
1587    /// Construct an effective role token for one actual relation owner.
1588    #[allow(clippy::too_many_arguments)]
1589    pub fn new(
1590        owner: TypeId,
1591        role: RoleId,
1592        target_name: TargetIdentifier,
1593        accepted_players: BTreeSet<TypeId>,
1594        specializes: Option<RoleId>,
1595        multiplicity: ProjectedMultiplicity,
1596        is_abstract: bool,
1597        annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
1598    ) -> Result<Self, Diagnostic> {
1599        if owner.kind() != TypeKind::Relation
1600            || accepted_players
1601                .iter()
1602                .any(|id| !matches!(id.kind(), TypeKind::Entity | TypeKind::Relation))
1603        {
1604            return Err(invalid_projection(
1605                "invalid_projected_role_token",
1606                "role tokens require a relation owner and entity/relation players",
1607            ));
1608        }
1609        let effective_role = RoleId::new(
1610            owner.label().as_str().to_owned(),
1611            role.label().as_str().to_owned(),
1612        )?;
1613        let effective_subject =
1614            AnnotationSubjectId::Relates(RelatesFactId::new(owner.clone(), effective_role)?);
1615        if annotations
1616            .iter()
1617            .any(|(key, value)| key != value.id() || value.id().subject() != &effective_subject)
1618        {
1619            return Err(invalid_projection(
1620                "invalid_projected_relates_annotation",
1621                "relates annotations require matching exact effective relates subjects",
1622            ));
1623        }
1624        if multiplicity.collection_mode().is_unordered()
1625            && annotations
1626                .keys()
1627                .any(|id| id.kind() == &AnnotationKindId::Distinct)
1628        {
1629            return Err(invalid_projection(
1630                "distinct_requires_ordered_collection",
1631                "distinct applies only to an ordered related-role collection",
1632            ));
1633        }
1634        ensure_collection_limit(accepted_players.len(), "too_many_projected_role_players")?;
1635        ensure_collection_limit(annotations.len(), "too_many_projected_annotations")?;
1636        Ok(Self {
1637            owner,
1638            role,
1639            target_name,
1640            player_union_target_name: None,
1641            accepted_players,
1642            specializes,
1643            multiplicity,
1644            is_abstract,
1645            annotations,
1646        })
1647    }
1648    /// Attach the explicit native player-union type name.
1649    #[must_use]
1650    pub fn with_player_union_target_name(mut self, target_name: TargetIdentifier) -> Self {
1651        self.player_union_target_name = Some(target_name);
1652        self
1653    }
1654    /// Return the actual relation model that owns the token.
1655    #[must_use]
1656    pub const fn owner(&self) -> &TypeId {
1657        &self.owner
1658    }
1659    /// Return the canonical declaring-role identity.
1660    #[must_use]
1661    pub const fn role(&self) -> &RoleId {
1662        &self.role
1663    }
1664    /// Return the emitted member name.
1665    #[must_use]
1666    pub const fn target_name(&self) -> &TargetIdentifier {
1667        &self.target_name
1668    }
1669    /// Return the explicit native player-union type name, when the target uses one.
1670    #[must_use]
1671    pub const fn player_union_target_name(&self) -> Option<&TargetIdentifier> {
1672        self.player_union_target_name.as_ref()
1673    }
1674    /// Return exact logical player identities.
1675    #[must_use]
1676    pub const fn accepted_players(&self) -> &BTreeSet<TypeId> {
1677        &self.accepted_players
1678    }
1679    /// Return the immediate specialized role, if any.
1680    #[must_use]
1681    pub const fn specializes(&self) -> Option<&RoleId> {
1682        self.specializes.as_ref()
1683    }
1684    /// Return resolved role cardinality shape.
1685    #[must_use]
1686    pub const fn multiplicity(&self) -> ProjectedMultiplicity {
1687        self.multiplicity
1688    }
1689    /// Report whether the role is abstract.
1690    #[must_use]
1691    pub const fn is_abstract(&self) -> bool {
1692        self.is_abstract
1693    }
1694    /// Return effective relates annotations.
1695    #[must_use]
1696    pub const fn annotations(&self) -> &BTreeMap<AnnotationFactId, ProjectedAnnotation> {
1697        &self.annotations
1698    }
1699}
1700
1701/// One direct role declaration and its immediate specialization target.
1702#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1703pub struct DeclaredRoleProjection {
1704    role: RoleId,
1705    specializes: Option<RoleId>,
1706}
1707
1708impl DeclaredRoleProjection {
1709    /// Construct one direct projected role declaration.
1710    #[must_use]
1711    pub const fn new(role: RoleId, specializes: Option<RoleId>) -> Self {
1712        Self { role, specializes }
1713    }
1714    /// Return the declared role.
1715    #[must_use]
1716    pub const fn role(&self) -> &RoleId {
1717        &self.role
1718    }
1719    /// Return its immediate specialization target.
1720    #[must_use]
1721    pub const fn specializes(&self) -> Option<&RoleId> {
1722        self.specializes.as_ref()
1723    }
1724}
1725
1726/// The exact direct subtype declaration retained by every runtime projection.
1727#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1728pub struct DirectSubProjection {
1729    id: SubFactId,
1730    origin: SchemaFactId,
1731    #[serde(serialize_with = "serialize_map_values")]
1732    annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
1733}
1734
1735impl DirectSubProjection {
1736    /// Construct one direct subtype declaration from resolved schema values.
1737    pub fn new(
1738        id: SubFactId,
1739        origin: SchemaFactId,
1740        annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
1741    ) -> Result<Self, Diagnostic> {
1742        if origin != SchemaFactId::Sub(id.clone()) {
1743            return Err(invalid_projection(
1744                "invalid_projected_sub_origin",
1745                "projected subtype origin must identify its exact direct edge",
1746            ));
1747        }
1748        if annotations.iter().any(|(key, value)| {
1749            key != value.id()
1750                || !matches!(key.subject(), AnnotationSubjectId::Sub(subject) if subject == &id)
1751        }) {
1752            return Err(invalid_projection(
1753                "invalid_projected_sub_annotation",
1754                "subtype annotations require matching exact edge subjects",
1755            ));
1756        }
1757        ensure_collection_limit(annotations.len(), "too_many_projected_annotations")?;
1758        Ok(Self {
1759            id,
1760            origin,
1761            annotations,
1762        })
1763    }
1764
1765    /// Return the exact direct subtype-edge identity.
1766    #[must_use]
1767    pub const fn id(&self) -> &SubFactId {
1768        &self.id
1769    }
1770
1771    /// Return the direct declaration origin.
1772    #[must_use]
1773    pub const fn origin(&self) -> &SchemaFactId {
1774        &self.origin
1775    }
1776
1777    /// Return annotations attached to this exact subtype edge.
1778    #[must_use]
1779    pub const fn annotations(&self) -> &BTreeMap<AnnotationFactId, ProjectedAnnotation> {
1780        &self.annotations
1781    }
1782}
1783
1784/// The nominal declaration facet of one model.
1785#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1786pub struct DeclarationProjection {
1787    parent: Option<TypeId>,
1788    // Pre-release wire ledger: before any 2.0.0 artifact shipped,
1789    // binding-projection/v1 gained the exact direct subtype declaration.
1790    direct_sub: Option<DirectSubProjection>,
1791    value_type: Option<ValueTypeTag>,
1792    is_abstract: bool,
1793    is_constructible: bool,
1794    #[serde(serialize_with = "serialize_map_values")]
1795    annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
1796    #[serde(serialize_with = "serialize_map_values")]
1797    value_annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
1798    direct_fields: Vec<OwnsFactId>,
1799    #[serde(serialize_with = "serialize_map_values")]
1800    direct_roles: BTreeMap<RoleId, DeclaredRoleProjection>,
1801    direct_plays: BTreeSet<PlaysFactId>,
1802}
1803
1804impl DeclarationProjection {
1805    /// Construct one declaration facet from direct attachment identities.
1806    #[allow(clippy::too_many_arguments)]
1807    pub fn new(
1808        parent: Option<TypeId>,
1809        value_type: Option<ValueTypeTag>,
1810        is_abstract: bool,
1811        is_constructible: bool,
1812        annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
1813        direct_fields: Vec<OwnsFactId>,
1814        direct_roles: BTreeMap<RoleId, DeclaredRoleProjection>,
1815        direct_plays: BTreeSet<PlaysFactId>,
1816    ) -> Result<Self, Diagnostic> {
1817        for length in [
1818            annotations.len(),
1819            direct_fields.len(),
1820            direct_roles.len(),
1821            direct_plays.len(),
1822        ] {
1823            ensure_collection_limit(length, "projection_declaration_limit_exceeded")?;
1824        }
1825        Ok(Self {
1826            parent,
1827            direct_sub: None,
1828            value_type,
1829            is_abstract,
1830            is_constructible,
1831            annotations,
1832            value_annotations: BTreeMap::new(),
1833            direct_fields,
1834            direct_roles,
1835            direct_plays,
1836        })
1837    }
1838    /// Attach the direct subtype identity, origin, and annotations.
1839    pub fn with_direct_sub(
1840        mut self,
1841        direct_sub: Option<DirectSubProjection>,
1842    ) -> Result<Self, Diagnostic> {
1843        if direct_sub
1844            .as_ref()
1845            .is_some_and(|sub| self.parent.as_ref() != Some(sub.id().supertype()))
1846        {
1847            return Err(invalid_projection(
1848                "invalid_projected_sub_parent",
1849                "projected direct subtype edge must match the nominal parent",
1850            ));
1851        }
1852        self.direct_sub = direct_sub;
1853        Ok(self)
1854    }
1855    /// Attach effective attribute-value constraints without changing legacy construction.
1856    pub fn with_value_annotations(
1857        mut self,
1858        annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
1859    ) -> Result<Self, Diagnostic> {
1860        if annotations.iter().any(|(key, value)| {
1861            key != value.id() || !matches!(key.subject(), AnnotationSubjectId::Value(_))
1862        }) {
1863            return Err(invalid_projection(
1864                "invalid_projected_value_annotation",
1865                "attribute value annotations require matching effective value subjects",
1866            ));
1867        }
1868        ensure_collection_limit(annotations.len(), "too_many_projected_annotations")?;
1869        self.value_annotations = annotations;
1870        Ok(self)
1871    }
1872    /// Return the direct nominal parent.
1873    #[must_use]
1874    pub const fn parent(&self) -> Option<&TypeId> {
1875        self.parent.as_ref()
1876    }
1877    /// Return the exact direct subtype declaration, if this model has a parent.
1878    #[must_use]
1879    pub const fn direct_sub(&self) -> Option<&DirectSubProjection> {
1880        self.direct_sub.as_ref()
1881    }
1882    /// Return an attribute's effective scalar domain.
1883    #[must_use]
1884    pub const fn value_type(&self) -> Option<ValueTypeTag> {
1885        self.value_type
1886    }
1887    /// Report abstractness.
1888    #[must_use]
1889    pub const fn is_abstract(&self) -> bool {
1890        self.is_abstract
1891    }
1892    /// Report constructibility.
1893    #[must_use]
1894    pub const fn is_constructible(&self) -> bool {
1895        self.is_constructible
1896    }
1897    /// Return effective type annotations.
1898    #[must_use]
1899    pub const fn annotations(&self) -> &BTreeMap<AnnotationFactId, ProjectedAnnotation> {
1900        &self.annotations
1901    }
1902    /// Return effective attribute-value constraints.
1903    #[must_use]
1904    pub const fn value_annotations(&self) -> &BTreeMap<AnnotationFactId, ProjectedAnnotation> {
1905        &self.value_annotations
1906    }
1907    /// Return direct ownership attachment identities in semantic order.
1908    #[must_use]
1909    pub fn direct_fields(&self) -> &[OwnsFactId] {
1910        &self.direct_fields
1911    }
1912    /// Return direct role declarations.
1913    #[must_use]
1914    pub const fn direct_roles(&self) -> &BTreeMap<RoleId, DeclaredRoleProjection> {
1915        &self.direct_roles
1916    }
1917    /// Return direct role-playing attachments.
1918    #[must_use]
1919    pub const fn direct_plays(&self) -> &BTreeSet<PlaysFactId> {
1920        &self.direct_plays
1921    }
1922}
1923
1924/// One owned-attribute input in a create facet.
1925#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1926pub struct CreateFieldProjection {
1927    token: OwnsFactId,
1928    value: ProjectedTypeRef,
1929    multiplicity: ProjectedMultiplicity,
1930}
1931
1932impl CreateFieldProjection {
1933    /// Construct one create input.
1934    #[must_use]
1935    pub const fn new(
1936        token: OwnsFactId,
1937        value: ProjectedTypeRef,
1938        multiplicity: ProjectedMultiplicity,
1939    ) -> Self {
1940        Self {
1941            token,
1942            value,
1943            multiplicity,
1944        }
1945    }
1946    /// Return the field token identity.
1947    #[must_use]
1948    pub const fn token(&self) -> &OwnsFactId {
1949        &self.token
1950    }
1951    /// Return the accepted input value type.
1952    #[must_use]
1953    pub const fn value(&self) -> &ProjectedTypeRef {
1954        &self.value
1955    }
1956    /// Return input requiredness/container shape.
1957    #[must_use]
1958    pub const fn multiplicity(&self) -> ProjectedMultiplicity {
1959        self.multiplicity
1960    }
1961}
1962
1963/// One active related-role input in a create facet.
1964#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
1965pub struct CreateRoleProjection {
1966    role: RoleId,
1967    players: BTreeSet<ProjectedModelUse>,
1968    multiplicity: ProjectedMultiplicity,
1969}
1970
1971impl CreateRoleProjection {
1972    /// Construct one role input from its exact accepted player uses.
1973    pub fn new(
1974        role: RoleId,
1975        players: BTreeSet<ProjectedModelUse>,
1976        multiplicity: ProjectedMultiplicity,
1977    ) -> Result<Self, Diagnostic> {
1978        ensure_collection_limit(players.len(), "too_many_projected_role_players")?;
1979        Ok(Self {
1980            role,
1981            players,
1982            multiplicity,
1983        })
1984    }
1985    /// Return the canonical role identity.
1986    #[must_use]
1987    pub const fn role(&self) -> &RoleId {
1988        &self.role
1989    }
1990    /// Return exact accepted player forms.
1991    #[must_use]
1992    pub const fn players(&self) -> &BTreeSet<ProjectedModelUse> {
1993        &self.players
1994    }
1995    /// Return input requiredness/container shape.
1996    #[must_use]
1997    pub const fn multiplicity(&self) -> ProjectedMultiplicity {
1998        self.multiplicity
1999    }
2000}
2001
2002/// The exact generated constructor facet.
2003#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
2004pub struct CreateProjection {
2005    #[serde(skip_serializing_if = "Option::is_none")]
2006    target_name: Option<TargetIdentifier>,
2007    enabled: bool,
2008    fields: Vec<CreateFieldProjection>,
2009    #[serde(serialize_with = "serialize_map_values")]
2010    roles: BTreeMap<RoleId, CreateRoleProjection>,
2011}
2012
2013impl CreateProjection {
2014    /// Construct one exact constructor shape.
2015    pub fn new(
2016        enabled: bool,
2017        fields: Vec<CreateFieldProjection>,
2018        roles: BTreeMap<RoleId, CreateRoleProjection>,
2019    ) -> Result<Self, Diagnostic> {
2020        ensure_collection_limit(fields.len(), "too_many_projected_create_fields")?;
2021        ensure_collection_limit(roles.len(), "too_many_projected_create_roles")?;
2022        Ok(Self {
2023            target_name: None,
2024            enabled,
2025            fields,
2026            roles,
2027        })
2028    }
2029    /// Attach the explicit generated create-input type name.
2030    #[must_use]
2031    pub fn with_target_name(mut self, target_name: TargetIdentifier) -> Self {
2032        self.target_name = Some(target_name);
2033        self
2034    }
2035    /// Return the generated create-input type name, when construction is exposed.
2036    #[must_use]
2037    pub const fn target_name(&self) -> Option<&TargetIdentifier> {
2038        self.target_name.as_ref()
2039    }
2040    /// Report whether generated construction is available.
2041    #[must_use]
2042    pub const fn enabled(&self) -> bool {
2043        self.enabled
2044    }
2045    /// Return owned-attribute inputs in semantic order.
2046    #[must_use]
2047    pub fn fields(&self) -> &[CreateFieldProjection] {
2048        &self.fields
2049    }
2050    /// Return active role inputs.
2051    #[must_use]
2052    pub const fn roles(&self) -> &BTreeMap<RoleId, CreateRoleProjection> {
2053        &self.roles
2054    }
2055}
2056
2057/// One owned-attribute field in a complete read facet.
2058#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
2059pub struct ReadFieldProjection {
2060    token: OwnsFactId,
2061    value: ProjectedTypeRef,
2062    multiplicity: ProjectedMultiplicity,
2063}
2064
2065impl ReadFieldProjection {
2066    /// Construct one complete-read field.
2067    #[must_use]
2068    pub const fn new(
2069        token: OwnsFactId,
2070        value: ProjectedTypeRef,
2071        multiplicity: ProjectedMultiplicity,
2072    ) -> Self {
2073        Self {
2074            token,
2075            value,
2076            multiplicity,
2077        }
2078    }
2079    /// Return the field token identity.
2080    #[must_use]
2081    pub const fn token(&self) -> &OwnsFactId {
2082        &self.token
2083    }
2084    /// Return the read value type.
2085    #[must_use]
2086    pub const fn value(&self) -> &ProjectedTypeRef {
2087        &self.value
2088    }
2089    /// Return read container shape.
2090    #[must_use]
2091    pub const fn multiplicity(&self) -> ProjectedMultiplicity {
2092        self.multiplicity
2093    }
2094}
2095
2096/// One active role field in a complete read facet.
2097#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
2098pub struct ReadRoleProjection {
2099    role: RoleId,
2100    players: BTreeSet<ProjectedModelUse>,
2101    multiplicity: ProjectedMultiplicity,
2102}
2103
2104impl ReadRoleProjection {
2105    /// Construct one complete-read role field.
2106    pub fn new(
2107        role: RoleId,
2108        players: BTreeSet<ProjectedModelUse>,
2109        multiplicity: ProjectedMultiplicity,
2110    ) -> Result<Self, Diagnostic> {
2111        ensure_collection_limit(players.len(), "too_many_projected_role_players")?;
2112        Ok(Self {
2113            role,
2114            players,
2115            multiplicity,
2116        })
2117    }
2118    /// Return the canonical role identity.
2119    #[must_use]
2120    pub const fn role(&self) -> &RoleId {
2121        &self.role
2122    }
2123    /// Return exact read player forms.
2124    #[must_use]
2125    pub const fn players(&self) -> &BTreeSet<ProjectedModelUse> {
2126        &self.players
2127    }
2128    /// Return read container shape.
2129    #[must_use]
2130    pub const fn multiplicity(&self) -> ProjectedMultiplicity {
2131        self.multiplicity
2132    }
2133}
2134
2135/// The complete materialized read facet.
2136#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
2137pub struct CompleteReadProjection {
2138    fields: Vec<ReadFieldProjection>,
2139    #[serde(serialize_with = "serialize_map_values")]
2140    roles: BTreeMap<RoleId, ReadRoleProjection>,
2141    nominal_upcasts: Vec<TypeId>,
2142    #[serde(serialize_with = "serialize_role_upcasts")]
2143    role_upcasts: BTreeMap<RoleId, Vec<RoleId>>,
2144}
2145
2146impl CompleteReadProjection {
2147    /// Construct one complete-read shape.
2148    pub fn new(
2149        fields: Vec<ReadFieldProjection>,
2150        roles: BTreeMap<RoleId, ReadRoleProjection>,
2151        nominal_upcasts: Vec<TypeId>,
2152    ) -> Result<Self, Diagnostic> {
2153        for length in [fields.len(), roles.len(), nominal_upcasts.len()] {
2154            ensure_collection_limit(length, "projection_read_limit_exceeded")?;
2155        }
2156        Ok(Self {
2157            fields,
2158            roles,
2159            nominal_upcasts,
2160            role_upcasts: BTreeMap::new(),
2161        })
2162    }
2163    /// Attach active-child-role to ordered ancestor-role read mappings.
2164    pub fn with_role_upcasts(
2165        mut self,
2166        role_upcasts: BTreeMap<RoleId, Vec<RoleId>>,
2167    ) -> Result<Self, Diagnostic> {
2168        if role_upcasts.iter().any(|(role, ancestors)| {
2169            !self.roles.contains_key(role)
2170                || ancestors.is_empty()
2171                || ancestors.iter().collect::<BTreeSet<_>>().len() != ancestors.len()
2172        }) {
2173            return Err(invalid_projection(
2174                "invalid_projected_role_upcast",
2175                "role upcasts require an active role and unique non-empty ancestor roles",
2176            ));
2177        }
2178        ensure_collection_limit(role_upcasts.len(), "projection_read_limit_exceeded")?;
2179        self.role_upcasts = role_upcasts;
2180        Ok(self)
2181    }
2182    /// Return owned-attribute fields in semantic order.
2183    #[must_use]
2184    pub fn fields(&self) -> &[ReadFieldProjection] {
2185        &self.fields
2186    }
2187    /// Return active role fields.
2188    #[must_use]
2189    pub const fn roles(&self) -> &BTreeMap<RoleId, ReadRoleProjection> {
2190        &self.roles
2191    }
2192    /// Return legal nominal upcast model identities, nearest first.
2193    #[must_use]
2194    pub fn nominal_upcasts(&self) -> &[TypeId] {
2195        &self.nominal_upcasts
2196    }
2197    /// Return specialized child-role to nearest-first ancestor-role mappings.
2198    #[must_use]
2199    pub const fn role_upcasts(&self) -> &BTreeMap<RoleId, Vec<RoleId>> {
2200        &self.role_upcasts
2201    }
2202}
2203
2204/// How a binding may construct a nonrecursive reference value.
2205#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
2206#[serde(rename_all = "snake_case")]
2207pub enum ReferenceConstructionPolicy {
2208    /// Only an engine IID may construct a reference in the initial runtime contract.
2209    IidOnly,
2210    /// Reference construction admits typed key fallback.
2211    KeyFallback,
2212}
2213
2214/// The nonrecursive identity/reference read facet.
2215#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
2216pub struct ReferenceReadProjection {
2217    target_name: Option<TargetIdentifier>,
2218    key_fields: Vec<OwnsFactId>,
2219    construction_policy: ReferenceConstructionPolicy,
2220}
2221
2222impl ReferenceReadProjection {
2223    /// Construct a reference shape; attributes deliberately have no reference class.
2224    pub fn new(
2225        target_name: Option<TargetIdentifier>,
2226        key_fields: Vec<OwnsFactId>,
2227    ) -> Result<Self, Diagnostic> {
2228        ensure_collection_limit(key_fields.len(), "too_many_projected_reference_keys")?;
2229        let mut unique = BTreeSet::new();
2230        if key_fields.iter().any(|id| !unique.insert(id)) {
2231            return Err(invalid_projection(
2232                "duplicate_projected_reference_key",
2233                "reference key identities must be unique",
2234            ));
2235        }
2236        let construction_policy = if key_fields.is_empty() {
2237            ReferenceConstructionPolicy::IidOnly
2238        } else {
2239            ReferenceConstructionPolicy::KeyFallback
2240        };
2241        Ok(Self {
2242            target_name,
2243            key_fields,
2244            construction_policy,
2245        })
2246    }
2247    /// Return the generated reference type name, if supported.
2248    #[must_use]
2249    pub const fn target_name(&self) -> Option<&TargetIdentifier> {
2250        self.target_name.as_ref()
2251    }
2252    /// Return effective key fields in semantic order.
2253    #[must_use]
2254    pub fn key_fields(&self) -> &[OwnsFactId] {
2255        &self.key_fields
2256    }
2257    /// Return the explicit checked reference-construction policy.
2258    #[must_use]
2259    pub const fn construction_policy(&self) -> ReferenceConstructionPolicy {
2260        self.construction_policy
2261    }
2262}
2263
2264/// Schema-only query tokens for one projected model.
2265#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
2266pub struct QueryTokenProjection {
2267    type_id: TypeId,
2268    #[serde(skip_serializing_if = "Option::is_none")]
2269    target_name: Option<TargetIdentifier>,
2270    #[serde(serialize_with = "serialize_map_values")]
2271    fields: BTreeMap<OwnsFactId, FieldTokenProjection>,
2272    #[serde(serialize_with = "serialize_map_values")]
2273    roles: BTreeMap<RoleId, RoleTokenProjection>,
2274}
2275
2276impl QueryTokenProjection {
2277    /// Construct schema-only tokens without query-plan or invocation state.
2278    pub fn new(
2279        type_id: TypeId,
2280        fields: BTreeMap<OwnsFactId, FieldTokenProjection>,
2281        roles: BTreeMap<RoleId, RoleTokenProjection>,
2282    ) -> Result<Self, Diagnostic> {
2283        ensure_collection_limit(fields.len(), "too_many_projected_field_tokens")?;
2284        ensure_collection_limit(roles.len(), "too_many_projected_role_tokens")?;
2285        Ok(Self {
2286            type_id,
2287            target_name: None,
2288            fields,
2289            roles,
2290        })
2291    }
2292    /// Attach the explicit nominal type/query-token name.
2293    #[must_use]
2294    pub fn with_target_name(mut self, target_name: TargetIdentifier) -> Self {
2295        self.target_name = Some(target_name);
2296        self
2297    }
2298    /// Return the model type token.
2299    #[must_use]
2300    pub const fn type_id(&self) -> &TypeId {
2301        &self.type_id
2302    }
2303    /// Return the explicit nominal type/query-token name, when the target uses one.
2304    #[must_use]
2305    pub const fn target_name(&self) -> Option<&TargetIdentifier> {
2306        self.target_name.as_ref()
2307    }
2308    /// Return owner-branded owned-attribute tokens.
2309    #[must_use]
2310    pub const fn fields(&self) -> &BTreeMap<OwnsFactId, FieldTokenProjection> {
2311        &self.fields
2312    }
2313    /// Return owner-branded role tokens.
2314    #[must_use]
2315    pub const fn roles(&self) -> &BTreeMap<RoleId, RoleTokenProjection> {
2316        &self.roles
2317    }
2318}
2319
2320/// All five runtime facets for one resolved schema type.
2321#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
2322pub struct ModelProjection {
2323    id: TypeId,
2324    target_name: TargetIdentifier,
2325    declaration: DeclarationProjection,
2326    create: CreateProjection,
2327    complete_read: CompleteReadProjection,
2328    reference_read: ReferenceReadProjection,
2329    query_tokens: QueryTokenProjection,
2330}
2331
2332impl ModelProjection {
2333    /// Construct and cross-check all five facets for one model.
2334    #[allow(clippy::too_many_arguments)]
2335    pub fn new(
2336        id: TypeId,
2337        target_name: TargetIdentifier,
2338        declaration: DeclarationProjection,
2339        create: CreateProjection,
2340        complete_read: CompleteReadProjection,
2341        reference_read: ReferenceReadProjection,
2342        query_tokens: QueryTokenProjection,
2343    ) -> Result<Self, Diagnostic> {
2344        let exact_required_scalar = |multiplicity: ProjectedMultiplicity| {
2345            multiplicity.required()
2346                && multiplicity.container() == ProjectedContainer::Scalar
2347                && multiplicity.cardinality().min() == 1
2348                && multiplicity.cardinality().max() == Some(1)
2349        };
2350        let reference_keys_valid = reference_read.key_fields().iter().all(|key| {
2351            let Some(token) = query_tokens.fields().get(key) else {
2352                return false;
2353            };
2354            if !token.is_key() || !exact_required_scalar(token.multiplicity()) {
2355                return false;
2356            }
2357            let mut complete = complete_read
2358                .fields()
2359                .iter()
2360                .filter(|field| field.token() == key);
2361            let Some(field) = complete.next() else {
2362                return false;
2363            };
2364            if complete.next().is_some() || !exact_required_scalar(field.multiplicity()) {
2365                return false;
2366            }
2367            matches!(
2368                field.value(),
2369                ProjectedTypeRef::Model(value)
2370                    if value.form() == ProjectedModelForm::Complete
2371                        && value.id().kind() == TypeKind::Attribute
2372                        && value.id().label() == key.attribute().label()
2373            )
2374        });
2375        if (!reference_read.key_fields().is_empty() && reference_read.target_name().is_none())
2376            || !reference_keys_valid
2377        {
2378            return Err(invalid_projection(
2379                "invalid_projected_reference_key",
2380                "reference keys require exact required-scalar complete/query key facets",
2381            ));
2382        }
2383        let field_multiplicities_match = create.fields().iter().all(|field| {
2384            query_tokens
2385                .fields()
2386                .get(field.token())
2387                .is_some_and(|token| token.multiplicity() == field.multiplicity())
2388        }) && complete_read.fields().iter().all(|field| {
2389            query_tokens
2390                .fields()
2391                .get(field.token())
2392                .is_some_and(|token| token.multiplicity() == field.multiplicity())
2393        });
2394        let role_multiplicities_match = create.roles().iter().all(|(id, role)| {
2395            query_tokens
2396                .roles()
2397                .get(id)
2398                .is_some_and(|token| token.multiplicity() == role.multiplicity())
2399        }) && complete_read.roles().iter().all(|(id, role)| {
2400            query_tokens
2401                .roles()
2402                .get(id)
2403                .is_some_and(|token| token.multiplicity() == role.multiplicity())
2404        });
2405        if !field_multiplicities_match || !role_multiplicities_match {
2406            return Err(invalid_projection(
2407                "projected_multiplicity_mismatch",
2408                "create, complete-read, and query-token facets require exact multiplicity equality",
2409            ));
2410        }
2411        if query_tokens.type_id() != &id
2412            || match (declaration.parent(), declaration.direct_sub()) {
2413                (None, None) => false,
2414                (Some(parent), Some(sub)) => {
2415                    sub.id().subtype() != &id || sub.id().supertype() != parent
2416                }
2417                (None, Some(_)) | (Some(_), None) => true,
2418            }
2419            || query_tokens
2420                .fields()
2421                .values()
2422                .any(|field| field.id().owner() != &id)
2423            || query_tokens
2424                .roles()
2425                .values()
2426                .any(|role| role.owner() != &id)
2427            || create
2428                .fields()
2429                .iter()
2430                .any(|field| !query_tokens.fields().contains_key(field.token()))
2431            || complete_read
2432                .fields()
2433                .iter()
2434                .any(|field| !query_tokens.fields().contains_key(field.token()))
2435            || create
2436                .roles()
2437                .iter()
2438                .any(|(id, role)| id != role.role() || !query_tokens.roles().contains_key(id))
2439            || complete_read
2440                .roles()
2441                .iter()
2442                .any(|(id, role)| id != role.role() || !query_tokens.roles().contains_key(id))
2443        {
2444            return Err(invalid_projection(
2445                "invalid_model_projection_reference",
2446                "model facets contain a mismatched owner or token reference",
2447            ));
2448        }
2449        Ok(Self {
2450            id,
2451            target_name,
2452            declaration,
2453            create,
2454            complete_read,
2455            reference_read,
2456            query_tokens,
2457        })
2458    }
2459    /// Return the schema model identity.
2460    #[must_use]
2461    pub const fn id(&self) -> &TypeId {
2462        &self.id
2463    }
2464    /// Return the emitted nominal name.
2465    #[must_use]
2466    pub const fn target_name(&self) -> &TargetIdentifier {
2467        &self.target_name
2468    }
2469    /// Return the nominal declaration facet.
2470    #[must_use]
2471    pub const fn declaration(&self) -> &DeclarationProjection {
2472        &self.declaration
2473    }
2474    /// Return the create facet.
2475    #[must_use]
2476    pub const fn create(&self) -> &CreateProjection {
2477        &self.create
2478    }
2479    /// Return the complete-read facet.
2480    #[must_use]
2481    pub const fn complete_read(&self) -> &CompleteReadProjection {
2482        &self.complete_read
2483    }
2484    /// Return the reference-read facet.
2485    #[must_use]
2486    pub const fn reference_read(&self) -> &ReferenceReadProjection {
2487        &self.reference_read
2488    }
2489    /// Return schema-only query tokens.
2490    #[must_use]
2491    pub const fn query_tokens(&self) -> &QueryTokenProjection {
2492        &self.query_tokens
2493    }
2494}
2495
2496/// One ordered struct field and its emitted identifier.
2497#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
2498pub struct StructFieldProjection {
2499    name: Label,
2500    target_name: TargetIdentifier,
2501    value_type: ValueTypeTag,
2502    optional: bool,
2503}
2504
2505impl StructFieldProjection {
2506    /// Construct one struct value field.
2507    #[must_use]
2508    pub const fn new(
2509        name: Label,
2510        target_name: TargetIdentifier,
2511        value_type: ValueTypeTag,
2512        optional: bool,
2513    ) -> Self {
2514        Self {
2515            name,
2516            target_name,
2517            value_type,
2518            optional,
2519        }
2520    }
2521    /// Return the schema field name.
2522    #[must_use]
2523    pub const fn name(&self) -> &Label {
2524        &self.name
2525    }
2526    /// Return the emitted field name.
2527    #[must_use]
2528    pub const fn target_name(&self) -> &TargetIdentifier {
2529        &self.target_name
2530    }
2531    /// Return the scalar value domain.
2532    #[must_use]
2533    pub const fn value_type(&self) -> ValueTypeTag {
2534        self.value_type
2535    }
2536    /// Report optionality.
2537    #[must_use]
2538    pub const fn optional(&self) -> bool {
2539        self.optional
2540    }
2541}
2542
2543/// One projected schema struct value type.
2544#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
2545pub struct StructProjection {
2546    id: StructId,
2547    target_name: TargetIdentifier,
2548    fields: Vec<StructFieldProjection>,
2549}
2550
2551impl StructProjection {
2552    /// Construct one ordered struct projection.
2553    pub fn new(
2554        id: StructId,
2555        target_name: TargetIdentifier,
2556        fields: Vec<StructFieldProjection>,
2557    ) -> Result<Self, Diagnostic> {
2558        ensure_collection_limit(fields.len(), "too_many_projected_struct_fields")?;
2559        Ok(Self {
2560            id,
2561            target_name,
2562            fields,
2563        })
2564    }
2565    /// Return the struct identity.
2566    #[must_use]
2567    pub const fn id(&self) -> &StructId {
2568        &self.id
2569    }
2570    /// Return the emitted value-type name.
2571    #[must_use]
2572    pub const fn target_name(&self) -> &TargetIdentifier {
2573        &self.target_name
2574    }
2575    /// Return fields in semantic declaration order.
2576    #[must_use]
2577    pub fn fields(&self) -> &[StructFieldProjection] {
2578        &self.fields
2579    }
2580}
2581
2582/// One ordered projected function parameter.
2583#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
2584pub struct FunctionParameterProjection {
2585    name: Label,
2586    target_name: TargetIdentifier,
2587    type_ref: ProjectedTypeRef,
2588}
2589
2590impl FunctionParameterProjection {
2591    /// Construct one typed function parameter.
2592    #[must_use]
2593    pub const fn new(
2594        name: Label,
2595        target_name: TargetIdentifier,
2596        type_ref: ProjectedTypeRef,
2597    ) -> Self {
2598        Self {
2599            name,
2600            target_name,
2601            type_ref,
2602        }
2603    }
2604    /// Return the schema parameter name.
2605    #[must_use]
2606    pub const fn name(&self) -> &Label {
2607        &self.name
2608    }
2609    /// Return the emitted parameter name.
2610    #[must_use]
2611    pub const fn target_name(&self) -> &TargetIdentifier {
2612        &self.target_name
2613    }
2614    /// Return the exact resolved parameter type.
2615    #[must_use]
2616    pub const fn type_ref(&self) -> &ProjectedTypeRef {
2617        &self.type_ref
2618    }
2619}
2620
2621/// One projected function return element.
2622#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
2623pub struct FunctionReturnElementProjection {
2624    type_ref: ProjectedTypeRef,
2625    optional: bool,
2626}
2627
2628impl FunctionReturnElementProjection {
2629    /// Construct one return element.
2630    #[must_use]
2631    pub const fn new(type_ref: ProjectedTypeRef, optional: bool) -> Self {
2632        Self { type_ref, optional }
2633    }
2634    /// Return the exact resolved result type.
2635    #[must_use]
2636    pub const fn type_ref(&self) -> &ProjectedTypeRef {
2637        &self.type_ref
2638    }
2639    /// Report optionality.
2640    #[must_use]
2641    pub const fn optional(&self) -> bool {
2642        self.optional
2643    }
2644}
2645
2646/// Projected scalar, tuple, or stream function return shape.
2647#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
2648#[serde(tag = "kind", content = "elements", rename_all = "snake_case")]
2649pub enum FunctionReturnProjection {
2650    /// One scalar result element.
2651    Scalar(FunctionReturnElementProjection),
2652    /// Two or more ordered tuple elements.
2653    Tuple(Vec<FunctionReturnElementProjection>),
2654    /// One or more ordered stream-row elements.
2655    Stream(Vec<FunctionReturnElementProjection>),
2656}
2657
2658/// A schema-only typed function token/reference.
2659#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
2660pub struct FunctionProjection {
2661    id: FunctionId,
2662    target_name: TargetIdentifier,
2663    parameters: Vec<FunctionParameterProjection>,
2664    returns: FunctionReturnProjection,
2665    #[serde(serialize_with = "serialize_map_values")]
2666    annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
2667}
2668
2669impl FunctionProjection {
2670    /// Construct a function token without translating its body.
2671    pub fn new(
2672        id: FunctionId,
2673        target_name: TargetIdentifier,
2674        parameters: Vec<FunctionParameterProjection>,
2675        returns: FunctionReturnProjection,
2676    ) -> Result<Self, Diagnostic> {
2677        ensure_collection_limit(parameters.len(), "too_many_projected_function_parameters")?;
2678        Ok(Self {
2679            id,
2680            target_name,
2681            parameters,
2682            returns,
2683            annotations: BTreeMap::new(),
2684        })
2685    }
2686    /// Attach effective function documentation and metadata.
2687    pub fn with_annotations(
2688        mut self,
2689        annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
2690    ) -> Result<Self, Diagnostic> {
2691        if annotations.iter().any(|(key, value)| {
2692            key != value.id() || key.subject() != &AnnotationSubjectId::Function(self.id.clone())
2693        }) {
2694            return Err(invalid_projection(
2695                "invalid_projected_function_annotation",
2696                "function annotations require the projected function subject",
2697            ));
2698        }
2699        ensure_collection_limit(annotations.len(), "too_many_projected_annotations")?;
2700        self.annotations = annotations;
2701        Ok(self)
2702    }
2703    /// Return the function identity.
2704    #[must_use]
2705    pub const fn id(&self) -> &FunctionId {
2706        &self.id
2707    }
2708    /// Return the emitted function-token name.
2709    #[must_use]
2710    pub const fn target_name(&self) -> &TargetIdentifier {
2711        &self.target_name
2712    }
2713    /// Return parameters in signature order.
2714    #[must_use]
2715    pub fn parameters(&self) -> &[FunctionParameterProjection] {
2716        &self.parameters
2717    }
2718    /// Return the native return shape.
2719    #[must_use]
2720    pub const fn returns(&self) -> &FunctionReturnProjection {
2721        &self.returns
2722    }
2723    /// Return function documentation and metadata.
2724    #[must_use]
2725    pub const fn annotations(&self) -> &BTreeMap<AnnotationFactId, ProjectedAnnotation> {
2726        &self.annotations
2727    }
2728}
2729
2730/// Effective per-player metadata keyed independently from shared role tokens.
2731#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
2732pub struct PlayingProjection {
2733    id: PlaysFactId,
2734    role: RoleId,
2735    target_name: Option<TargetIdentifier>,
2736    multiplicity: ProjectedMultiplicity,
2737    #[serde(serialize_with = "serialize_map_values")]
2738    annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
2739}
2740
2741impl PlayingProjection {
2742    /// Construct metadata for one exact effective playing edge.
2743    pub fn new(
2744        id: PlaysFactId,
2745        role: RoleId,
2746        multiplicity: ProjectedMultiplicity,
2747        annotations: BTreeMap<AnnotationFactId, ProjectedAnnotation>,
2748    ) -> Result<Self, Diagnostic> {
2749        if !multiplicity.collection_mode().is_unordered() {
2750            return Err(invalid_projection(
2751                "ordered_playing_projection",
2752                "playing multiplicity must remain unordered",
2753            ));
2754        }
2755        if id.role() != &role
2756            || annotations.iter().any(|(key, value)| {
2757                key != value.id() || key.subject() != &AnnotationSubjectId::Plays(id.clone())
2758            })
2759        {
2760            return Err(invalid_projection(
2761                "invalid_playing_projection_reference",
2762                "playing metadata has a mismatched role or annotation subject",
2763            ));
2764        }
2765        ensure_collection_limit(annotations.len(), "too_many_projected_annotations")?;
2766        Ok(Self {
2767            id,
2768            role,
2769            target_name: None,
2770            multiplicity,
2771            annotations,
2772        })
2773    }
2774    /// Attach the owner-branded emitted plays-token name.
2775    #[must_use]
2776    pub fn with_target_name(mut self, target_name: TargetIdentifier) -> Self {
2777        self.target_name = Some(target_name);
2778        self
2779    }
2780    /// Return the exact playing identity.
2781    #[must_use]
2782    pub const fn id(&self) -> &PlaysFactId {
2783        &self.id
2784    }
2785    /// Return the shared canonical role identity.
2786    #[must_use]
2787    pub const fn role(&self) -> &RoleId {
2788        &self.role
2789    }
2790    /// Return the emitted owner-branded plays-token name when projected.
2791    #[must_use]
2792    pub const fn target_name(&self) -> Option<&TargetIdentifier> {
2793        self.target_name.as_ref()
2794    }
2795    /// Return the player-edge cardinality metadata.
2796    #[must_use]
2797    pub const fn multiplicity(&self) -> ProjectedMultiplicity {
2798        self.multiplicity
2799    }
2800    /// Return effective per-edge annotations.
2801    #[must_use]
2802    pub const fn annotations(&self) -> &BTreeMap<AnnotationFactId, ProjectedAnnotation> {
2803        &self.annotations
2804    }
2805}
2806
2807/// Deterministic shells-first and SCC-link emission schedule.
2808#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
2809pub struct EmissionPlan {
2810    model_shells: Vec<TypeId>,
2811    model_link_components: Vec<BTreeSet<TypeId>>,
2812    structs: Vec<StructId>,
2813    functions: Vec<FunctionId>,
2814}
2815
2816impl EmissionPlan {
2817    /// Construct a deterministic two-phase emission schedule.
2818    pub fn new(
2819        model_shells: Vec<TypeId>,
2820        model_link_components: Vec<BTreeSet<TypeId>>,
2821        structs: Vec<StructId>,
2822        functions: Vec<FunctionId>,
2823    ) -> Result<Self, Diagnostic> {
2824        for length in [
2825            model_shells.len(),
2826            model_link_components.len(),
2827            structs.len(),
2828            functions.len(),
2829        ] {
2830            ensure_collection_limit(length, "projection_emission_limit_exceeded")?;
2831        }
2832        Ok(Self {
2833            model_shells,
2834            model_link_components,
2835            structs,
2836            functions,
2837        })
2838    }
2839    /// Return parent-first nominal model shell order.
2840    #[must_use]
2841    pub fn model_shells(&self) -> &[TypeId] {
2842        &self.model_shells
2843    }
2844    /// Return dependency-first SCC link components.
2845    #[must_use]
2846    pub fn model_link_components(&self) -> &[BTreeSet<TypeId>] {
2847        &self.model_link_components
2848    }
2849    /// Return stable struct emission order.
2850    #[must_use]
2851    pub fn structs(&self) -> &[StructId] {
2852        &self.structs
2853    }
2854    /// Return stable function-token emission order.
2855    #[must_use]
2856    pub fn functions(&self) -> &[FunctionId] {
2857        &self.functions
2858    }
2859}
2860
2861/// A validated target-specific runtime projection derived from resolved semantics.
2862#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
2863pub struct RuntimeProjection {
2864    target: BindingTarget,
2865    config: ProjectionConfig,
2866    semantic_fingerprint: SemanticSchemaFingerprint,
2867    projection_fingerprint: BindingProjectionFingerprint,
2868    generator_handlers: Vec<ProjectionHandler>,
2869    code_resources: Vec<CodeResourceDigest>,
2870    #[serde(serialize_with = "serialize_map_values")]
2871    models: BTreeMap<TypeId, ModelProjection>,
2872    #[serde(serialize_with = "serialize_map_values")]
2873    structs: BTreeMap<StructId, StructProjection>,
2874    #[serde(serialize_with = "serialize_map_values")]
2875    functions: BTreeMap<FunctionId, FunctionProjection>,
2876    #[serde(serialize_with = "serialize_map_values")]
2877    playing_facts: BTreeMap<PlaysFactId, PlayingProjection>,
2878    emission: EmissionPlan,
2879}
2880
2881#[derive(Serialize)]
2882struct RuntimeProjectionContentView<'a> {
2883    #[serde(serialize_with = "serialize_map_values")]
2884    models: &'a BTreeMap<TypeId, ModelProjection>,
2885    #[serde(serialize_with = "serialize_map_values")]
2886    structs: &'a BTreeMap<StructId, StructProjection>,
2887    #[serde(serialize_with = "serialize_map_values")]
2888    functions: &'a BTreeMap<FunctionId, FunctionProjection>,
2889    #[serde(serialize_with = "serialize_map_values")]
2890    playing_facts: &'a BTreeMap<PlaysFactId, PlayingProjection>,
2891    emission: &'a EmissionPlan,
2892}
2893
2894impl RuntimeProjection {
2895    /// Validate a complete projection graph and compute its content-bound fingerprint.
2896    #[allow(clippy::too_many_arguments)]
2897    pub fn try_new(
2898        target: BindingTarget,
2899        config: ProjectionConfig,
2900        semantic_fingerprint: SemanticSchemaFingerprint,
2901        handlers: &[ProjectionHandler],
2902        resources: &[CodeResourceDigest],
2903        models: BTreeMap<TypeId, ModelProjection>,
2904        structs: BTreeMap<StructId, StructProjection>,
2905        functions: BTreeMap<FunctionId, FunctionProjection>,
2906        playing_facts: BTreeMap<PlaysFactId, PlayingProjection>,
2907        emission: EmissionPlan,
2908    ) -> Result<Self, Diagnostic> {
2909        if config.target() != target
2910            || models.iter().any(|(key, value)| key != value.id())
2911            || structs.iter().any(|(key, value)| key != value.id())
2912            || functions.iter().any(|(key, value)| key != value.id())
2913            || playing_facts.iter().any(|(key, value)| key != value.id())
2914        {
2915            return Err(invalid_projection(
2916                "invalid_runtime_projection_map",
2917                "runtime projection map keys or target configuration are inconsistent",
2918            ));
2919        }
2920        for length in [
2921            models.len(),
2922            structs.len(),
2923            functions.len(),
2924            playing_facts.len(),
2925        ] {
2926            ensure_collection_limit(length, "runtime_projection_limit_exceeded")?;
2927        }
2928        if target == BindingTarget::C {
2929            validate_c_create_limits(&models)?;
2930        }
2931        if matches!(target, BindingTarget::Rust | BindingTarget::C) {
2932            let native_names_complete = models.values().all(|model| {
2933                model.create().enabled() == model.create().target_name().is_some()
2934                    && model.query_tokens().target_name().is_some()
2935                    && model
2936                        .query_tokens()
2937                        .roles()
2938                        .values()
2939                        .all(|role| role.player_union_target_name().is_some())
2940                    && matches!(model.id().kind(), TypeKind::Entity | TypeKind::Relation)
2941                        == model.reference_read().target_name().is_some()
2942            }) && playing_facts
2943                .values()
2944                .all(|playing| playing.target_name().is_some());
2945            if !native_names_complete {
2946                let (code, message) = match target {
2947                    BindingTarget::Rust => (
2948                        "missing_rust_projection_identifier",
2949                        "Rust projection omits a required create, reference, query-token, player-union, or plays identifier",
2950                    ),
2951                    BindingTarget::C => (
2952                        "missing_c_projection_identifier",
2953                        "C projection omits a required create, reference, query-token, player-union, or plays identifier",
2954                    ),
2955                    BindingTarget::Python | BindingTarget::TypeScript => unreachable!(),
2956                };
2957                return Err(invalid_projection(code, message));
2958            }
2959        }
2960        let model_ids = models.keys().cloned().collect::<BTreeSet<_>>();
2961        if emission
2962            .model_shells()
2963            .iter()
2964            .cloned()
2965            .collect::<BTreeSet<_>>()
2966            != model_ids
2967            || emission.model_shells().len() != model_ids.len()
2968            || emission
2969                .model_link_components()
2970                .iter()
2971                .flat_map(BTreeSet::iter)
2972                .cloned()
2973                .collect::<BTreeSet<_>>()
2974                != model_ids
2975            || emission
2976                .model_link_components()
2977                .iter()
2978                .map(BTreeSet::len)
2979                .sum::<usize>()
2980                != model_ids.len()
2981            || emission.structs() != structs.keys().cloned().collect::<Vec<_>>()
2982            || emission.functions() != functions.keys().cloned().collect::<Vec<_>>()
2983        {
2984            return Err(invalid_projection(
2985                "invalid_projection_emission_plan",
2986                "emission plan does not cover each projected value exactly once",
2987            ));
2988        }
2989        let all_model_refs_valid = models.values().all(|model| {
2990            model.query_tokens().roles().values().all(|role| {
2991                role.accepted_players()
2992                    .iter()
2993                    .all(|id| models.contains_key(id))
2994            }) && model.create().roles().values().all(|role| {
2995                role.players()
2996                    .iter()
2997                    .all(|value| models.contains_key(value.id()))
2998            }) && model.complete_read().roles().values().all(|role| {
2999                role.players()
3000                    .iter()
3001                    .all(|value| models.contains_key(value.id()))
3002            })
3003        });
3004        if !all_model_refs_valid {
3005            return Err(invalid_projection(
3006                "invalid_projection_reference",
3007                "projection references a model that is not present",
3008            ));
3009        }
3010        let declaring_owners_valid = models.values().all(|model| {
3011            model.query_tokens().fields().values().all(|token| {
3012                let declaring_owner = token.declaring_id().owner();
3013                let mut curr = Some(model.id());
3014                let mut found = false;
3015                let mut visited = BTreeSet::new();
3016                while let Some(curr_id) = curr {
3017                    if !visited.insert(curr_id) {
3018                        return false;
3019                    }
3020                    if curr_id == declaring_owner {
3021                        found = true;
3022                        break;
3023                    }
3024                    curr = models.get(curr_id).and_then(|m| m.declaration().parent());
3025                }
3026                found
3027            })
3028        });
3029        if !declaring_owners_valid {
3030            return Err(invalid_projection(
3031                "invalid_projection_reference",
3032                "field token declaring owner is not the effective owner or a valid ancestor",
3033            ));
3034        }
3035        let model_use_is_valid = |value: &ProjectedModelUse| {
3036            models.get(value.id()).is_some_and(|model| {
3037                value.form() != ProjectedModelForm::Reference
3038                    || model.reference_read().target_name().is_some()
3039            })
3040        };
3041        let read_model_use_is_valid = |value: &ProjectedModelUse| {
3042            model_use_is_valid(value)
3043                && (value.form() != ProjectedModelForm::Complete
3044                    || value.id().kind() != TypeKind::Relation)
3045        };
3046        let type_ref_is_valid = |value: &ProjectedTypeRef| match value {
3047            ProjectedTypeRef::Scalar(_) => true,
3048            ProjectedTypeRef::Model(value) => model_use_is_valid(value),
3049            ProjectedTypeRef::Struct(id) => structs.contains_key(id),
3050        };
3051        let role_exists = |role: &RoleId| {
3052            models.values().any(|model| {
3053                model.id().kind() == TypeKind::Relation
3054                    && model.id().label() == role.declaring_relation()
3055                    && model.query_tokens().roles().contains_key(role)
3056            })
3057        };
3058        let shell_positions = emission
3059            .model_shells()
3060            .iter()
3061            .enumerate()
3062            .map(|(index, id)| (id.clone(), index))
3063            .collect::<BTreeMap<_, _>>();
3064        let closed_models = models.values().all(|model| {
3065            let declaration = model.declaration();
3066            let parent_is_valid = declaration.parent().is_none_or(|parent| {
3067                models.contains_key(parent) && shell_positions[parent] < shell_positions[model.id()]
3068            });
3069            let direct_sub_is_valid = match (declaration.parent(), declaration.direct_sub()) {
3070                (None, None) => true,
3071                (Some(parent), Some(sub)) => {
3072                    sub.id().subtype() == model.id() && sub.id().supertype() == parent
3073                }
3074                (None, Some(_)) | (Some(_), None) => false,
3075            };
3076            let direct_fields_are_valid = declaration
3077                .direct_fields()
3078                .iter()
3079                .all(|id| model.query_tokens().fields().contains_key(id));
3080            let direct_roles_are_valid = declaration.direct_roles().iter().all(|(id, role)| {
3081                id == role.role()
3082                    && model.query_tokens().roles().contains_key(id)
3083                    && role.specializes().is_none_or(&role_exists)
3084            });
3085            let direct_plays_are_valid = declaration
3086                .direct_plays()
3087                .iter()
3088                .all(|id| id.player() == model.id() && playing_facts.contains_key(id));
3089            let fields_are_valid = model.query_tokens().fields().values().all(|field| {
3090                models.keys().any(|id| {
3091                    id.kind() == TypeKind::Attribute && id.label() == field.id().attribute().label()
3092                })
3093            }) && model
3094                .create()
3095                .fields()
3096                .iter()
3097                .all(|field| type_ref_is_valid(field.value()))
3098                && model
3099                    .complete_read()
3100                    .fields()
3101                    .iter()
3102                    .all(|field| type_ref_is_valid(field.value()));
3103            let roles_are_valid = model
3104                .query_tokens()
3105                .roles()
3106                .values()
3107                .all(|role| role.specializes().is_none_or(&role_exists))
3108                && model
3109                    .create()
3110                    .roles()
3111                    .values()
3112                    .all(|role| role.players().iter().all(&model_use_is_valid))
3113                && model
3114                    .complete_read()
3115                    .roles()
3116                    .values()
3117                    .all(|role| role.players().iter().all(&read_model_use_is_valid));
3118            let role_upcasts_are_valid =
3119                model
3120                    .complete_read()
3121                    .role_upcasts()
3122                    .iter()
3123                    .all(|(active, ancestors)| {
3124                        model.complete_read().roles().contains_key(active)
3125                            && ancestors.iter().all(&role_exists)
3126                    });
3127            let references_are_valid = model.reference_read().key_fields().iter().all(|id| {
3128                model
3129                    .query_tokens()
3130                    .fields()
3131                    .get(id)
3132                    .is_some_and(FieldTokenProjection::is_key)
3133            });
3134            let value_subject = model.id().kind() != TypeKind::Attribute
3135                || model.declaration().value_annotations().keys().all(|id| {
3136                    id.subject()
3137                        == &AnnotationSubjectId::Value(ValueFactId::new(
3138                            AttributeId::new(model.id().label().as_str())
3139                                .expect("projected attribute label is valid"),
3140                        ))
3141                });
3142            parent_is_valid
3143                && direct_sub_is_valid
3144                && direct_fields_are_valid
3145                && direct_roles_are_valid
3146                && direct_plays_are_valid
3147                && fields_are_valid
3148                && roles_are_valid
3149                && role_upcasts_are_valid
3150                && references_are_valid
3151                && value_subject
3152        });
3153        let closed_playing = playing_facts.values().all(|playing| {
3154            models.contains_key(playing.id().player())
3155                && role_exists(playing.role())
3156                && (!matches!(
3157                    target,
3158                    BindingTarget::TypeScript | BindingTarget::Rust | BindingTarget::C
3159                ) || playing.target_name().is_some())
3160        });
3161        let closed_functions = functions.values().all(|function| {
3162            function
3163                .parameters()
3164                .iter()
3165                .all(|parameter| type_ref_is_valid(parameter.type_ref()))
3166                && match function.returns() {
3167                    FunctionReturnProjection::Scalar(element) => {
3168                        type_ref_is_valid(element.type_ref())
3169                    }
3170                    FunctionReturnProjection::Tuple(elements)
3171                    | FunctionReturnProjection::Stream(elements) => elements
3172                        .iter()
3173                        .all(|element| type_ref_is_valid(element.type_ref())),
3174                }
3175        });
3176        if !closed_models || !closed_playing || !closed_functions {
3177            return Err(invalid_projection(
3178                "invalid_projection_reference",
3179                "projection graph contains an unavailable type, field, role, specialization, reference, or function dependency",
3180            ));
3181        }
3182        let content = to_canonical_json(&RuntimeProjectionContentView {
3183            models: &models,
3184            structs: &structs,
3185            functions: &functions,
3186            playing_facts: &playing_facts,
3187            emission: &emission,
3188        })?;
3189        let projection_fingerprint = BindingProjectionFingerprint::compute_with_projection(
3190            target,
3191            &semantic_fingerprint,
3192            &config,
3193            handlers,
3194            resources,
3195            &content,
3196        )?;
3197        let mut generator_handlers = handlers.to_vec();
3198        generator_handlers.sort_by(|left, right| left.id().cmp(right.id()));
3199        let mut code_resources = resources.to_vec();
3200        code_resources.sort_by(|left, right| left.id().cmp(right.id()));
3201        Ok(Self {
3202            target,
3203            config,
3204            semantic_fingerprint,
3205            projection_fingerprint,
3206            generator_handlers,
3207            code_resources,
3208            models,
3209            structs,
3210            functions,
3211            playing_facts,
3212            emission,
3213        })
3214    }
3215    /// Return the binding target.
3216    #[must_use]
3217    pub const fn target(&self) -> BindingTarget {
3218        self.target
3219    }
3220    /// Return the exact projection configuration.
3221    #[must_use]
3222    pub const fn config(&self) -> &ProjectionConfig {
3223        &self.config
3224    }
3225    /// Return the source semantic schema fingerprint.
3226    #[must_use]
3227    pub const fn semantic_fingerprint(&self) -> &SemanticSchemaFingerprint {
3228        &self.semantic_fingerprint
3229    }
3230    /// Return the content-bound target projection fingerprint.
3231    #[must_use]
3232    pub const fn projection_fingerprint(&self) -> &BindingProjectionFingerprint {
3233        &self.projection_fingerprint
3234    }
3235    /// Resolve one generated-only token ordinal through canonical projection order.
3236    ///
3237    /// Ordinals are zero-based within their kind. Models and functions follow
3238    /// their canonical identity-map order. Fields and roles follow model order
3239    /// and then the canonical order of that model's token map. This method is the sole
3240    /// semantic owner of the ordinal algorithm used by native generated SDKs.
3241    #[must_use]
3242    pub fn projected_token_identity(
3243        &self,
3244        kind: ProjectedTokenKind,
3245        ordinal: u32,
3246    ) -> Option<ProjectedTokenIdentity> {
3247        let index = usize::try_from(ordinal).ok()?;
3248        match kind {
3249            ProjectedTokenKind::Model => self
3250                .models
3251                .keys()
3252                .nth(index)
3253                .cloned()
3254                .map(ProjectedTokenIdentity::Model),
3255            ProjectedTokenKind::Field => self
3256                .models
3257                .iter()
3258                .flat_map(|(owner, model)| {
3259                    model.query_tokens().fields().keys().map(move |field| {
3260                        ProjectedTokenIdentity::Field {
3261                            owner: owner.clone(),
3262                            field: field.clone(),
3263                        }
3264                    })
3265                })
3266                .nth(index),
3267            ProjectedTokenKind::Role => self
3268                .models
3269                .iter()
3270                .flat_map(|(owner, model)| {
3271                    model.query_tokens().roles().keys().map(move |role| {
3272                        ProjectedTokenIdentity::Role {
3273                            owner: owner.clone(),
3274                            role: role.clone(),
3275                        }
3276                    })
3277                })
3278                .nth(index),
3279            ProjectedTokenKind::Function => self
3280                .functions
3281                .keys()
3282                .nth(index)
3283                .cloned()
3284                .map(ProjectedTokenIdentity::Function),
3285            ProjectedTokenKind::Struct => self
3286                .structs
3287                .keys()
3288                .nth(index)
3289                .cloned()
3290                .map(ProjectedTokenIdentity::Struct),
3291            ProjectedTokenKind::Attribute => self
3292                .models
3293                .keys()
3294                .filter(|model| model.kind() == TypeKind::Attribute)
3295                .nth(index)
3296                .and_then(|model| AttributeId::new(model.label().as_str()).ok())
3297                .map(ProjectedTokenIdentity::Attribute),
3298        }
3299    }
3300
3301    /// Resolve one semantic identity to its generated-only zero-based ordinal.
3302    ///
3303    /// Returns `None` when the identity does not belong to this exact
3304    /// projection or its owner branding is inconsistent.
3305    #[must_use]
3306    pub fn projected_token_ordinal(&self, identity: &ProjectedTokenIdentity) -> Option<u32> {
3307        let index = match identity {
3308            ProjectedTokenIdentity::Model(expected) => self
3309                .models
3310                .keys()
3311                .position(|candidate| candidate == expected),
3312            ProjectedTokenIdentity::Field {
3313                owner: expected_owner,
3314                field: expected_field,
3315            } => self
3316                .models
3317                .iter()
3318                .flat_map(|(owner, model)| {
3319                    model
3320                        .query_tokens()
3321                        .fields()
3322                        .keys()
3323                        .map(move |field| (owner, field))
3324                })
3325                .position(|(owner, field)| owner == expected_owner && field == expected_field),
3326            ProjectedTokenIdentity::Role {
3327                owner: expected_owner,
3328                role: expected_role,
3329            } => self
3330                .models
3331                .iter()
3332                .flat_map(|(owner, model)| {
3333                    model
3334                        .query_tokens()
3335                        .roles()
3336                        .keys()
3337                        .map(move |role| (owner, role))
3338                })
3339                .position(|(owner, role)| owner == expected_owner && role == expected_role),
3340            ProjectedTokenIdentity::Function(expected) => self
3341                .functions
3342                .keys()
3343                .position(|candidate| candidate == expected),
3344            ProjectedTokenIdentity::Struct(expected) => self
3345                .structs
3346                .keys()
3347                .position(|candidate| candidate == expected),
3348            ProjectedTokenIdentity::Attribute(expected) => self
3349                .models
3350                .keys()
3351                .filter(|model| model.kind() == TypeKind::Attribute)
3352                .position(|candidate| candidate.label() == expected.label()),
3353        }?;
3354        u32::try_from(index).ok()
3355    }
3356    /// Return the ordered handler evidence committed by the projection fingerprint.
3357    #[must_use]
3358    pub fn generator_handlers(&self) -> &[ProjectionHandler] {
3359        &self.generator_handlers
3360    }
3361    /// Return the ordered code-resource evidence committed by the projection fingerprint.
3362    #[must_use]
3363    pub fn code_resources(&self) -> &[CodeResourceDigest] {
3364        &self.code_resources
3365    }
3366    /// Return projected models in canonical identity order.
3367    #[must_use]
3368    pub const fn models(&self) -> &BTreeMap<TypeId, ModelProjection> {
3369        &self.models
3370    }
3371    /// Return projected structs in canonical identity order.
3372    #[must_use]
3373    pub const fn structs(&self) -> &BTreeMap<StructId, StructProjection> {
3374        &self.structs
3375    }
3376    /// Return projected schema functions in canonical identity order.
3377    #[must_use]
3378    pub const fn functions(&self) -> &BTreeMap<FunctionId, FunctionProjection> {
3379        &self.functions
3380    }
3381    /// Return effective per-player metadata keyed by exact playing identity.
3382    #[must_use]
3383    pub const fn playing_facts(&self) -> &BTreeMap<PlaysFactId, PlayingProjection> {
3384        &self.playing_facts
3385    }
3386    /// Return the shells-first generation schedule.
3387    #[must_use]
3388    pub const fn emission(&self) -> &EmissionPlan {
3389        &self.emission
3390    }
3391}
3392
3393impl CodeResourceDigest {
3394    /// Adopt decoded resource evidence only after checking its exact fingerprint domain.
3395    pub(crate) fn from_wire(
3396        id: impl Into<String>,
3397        content_fingerprint: Fingerprint,
3398    ) -> Result<Self, Diagnostic> {
3399        if content_fingerprint.domain().as_str() != CODE_RESOURCE_DOMAIN
3400            || content_fingerprint.canonicalization().as_str() != RAW_BYTES_CANONICALIZATION
3401            || content_fingerprint.semantic_profile().is_some()
3402        {
3403            return Err(Diagnostic::stable(
3404                DiagnosticCategory::Integrity,
3405                "invalid_code_resource_fingerprint",
3406                "code resource fingerprint wire metadata is inconsistent",
3407            ));
3408        }
3409        Ok(Self {
3410            id: CodeResourceId::new(id)?,
3411            content_fingerprint,
3412        })
3413    }
3414}
3415/// One compatibility lookup resolved against generated field names.
3416#[derive(Clone, Copy, Debug, Eq, PartialEq)]
3417pub struct GeneratedManagerLookup<'a> {
3418    field_name: &'a str,
3419    lookup: &'a str,
3420}
3421
3422impl<'a> GeneratedManagerLookup<'a> {
3423    /// Return the exact generated field selected by the compatibility spelling.
3424    #[must_use]
3425    pub const fn field_name(self) -> &'a str {
3426        self.field_name
3427    }
3428
3429    /// Return the normalized lookup operator.
3430    #[must_use]
3431    pub const fn lookup(self) -> &'a str {
3432        self.lookup
3433    }
3434}
3435
3436/// Resolve a generated-manager compatibility spelling without losing literal
3437/// field names that contain `__`.
3438///
3439/// A recognized trailing lookup wins only when its prefix is itself a field.
3440/// Otherwise an exact full-field match wins and defaults to equality.
3441#[must_use]
3442pub fn resolve_generated_manager_lookup<'a>(
3443    key: &'a str,
3444    has_field: impl Fn(&str) -> bool,
3445) -> GeneratedManagerLookup<'a> {
3446    let parsed = key.rsplit_once("__");
3447    match parsed {
3448        Some((field_name, lookup))
3449            if matches!(
3450                lookup,
3451                "eq" | "exact"
3452                    | "ne"
3453                    | "gt"
3454                    | "gte"
3455                    | "lt"
3456                    | "lte"
3457                    | "contains"
3458                    | "startswith"
3459                    | "endswith"
3460                    | "regex"
3461                    | "like"
3462                    | "in"
3463                    | "isnull"
3464            ) && has_field(field_name) =>
3465        {
3466            GeneratedManagerLookup { field_name, lookup }
3467        }
3468        _ if has_field(key) => GeneratedManagerLookup {
3469            field_name: key,
3470            lookup: "eq",
3471        },
3472        Some((field_name, lookup)) => GeneratedManagerLookup { field_name, lookup },
3473        None => GeneratedManagerLookup {
3474            field_name: key,
3475            lookup: "eq",
3476        },
3477    }
3478}