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 => SchemaAuthorityErrorCode::ResourceLimit,
147 DiagnosticCategory::Integrity => SchemaAuthorityErrorCode::IntegrityMismatch,
148 };
149 Self {
150 code,
151 message: "a schema-authority contract is invalid",
152 contract: Some(Box::new(value)),
153 schema: None,
154 }
155 }
156}
157
158impl From<SchemaDiagnostics> for SchemaAuthorityError {
159 fn from(value: SchemaDiagnostics) -> Self {
160 Self {
161 code: SchemaAuthorityErrorCode::Schema,
162 message: "schema-authority reconstruction failed",
163 contract: None,
164 schema: Some(Box::new(value)),
165 }
166 }
167}
168
169impl From<DeltaError> for SchemaAuthorityError {
170 fn from(value: DeltaError) -> Self {
171 match value {
172 DeltaError::Contract(error) => error.into(),
173 DeltaError::Schema(error) => error.into(),
174 }
175 }
176}
177
178#[derive(Clone, Debug, Deserialize, Serialize)]
179#[serde(deny_unknown_fields)]
180struct SemanticProfileBindingWire {
181 fingerprint: Value,
182 id: SemanticProfileId,
183}
184
185impl SemanticProfileBindingWire {
186 fn from_binding(binding: &SemanticProfileBinding) -> Result<Self, SchemaAuthorityError> {
187 Ok(Self {
188 fingerprint: canonical_value(binding.fingerprint().as_fingerprint())?,
189 id: binding.id().clone(),
190 })
191 }
192
193 fn rebuild(self) -> Result<SemanticProfileBinding, SchemaAuthorityError> {
194 let trusted = SemanticProfileBinding::resolve(self.id.clone())?;
195 require_exact_value(
196 &self.fingerprint,
197 trusted.fingerprint().as_fingerprint(),
198 "semantic-profile fingerprint is stale",
199 )?;
200 Ok(trusted)
201 }
202}
203
204#[derive(Clone, Debug, Deserialize, Serialize)]
205#[serde(deny_unknown_fields)]
206struct ManagedScopeProfileBindingWire {
207 fingerprint: Value,
208 id: ManagedScopeProfileId,
209}
210
211#[derive(Clone, Debug, Deserialize, Serialize)]
212#[serde(deny_unknown_fields)]
213struct ManagedScopeBindingWire {
214 id: ManagedScopeId,
215 profile: ManagedScopeProfileBindingWire,
216}
217
218impl ManagedScopeBindingWire {
219 fn from_binding(binding: &ManagedScopeBinding) -> Result<Self, SchemaAuthorityError> {
220 Ok(Self {
221 id: binding.id().clone(),
222 profile: ManagedScopeProfileBindingWire {
223 fingerprint: canonical_value(binding.profile().fingerprint().as_fingerprint())?,
224 id: binding.profile().id().clone(),
225 },
226 })
227 }
228
229 fn rebuild(self) -> Result<ManagedScopeBinding, SchemaAuthorityError> {
230 let trusted = ManagedScopeBinding::exclusive(self.id.clone())?;
231 if self.profile.id != *trusted.profile().id() {
232 return Err(integrity_failure("managed-scope profile identity is stale"));
233 }
234 require_exact_value(
235 &self.profile.fingerprint,
236 trusted.profile().fingerprint().as_fingerprint(),
237 "managed-scope profile fingerprint is stale",
238 )?;
239 Ok(trusted)
240 }
241}
242
243#[derive(Clone, Debug, Deserialize, Serialize)]
244#[serde(deny_unknown_fields)]
245struct SchemaAuthorityContentWire {
246 authority_version: String,
247 codec_version: u16,
248 declared_identity: Value,
249 declared_schema: Value,
250 managed_scope: ManagedScopeBindingWire,
251 managed_state: Value,
252 required_capabilities: CapabilitySet,
253 schema_ir_version: u16,
254 semantic_profile: SemanticProfileBindingWire,
255 semantic_schema: Value,
256}
257
258#[derive(Clone, Debug, Deserialize, Serialize)]
259#[serde(deny_unknown_fields)]
260struct SchemaAuthorityWire {
261 authority_fingerprint: Value,
262 content: SchemaAuthorityContentWire,
263}
264
265#[derive(Clone, Debug)]
267pub struct VerifiedSchemaAuthority {
268 authority_fingerprint: Fingerprint,
269 canonical_bytes: Vec<u8>,
270 declared: DeclaredSchema,
271 managed_scope: ManagedScopeBinding,
272 managed_state: ManagedSchemaState,
273 required_capabilities: CapabilitySet,
274 resolved: ResolvedSchema,
275 semantic_profile: SemanticProfileBinding,
276}
277
278impl VerifiedSchemaAuthority {
279 #[must_use]
281 pub const fn authority_fingerprint(&self) -> &Fingerprint {
282 &self.authority_fingerprint
283 }
284
285 #[must_use]
287 pub const fn declared_schema(&self) -> &DeclaredSchema {
288 &self.declared
289 }
290
291 #[must_use]
293 pub const fn resolved_schema(&self) -> &ResolvedSchema {
294 &self.resolved
295 }
296
297 #[must_use]
299 pub const fn semantic_profile(&self) -> &SemanticProfileBinding {
300 &self.semantic_profile
301 }
302
303 #[must_use]
305 pub const fn managed_scope(&self) -> &ManagedScopeBinding {
306 &self.managed_scope
307 }
308
309 #[must_use]
311 pub const fn managed_state(&self) -> &ManagedSchemaState {
312 &self.managed_state
313 }
314
315 #[must_use]
317 pub const fn required_capabilities(&self) -> &CapabilitySet {
318 &self.required_capabilities
319 }
320}
321
322pub fn build_schema_authority(
328 declared: &DeclaredSchema,
329 required_capabilities: &CapabilitySet,
330 context: &ManagedDeltaContext,
331) -> Result<VerifiedSchemaAuthority, SchemaAuthorityError> {
332 declared
333 .required_capabilities()
334 .ensure_supported_by(required_capabilities)?;
335 required_capabilities.ensure_supported_by(context.available_capabilities())?;
336
337 let semantic_profile = SemanticProfileBinding::resolve(context.semantic_profile().clone())?;
338 let managed_scope = ManagedScopeBinding::exclusive(context.scope_id().clone())
339 .map_err(SchemaAuthorityError::from)?;
340 let resolved = resolve_schema_with_capabilities(
341 declared,
342 context.semantic_profile(),
343 context.available_capabilities(),
344 )?;
345 let managed_state = managed_schema_state(declared, context)?;
346 let declared_bytes = encode_declared_schema(declared)?;
347 let content = SchemaAuthorityContentWire {
348 authority_version: TYPEBRIDGE_SCHEMA_AUTHORITY_V1.to_owned(),
349 codec_version: CodecVersion::V1.get(),
350 declared_identity: canonical_value(declared.declared_identity_fingerprint())?,
351 declared_schema: from_canonical_json(&declared_bytes)?,
352 managed_scope: ManagedScopeBindingWire::from_binding(&managed_scope)?,
353 managed_state: canonical_value(&managed_state)?,
354 required_capabilities: required_capabilities.clone(),
355 schema_ir_version: declared.format().get(),
356 semantic_profile: SemanticProfileBindingWire::from_binding(&semantic_profile)?,
357 semantic_schema: canonical_value(resolved.semantic_fingerprint())?,
358 };
359 let authority_fingerprint = compute_authority_fingerprint(&content)?;
360 let wire = SchemaAuthorityWire {
361 authority_fingerprint: canonical_value(&authority_fingerprint)?,
362 content,
363 };
364 let bytes = to_canonical_json(&wire)?;
365 decode_schema_authority(&bytes, context.available_capabilities())
366}
367
368#[must_use]
370pub fn encode_schema_authority(authority: &VerifiedSchemaAuthority) -> Vec<u8> {
371 authority.canonical_bytes.clone()
372}
373
374pub fn decode_schema_authority(
381 bytes: &[u8],
382 available_capabilities: &CapabilitySet,
383) -> Result<VerifiedSchemaAuthority, SchemaAuthorityError> {
384 let wire: SchemaAuthorityWire = from_canonical_json(bytes)?;
385 if to_canonical_json(&wire)? != bytes {
386 return Err(SchemaAuthorityError::new(
387 SchemaAuthorityErrorCode::Contract,
388 "schema-authority bytes normalize after typed reconstruction",
389 ));
390 }
391 if wire.content.authority_version != TYPEBRIDGE_SCHEMA_AUTHORITY_V1
392 || wire.content.codec_version != CodecVersion::V1.get()
393 || wire.content.schema_ir_version != FormatVersion::V1.get()
394 {
395 return Err(SchemaAuthorityError::new(
396 SchemaAuthorityErrorCode::UnsupportedVersion,
397 "schema-authority, codec, or schema-IR version is unsupported",
398 ));
399 }
400
401 let authority_fingerprint = compute_authority_fingerprint(&wire.content)?;
402 require_exact_value(
403 &wire.authority_fingerprint,
404 &authority_fingerprint,
405 "schema-authority content fingerprint is stale",
406 )?;
407
408 let SchemaAuthorityContentWire {
409 authority_version: _,
410 codec_version: _,
411 declared_identity,
412 declared_schema,
413 managed_scope,
414 managed_state,
415 required_capabilities,
416 schema_ir_version: _,
417 semantic_profile,
418 semantic_schema,
419 } = wire.content;
420
421 required_capabilities.ensure_supported_by(available_capabilities)?;
422 let semantic_profile = semantic_profile.rebuild()?;
423 let managed_scope = managed_scope.rebuild()?;
424 let declared_bytes = to_canonical_json(&declared_schema)?;
425 let declared = decode_declared_schema(&declared_bytes)?;
426 declared
427 .required_capabilities()
428 .ensure_supported_by(&required_capabilities)?;
429 require_exact_value(
430 &declared_identity,
431 declared.declared_identity_fingerprint(),
432 "declared-schema identity fingerprint is stale",
433 )?;
434
435 let resolved =
436 resolve_schema_with_capabilities(&declared, semantic_profile.id(), available_capabilities)?;
437 require_exact_value(
438 &semantic_schema,
439 resolved.semantic_fingerprint(),
440 "global semantic-schema fingerprint is stale",
441 )?;
442
443 let managed_context = ManagedDeltaContext::new(
444 managed_scope.id().clone(),
445 semantic_profile.id().clone(),
446 available_capabilities.clone(),
447 );
448 let rebuilt_managed_state = managed_schema_state(&declared, &managed_context)?;
449 require_exact_value(
450 &managed_state,
451 &rebuilt_managed_state,
452 "managed schema state is stale",
453 )?;
454
455 Ok(VerifiedSchemaAuthority {
456 authority_fingerprint,
457 canonical_bytes: bytes.to_vec(),
458 declared,
459 managed_scope,
460 managed_state: rebuilt_managed_state,
461 required_capabilities,
462 resolved,
463 semantic_profile,
464 })
465}
466
467fn compute_authority_fingerprint(
468 content: &SchemaAuthorityContentWire,
469) -> Result<Fingerprint, SchemaAuthorityError> {
470 Ok(Fingerprint::compute(
471 FingerprintDomain::new(SCHEMA_AUTHORITY_FINGERPRINT_DOMAIN)?,
472 CanonicalizationVersion::new(SCHEMA_AUTHORITY_FINGERPRINT_CANONICALIZATION)?,
473 None,
474 &to_canonical_json(content)?,
475 ))
476}
477
478fn canonical_value<T: Serialize>(value: &T) -> Result<Value, SchemaAuthorityError> {
479 Ok(from_canonical_json(&to_canonical_json(value)?)?)
480}
481
482fn require_exact_value<T: Serialize>(
483 actual: &Value,
484 expected: &T,
485 message: &'static str,
486) -> Result<(), SchemaAuthorityError> {
487 if actual == &canonical_value(expected)? {
488 Ok(())
489 } else {
490 Err(integrity_failure(message))
491 }
492}
493
494fn integrity_failure(message: &'static str) -> SchemaAuthorityError {
495 SchemaAuthorityError::new(SchemaAuthorityErrorCode::IntegrityMismatch, message)
496}