Skip to Content

API reference

Types and functions exported by APL-Core.

Prelude

Import common types with a single statement:

use apl_core::prelude::*;

Content-addressed types

Hash

A validated SHA-256 hash (32 bytes). Hash is a newtype struct, not a type alias.

pub struct Hash(/* private [u8; 32] */); impl Hash { pub const fn from_bytes(bytes: [u8; 32]) -> Self; pub const fn as_bytes(&self) -> &[u8; 32]; pub const fn into_bytes(self) -> [u8; 32]; } impl Display for Hash { /* renders as "sha256:<lowercase-hex>" */ } impl Serialize for Hash { /* emits the same `sha256:<hex>` string */ } impl<'de> Deserialize<'de> for Hash { /* parses `sha256:<hex>` via parse_hash_string */ }

Reference

A Reference Object per The Protocol § 2.3.

pub struct Reference { pub hash: Hash, pub resolver_hint: Option<String>, } impl Reference { pub fn parse(value: &serde_json::Value) -> Result<Self, ParseError>; }

Parse a hash string (sha256:<hex>) directly with parse_hash_string(&str) -> Result<Hash, ParseError>. Validation errors are carried by ParseError (enum with variants UnsupportedAlgorithm, InvalidLength, InvalidHexCharacter, Empty, NotObject, HashMissing, HashNotString, ResolverHintNotString, ResolverHintEmpty).

Protocol types

Claim

Claim is the parsed metadata.apl envelope — not the inner claim object. The inner object is modelled as ClaimInner.

pub struct Claim { pub version: String, pub claim: ClaimInner, pub frame_ref: Reference, pub bridge_refs: Option<Vec<Reference>>, pub transformation_refs: Option<Vec<Reference>>, } impl Claim { pub fn parse(value: &serde_json::Value) -> Result<Self, ClaimParseError>; }

When bridge_refs / transformation_refs are Some, the contained Vec is guaranteed non-empty (per The Protocol § 3.1). Absent fields are represented as None, never as Some(vec![]).

ClaimInner

The inner claim object — the situated assertion itself.

pub struct ClaimInner { pub kind: ClaimKind, pub subject: Subject, pub aspect_refs: Vec<String>, pub statement: Statement, pub related_frames: Option<Vec<Hash>>, } pub enum ClaimKind { Observation, } pub struct Subject { pub id: Option<String>, pub digest: Option<Hash>, pub full: serde_json::Map<String, serde_json::Value>, } pub struct Statement { pub predicate: String, pub content: serde_json::Value, }

Subject::full preserves the complete JSON object (including any profile-specific fields such as artifact_digest for AI-Eval) so vertical profiles can read them without re-parsing.

Frame

pub struct Frame { pub version: String, pub observer: Observer, pub aspect: Vec<String>, pub invariance: Vec<String>, pub exclusions: Vec<String>, pub procedure: Option<StringOrObject>, pub instrument: Option<StringOrObject>, pub scope: Option<StringOrObject>, pub resolution: Option<StringOrObject>, pub extends: Option<Reference>, pub raw: serde_json::Value, } pub enum Observer { String(String), Object(serde_json::Map<String, serde_json::Value>), } pub enum StringOrObject { String(String), Object(serde_json::Map<String, serde_json::Value>), } impl Frame { pub fn parse(value: &serde_json::Value) -> Result<Self, FrameParseError>; pub fn canonical_hash(&self) -> Hash; pub fn has_aspect(&self, aspect: &str) -> bool; }

raw retains the original JSON value for canonical_hash recomputation (required by The Protocol § 5.4) and for profile-level inspection of non-kernel fields. StringOrObject is the shared typing for Frame/Transformation fields that MAY be either a non-empty string or a non-empty object.

Bridge

pub struct Bridge { pub version: String, pub source_frame: Reference, pub target_frame: Reference, pub comparison_scope: ComparisonScope, pub assumptions: Vec<String>, pub losses: Vec<String>, pub raw: serde_json::Value, } pub struct ComparisonScope { pub source_aspects: Vec<String>, pub target_aspects: Vec<String>, pub relation_type: String, } impl Bridge { pub fn parse(value: &serde_json::Value) -> Result<Self, BridgeParseError>; pub fn canonical_hash(&self) -> Hash; }

