type-bridge-schema 2.2.2

Canonical schema resolution and projection engine for type-bridge
Documentation
use serde_json::Value;
use type_bridge_contract::capability::{CapabilityId, CapabilitySet};
use type_bridge_contract::codec::to_canonical_json;
use type_bridge_contract::fingerprint::{
    CanonicalizationVersion, Fingerprint, FingerprintDomain, SemanticProfileId,
};
use type_bridge_contract::limits::{MAX_CANONICAL_COLLECTION_LEN, MAX_CANONICAL_DEPTH};
use type_bridge_contract::managed_scope::ManagedScopeId;
use type_bridge_contract::schema::{DeclaredSchema, DocumentId, encode_declared_schema};
use type_bridge_schema::{
    MAX_SCHEMA_AUTHORITY_BYTES, ManagedDeltaContext, SCHEMA_AUTHORITY_FINGERPRINT_CANONICALIZATION,
    SCHEMA_AUTHORITY_FINGERPRINT_DOMAIN, SchemaAuthorityErrorCode, SchemaDocumentSet,
    build_schema_authority, decode_schema_authority, encode_schema_authority, normalize_documents,
    schema_authority_capability_vocabulary,
};

fn capabilities(ids: &[&str]) -> CapabilitySet {
    ids.iter()
        .map(|id| CapabilityId::new(*id).expect("test capability is canonical"))
        .collect()
}

fn declared_schema() -> DeclaredSchema {
    let source = r#"format: typebridge.schema/v2
capabilities:
  required: [schema.roles]
attributes:
  name: { value: string }
entities:
  person: { owns: [name] }
relations:
  membership: { relates: [member] }
"#;
    let documents = SchemaDocumentSet::parse([(
        DocumentId::new("schema/person.yaml").expect("fixture path is valid"),
        source,
    )])
    .expect("fresh Split-YAML document parses");
    normalize_documents(&documents).expect("fresh Split-YAML document normalizes")
}

fn fixture() -> (DeclaredSchema, CapabilitySet, ManagedDeltaContext) {
    let declared = declared_schema();
    let required = capabilities(&["schema.roles", "server.query-v2"]);
    let context = ManagedDeltaContext::new(
        ManagedScopeId::new("example-application").expect("scope is valid"),
        SemanticProfileId::new("typedb-3.12.1/v1").expect("profile is valid"),
        required.clone(),
    );
    (declared, required, context)
}

fn authority_value() -> (Value, CapabilitySet) {
    let (declared, required, context) = fixture();
    let authority =
        build_schema_authority(&declared, &required, &context).expect("fixture authority builds");
    (
        serde_json::from_slice(&encode_schema_authority(&authority))
            .expect("authority is canonical JSON"),
        required,
    )
}

fn resign(value: &mut Value) {
    let content = to_canonical_json(&value["content"]).expect("mutated content is canonicalizable");
    let fingerprint = Fingerprint::compute(
        FingerprintDomain::new(SCHEMA_AUTHORITY_FINGERPRINT_DOMAIN)
            .expect("authority fingerprint domain is valid"),
        CanonicalizationVersion::new(SCHEMA_AUTHORITY_FINGERPRINT_CANONICALIZATION)
            .expect("authority canonicalization is valid"),
        None,
        &content,
    );
    value["authority_fingerprint"] =
        serde_json::to_value(fingerprint).expect("fingerprint is JSON-representable");
}

fn canonical_bytes(value: &Value) -> Vec<u8> {
    to_canonical_json(value).expect("test mutation is canonicalizable")
}

fn assert_resigned_mutation_rejected(
    base: &Value,
    available: &CapabilitySet,
    mutate: impl FnOnce(&mut Value),
    expected: SchemaAuthorityErrorCode,
) {
    let mut value = base.clone();
    mutate(&mut value);
    resign(&mut value);
    let error = decode_schema_authority(&canonical_bytes(&value), available)
        .expect_err("resigned mutation must fail independent reconstruction");
    assert_eq!(error.code(), expected);
}

#[test]
fn split_yaml_authority_round_trips_without_source_access() {
    let (declared, required, context) = fixture();
    let authority = build_schema_authority(&declared, &required, &context)
        .expect("authority builds from the resolved declaration");
    let bytes = encode_schema_authority(&authority);
    let declared_bytes = encode_declared_schema(&declared).expect("declaration encodes");

    drop(declared);
    let decoded = decode_schema_authority(&bytes, context.available_capabilities())
        .expect("source-free authority decode succeeds");
    let rebuilt = build_schema_authority(
        decoded.declared_schema(),
        decoded.required_capabilities(),
        &ManagedDeltaContext::new(
            decoded.managed_scope().id().clone(),
            decoded.semantic_profile().id().clone(),
            context.available_capabilities().clone(),
        ),
    )
    .expect("decoded authority can rebuild itself");

    assert_eq!(
        encode_declared_schema(decoded.declared_schema()).unwrap(),
        declared_bytes,
    );
    assert_eq!(decoded.resolved_schema(), authority.resolved_schema());
    assert_eq!(decoded.managed_state(), authority.managed_state());
    assert_eq!(decoded.managed_scope(), authority.managed_scope());
    assert_eq!(decoded.semantic_profile(), authority.semantic_profile());
    assert_eq!(decoded.required_capabilities(), &required);
    assert_eq!(
        decoded.authority_fingerprint(),
        authority.authority_fingerprint()
    );
    assert_eq!(encode_schema_authority(&rebuilt), bytes);
    assert_eq!(
        authority.authority_fingerprint().digest().to_hex(),
        "a1cac0a9577e883b48ff33a2fb5790a799b676e6a65f2252f308987be0c0b49b"
    );
}

