cleanlib-client 0.3.0

HTTP client SDK for the CleanLibrary verdict API — VerdictEnvelopeV1 types, derive_status logic, transport, config, and risk-acceptance YAML emitter shared between cleanlib-cli and other CleanLibrary consumers.
Documentation
[package]
name = "cleanlib-client"
# 0.3.0 — CLEANLIB-743/813 crash fix (2026-09-15): BREAKING — Test-mgr's
# retroactive empirical audit installed the real published cleanlib-cli 0.1.21
# / cleanlib-client 0.2.0 and found `cleanlib audit` CRASHES outright against
# real production: `invalid type: null, expected a string`. Root cause: the
# App's own CLEANLIB-794 fix legitimately masks its nil-UUID audit-identity
# placeholder to a literal JSON `null` at the customer serialization boundary
# (`request_id`/`verdict_id` on `GET /v1/audit` records — the WORM decision
# SoR behind these rows carries no id), but `AuditEntry.request_id` /
# `.verdict_id` were plain non-optional `String` — a hard deserialize failure
# on `null`, not a graceful default. This made `audit` non-functional for
# EVERY real caller, filtered or not — worse than either ticket's original
# scope (743 was "ids are a fake nil-UUID", 813 was "no `until` param"; both
# closed prematurely on unit tests against synthetic no-null fixtures, which
# is exactly the class of gap this fix's new tests close). Fix: both fields
# become `Option<String>`, normalizing null / the legacy nil-UUID string /
# an empty string / an absent key all to `None` via one shared deserializer.
# Minor bump (not patch) per this crate's own 0.x breaking-change precedent.
# Live-verified against real production post-fix (not unit tests alone) —
# `cleanlib audit`, `--decision DENY --ecosystem npm`, and the 813 `until`
# filter all now return 200/exit-0 against the exact same 500-record,
# all-null-id population that crashed before.
# Migration: `AuditEntry::request_id` / `::verdict_id` are now `Option<String>`
# instead of `String` — callers reading them directly need `.unwrap_or_default()`
# (to preserve the old "blank means absent" rendering) or an explicit `match`/
# `if let Some(..)` (to distinguish "stamped" from "absent", which the old
# `String` shape could not represent honestly).
# 0.2.0 — CLEANLIB-813 (overnight autonomous batch, 2026-09-15): BREAKING —
# `Client::audit()` now takes an `AuditFilters<'_>` options struct
# (`{ since, until, decision, ecosystem }`) instead of 3 positional
# `Option<&str>` params, and adds the `until` filter the real server
# already accepted but this SDK never exposed. Minor bump (not patch) per
# this crate's own 0.x breaking-change precedent — see cleanlib-sdk-go's
# jump straight to v1.0.0 for CLEANLIB-610's breaking module-path rename.
# Migration: `client.audit(since, decision, ecosystem).await` becomes
# `client.audit(AuditFilters { since, decision, ecosystem, ..Default::default() }).await`.
# CLEANLIB-126 M1″ cycle-13 — per-crate version (overrides workspace 0.1.0)
# to ship crate-root re-exports (Client, Verdict, PolicyDecision). cleanlib-cli
# stays on workspace 0.1.0 (no re-publish this cycle); workspace.dependencies
# pin bumped to 0.1.1 so the next cli ship picks up automatically.
# 0.1.19 — CLEANLIB-780 (axes envelope): adds `axes: Option<Axes>` to Verdict
# (Axes / AxisAdvisory / AxisThreat / AxisAvailability), mirroring the App #477
# wire, so consumers render the advisory / threat / availability planes SEPARATELY
# (a consulted-and-empty advisory reads as honest GREEN, not an alarmist data-gap).
# Purely ADDITIVE over the published 0.1.15 (tail-appended, serde default, skip-None
# on the wire, self-healing bincode cache). Shipped in a COMBINED cleanlib 0.1.19
# release with the cleanlib-cli v1-lock fix (CLEANLIB-682) per BD direct-word "ship
# both together 0.1.19"; 0.1.16-0.1.18 skipped for the client so both crates align
# at 0.1.19. cleanlib-cli 0.1.19 consumes these types (path-1) to render axes.
# 0.1.15 — CLEANLIB-682 release train: the published 0.1.14 is stale vs source.
# Republish so cleanlib-cli 0.1.11 can build against the API it uses — remediation
# / freshness / coverage / reason_class / policy_version / attestation_status /
# customer_state on Verdict+ScanResult+ScanResponse, plus telemetry_enabled /
# migrate_telemetry_consent / ecosystem_from_path (CX-3/627/691 + siblings).
version = "0.3.0"
edition.workspace = true
rust-version.workspace = true
license.workspace = true
repository.workspace = true
homepage.workspace = true
description = "HTTP client SDK for the CleanLibrary verdict API — VerdictEnvelopeV1 types, derive_status logic, transport, config, and risk-acceptance YAML emitter shared between cleanlib-cli and other CleanLibrary consumers."
keywords = ["security", "dependencies", "verdicts", "supply-chain", "sdk"]
categories = ["api-bindings", "development-tools"]

[dependencies]
serde = { workspace = true }
serde_json = { workspace = true }
toml = { workspace = true }
dirs = { workspace = true }
thiserror = { workspace = true }
chrono = { workspace = true }
reqwest = { workspace = true }
# `sync` adds `tokio::sync::{OnceCell,RwLock}` — used by attestation_verify's
# lazily-built, TTL-cached `/v1/pubkeys` lookup (cosign gate 3, Q6=a).
tokio = { workspace = true, features = ["sync"] }
futures-util = { workspace = true }
# CLEANLIB-379 (cosign gate 3, Q6=a): verify_attestation() — ECDSA P-256 +
# SHA-256 signature verify over the App's SignedAttestation envelope. Same
# RustCrypto family + versions already pinned in workspace.dependencies for
# cleanlib-cosign-signer (the producer side) so the verifier and signer agree
# on DER/PEM encoding without a second dependency graph.
sha2 = { workspace = true }
p256 = { workspace = true }
ecdsa = { workspace = true }
signature = { workspace = true }
base64 = { workspace = true }
async-trait = { workspace = true }
# RFC-8785 JSON Canonicalization Scheme — reconstructs the EXACT bytes
# cleanlib-cosign-signer signs (Attestation::canonical_bytes()) from the
# passthrough `serde_json::Value` the App emits on the wire, so a natural
# parse + reserialize verifies without hand-replicating field order (mirrors
# cleanlib-cosign-signer's own dependency; see its `partner_guide_7_2_method_*`
# test for the recipe this module reproduces from the consumer side).
serde_jcs = "0.1"