Examples
These examples use the real exported surface of ahl-core (see API Reference, generated from src/lib.rs and src/receipt.rs). ahl-core is a private, pre-release repository tracking Core Specification v0.3-draft; the surface can still change before publication.
Verify an Evidence Receipt
use ahl_core::receipt::{verify_receipt, TrustPolicy};
use serde_json::Value;
use std::fs;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let receipt_json = fs::read_to_string("decision-d142.ahl")?;
let receipt: Value = serde_json::from_str(&receipt_json)?;
// TrustPolicy is the verifier's locally configured trust anchor — nothing in it
// is taken from the receipt itself (Data Structures §11 design rule 1).
let policy = TrustPolicy {
genesis_entry_id: "sha256:<published genesis entry id>".to_owned(),
genesis_key_ids: ["sha256:<published producer key fingerprint>".to_owned()].into(),
..Default::default()
};
match verify_receipt(&receipt, &policy) {
Ok(verdict) => {
println!("Rendered boundary: {}", verdict.boundary);
println!("Claim type: {}", verdict.claim_type);
println!("Statement id: {}", verdict.subject_statement_id);
println!("Entry index: {}", verdict.subject_entry_index);
}
Err(e) => println!("Rejected: {e}"),
}
Ok(())
}verdict.boundary is rendered only from claim.type and the verified assurance block — never from the receipt’s informative note field, and never a stronger claim than what verification established.
Compute a record commitment
use ahl_core::{commit_keyed, commit_plain, jcs};
use serde_json::json;
fn main() -> ahl_core::AhlResult<()> {
let record = json!({ "customer_id": "C-1001", "score": 712 });
let bytes = jcs(&record);
// `plain` mode: SHA-256(dsid || 0x1F || canonical_bytes).
let plain = commit_plain("decisions", &bytes);
println!("plain commitment: {plain}");
// `keyed` mode is REQUIRED for personal or sensitive data (Data Structures §7).
let dataset_key = [0x11u8; 32]; // held by the producer, never packaged in a receipt
let keyed = commit_keyed(&dataset_key, "bureau", &bytes)?;
println!("keyed commitment: {keyed}");
Ok(())
}Build and verify a signed statement envelope
use ahl_core::{envelope, verify_envelope, TestKey};
use serde_json::json;
fn main() -> ahl_core::AhlResult<()> {
// TestKey builds deterministic Ed25519 keys from committed seeds. The crate is
// explicit that this is test-vector machinery, not production key handling.
let producer = TestKey::from_seed_hex(
"producer-1",
"0101010101010101010101010101010101010101010101010101010101010101",
)?;
let payload = json!({
"ahl_version": "0.3",
"type": "ingestion",
"producer": "acme-lending",
"dataset": "bureau",
"record": "hmac-sha256:9f86d081...",
});
let env = envelope(payload, &producer);
// A minimal resolver: in a real verifier this looks up the key set active at
// the statement's entry index under the manifest snapshot rule (Protocol §8.2).
let resolve = |key_id: &str| (key_id == producer.key_id()).then(|| producer.pubkey());
let signed = verify_envelope(&env, resolve)?;
println!("envelope signature valid: {signed}");
Ok(())
}Compute a revocation closure
use ahl_core::closure::affected_set;
use std::collections::BTreeMap;
fn main() -> ahl_core::AhlResult<()> {
// `envelopes` is the corpus in entry-index order — the slice index *is* the
// entry index, AHL's only ordering primitive (Protocol §3.2).
let envelopes: Vec<serde_json::Value> = load_corpus();
let trigger_index = 12; // the entry index of a `retraction` or `correction`
let trees = BTreeMap::new(); // committed batch/input-set trees, if any are referenced
let closure = affected_set(&envelopes, &trees, trigger_index, envelopes.len())?;
println!("seeds: {:?}", closure.seeds);
println!("affected (derived records requiring disposition): {:?}", closure.affected);
// The seeds themselves are never members of `affected` — they are what the
// trigger names directly, not what must be dispositioned (Protocol §6.1).
Ok(())
}
fn load_corpus() -> Vec<serde_json::Value> {
vec![]
}Handle verification errors
use ahl_core::receipt::{verify_receipt, ReceiptError, TrustPolicy};
use serde_json::Value;
fn handle(receipt: &Value, policy: &TrustPolicy) {
match verify_receipt(receipt, policy) {
Ok(verdict) => println!("verified: {}", verdict.boundary),
Err(ReceiptError::GenesisAnchorMismatch) => {
eprintln!("receipt's genesis anchor does not match locally configured policy");
}
Err(ReceiptError::KeyNotBound { key_id, entry_index }) => {
eprintln!("key `{key_id}` is not bound to a manifest key object at entry index {entry_index}");
}
Err(ReceiptError::CompetingRangeInsufficient { got_from, got_to, tree_size, .. }) => {
eprintln!("competing-trigger range [{got_from}, {got_to}) does not cover the required [0, {tree_size})");
}
Err(ReceiptError::ClosureMismatch(detail)) => {
eprintln!("recomputed closure disagrees with the anchored disposition tree: {detail}");
}
Err(other) => eprintln!("rejected: {other}"),
}
}ReceiptError is #[non_exhaustive] — each variant names the specific rule that fired (format §5 algorithm), so a test can assert which rule rejected a deliberately malformed receipt rather than that verification “failed somehow”.
Last updated on