techne-sdk 0.3.5

TechneSDK's logic written once: version ranking (eventState) and offline record verification over the published check, shipped to TypeScript as WebAssembly; with opt-in atproto plumbing (service auth, DID resolution, signed labels, proven record fetches, PDS password sessions) behind the `crypto` and `net` features.
Documentation
//! One record fetched with its proof, and verified (techne P3.2; ported from the platform indexer, P20.7 G4).
//!
//! `com.atproto.sync.getRecord` answers a CAR file: the repo's signed commit as the root, the MST
//! nodes on the path to the record's key, and the record's block. [`verify_record_proof`] checks it
//! the way Tap checks what it relays:
//!
//! - every block's CID is the SHA-256 of its bytes (so no block can be swapped for another);
//! - the root is a commit for the repo asked for (`did`), signed with the `#atproto` key in that DID's
//!   document (secp256k1 or P-256, low-S, over the commit's DAG-CBOR without `sig`);
//! - the MST under the commit's `data` leads to the key `collection/rkey`, through nodes whose keys are
//!   in order, to the record's CID, and the record block is that CID.
//!
//! A proof that the key is NOT in the tree (the path ends where the key would be) is a verified
//! absence. A missing block on the path, a hash mismatch, a bad or wrong-key signature, or a commit for
//! another repo is a refusal ([`ProofError`]): the caller stores nothing.
//!
//! Use it instead of `com.atproto.repo.getRecord` (unsigned JSON, a CID the PDS merely asserts) for any
//! one-off fetch that feeds an index ([`crate::fetch`] resolves the PDS and key first, with `net`).
//! [`build_proof`] makes a valid proof for tests and fakes.
//!
//! The verifier itself is the published check's (`techne_lexicon_check::proof`, P13.1/P13.1b), re-exported
//! here: one verifier for the SDK, the commons and any third party. This module adds the fetch.

pub use techne_lexicon_check::proof::{
    atproto_key_in_doc, build_proof, key_layer, multikey, read_car, verify_record, verify_record_proof, ProofError, Proven, ProvenRecord,
    RepoKey,
};
#[cfg(feature = "net")]
use serde_json::Value;

/// The largest proof `fetch_proven` (feature `net`) reads: a record (at most 1 MiB in atproto) plus its MST path
/// and commit, with room to spare.
pub const MAX_PROOF_BYTES: u64 = 4 * 1024 * 1024;

/// `com.atproto.sync.getRecord` from `pds` for `did`'s `collection/rkey`, verified against `key`.
/// `Ok(None)`: the record is not there (a verified absence, or the PDS answering `RecordNotFound` /
/// `RepoNotFound`, which hands us nothing to store). A proof that does not verify is an error.
#[cfg(feature = "net")]
pub async fn fetch_proven(
    http: &reqwest::Client,
    pds: &str,
    did: &str,
    collection: &str,
    rkey: &str,
    key: &RepoKey,
) -> anyhow::Result<Option<ProvenRecord>> {
    let resp = http
        .get(format!("{}/xrpc/com.atproto.sync.getRecord", pds.trim_end_matches('/')))
        .query(&[("did", did), ("collection", collection), ("rkey", rkey)])
        .send()
        .await?;
    let status = resp.status();
    if resp.content_length().is_some_and(|n| n > MAX_PROOF_BYTES) {
        anyhow::bail!("sync.getRecord answered more than {MAX_PROOF_BYTES} bytes");
    }
    let mut resp = resp;
    let mut body: Vec<u8> = Vec::new();
    while let Some(chunk) = resp.chunk().await? {
        body.extend_from_slice(&chunk);
        anyhow::ensure!(body.len() as u64 <= MAX_PROOF_BYTES, "sync.getRecord answered more than {MAX_PROOF_BYTES} bytes");
    }
    if !status.is_success() {
        let err: Value = serde_json::from_slice(&body).unwrap_or(Value::Null);
        let absent = matches!(err["error"].as_str(), Some("RecordNotFound" | "RepoNotFound" | "RepoDeactivated" | "RepoTakendown"));
        if absent && (status == reqwest::StatusCode::BAD_REQUEST || status == reqwest::StatusCode::NOT_FOUND) {
            return Ok(None);
        }
        anyhow::bail!("sync.getRecord answered {status} {}", err["error"].as_str().unwrap_or_default());
    }
    match verify_record_proof(&body, did, collection, rkey, key) {
        Ok(Proven::Present(r)) => Ok(Some(r)),
        Ok(Proven::Absent { .. }) => Ok(None),
        Err(e) => {
            tracing::warn!(%did, %collection, %rkey, %pds, error = %e, "a sync.getRecord proof was refused");
            Err(anyhow::Error::new(e).context(format!("verifying {did}/{collection}/{rkey}")))
        }
    }
}