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() -> Stringoutput.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 isFrameResolution::NotFound).Jcs— JCS canonicalization reported an internal error.ProfileMisuse— caller supplied a profile that declared an unsupportedrelation_typeorbridge_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.