raw retains the parsed JSON value. canonical_hash does NOT hash the original input bytes (whitespace, key order, etc. are not preserved); it re-serializes raw through JCS (RFC 8785)  and returns SHA-256(JCS(raw)). This is the identity required by The Protocol § 5.8.6: two byte-wise different but semantically equal JSON inputs produce the same bridge identity. raw is also the hook through which vertical profiles read non-kernel fields such as bridge_kind.

Transformation

pub struct Transformation { pub version: String, pub procedure: StringOrObject, pub preserves: Vec<String>, pub losses: Vec<String>, pub raw: serde_json::Value, } impl Transformation { pub fn parse(value: &serde_json::Value) -> Result<Self, TransformationParseError>; pub fn canonical_hash(&self) -> Hash; }

Empty preserves / losses are valid declarations (“nothing preserved” / “nothing lost”). Missing entirely is a parse error — empty array MUST NOT be used to mean “unknown”.

Carrier and resolvers

CarrierVerifier

The sole integration point with the carrier layer.

pub trait CarrierVerifier: Send + Sync { fn verify_carrier(&self, receipt_bytes: &[u8]) -> CarrierOutcome; } pub enum CarrierOutcome { Valid { payload: Vec<u8>, metadata: serde_json::Value, }, Invalid { reason: Option<String>, }, }

CarrierOutcome is an enum — not a struct with an is_valid flag. Valid carries the opaque payload bytes plus the metadata object (from which apl-core extracts metadata.apl); Invalid carries an optional diagnostic string for logging only. The reference implementation of the trait lives in apl-cli and delegates to atl-core.

FrameResolver / BridgeResolver

pub trait FrameResolver: Send + Sync { fn resolve(&self, hash: &Hash) -> FrameResolution; } pub trait BridgeResolver: Send + Sync { fn resolve(&self, hash: &Hash) -> BridgeResolution; } pub enum FrameResolution { Found(serde_json::Value), NotFound, ResolverError(String), } pub enum BridgeResolution { Found(serde_json::Value), NotFound, ResolverError(String), } pub struct InMemoryFrameResolver { /* ... */ } pub struct InMemoryBridgeResolver { /* ... */ }

Found(Value) returns the raw resolved JSON — the caller MUST verify its canonical hash against the pinned Reference::hash. NotFound means no compatible source has this hash. ResolverError is a transient infrastructure failure; per The Protocol § 5.4, registry unavailability alone is not a core failure as long as another source can supply the artifact.

Entry points

verify_receipt

Single-receipt verification.

pub fn verify_receipt( receipt_bytes: &[u8], carrier: &dyn CarrierVerifier, frames: &dyn FrameResolver, bridges: &dyn BridgeResolver, profile: Option<&dyn Profile>, ) -> (VerifierOutput, Option<VerifiedReceipt>);

Returns a VerifierOutput plus, on apl-valid, an opaque VerifiedReceipt token that can be passed to evaluate_relation to avoid re-verifying the carrier.

evaluate_relation

Pairwise relation evaluation.

pub fn evaluate_relation( input: PairwiseInput<'_>, carrier: &dyn CarrierVerifier, frames: &dyn FrameResolver, bridges: &dyn BridgeResolver, profile: Option<&dyn Profile>, ) -> PairwiseOutput; pub struct PairwiseInput<'a> { pub left: ReceiptInput<'a>, pub right: ReceiptInput<'a>, pub query: RelationQuery, pub supplied_bridges: Vec<serde_json::Value>, } pub enum ReceiptInput<'a> { Bytes(&'a [u8]), Prevalidated(VerifiedReceipt), }

supplied_bridges is REQUIRED (use Vec::new() when none). Each supplied value whose canonical hash matches a bridge_refs entry on either claim is added to the pairwise candidate set and then subject to the normal structural / frame / scope / profile checks. Values whose hash does not match any declared bridge_refs are silently ignored — callers may supply a superset.