#[test]
fn artifact_requirements_are_additive_but_fail_closed() {
    let (declared, required, context) = fixture();
    let authority = build_schema_authority(&declared, &required, &context)
        .expect("workspace-additive requirement is accepted");
    assert_eq!(authority.required_capabilities(), &required);
    assert_eq!(
        authority.managed_state().required_capabilities(),
        declared.required_capabilities(),
    );

    let missing_declared = capabilities(&["server.query-v2"]);
    let error = build_schema_authority(&declared, &missing_declared, &context)
        .expect_err("artifact cannot omit a declared requirement");
    assert_eq!(
        error.code(),
        SchemaAuthorityErrorCode::UnsupportedCapability
    );

    let unavailable_context = ManagedDeltaContext::new(
        context.scope_id().clone(),
        context.semantic_profile().clone(),
        capabilities(&["schema.roles"]),
    );
    let error = build_schema_authority(&declared, &required, &unavailable_context)
        .expect_err("artifact cannot claim an unavailable requirement");
    assert_eq!(
        error.code(),
        SchemaAuthorityErrorCode::UnsupportedCapability
    );

    let error = decode_schema_authority(
        &encode_schema_authority(&authority),
        &capabilities(&["schema.roles"]),
    )
    .expect_err("consumer must advertise every artifact requirement");
    assert_eq!(
        error.code(),
        SchemaAuthorityErrorCode::UnsupportedCapability
    );
}

#[test]
fn shared_consumers_accept_additive_workspace_execution_requirements() {
    let declared = declared_schema();
    let available = schema_authority_capability_vocabulary();
    let mut required = declared.required_capabilities().clone();
    required.insert(
        CapabilityId::new("schema.transition.define")
            .expect("additive execution capability is canonical"),
    );
    let context = ManagedDeltaContext::new(
        ManagedScopeId::new("additive-workspace").expect("scope is valid"),
        SemanticProfileId::new("typedb-3.12.1/v1").expect("profile is valid"),
        available.clone(),
    );

    let authority = build_schema_authority(&declared, &required, &context)
        .expect("shared authority vocabulary accepts the workspace requirement");
    let decoded = decode_schema_authority(&encode_schema_authority(&authority), &available)
        .expect("every generated and server consumer can decode the emitted authority");

    assert_eq!(decoded.required_capabilities(), &required);
}

#[test]
fn every_derived_authority_claim_is_reconstructed_after_resigning() {
    let (base, available) = authority_value();
    let stale = "0".repeat(64);

    assert_resigned_mutation_rejected(
        &base,
        &available,
        |value| value["content"]["declared_identity"]["digest"] = stale.clone().into(),
        SchemaAuthorityErrorCode::IntegrityMismatch,
    );
    assert_resigned_mutation_rejected(
        &base,
        &available,
        |value| value["content"]["declared_schema"]["required_capabilities"] = Value::Array(vec![]),
        SchemaAuthorityErrorCode::IntegrityMismatch,
    );
    assert_resigned_mutation_rejected(
        &base,
        &available,
        |value| {
            value["content"]["semantic_profile"]["fingerprint"]["digest"] = stale.clone().into()
        },
        SchemaAuthorityErrorCode::IntegrityMismatch,
    );
    assert_resigned_mutation_rejected(
        &base,
        &available,
        |value| value["content"]["semantic_profile"]["id"] = "typedb-3.11.5/v1".into(),
        SchemaAuthorityErrorCode::IntegrityMismatch,
    );
    assert_resigned_mutation_rejected(
        &base,
        &available,
        |value| value["content"]["semantic_schema"]["digest"] = stale.clone().into(),
        SchemaAuthorityErrorCode::IntegrityMismatch,
    );
    assert_resigned_mutation_rejected(
        &base,
        &available,
        |value| value["content"]["managed_scope"]["id"] = "other-application".into(),
        SchemaAuthorityErrorCode::IntegrityMismatch,
    );
    assert_resigned_mutation_rejected(
        &base,
        &available,
        |value| {
            value["content"]["managed_scope"]["profile"]["fingerprint"]["digest"] =
                stale.clone().into()
        },
        SchemaAuthorityErrorCode::IntegrityMismatch,
    );
    for field in [
        "declared_identity",
        "managed_declared_identity",
        "managed_semantic_schema",
    ] {
        assert_resigned_mutation_rejected(
            &base,
            &available,
            |value| value["content"]["managed_state"][field]["digest"] = stale.clone().into(),
            SchemaAuthorityErrorCode::IntegrityMismatch,
        );
    }
    assert_resigned_mutation_rejected(
        &base,
        &available,
        |value| {
            value["content"]["managed_state"]["selection"]
                .as_array_mut()
                .expect("selection is an array")
                .pop();
        },
        SchemaAuthorityErrorCode::IntegrityMismatch,
    );
    assert_resigned_mutation_rejected(
        &base,
        &available,
        |value| {
            value["content"]["required_capabilities"] =
                Value::Array(vec![Value::String("server.query-v2".to_owned())]);
        },
        SchemaAuthorityErrorCode::UnsupportedCapability,
    );
}

