1use std::error::Error;
4use std::fmt;
5
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8use type_bridge_contract::capability::{CapabilityId, CapabilitySet};
9use type_bridge_contract::codec::{
10 CodecVersion, FormatVersion, from_canonical_json, to_canonical_json,
11};
12use type_bridge_contract::diagnostic::{Diagnostic, DiagnosticCategory};
13use type_bridge_contract::fingerprint::{
14 CanonicalizationVersion, Fingerprint, FingerprintDomain, SemanticProfileId,
15};
16use type_bridge_contract::limits::MAX_CANONICAL_BYTES;
17use type_bridge_contract::managed_scope::{
18 ManagedScopeBinding, ManagedScopeId, ManagedScopeProfileId, SemanticProfileBinding,
19};
20use type_bridge_contract::migration::CONDITIONAL_RESOLUTION_CAPABILITY;
21use type_bridge_contract::migration_assertion_capability_vocabulary;
22use type_bridge_contract::query_plan::query_plan_v2_capability_vocabulary;
23use type_bridge_contract::schema::{
24 DeclaredSchema, ManagedSchemaState, SchemaDiagnostics, decode_declared_schema,
25 encode_declared_schema,
26};
27use type_bridge_contract::schema_delta::{
28 SCHEMA_REDEFINE_CAPABILITY, schema_transition_capability_vocabulary,
29};
30
31use crate::{
32 BUILTIN_SCHEMA_CAPABILITY_IDS, DeltaError, ManagedDeltaContext, ResolvedSchema,
33 managed_schema_state, resolve_schema_with_capabilities,
34};
35
36pub const TYPEBRIDGE_SCHEMA_AUTHORITY_V1: &str = "typebridge.schema-authority/v1";
38pub const SCHEMA_AUTHORITY_FINGERPRINT_DOMAIN: &str = "typebridge.schema.authority";
40pub const SCHEMA_AUTHORITY_FINGERPRINT_CANONICALIZATION: &str = "typebridge.schema-authority/v1";
42pub const MAX_SCHEMA_AUTHORITY_BYTES: usize = MAX_CANONICAL_BYTES;
44
45#[must_use]
52pub fn schema_authority_capability_vocabulary() -> CapabilitySet {
53 let mut capabilities = query_plan_v2_capability_vocabulary();
54 for capability in schema_transition_capability_vocabulary()
55 .into_iter()
56 .chain(migration_assertion_capability_vocabulary())
57 {
58 capabilities.insert(capability);
59 }
60 for capability in BUILTIN_SCHEMA_CAPABILITY_IDS {
61 capabilities.insert(
62 CapabilityId::new(*capability).expect("built-in schema capability ID is canonical"),
63 );
64 }
65 for capability in [
66 SCHEMA_REDEFINE_CAPABILITY,
67 CONDITIONAL_RESOLUTION_CAPABILITY,
68 ] {
69 capabilities.insert(
70 CapabilityId::new(capability).expect("static authority capability ID is canonical"),
71 );
72 }
73 capabilities
74}
75
76#[derive(Clone, Copy, Debug, Eq, PartialEq)]
78pub enum SchemaAuthorityErrorCode {
79 Contract,
81 Schema,
83 UnsupportedVersion,
85 UnsupportedCapability,
87 ResourceLimit,
89 IntegrityMismatch,
91}
92
93#[derive(Clone, Debug, Eq, PartialEq)]
95pub struct SchemaAuthorityError {
96 code: SchemaAuthorityErrorCode,
97 message: &'static str,
98 contract: Option<Box<Diagnostic>>,
99 schema: Option<Box<SchemaDiagnostics>>,
100}
101
102impl SchemaAuthorityError {
103 fn new(code: SchemaAuthorityErrorCode, message: &'static str) -> Self {
104 Self {
105 code,
106 message,
107 contract: None,
108 schema: None,
109 }
110 }
111
112 #[must_use]
114 pub const fn code(&self) -> SchemaAuthorityErrorCode {
115 self.code
116 }
117
118 #[must_use]
120 pub fn contract(&self) -> Option<&Diagnostic> {
121 self.contract.as_deref()
122 }
123
124 #[must_use]
126 pub fn schema(&self) -> Option<&SchemaDiagnostics> {
127 self.schema.as_deref()
128 }
129}
130
131impl fmt::Display for SchemaAuthorityError {
132 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
133 formatter.write_str(self.message)
134 }
135}
136
137impl Error for SchemaAuthorityError {}
138
139impl From<Diagnostic> for SchemaAuthorityError {
140 fn from(value: Diagnostic) -> Self {
141 let code = match value.category() {
142 DiagnosticCategory::InvalidContract => SchemaAuthorityErrorCode::Contract,
143 DiagnosticCategory::UnsupportedCapability => {
144 SchemaAuthorityErrorCode::UnsupportedCapability
145 }
146 DiagnosticCategory::ResourceLimit | DiagnosticCategory::Cancelled => {
147 SchemaAuthorityErrorCode::ResourceLimit
148 }
149 DiagnosticCategory::Integrity => SchemaAuthorityErrorCode::IntegrityMismatch,
150 };
151 Self {
152 code,
153 message: "a schema-authority contract is invalid",
154 contract: Some(Box::new(value)),
155 schema: None,
156 }
157 }
158}
159
160impl From<SchemaDiagnostics> for SchemaAuthorityError {
161 fn from(value: SchemaDiagnostics) -> Self {
162 Self {
163 code: SchemaAuthorityErrorCode::Schema,
164 message: "schema-authority reconstruction failed",
165 contract: None,
166 schema: Some(Box::new(value)),
167 }
168 }
169}
170
171impl From<DeltaError> for SchemaAuthorityError {
172 fn from(value: DeltaError) -> Self {
173 match value {
174 DeltaError::Contract(error) => error.into(),
175 DeltaError::Schema(error) => error.into(),
176 }
177 }
178}
179
180#[derive(Clone, Debug, Deserialize, Serialize)]
181#[serde(deny_unknown_fields)]
182struct SemanticProfileBindingWire {
183 fingerprint: Value,
184 id: SemanticProfileId,
185}
186
187impl SemanticProfileBindingWire {
188 fn from_binding(binding: &SemanticProfileBinding) -> Result<Self, SchemaAuthorityError> {
189 Ok(Self {
190 fingerprint: canonical_value(binding.fingerprint().as_fingerprint())?,
191 id: binding.id().clone(),
192 })
193 }
194
195 fn rebuild(self) -> Result<SemanticProfileBinding, SchemaAuthorityError> {
196 let trusted = SemanticProfileBinding::resolve(self.id.clone())?;
197 require_exact_value(
198 &self.fingerprint,
199 trusted.fingerprint().as_fingerprint(),
200 "semantic-profile fingerprint is stale",
201 )?;
202 Ok(trusted)
203 }
204}
205
206#[derive(Clone, Debug, Deserialize, Serialize)]
207#[serde(deny_unknown_fields)]
208struct ManagedScopeProfileBindingWire {
209 fingerprint: Value,
210 id: ManagedScopeProfileId,
211}
212
213#[derive(Clone, Debug, Deserialize, Serialize)]
214#[serde(deny_unknown_fields)]
215struct ManagedScopeBindingWire {
216 id: ManagedScopeId,
217 profile: ManagedScopeProfileBindingWire,
218}
219
220impl ManagedScopeBindingWire {
221 fn from_binding(binding: &ManagedScopeBinding) -> Result<Self, SchemaAuthorityError> {
222 Ok(Self {
223 id: binding.id().clone(),
224 profile: ManagedScopeProfileBindingWire {
225 fingerprint: canonical_value(binding.profile().fingerprint().as_fingerprint())?,
226 id: binding.profile().id().clone(),
227 },
228 })
229 }
230
231 fn rebuild(self) -> Result<ManagedScopeBinding, SchemaAuthorityError> {
232 let trusted = ManagedScopeBinding::exclusive(self.id.clone())?;
233 if self.profile.id != *trusted.profile().id() {
234 return Err(integrity_failure("managed-scope profile identity is stale"));
235 }
236 require_exact_value(
237 &self.profile.fingerprint,
238 trusted.profile().fingerprint().as_fingerprint(),
239 "managed-scope profile fingerprint is stale",
240 )?;
241 Ok(trusted)
242 }
243}
244
245#[derive(Clone, Debug, Deserialize, Serialize)]
246#[serde(deny_unknown_fields)]
247struct SchemaAuthorityContentWire {
248 authority_version: String,
249 codec_version: u16,
250 declared_identity: Value,
251 declared_schema: Value,
252 managed_scope: ManagedScopeBindingWire,
253 managed_state: Value,
254 required_capabilities: CapabilitySet,
255 schema_ir_version: u16,
256 semantic_profile: SemanticProfileBindingWire,
257 semantic_schema: Value,
258}
259
260#[derive(Clone, Debug, Deserialize, Serialize)]
261#[serde(deny_unknown_fields)]
262struct SchemaAuthorityWire {
263 authority_fingerprint: Value,
264 content: SchemaAuthorityContentWire,
265}
266
267#[derive(Clone, Debug)]
269pub struct VerifiedSchemaAuthority {
270 authority_fingerprint: Fingerprint,
271 canonical_bytes: Vec<u8>,
272 declared: DeclaredSchema,
273 managed_scope: ManagedScopeBinding,
274 managed_state: ManagedSchemaState,
275 required_capabilities: CapabilitySet,
276 resolved: ResolvedSchema,
277 semantic_profile: SemanticProfileBinding,
278}
279
280impl VerifiedSchemaAuthority {
281 #[must_use]
283 pub const fn authority_fingerprint(&self) -> &Fingerprint {
284 &self.authority_fingerprint
285 }
286
287 #[must_use]
289 pub const fn declared_schema(&self) -> &DeclaredSchema {
290 &self.declared
291 }
292
293 #[must_use]
295 pub const fn resolved_schema(&self) -> &ResolvedSchema {
296 &self.resolved
297 }
298
299 #[must_use]
301 pub const fn semantic_profile(&self) -> &SemanticProfileBinding {
302 &self.semantic_profile
303 }
304
305 #[must_use]
307 pub const fn managed_scope(&self) -> &ManagedScopeBinding {
308 &self.managed_scope
309 }
310
311 #[must_use]
313 pub const fn managed_state(&self) -> &ManagedSchemaState {
314 &self.managed_state
315 }
316
317 #[must_use]
319 pub const fn required_capabilities(&self) -> &CapabilitySet {
320 &self.required_capabilities
321 }
322}
323
324pub fn build_schema_authority(
330 declared: &DeclaredSchema,
331 required_capabilities: &CapabilitySet,
332 context: &ManagedDeltaContext,
333) -> Result<VerifiedSchemaAuthority, SchemaAuthorityError> {
334 declared
335 .required_capabilities()
336 .ensure_supported_by(required_capabilities)?;
337 required_capabilities.ensure_supported_by(context.available_capabilities())?;
338
339 let semantic_profile = SemanticProfileBinding::resolve(context.semantic_profile().clone())?;
340 let managed_scope = ManagedScopeBinding::exclusive(context.scope_id().clone())
341 .map_err(SchemaAuthorityError::from)?;
342 let resolved = resolve_schema_with_capabilities(
343 declared,
344 context.semantic_profile(),
345 context.available_capabilities(),
346 )?;
347 let managed_state = managed_schema_state(declared, context)?;
348 let declared_bytes = encode_declared_schema(declared)?;
349 let content = SchemaAuthorityContentWire {
350 authority_version: TYPEBRIDGE_SCHEMA_AUTHORITY_V1.to_owned(),
351 codec_version: CodecVersion::V1.get(),
352 declared_identity: canonical_value(declared.declared_identity_fingerprint())?,
353 declared_schema: from_canonical_json(&declared_bytes)?,
354 managed_scope: ManagedScopeBindingWire::from_binding(&managed_scope)?,
355 managed_state: canonical_value(&managed_state)?,
356 required_capabilities: required_capabilities.clone(),
357 schema_ir_version: declared.format().get(),
358 semantic_profile: SemanticProfileBindingWire::from_binding(&semantic_profile)?,
359 semantic_schema: canonical_value(resolved.semantic_fingerprint())?,
360 };
361 let authority_fingerprint = compute_authority_fingerprint(&content)?;
362 let wire = SchemaAuthorityWire {
363 authority_fingerprint: canonical_value(&authority_fingerprint)?,
364 content,
365 };
366 let bytes = to_canonical_json(&wire)?;
367 decode_schema_authority(&bytes, context.available_capabilities())
368}
369
370#[must_use]
372pub fn encode_schema_authority(authority: &VerifiedSchemaAuthority) -> Vec<u8> {
373 authority.canonical_bytes.clone()
374}
375
376pub fn decode_schema_authority(
383 bytes: &[u8],
384 available_capabilities: &CapabilitySet,
385) -> Result<VerifiedSchemaAuthority, SchemaAuthorityError> {
386 let wire: SchemaAuthorityWire = from_canonical_json(bytes)?;
387 if to_canonical_json(&wire)? != bytes {
388 return Err(SchemaAuthorityError::new(
389 SchemaAuthorityErrorCode::Contract,
390 "schema-authority bytes normalize after typed reconstruction",
391 ));
392 }
393 if wire.content.authority_version != TYPEBRIDGE_SCHEMA_AUTHORITY_V1
394 || wire.content.codec_version != CodecVersion::V1.get()
395 || wire.content.schema_ir_version != FormatVersion::V1.get()
396 {
397 return Err(SchemaAuthorityError::new(
398 SchemaAuthorityErrorCode::UnsupportedVersion,
399 "schema-authority, codec, or schema-IR version is unsupported",
400 ));
401 }
402
403 let authority_fingerprint = compute_authority_fingerprint(&wire.content)?;
404 require_exact_value(
405 &wire.authority_fingerprint,
406 &authority_fingerprint,
407 "schema-authority content fingerprint is stale",
408 )?;
409
410 let SchemaAuthorityContentWire {
411 authority_version: _,
412 codec_version: _,
413 declared_identity,
414 declared_schema,
415 managed_scope,
416 managed_state,
417 required_capabilities,
418 schema_ir_version: _,
419 semantic_profile,
420 semantic_schema,
421 } = wire.content;
422
423 required_capabilities.ensure_supported_by(available_capabilities)?;
424 let semantic_profile = semantic_profile.rebuild()?;
425 let managed_scope = managed_scope.rebuild()?;
426 let declared_bytes = to_canonical_json(&declared_schema)?;
427 let declared = decode_declared_schema(&declared_bytes)?;
428 declared
429 .required_capabilities()
430 .ensure_supported_by(&required_capabilities)?;
431 require_exact_value(
432 &declared_identity,
433 declared.declared_identity_fingerprint(),
434 "declared-schema identity fingerprint is stale",
435 )?;
436
437 let resolved =
438 resolve_schema_with_capabilities(&declared, semantic_profile.id(), available_capabilities)?;
439 require_exact_value(
440 &semantic_schema,
441 resolved.semantic_fingerprint(),
442 "global semantic-schema fingerprint is stale",
443 )?;
444
445 let managed_context = ManagedDeltaContext::new(
446 managed_scope.id().clone(),
447 semantic_profile.id().clone(),
448 available_capabilities.clone(),
449 );
450 let rebuilt_managed_state = managed_schema_state(&declared, &managed_context)?;
451 require_exact_value(
452 &managed_state,
453 &rebuilt_managed_state,
454 "managed schema state is stale",
455 )?;
456
457 Ok(VerifiedSchemaAuthority {
458 authority_fingerprint,
459 canonical_bytes: bytes.to_vec(),
460 declared,
461 managed_scope,
462 managed_state: rebuilt_managed_state,
463 required_capabilities,
464 resolved,
465 semantic_profile,
466 })
467}
468
469fn compute_authority_fingerprint(
470 content: &SchemaAuthorityContentWire,
471) -> Result<Fingerprint, SchemaAuthorityError> {
472 Ok(Fingerprint::compute(
473 FingerprintDomain::new(SCHEMA_AUTHORITY_FINGERPRINT_DOMAIN)?,
474 CanonicalizationVersion::new(SCHEMA_AUTHORITY_FINGERPRINT_CANONICALIZATION)?,
475 None,
476 &to_canonical_json(content)?,
477 ))
478}
479
480fn canonical_value<T: Serialize>(value: &T) -> Result<Value, SchemaAuthorityError> {
481 Ok(from_canonical_json(&to_canonical_json(value)?)?)
482}
483
484fn require_exact_value<T: Serialize>(
485 actual: &Value,
486 expected: &T,
487 message: &'static str,
488) -> Result<(), SchemaAuthorityError> {
489 if actual == &canonical_value(expected)? {
490 Ok(())
491 } else {
492 Err(integrity_failure(message))
493 }
494}
495
496fn integrity_failure(message: &'static str) -> SchemaAuthorityError {
497 SchemaAuthorityError::new(SchemaAuthorityErrorCode::IntegrityMismatch, message)
498}