1use core::marker::PhantomData;
4use std::sync::Arc;
5
6use type_bridge_contract::schema::encode_declared_schema;
7use type_bridge_schema::{
8 MAX_SCHEMA_AUTHORITY_BYTES, VerifiedSchemaAuthority, decode_schema_authority,
9 schema_authority_capability_vocabulary,
10};
11
12use crate::error::{Error, Result};
13
14#[doc(hidden)]
15pub mod sealed {
16 pub trait Sealed {}
17}
18
19pub trait Schema: sealed::Sealed + Send + Sync + 'static {}
21
22#[derive(Clone, Copy, Debug, Eq, PartialEq)]
24pub struct Unbound;
25
26impl sealed::Sealed for Unbound {}
27impl Schema for Unbound {}
28
29#[derive(Clone, Copy, Debug, Eq, PartialEq)]
31pub struct SchemaPackage<S: Schema> {
32 semantic_fingerprint_json: &'static str,
33 projection_fingerprint_json: &'static str,
34 runtime_projection_json: &'static str,
35 declared_schema_json: Option<&'static str>,
36 schema_authority_json: Option<&'static str>,
37 managed_scope_id: Option<&'static str>,
38 semantic_profile_id: Option<&'static str>,
39 marker: PhantomData<fn() -> S>,
40}
41
42impl<S: Schema> SchemaPackage<S> {
43 #[doc(hidden)]
45 #[must_use]
46 pub const fn new(
47 semantic_fingerprint_json: &'static str,
48 projection_fingerprint_json: &'static str,
49 runtime_projection_json: &'static str,
50 ) -> Self {
51 Self {
52 semantic_fingerprint_json,
53 projection_fingerprint_json,
54 runtime_projection_json,
55 declared_schema_json: None,
56 schema_authority_json: None,
57 managed_scope_id: None,
58 semantic_profile_id: None,
59 marker: PhantomData,
60 }
61 }
62
63 #[doc(hidden)]
66 #[must_use]
67 pub const fn new_with_declared(
68 semantic_fingerprint_json: &'static str,
69 projection_fingerprint_json: &'static str,
70 runtime_projection_json: &'static str,
71 declared_schema_json: &'static str,
72 ) -> Self {
73 Self {
74 semantic_fingerprint_json,
75 projection_fingerprint_json,
76 runtime_projection_json,
77 declared_schema_json: Some(declared_schema_json),
78 schema_authority_json: None,
79 managed_scope_id: None,
80 semantic_profile_id: None,
81 marker: PhantomData,
82 }
83 }
84
85 #[doc(hidden)]
88 #[must_use]
89 pub const fn new_with_authority(
90 semantic_fingerprint_json: &'static str,
91 projection_fingerprint_json: &'static str,
92 runtime_projection_json: &'static str,
93 schema_authority_json: &'static str,
94 declared_schema_json: &'static str,
95 managed_scope_id: &'static str,
96 semantic_profile_id: &'static str,
97 ) -> Self {
98 Self {
99 semantic_fingerprint_json,
100 projection_fingerprint_json,
101 runtime_projection_json,
102 declared_schema_json: Some(declared_schema_json),
103 schema_authority_json: Some(schema_authority_json),
104 managed_scope_id: Some(managed_scope_id),
105 semantic_profile_id: Some(semantic_profile_id),
106 marker: PhantomData,
107 }
108 }
109
110 pub fn verify(&self) -> Result<()> {
112 let _ = self.verify_and_install_with_authority()?;
113 Ok(())
114 }
115
116 #[doc(hidden)]
118 #[must_use]
119 pub const fn semantic_fingerprint_json(&self) -> &'static str {
120 self.semantic_fingerprint_json
121 }
122
123 #[doc(hidden)]
125 #[must_use]
126 pub const fn projection_fingerprint_json(&self) -> &'static str {
127 self.projection_fingerprint_json
128 }
129
130 #[doc(hidden)]
132 #[must_use]
133 pub const fn runtime_projection_json(&self) -> &'static str {
134 self.runtime_projection_json
135 }
136
137 pub(crate) const fn declared_schema_json(&self) -> Option<&'static str> {
138 self.declared_schema_json
139 }
140
141 pub(crate) fn verify_and_install(
143 &self,
144 ) -> Result<Arc<type_bridge_orm::InstalledRuntimeProjection>> {
145 self.verify_and_install_with_authority()
146 .map(|(projection, _authority)| projection)
147 }
148
149 pub(crate) fn verify_and_install_with_authority(
150 &self,
151 ) -> Result<(
152 Arc<type_bridge_orm::InstalledRuntimeProjection>,
153 Option<VerifiedSchemaAuthority>,
154 )> {
155 let authority = self.verify_embedded_authority()?;
156 let projection = type_bridge_orm::InstalledRuntimeProjection::from_verified_rust_json(
157 self.runtime_projection_json.as_bytes(),
158 self.semantic_fingerprint_json.as_bytes(),
159 self.projection_fingerprint_json.as_bytes(),
160 )
161 .map_err(|err| Error::SchemaVerification {
162 message: err.to_string(),
163 source: Some(Box::new(err)),
164 })?;
165 if authority.as_ref().is_some_and(|authority| {
166 authority.resolved_schema().semantic_fingerprint()
167 != projection.projection().semantic_fingerprint()
168 }) {
169 return Err(authority_error(
170 "generated schema authority does not match the installed runtime projection",
171 ));
172 }
173 Ok((Arc::new(projection), authority))
174 }
175
176 fn verify_embedded_authority(&self) -> Result<Option<VerifiedSchemaAuthority>> {
177 let parts = (
178 self.schema_authority_json,
179 self.declared_schema_json,
180 self.managed_scope_id,
181 self.semantic_profile_id,
182 );
183 let (Some(envelope), Some(declared), Some(scope), Some(profile)) = parts else {
184 if parts.0.is_none() && parts.2.is_none() && parts.3.is_none() {
185 return Ok(None);
186 }
187 return Err(authority_error(
188 "generated schema package contains incomplete compiled authority evidence",
189 ));
190 };
191 if envelope.len() > MAX_SCHEMA_AUTHORITY_BYTES {
192 return Err(authority_error(
193 "generated schema authority exceeds the canonical byte ceiling",
194 ));
195 }
196 let authority = decode_schema_authority(
197 envelope.as_bytes(),
198 &schema_authority_capability_vocabulary(),
199 )
200 .map_err(|error| Error::SchemaVerification {
201 message: format!(
202 "generated schema package contains invalid compiled authority ({:?})",
203 error.code()
204 ),
205 source: Some(Box::new(error)),
206 })?;
207 let reconstructed_declared =
208 encode_declared_schema(authority.declared_schema()).map_err(|error| {
209 Error::SchemaVerification {
210 message: "generated schema authority declaration cannot be reconstructed"
211 .into(),
212 source: Some(Box::new(error)),
213 }
214 })?;
215 if reconstructed_declared != declared.as_bytes()
216 || authority.managed_scope().id().as_str() != scope
217 || authority.semantic_profile().id().as_str() != profile
218 {
219 return Err(authority_error(
220 "generated schema authority disagrees with its extracted query evidence",
221 ));
222 }
223 Ok(Some(authority))
224 }
225}
226
227fn authority_error(message: &'static str) -> Error {
228 Error::SchemaVerification {
229 message: message.into(),
230 source: None,
231 }
232}
233
234#[cfg(test)]
235mod tests {
236 use super::*;
237 use serde_json::Value;
238 use type_bridge_contract::codec::to_canonical_json;
239 use type_bridge_contract::fingerprint::{
240 CanonicalizationVersion, Fingerprint, FingerprintDomain, SemanticProfileId,
241 };
242 use type_bridge_contract::managed_scope::ManagedScopeId;
243 use type_bridge_contract::projection::{BindingTarget, ProjectionConfig};
244 use type_bridge_contract::schema::{DocumentId, encode_declared_schema};
245 use type_bridge_schema::{
246 ManagedDeltaContext, SCHEMA_AUTHORITY_FINGERPRINT_CANONICALIZATION,
247 SCHEMA_AUTHORITY_FINGERPRINT_DOMAIN, SchemaDocumentSet, build_schema_authority,
248 encode_schema_authority, normalize_documents, project, resolve,
249 };
250 use type_bridge_schema_codegen::{PythonEmitter, RustEmitter};
251
252 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
253 struct TestSchema;
254 impl sealed::Sealed for TestSchema {}
255 impl Schema for TestSchema {}
256
257 fn leak(bytes: Vec<u8>) -> &'static str {
258 Box::leak(String::from_utf8(bytes).unwrap().into_boxed_str())
259 }
260
261 fn generated_package(source: &str, scope: &str) -> SchemaPackage<TestSchema> {
262 let documents =
263 SchemaDocumentSet::parse([(DocumentId::new("authority-test.yaml").unwrap(), source)])
264 .unwrap();
265 let declared = normalize_documents(&documents).unwrap();
266 let profile = SemanticProfileId::new("typedb-3.12.1/v1").unwrap();
267 let resolved = resolve(&declared, &profile).unwrap();
268 let authority = build_schema_authority(
269 &declared,
270 declared.required_capabilities(),
271 &ManagedDeltaContext::new(
272 ManagedScopeId::new(scope).unwrap(),
273 profile,
274 schema_authority_capability_vocabulary(),
275 ),
276 )
277 .unwrap();
278 let emitter = RustEmitter::new();
279 let projection = project(
280 &resolved,
281 BindingTarget::Rust,
282 &ProjectionConfig::rust(),
283 &emitter.generator_handlers(),
284 &emitter.code_resources().unwrap(),
285 )
286 .unwrap();
287 SchemaPackage::new_with_authority(
288 leak(to_canonical_json(projection.semantic_fingerprint()).unwrap()),
289 leak(to_canonical_json(projection.projection_fingerprint()).unwrap()),
290 leak(to_canonical_json(&projection).unwrap()),
291 leak(encode_schema_authority(&authority)),
292 leak(encode_declared_schema(&declared).unwrap()),
293 Box::leak(scope.to_owned().into_boxed_str()),
294 "typedb-3.12.1/v1",
295 )
296 }
297
298 fn with_envelope(
299 package: SchemaPackage<TestSchema>,
300 envelope: &'static str,
301 ) -> SchemaPackage<TestSchema> {
302 SchemaPackage::new_with_authority(
303 package.semantic_fingerprint_json,
304 package.projection_fingerprint_json,
305 package.runtime_projection_json,
306 envelope,
307 package.declared_schema_json.unwrap(),
308 package.managed_scope_id.unwrap(),
309 package.semantic_profile_id.unwrap(),
310 )
311 }
312
313 fn canonical(value: &Value) -> &'static str {
314 leak(to_canonical_json(value).unwrap())
315 }
316
317 fn resign(value: &mut Value) {
318 let content = to_canonical_json(&value["content"]).unwrap();
319 let fingerprint = Fingerprint::compute(
320 FingerprintDomain::new(SCHEMA_AUTHORITY_FINGERPRINT_DOMAIN).unwrap(),
321 CanonicalizationVersion::new(SCHEMA_AUTHORITY_FINGERPRINT_CANONICALIZATION).unwrap(),
322 None,
323 &content,
324 );
325 value["authority_fingerprint"] = serde_json::to_value(fingerprint).unwrap();
326 }
327
328 #[test]
329 fn compiled_authority_is_fully_verified_and_bound_to_projection() {
330 let package = generated_package(
331 "format: typebridge.schema/v2\nentities:\n person: {}\n",
332 "rust-authority-test",
333 );
334 let (projection, authority) = package.verify_and_install_with_authority().unwrap();
335 let authority = authority.expect("generated package has compiled authority");
336 assert_eq!(
337 projection.projection().semantic_fingerprint(),
338 authority.resolved_schema().semantic_fingerprint(),
339 );
340
341 let foreign = generated_package(
342 "format: typebridge.schema/v2\nentities:\n organization: {}\n",
343 "rust-authority-test",
344 );
345 let mismatched: SchemaPackage<TestSchema> = SchemaPackage::new_with_authority(
346 package.semantic_fingerprint_json,
347 package.projection_fingerprint_json,
348 package.runtime_projection_json,
349 foreign.schema_authority_json.unwrap(),
350 foreign.declared_schema_json.unwrap(),
351 foreign.managed_scope_id.unwrap(),
352 foreign.semantic_profile_id.unwrap(),
353 );
354 let error = mismatched
355 .verify()
356 .expect_err("foreign semantic authority must not bind to the projection");
357 assert!(error.to_string().contains("installed runtime projection"));
358 }
359
360 #[test]
361 fn compiled_authority_rejects_missing_and_stale_outer_fingerprints() {
362 let package = generated_package(
363 "format: typebridge.schema/v2\nentities:\n person: {}\n",
364 "rust-authority-test",
365 );
366 let original: Value = serde_json::from_str(package.schema_authority_json.unwrap()).unwrap();
367
368 let mut missing = original.clone();
369 missing
370 .as_object_mut()
371 .unwrap()
372 .remove("authority_fingerprint");
373 assert!(
374 with_envelope(package, canonical(&missing))
375 .verify()
376 .is_err()
377 );
378
379 let mut stale = original;
380 stale["authority_fingerprint"]["digest"] = "0".repeat(64).into();
381 assert!(with_envelope(package, canonical(&stale)).verify().is_err());
382 }
383
384 #[test]
385 fn compiled_authority_reconstructs_managed_state_and_rejects_unsupported_claims() {
386 let package = generated_package(
387 "format: typebridge.schema/v2\nentities:\n person: {}\n",
388 "rust-authority-test",
389 );
390 let original: Value = serde_json::from_str(package.schema_authority_json.unwrap()).unwrap();
391
392 let mut managed = original.clone();
393 managed["content"]["managed_state"]["declared_identity"]["digest"] = "0".repeat(64).into();
394 resign(&mut managed);
395 assert!(
396 with_envelope(package, canonical(&managed))
397 .verify()
398 .is_err()
399 );
400
401 let mut capabilities = original.clone();
402 capabilities["content"]["required_capabilities"] =
403 Value::Array(vec![Value::String("unsupported.runtime".into())]);
404 resign(&mut capabilities);
405 let error = with_envelope(package, canonical(&capabilities))
406 .verify()
407 .expect_err("unsupported artifact capability must fail closed");
408 assert!(error.to_string().contains("UnsupportedCapability"));
409
410 let mut version = original;
411 version["content"]["authority_version"] =
412 Value::String("typebridge.schema-authority/v2".into());
413 resign(&mut version);
414 let error = with_envelope(package, canonical(&version))
415 .verify()
416 .expect_err("unsupported artifact version must fail closed");
417 assert!(error.to_string().contains("UnsupportedVersion"));
418 }
419
420 #[test]
421 fn compiled_authority_rejects_oversize_and_detached_evidence() {
422 let package = generated_package(
423 "format: typebridge.schema/v2\nentities:\n person: {}\n",
424 "rust-authority-test",
425 );
426 let oversized = Box::leak(" ".repeat(MAX_SCHEMA_AUTHORITY_BYTES + 1).into_boxed_str());
427 let error = with_envelope(package, oversized)
428 .verify()
429 .expect_err("oversize authority must fail before parsing");
430 assert!(error.to_string().contains("byte ceiling"));
431
432 let detached: SchemaPackage<TestSchema> = SchemaPackage::new_with_authority(
433 package.semantic_fingerprint_json,
434 package.projection_fingerprint_json,
435 package.runtime_projection_json,
436 package.schema_authority_json.unwrap(),
437 package.declared_schema_json.unwrap(),
438 "other-scope",
439 package.semantic_profile_id.unwrap(),
440 );
441 let error = detached
442 .verify()
443 .expect_err("detached scope must not override compiled authority");
444 assert!(error.to_string().contains("extracted query evidence"));
445 }
446
447 #[test]
448 fn schema_package_fingerprint_verification() {
449 let documents = SchemaDocumentSet::parse([(
450 DocumentId::new("test.yaml").unwrap(),
451 "format: typebridge.schema/v2\nentities:\n person: {}\n",
452 )])
453 .unwrap();
454 let declared = normalize_documents(&documents).unwrap();
455 let profile = SemanticProfileId::new("typedb-3.12.1/v1").unwrap();
456 let resolved = resolve(&declared, &profile).unwrap();
457 let emitter = RustEmitter::new();
458 let resources = emitter.code_resources().unwrap();
459 let projection = project(
460 &resolved,
461 BindingTarget::Rust,
462 &ProjectionConfig::rust(),
463 &emitter.generator_handlers(),
464 &resources,
465 )
466 .unwrap();
467
468 let semantic_json =
469 String::from_utf8(to_canonical_json(projection.semantic_fingerprint()).unwrap())
470 .unwrap();
471 let projection_json =
472 String::from_utf8(to_canonical_json(projection.projection_fingerprint()).unwrap())
473 .unwrap();
474 let runtime_json = String::from_utf8(to_canonical_json(&projection).unwrap()).unwrap();
475
476 let semantic_ref: &'static str = Box::leak(semantic_json.into_boxed_str());
477 let projection_ref: &'static str = Box::leak(projection_json.into_boxed_str());
478 let runtime_ref: &'static str = Box::leak(runtime_json.into_boxed_str());
479
480 let valid_package: SchemaPackage<TestSchema> =
481 SchemaPackage::new(semantic_ref, projection_ref, runtime_ref);
482 assert!(valid_package.verify().is_ok());
483
484 let tampered_package: SchemaPackage<TestSchema> =
485 SchemaPackage::new(semantic_ref, r#""rust/v1-tampered""#, runtime_ref);
486 match tampered_package.verify() {
487 Err(err) => {
488 use std::error::Error as _;
489 assert!(err.source().is_some());
490 }
491 Ok(_) => panic!("tampered schema package must fail verification"),
492 }
493 }
494
495 #[test]
496 fn rejects_non_rust_target_projection() {
497 let documents = SchemaDocumentSet::parse([(
498 DocumentId::new("test.yaml").unwrap(),
499 "format: typebridge.schema/v2\nentities:\n person: {}\n",
500 )])
501 .unwrap();
502 let declared = normalize_documents(&documents).unwrap();
503 let profile = SemanticProfileId::new("typedb-3.12.1/v1").unwrap();
504 let resolved = resolve(&declared, &profile).unwrap();
505 let py_emitter = PythonEmitter::new();
506 let py_resources = py_emitter.code_resources().unwrap();
507 let py_projection = project(
508 &resolved,
509 BindingTarget::Python,
510 &ProjectionConfig::python(),
511 &py_emitter.generator_handlers(),
512 &py_resources,
513 )
514 .unwrap();
515
516 let semantic_json =
517 String::from_utf8(to_canonical_json(py_projection.semantic_fingerprint()).unwrap())
518 .unwrap();
519 let projection_json =
520 String::from_utf8(to_canonical_json(py_projection.projection_fingerprint()).unwrap())
521 .unwrap();
522 let runtime_json = String::from_utf8(to_canonical_json(&py_projection).unwrap()).unwrap();
523
524 let semantic_ref: &'static str = Box::leak(semantic_json.into_boxed_str());
525 let projection_ref: &'static str = Box::leak(projection_json.into_boxed_str());
526 let runtime_ref: &'static str = Box::leak(runtime_json.into_boxed_str());
527
528 let py_package: SchemaPackage<TestSchema> =
529 SchemaPackage::new(semantic_ref, projection_ref, runtime_ref);
530 let err = py_package.verify().unwrap_err();
531 assert!(err.to_string().contains("target mismatch") || err.to_string().contains("Rust"));
532 }
533}