#[test]
fn outer_fingerprint_unknown_fields_and_typed_normalization_fail_closed() {
    let (base, available) = authority_value();
    let mut stale_fingerprint = base.clone();
    stale_fingerprint["content"]["managed_scope"]["id"] = "other-application".into();
    let error = decode_schema_authority(&canonical_bytes(&stale_fingerprint), &available)
        .expect_err("unresigned mutation is rejected");
    assert_eq!(error.code(), SchemaAuthorityErrorCode::IntegrityMismatch);

    let mut unknown_root = base.clone();
    unknown_root["unknown"] = Value::Bool(true);
    let error = decode_schema_authority(&canonical_bytes(&unknown_root), &available)
        .expect_err("unknown root field is rejected");
    assert_eq!(error.code(), SchemaAuthorityErrorCode::Contract);

    assert_resigned_mutation_rejected(
        &base,
        &available,
        |value| value["content"]["semantic_profile"]["unknown"] = Value::Bool(true),
        SchemaAuthorityErrorCode::Contract,
    );
    assert_resigned_mutation_rejected(
        &base,
        &available,
        |value| value["content"]["declared_schema"]["unknown"] = Value::Bool(true),
        SchemaAuthorityErrorCode::Contract,
    );
    assert_resigned_mutation_rejected(
        &base,
        &available,
        |value| value["content"]["managed_state"]["unknown"] = Value::Bool(true),
        SchemaAuthorityErrorCode::IntegrityMismatch,
    );

    let mut duplicate_capability = base.clone();
    duplicate_capability["content"]["required_capabilities"]
        .as_array_mut()
        .expect("capabilities are an array")
        .push(Value::String("schema.roles".to_owned()));
    resign(&mut duplicate_capability);
    let error = decode_schema_authority(&canonical_bytes(&duplicate_capability), &available)
        .expect_err("typed canonical normalization rejects duplicate set entries");
    assert_eq!(error.code(), SchemaAuthorityErrorCode::Contract);
}

#[test]
fn versions_canonical_bytes_and_shared_structural_limits_fail_closed() {
    let (base, available) = authority_value();
    for (field, value) in [
        (
            "authority_version",
            Value::String("typebridge.schema-authority/v2".to_owned()),
        ),
        ("codec_version", Value::from(2)),
        ("schema_ir_version", Value::from(2)),
    ] {
        let mut mutated = base.clone();
        mutated["content"][field] = value;
        resign(&mut mutated);
        let error = decode_schema_authority(&canonical_bytes(&mutated), &available)
            .expect_err("unsupported version is rejected");
        assert_eq!(error.code(), SchemaAuthorityErrorCode::UnsupportedVersion);
    }

    assert_resigned_mutation_rejected(
        &base,
        &available,
        |value| value["content"]["semantic_profile"]["id"] = "typedb-3.13.0/v1".into(),
        SchemaAuthorityErrorCode::UnsupportedCapability,
    );

    let mut non_canonical = canonical_bytes(&base);
    non_canonical.push(b'\n');
    let error = decode_schema_authority(&non_canonical, &available)
        .expect_err("non-canonical JSON is rejected");
    assert_eq!(error.code(), SchemaAuthorityErrorCode::Contract);

    let oversized = vec![b' '; MAX_SCHEMA_AUTHORITY_BYTES + 1];
    let error = decode_schema_authority(&oversized, &available)
        .expect_err("oversized authority is rejected before parsing");
    assert_eq!(error.code(), SchemaAuthorityErrorCode::ResourceLimit);

    let too_deep = format!(
        "{}0{}",
        "[".repeat(MAX_CANONICAL_DEPTH + 1),
        "]".repeat(MAX_CANONICAL_DEPTH + 1)
    );
    let error = decode_schema_authority(too_deep.as_bytes(), &available)
        .expect_err("over-deep authority is rejected before reconstruction");
    assert_eq!(error.code(), SchemaAuthorityErrorCode::ResourceLimit);

    let too_many = format!(
        "[{}]",
        vec!["0"; MAX_CANONICAL_COLLECTION_LEN + 1].join(",")
    );
    let error = decode_schema_authority(too_many.as_bytes(), &available)
        .expect_err("oversized collection is rejected before reconstruction");
    assert_eq!(error.code(), SchemaAuthorityErrorCode::ResourceLimit);
}