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
# cleanlib-client changelog

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this crate adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [0.3.0] — 2026-09-15

### Fixed (BREAKING)

- **CLEANLIB-743 / CLEANLIB-813**`cleanlib audit` (and any direct
  `Client::audit()` / `AuditFilters` caller) hard-CRASHED against real
  production: `invalid type: null, expected a string`. Found by Test-mgr's
  retroactive empirical audit, which installed the real published
  `cleanlib-cli 0.1.21` / `cleanlib-client 0.2.0` and ran `cleanlib audit`
  against live data. Root cause: the App's own CLEANLIB-794 fix correctly
  masks its nil-UUID audit-identity placeholder to a literal JSON `null` at
  the customer 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 broke
  the ENTIRE `audit` command for every real caller, filtered or not — both
  tickets had been closed prematurely on unit tests against synthetic
  fixtures that never included a null id. Fix: both fields are now
  `Option<String>` (`None` = "no id recorded for this row"), normalizing a
  literal `null`, the legacy nil-UUID string, an empty string, and an
  absent key all to the same `None` outcome via one shared deserializer.
  **Breaking**: any caller reading `AuditEntry::request_id` / `::verdict_id`
  as a bare `String` needs `.unwrap_or_default()` (old blank-cell rendering)
  or an explicit `Option` match (to actually distinguish stamped vs. absent,
  which the old shape could not express). Live-verified post-fix against the
  exact real production population that crashed (500 records, both id
  fields null on all of them) — `cleanlib audit`, the `--decision
  DENY --ecosystem npm` filter combo, and the 813 `until` filter all now
  complete successfully (exit 0 / 200) against that same data.

## [0.2.0] — 2026-09-15

### Changed (BREAKING)

- **CLEANLIB-813**`Client::audit()` now takes an `AuditFilters<'_>`
  options struct (`{ since, until, decision, ecosystem }`) instead of 3
  positional `Option<&str>` params. Adds the `until` filter the real
  server already accepted but this SDK never exposed (Go's SDK already
  had it). Chosen over adding a 4th positional param specifically to
  avoid paying this same breaking-change cost again at the next filter —
  every future field is purely additive via `AuditFilters { since: Some(x),
  ..Default::default() }`. Migration: replace
  `client.audit(since, decision, ecosystem).await` with
  `client.audit(AuditFilters { since, decision, ecosystem, ..Default::default() }).await`.

## [0.1.5] — 2026-07-04

### Fixed

- CLEANLIB-297 / CLEANLIB-298: rustdoc-drift closeout for the Rust SDK
  audit endpoint. The transport paths themselves were already corrected
  in prior cycles — `audit()` mounts at `/v1/audit` (fixed by the
  cycle-14 CLEANLIB-72 sweep, commit `8a7f970`) and `policy_preview()`
  mounts at `/v1/policy/preview` (fixed by CLEANLIB-298 / CLEANLIB-305,
  commits `ec013c9` and `48d0076` / PR #220) — but the `AuditEntry` /
  `AuditResponse` docstrings in `types.rs` continued to cite the stale
  `/v1/customer/audit` path, so the crates.io / docs.rs rustdoc surface
  contradicted the code. Docstrings now match the actual endpoint. No
  behavior change; runtime paths were already correct at HEAD.

## [0.1.4] — 2026-06-05

### Added

- `verdict_to_envelope_v1(verdict)` — pure-function structural adapter that
  converts the App's wire-shape `Verdict` to the canonical customer-facing
  `VerdictEnvelopeV1`. Re-exported from the crate root. First of four
  parity-locked implementations across the SDK fleet
  (`cleanlib-client` Rust, `cleanlib-sdk` Python, `@cleanstart/cleanlib-sdk`
  JS, `sdk-go`); ratified at
  `decisions/2026-06-05-cycle-14-cleanlib-48-envelope-shape-ratification.md`.
- Status/reason_code derivation table: `VECTOR_VERDICT + CRITICAL → DENY
  + VERDICT_EXPLOITATION_CRITICAL`, `+ HIGH → DENY + VERDICT_LOW_TRUST`,
  `+ MEDIUM → WARN + VERDICT_ABANDONED`, `DM_THRESHOLD_BLOCK + HIGH/CRITICAL
  → DENY + VERDICT_DENY_LIST`, `+ MEDIUM → WARN + VERDICT_DENY_LIST`,
  `ALLOWED_NO_FINDINGS / INSUFFICIENT_DATA / unknown → ALLOW + VERDICT_CLEAN`.
  `stale_since_at` Some overrides `reason_code` to `VERDICT_DEGRADED_STALE`
  while preserving the status tier.
- `rich_data` sub-object populated with `suggested_actions`, `similar_to`,
  `evidence_gaps`, `previous_verdict`, `confidence`, `composite_score`,
  `source`, `severity`, `staleness_reason` when present on the Verdict.
- Enrich-cascade slots (`remediation`, `exploitability`, `availability`)
  are left `None`; consumers populate them via the enrich client before
  calling `derive_status` for the canonical (status, reason_code).

## [0.1.3] — 2026-06-04

### Added

- `Verdict.previous_verdict` field plus a new `PreviousVerdict` struct
  (`verdict_id`, `verdict`, `computed_at`, `diff`). Parity-ripple with
  `sdk-go::PreviousVerdict` from the prior release. The CleanLibrary App
  emits this field in its `/v1/customer/verdicts/...` envelope when a prior
  verdict exists for the same package version; previously the Rust SDK
  deserialized it as a no-op via `serde(default)` and customers lost the
  verdict-state-change signal. `PreviousVerdict` is re-exported at the
  crate root alongside `Client`, `Verdict`, and `PolicyDecision`.

## [0.1.2] — 2026-06-04

### Changed

- CHANGELOG entries rewritten to customer-language only. The previous v0.1.1
  entry included internal markers that did not belong on the customer-facing
  crates.io listing.

## [0.1.1] — 2026-06-03

### Added

- Crate-root re-exports for extern consumers: `Client`, `Verdict`,
  `PolicyDecision`. These types existed in submodules (`transport::Client`,
  `types::Verdict`, `types::PolicyDecision`) and are documented as the public
  surface on `cleanlibrary.clnstrt.dev/SDK-Rust`, but the crate root did not
  re-export them — third-party consumers importing
  `cleanlib_client::{Client, Verdict, PolicyDecision}` hit
  `error[E0432]: unresolved imports`. Existing re-exports
  (`derive_status`, `DerivedStatus`, `ReasonCode`, `Status`,
  `VerdictEnvelopeV1`, `ALL_REASON_CODES`) preserved.

## 0.1.0 — initial publish

- HTTP client SDK for the CleanLibrary verdict API: `VerdictEnvelopeV1` types,
  `derive_status` logic, transport, config, and risk-acceptance YAML emitter.