cleanlib-client 0.1.7

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-244 (C18.3) — cross-surface customer-state contract test (Rust, reference).
//!
//! Loads the canonical `CUSTOMER_STATE_EXPECTED.json` golden and asserts that
//! `cleanlib_client::CustomerState::from_wire(source)` yields byte-identical
//! `{state, label, copy, tier, color_token, color_hex, emoji}` for every entry.
//!
//! This is the REFERENCE implementation of the contract. The sibling surfaces
//! each load the SAME golden and run the equivalent assertion:
//! - sdk-go  `customer_state_contract_test.go`
//! - sdk-js  `customer-state.contract.test.ts`
//! - sdk-py  `test_customer_state_contract.py`
//! - vscode-extension `customerState.contract.test.ts` (required fields minus emoji — tints SVG by tier)
//! - mcp-server `test_customer_state_contract.py`
//!
//! Drift on any required field (`_schema_lock.required_identical_fields`) =
//! CI failure on that surface. The `__UNKNOWN__` sentinel pins the fail-closed
//! rule (any out-of-corpus source → not_yet_assessed, never clean).

use std::collections::HashMap;
use std::fs;
use std::path::PathBuf;

use cleanlib_client::{CustomerState, Tier};

#[derive(Debug, serde::Deserialize)]
struct Golden {
    mappings: HashMap<String, Row>,
}

#[derive(Debug, serde::Deserialize)]
struct Row {
    state: String,
    label: String,
    copy: String,
    tier: String,
    color_token: String,
    color_hex: String,
    emoji: String,
}

fn golden_path() -> PathBuf {
    PathBuf::from(env!("CARGO_MANIFEST_DIR"))
        .join("tests/fixtures/contract-fixtures/CUSTOMER_STATE_EXPECTED.json")
}

fn tier_str(t: Tier) -> &'static str {
    match t {
        Tier::Block => "block",
        Tier::Warn => "warn",
        Tier::Clean => "clean",
    }
}

/// The wire string `from_wire` should receive for the `__UNKNOWN__` sentinel
/// row — an out-of-corpus value that must fail closed.
const UNKNOWN_PROBE: &str = "SOME_FUTURE_VARIANT";

#[test]
fn customer_state_matches_cross_surface_golden() {
    let raw = fs::read_to_string(golden_path())
        .unwrap_or_else(|e| panic!("could not read CUSTOMER_STATE_EXPECTED.json: {e}"));
    let golden: Golden =
        serde_json::from_str(&raw).expect("CUSTOMER_STATE_EXPECTED.json must parse");

    let mut failures: Vec<String> = Vec::new();

    for (wire, row) in &golden.mappings {
        // The sentinel exercises the fail-closed arm with an out-of-corpus source.
        let source = if wire == "__UNKNOWN__" { UNKNOWN_PROBE } else { wire.as_str() };
        let s = CustomerState::from_wire(source);

        let got = [
            ("state", s.as_str(), row.state.as_str()),
            ("label", s.label(), row.label.as_str()),
            ("copy", s.copy(), row.copy.as_str()),
            ("tier", tier_str(s.tier()), row.tier.as_str()),
            ("color_token", s.color_token(), row.color_token.as_str()),
            ("color_hex", s.color_hex(), row.color_hex.as_str()),
            ("emoji", s.emoji(), row.emoji.as_str()),
        ];
        for (field, got, want) in got {
            if got != want {
                failures.push(format!("  [{wire}] {field}: got {got:?}, want {want:?}"));
            }
        }
    }

    assert!(
        failures.is_empty(),
        "customer-state contract drift vs CUSTOMER_STATE_EXPECTED.json:\n{}",
        failures.join("\n")
    );
}

#[test]
fn golden_covers_every_known_wire_source() {
    // Anti-drift the other direction: every wire source the client knows about
    // (ALL_VERDICT_SOURCES) must appear in the golden, so a new 176/177 variant
    // can't be added to the enum without also being pinned in the contract.
    let raw = fs::read_to_string(golden_path()).unwrap();
    let golden: Golden = serde_json::from_str(&raw).unwrap();
    let missing: Vec<&str> = cleanlib_client::ALL_VERDICT_SOURCES
        .iter()
        .copied()
        .filter(|src| !golden.mappings.contains_key(*src))
        .collect();
    assert!(missing.is_empty(), "wire sources absent from golden: {missing:?}");
}

#[test]
fn unknown_sentinel_is_fail_closed_not_clean() {
    let raw = fs::read_to_string(golden_path()).unwrap();
    let golden: Golden = serde_json::from_str(&raw).unwrap();
    let sentinel = golden.mappings.get("__UNKNOWN__").expect("golden must carry __UNKNOWN__ sentinel");
    assert_eq!(sentinel.state, "not_yet_assessed");
    assert_ne!(sentinel.state, "clean");
    // And the implementation actually honors it.
    assert_eq!(CustomerState::from_wire(UNKNOWN_PROBE), CustomerState::NotYetAssessed);
}