type_bridge_schema/
adoption.rs1use std::collections::BTreeSet;
4
5use type_bridge_contract::capability::CapabilitySet;
6use type_bridge_contract::diagnostic::{Diagnostic, DiagnosticCategory, DiagnosticCode};
7use type_bridge_contract::fingerprint::SemanticProfileId;
8use type_bridge_contract::schema::{
9 DeclaredSchema, ManagedDeclaredIdentityFingerprint, ManagedFactSelection, ManagedSchemaState,
10 ManagedScopeId, ManagedSemanticSchemaFingerprint, SchemaDiagnostic, SchemaDiagnostics,
11 SchemaFactId, SchemaOperation, SourcedSchemaFact,
12};
13use type_bridge_contract::semantic_profile::SemanticProfile;
14
15use crate::{
16 BoundManagedSchemaScope, CanonicalObservedSchema, ManagedSchemaScope, ResolvedSchema,
17 managed_declared_identity_fingerprint, managed_semantic_schema_fingerprint,
18 resolve_schema_with_capabilities,
19};
20
21#[derive(Clone, Debug)]
23pub struct AdoptionBaseline {
24 declared_schema: DeclaredSchema,
25 resolved_schema: ResolvedSchema,
26 bound_scope: BoundManagedSchemaScope,
27 semantic_profile: SemanticProfileId,
28 managed_state: ManagedSchemaState,
29 operations: Vec<SchemaOperation>,
30}
31
32impl AdoptionBaseline {
33 pub const fn declared_schema(&self) -> &DeclaredSchema {
35 &self.declared_schema
36 }
37
38 pub const fn resolved_schema(&self) -> &ResolvedSchema {
40 &self.resolved_schema
41 }
42
43 pub const fn bound_scope(&self) -> &BoundManagedSchemaScope {
45 &self.bound_scope
46 }
47
48 pub const fn semantic_profile(&self) -> &SemanticProfileId {
50 &self.semantic_profile
51 }
52
53 pub const fn managed_state(&self) -> &ManagedSchemaState {
55 &self.managed_state
56 }
57
58 pub const fn managed_declared_identity(&self) -> &ManagedDeclaredIdentityFingerprint {
60 self.managed_state.managed_declared_identity()
61 }
62
63 pub const fn managed_semantic_schema(&self) -> &ManagedSemanticSchemaFingerprint {
65 self.managed_state.managed_semantic_schema()
66 }
67
68 pub fn operations(&self) -> &[SchemaOperation] {
70 &self.operations
71 }
72}
73
74pub fn adopt_observed_schema(
76 observed: &CanonicalObservedSchema,
77 scope_id: ManagedScopeId,
78 semantic_profile: &SemanticProfileId,
79 available_capabilities: &CapabilitySet,
80) -> Result<AdoptionBaseline, SchemaDiagnostics> {
81 SemanticProfile::resolve(semantic_profile).map_err(no_source)?;
82 if observed.semantic_profile() != semantic_profile {
83 return Err(adoption_failure(
84 "adoption_semantic_profile_mismatch",
85 "the adoption profile differs from the profile used to canonicalize observation",
86 ));
87 }
88 observed
89 .direct_schema()
90 .required_capabilities()
91 .ensure_supported_by(available_capabilities)
92 .map_err(no_source)?;
93
94 let selected: BTreeSet<SchemaFactId> = observed.managed_scope().iter().cloned().collect();
95 let mut sourced = Vec::with_capacity(selected.len());
96 for id in &selected {
97 let fact = observed.direct_schema().fact(id).ok_or_else(|| {
98 adoption_failure(
99 "adoption_scope_fact_mismatch",
100 "the canonical managed selection references an absent direct fact",
101 )
102 })?;
103 let source = observed
104 .direct_schema()
105 .source(id)
106 .cloned()
107 .ok_or_else(|| {
108 adoption_failure(
109 "adoption_scope_source_mismatch",
110 "the canonical managed selection references a fact without provenance",
111 )
112 })?;
113 sourced.push(SourcedSchemaFact::new(fact.clone(), source));
114 }
115
116 let declared_schema = DeclaredSchema::from_facts(
117 observed.direct_schema().format(),
118 observed.direct_schema().required_capabilities().clone(),
119 sourced,
120 )?;
121 let rebuilt_ids: BTreeSet<_> = declared_schema.facts().map(|fact| fact.id()).collect();
122 if rebuilt_ids != selected {
123 return Err(adoption_failure(
124 "adoption_scope_selection_mismatch",
125 "the rebuilt declaration is not exactly the canonical managed direct selection",
126 ));
127 }
128
129 let bound_scope = ManagedSchemaScope::bind_exclusive(scope_id.clone(), &declared_schema)?;
130 if bound_scope.binding().id() != &scope_id || bound_scope.selection().iter().ne(selected.iter())
131 {
132 return Err(adoption_failure(
133 "adoption_exclusive_scope_mismatch",
134 "the fixed exclusive scope does not exactly bind the requested direct selection",
135 ));
136 }
137
138 let resolved_schema = resolve_schema_with_capabilities(
139 &declared_schema,
140 semantic_profile,
141 available_capabilities,
142 )?;
143 let selection =
144 ManagedFactSelection::new(bound_scope.selection().iter().cloned()).map_err(no_source)?;
145 let declared_fingerprint =
146 managed_declared_identity_fingerprint(&declared_schema, &bound_scope)?;
147 let semantic_fingerprint =
148 managed_semantic_schema_fingerprint(&declared_schema, semantic_profile, &bound_scope)?;
149 let managed_state = ManagedSchemaState::new(
150 declared_schema.format(),
151 declared_schema.required_capabilities().clone(),
152 bound_scope.binding().clone(),
153 selection,
154 declared_schema.declared_identity_fingerprint().clone(),
155 declared_fingerprint,
156 semantic_fingerprint,
157 )
158 .map_err(no_source)?;
159
160 Ok(AdoptionBaseline {
161 declared_schema,
162 resolved_schema,
163 bound_scope,
164 semantic_profile: semantic_profile.clone(),
165 managed_state,
166 operations: Vec::new(),
167 })
168}
169
170fn adoption_failure(code: &'static str, message: &'static str) -> SchemaDiagnostics {
171 no_source(Diagnostic::new(
172 DiagnosticCategory::InvalidContract,
173 DiagnosticCode::new(code).expect("static adoption diagnostic code is canonical"),
174 message,
175 ))
176}
177
178fn no_source(diagnostic: Diagnostic) -> SchemaDiagnostics {
179 SchemaDiagnostics::one(SchemaDiagnostic::new(diagnostic, None))
180}