ReceiptInput::Bytes borrows the receipt slice (&'a [u8]), not an owned Vec<u8>. ReceiptInput::Prevalidated(VerifiedReceipt) carries an opaque token produced by a prior verify_receipt call that returned apl-valid; callers cannot construct it directly.

RelationQuery

pub struct RelationQuery { pub left_aspects: Vec<String>, pub right_aspects: Vec<String>, pub predicate: String, pub relation_type: String, }

Parse from a serde_json::Value with:

impl RelationQuery { pub fn parse(value: &serde_json::Value) -> Result<Self, RelationQueryParseError>; }

Callers that have a JSON string at hand MUST decode it to serde_json::Value first (e.g. serde_json::from_str). parse does not accept a &str.

Outputs

VerifierOutput

pub struct VerifierOutput { pub core_outcome: CoreOutcome, pub relation_outcome: RelationOutcome, pub failure_classes: Vec<FailureClass>, pub diagnostics: Vec<Diagnostic>, } pub enum CoreOutcome { AplValid, AplInvalid, } pub enum RelationOutcome { RelationNotEvaluated, SameFrameComparable, BridgedComparable, Incomparable, }

Methods:

  • output.to_json() -> String
  • output.to_json_pretty() -> String

PairwiseOutput

Separate value carrying both receipts’ core outcomes plus the relation outcome and diagnostics. See source for details.

Profile

Pluggable vertical-profile extension point. A profile MAY tighten core requirements but MUST NOT weaken them — the trait is structured so that each hook runs only AFTER the relevant Core check has already passed.

pub trait Profile: Send + Sync + 'static { fn id(&self) -> &'static str; // Single-receipt hooks (CORE-VERIFY-1), invoked in order: fn check_claim(&self, claim: &Claim) -> ProfileCheckResult { Ok(()) } fn check_frame(&self, frame: &Frame) -> ProfileCheckResult { Ok(()) } fn cross_check(&self, claim: &Claim, frame: &Frame) -> ProfileCheckResult { Ok(()) } // Pairwise hooks (RELATION-1): fn check_pairwise_relation( &self, left: &Claim, right: &Claim, left_frame: &Frame, right_frame: &Frame, query: &RelationQuery, ) -> BridgeCheckResult { Ok(()) } fn check_bridge_applicability( &self, bridge: &Bridge, source_frame: &Frame, target_frame: &Frame, query: &RelationQuery, ) -> BridgeCheckResult { Ok(()) } } pub struct ProfileFailure { pub failure_class: FailureClass, pub diagnostics: Vec<DiagnosticCode>, } pub type ProfileCheckResult = Result<(), ProfileFailure>; pub type BridgeCheckResult = Result<(), Vec<DiagnosticCode>>;

Each hook defaults to Ok(()); a profile implements only the hooks it needs. Hooks MUST be pure with respect to their inputs (no I/O, no mutable state) to preserve verifier determinism. The reference profile is AiEvalProfile in the apl-ai-eval crate.

Canonical equality

pub fn canonical_hash(value: &serde_json::Value) -> Hash; pub fn canonical_equal(a: &serde_json::Value, b: &serde_json::Value) -> bool; pub fn canonical_equal_after_strip( a: &serde_json::Value, b: &serde_json::Value, fields: &[&str], ) -> bool;

All operate via JCS (RFC 8785) canonicalization under the hood.

Error types

AplError

AplError represents library-level errors that are NOT verifier-level outcomes. A malformed metadata.apl or a frame hash mismatch is NOT an AplError — it becomes a FailureClass inside a normal VerifierOutput with core_outcome = apl-invalid. AplError covers only programmer mistakes, resolver infrastructure failures, and pre-parse catastrophic errors.

pub enum AplError { Json(serde_json::Error), ResolverInfrastructure(String), Jcs(String), ProfileMisuse(String), InvalidArgument(String), }
  • Json — the carrier payload (or another input) could not be decoded as JSON at all.
  • ResolverInfrastructure — resolver reported an internal failure (not “hash not found”; that is FrameResolution::NotFound).
  • Jcs — JCS canonicalization reported an internal error.
  • ProfileMisuse — caller supplied a profile that declared an unsupported relation_type or bridge_kind (programmer mistake).
  • InvalidArgument — e.g. empty byte slice passed as a receipt.

AplResult<T>

Type alias for Result<T, AplError>.

Failure classes and diagnostics

FailureClass

pub enum FailureClass { CarrierFailure, ClaimStructureFailure, ReferenceFailure, FrameFailure, SemanticLinkageFailure, RelationStructureFailure, }

Diagnostic

Diagnostic is a re-export of DiagnosticCode. It is not an enum — it is a #[repr(transparent)] newtype over &'static str. Every kebab-case code from Data Structures § Verifier Status Codes is exposed as a pub const constant in the apl_core::diagnostics module.

#[repr(transparent)] pub struct DiagnosticCode(&'static str);

New codes can be added without breaking the public surface. Deserialization is whitelist-based: arbitrary strings from untrusted JSON are rejected unless they match a registered code. APL Core auto-registers its core and pairwise codes; vertical profiles call their register() function at startup to extend the registry with profile-specific codes.

Last updated on