Metadata-Version: 2.4
Name: cleanlib-sdk
Version: 0.4.13
Summary: CleanLibrary Python SDK — HttpVerdictClient + HttpRemediationClient + HttpEnrichClient triad (sister-shape with @cleanstart/cleanlib-sdk v0.4.4)
Project-URL: Homepage, https://cleanlibrary.clnstrt.dev
Project-URL: Documentation, https://cleanlibrary.clnstrt.dev/docs/sdk-python
Project-URL: Bug Tracker, https://cleanlibrary.clnstrt.dev/support
Author: CleanStart
License: Proprietary
Classifier: Development Status :: 2 - Pre-Alpha
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27
Requires-Dist: pydantic>=2
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: ruff>=0.5; extra == 'dev'
Description-Content-Type: text/markdown

# cleanlib-sdk

CleanLibrary Python SDK — `asyncio` + `httpx`; mirrors the Rust
`cleanlib-client` HTTP surface and the JavaScript `@cleanstart/cleanlib-sdk`
sister-shape.

**Status**: `v0.4.7` — production-ready.

## Install

```bash
pip install cleanlib-sdk
```

Requires Python 3.10+. Depends on `httpx>=0.27` and `pydantic>=2`.

## Overview — per-domain client triad (v0.4.2+)

`v0.4.2` split the SDK into three per-domain HTTP clients, each pointed at
its own CleanLibrary surface:

| Client | Endpoint (default) | Purpose |
|---|---|---|
| `HttpVerdictClient` | `https://cleanapp.clnstrt.dev` | App customer-verdict surface — `fetch_verdict` / `scan` / `audit` / `policy_preview` / `risk_accept` |
| `HttpRemediationClient` | `DEFAULT_REMEDIATION_BASE_URL` (`https://cleanlib-enrich.clnstrt.dev`) | Sparse 7-block `/remediation` responses |
| `HttpEnrichClient` | `DEFAULT_ENRICH_BASE_URL` (`https://cleanlib-enrich.clnstrt.dev`) | 8-verb cleanlib-enrich cascade — `get_exploitability` / `get_exploitability_bulk` / `get_epss` / `get_kev` / `get_advisories` / `get_enrich` / `get_library_verdict` / `bulk_packages_check` |

The legacy `Client` (flat-method) still ships for source-compat with
v0.4.0 callers and emits `DeprecationWarning` on `__init__`; scheduled
removal in v1.0.0. New integrations should use the per-domain triad.

## Usage — verdict client

```python
import asyncio
from cleanlib_sdk import HttpVerdictClient, PolicyDenyError, RiskAcceptanceRequiredError

async def main() -> None:
    async with HttpVerdictClient(
        base_url="https://cleanapp.clnstrt.dev",
        api_key="clk_std_...",   # opaque CleanLibrary access key
    ) as c:
        try:
            v = await c.fetch_verdict("npm", "lodash", "4.17.21")
            print(f"{v.decision} composite_score={v.composite_score}")
            print(f"reasoning: {v.reasoning}")
        except PolicyDenyError as e:
            print(f"DENIED [{e.reason_code}]: {e.message}")
        except RiskAcceptanceRequiredError as e:
            print(f"RISK ACCEPT REQUIRED: {e.message}")
            if e.docs_url:
                print(f"see: {e.docs_url}")

asyncio.run(main())
```

## Usage — enrich cascade

```python
import asyncio
from cleanlib_sdk import (
    HttpEnrichClient,
    epss_found, kev_found,
    EXPLOITABILITY_BULK_CAP,
)

async def main() -> None:
    async with HttpEnrichClient(api_key="clk_std_...") as e:
        # KEV / EPSS return a discriminated union — one of *_found / *_not_found.
        kev = await e.get_kev("CVE-2021-44228")
        if kev_found(kev):
            print(f"KEV listed: due_date={kev.due_date}")

        epss = await e.get_epss("CVE-2021-44228")
        if epss_found(epss):
            print(f"EPSS score={epss.score} percentile={epss.percentile}")

        # Bulk: server caps at EXPLOITABILITY_BULK_CAP (100) per request.
        exp = await e.get_exploitability_bulk([
            "CVE-2021-44228", "CVE-2022-22965", "CVE-2023-4863",
        ])
        for cve in exp.results:
            print(cve.cve_id, cve.availability)

asyncio.run(main())
```

## Usage — remediation

```python
import asyncio
from cleanlib_sdk import HttpRemediationClient

async def main() -> None:
    async with HttpRemediationClient(api_key="clk_std_...") as r:
        rem = await r.get_remediation("npm", "lodash", "4.17.15")
        # Sparse 7-block: any block may be absent (RemediationOrAbsent).
        if rem.recommended_version is not None:
            print(f"upgrade to {rem.recommended_version.version}")

asyncio.run(main())
```

## Public API surface

Everything below is exported from the top-level `cleanlib_sdk` package
and covered by `__all__` (verified against `dir(cleanlib_sdk)` at
release-cut per CLEANLIB-83). New symbols are grouped by the version
they were introduced in.

### v0.4.7 — Verdict.decision auto-derived

- `Verdict.decision` is now auto-derived at the type layer (CLEANLIB-294)
  — consumers no longer need to re-run `derive_status` on a raw
  `Verdict`; the field is populated from the envelope substance +
  freshness precedence at parse time.

### v0.4.6 — envelope emit

- `verdict_to_envelope_v1(verdict) -> dict` — canonical Verdict→envelope
  packer per CLEANLIB-48. Sister-shape of the sdk-js emitter; the
  fixture-driven contract test in `tests/verdict_to_envelope_contract.py`
  keeps both SDKs byte-identical.

### v0.4.5 — previous-verdict parity

- `PreviousVerdict` — envelope carries the prior verdict when a re-scan
  changes the decision; enables audit-log diff view.

### v0.4.4 — customer-state taxonomy (CLEANLIB-178)

- `CustomerState` — enum mirroring `cleanlib-client/src/customer_state.rs`.
- `Tier` — customer tier enum (`FREE` / `STD` / `PRO` / …).
- `ALL_VERDICT_SOURCES` — canonical registry of verdict-source strings
  (used by cross-SDK contract test to guard against silent drift).

### v0.4.3 — brand/metadata sanitize

Pyproject brand cleanup; no new symbols.

### v0.4.2 — enrich cascade + F4 default-flip

**Clients:**
- `HttpVerdictClient` — App customer-verdict surface (completes the
  triad; the v0.4.1 split shipped remediation + enrich).
- `HttpEnrichClient` — 8-verb cleanlib-enrich cascade; defensive
  wrappers (advisories double-parse + dedup; `cve_id ↔ cveId`
  normalization; sparse-by-design tolerance).

**Constants:**
- `DEFAULT_ENRICH_BASE_URL` — `https://cleanlib-enrich.clnstrt.dev`.
- `DEFAULT_REMEDIATION_BASE_URL` — F4-flipped to
  `https://cleanlib-enrich.clnstrt.dev` (was the vva URL in v0.4.1).
  Consumers passing an explicit `base_url=` are unaffected.
- `EXPLOITABILITY_BULK_CAP` — server-side cap on
  `get_exploitability_bulk` batch size.
- `REMEDIATION_CACHE_TTL_SECS` — default TTL used by the remediation
  client's in-process cache.

**Enrich wire-shapes:**
- `AdvisoryRow`, `AdvisorySeverity` — advisories.
- `AvailabilityFlag`, `ExploitabilityAvailability`,
  `ExploitabilityResponse` — exploitability triage.
- `EpssResponse`, `EpssOrNotFound`, `epss_found`, `epss_not_found` —
  EPSS with found/not-found discriminated union + helpers.
- `KevResponse`, `KevOrNotFound`, `kev_found`, `kev_not_found` — KEV
  with found/not-found discriminated union + helpers.
- `EnrichResponse`, `LibraryVerdictResponse` — top-level cascade
  responses.
- `BulkPackageRef`, `BulkPackageResult`, `BulkPackagesCheckResponse` —
  `bulk_packages_check` request/response shapes.

### v0.4.1 — contract enforcement + remediation client

- `HttpRemediationClient` — sparse 7-block `/remediation` responses.
- `RemediationBlock`, `RemediationResponse`, `RemediationOrAbsent` —
  remediation wire-shapes.
- `ReasonCode` — 15-entry canonical registry (Enum).
- `ALL_REASON_CODES` — read-only tuple of every registered reason code.
- `VERDICT_ENVELOPE_V1_SCHEMA`, `SCHEMA_ID`, `STATUS_ENUM`,
  `AVAILABILITY_ENUM` — JSON-Schema mirror + enum sets.
- `derive_status(envelope) -> StatusResult` — canonical algorithm per
  App dispatch §4 binding contract (substance precedence + freshness
  override).
- `StatusResult` — output shape of `derive_status`.
- `Client` — legacy flat-method class; emits `DeprecationWarning` on
  `__init__` (v1.0.0 removal).

### v0.4.0 — verb cascade + rich-data ripple

Response types shared across the triad:

- `Verdict` — canonical verdict envelope with attestation.
- `Attestation`, `SignedAttestation` — signed provenance.
- `RecommendedVersion`, `PackageRisk`, `PackageRef` — rich-data fields.
- `ScanResult`, `ScanResponse` — `scan` endpoint.
- `AuditWindow`, `AuditWindowResponse` — `audit` endpoint.
- `PolicyPreviewResult`, `PolicyPreviewResponse` — `policy preview`.
- `RiskAcceptResponse` — `risk-accept` submission response.

### Sub-modules

Everything the flat surface re-exports also lives in a namespaced
sub-module — import from either. Use the sub-module path when you need
to disambiguate against a same-named symbol from a sister SDK.

- `cleanlib_sdk.client` — legacy `Client` (deprecated).
- `cleanlib_sdk.customer_state` — v0.4.4 state taxonomy.
- `cleanlib_sdk.derive_status` — `derive_status` + `StatusResult`.
- `cleanlib_sdk.errors` — every exception in the hierarchy below.
- `cleanlib_sdk.http` — the three `Http*Client`s, wire-shapes,
  constants, helper functions.
- `cleanlib_sdk.reason_codes` — `ReasonCode`, `ALL_REASON_CODES`.
- `cleanlib_sdk.schema` — `VERDICT_ENVELOPE_V1_SCHEMA`, enum sets.
- `cleanlib_sdk.transport` — internal `httpx.AsyncClient` wrapper (not
  re-exported at the top level; used by all three `Http*Client`s).
- `cleanlib_sdk.types` — response dataclasses.
- `cleanlib_sdk.verdict_to_envelope` — `verdict_to_envelope_v1`.

## Error hierarchy

All errors descend from `CleanLibraryError`. Subclasses:

| Exception | HTTP | Triggered by |
|---|---|---|
| `PolicyDenyError` | 403 / 451 | `POLICY_DENY_VERDICT` / `POLICY_DENY_RULE_EXPLICIT` |
| `IntegrityFailureError` | 403 | `INTEGRITY_FAILURE` |
| `RateLimitExceededError` | 429 | tier-throttled; carries `retry_after_seconds` |
| `RiskAcceptanceRequiredError` | 403 | `RISK_ACCEPTANCE_REQUIRED` |
| `AuthenticationError` | 401 / 403 | `KEY_INVALID` / `KEY_EXPIRED` / `KEY_SCOPE_INSUFFICIENT` |
| `InsufficientDataError` | 403 | `INSUFFICIENT_DATA_FAIL_CLOSED` |
| `PackageNotFoundError` | 404 | not in catalog + ingest declined |
| `ServerError` | 5xx | retryable on 502/503/504 |
| `TransportError` | — | network / TLS / timeout / DNS |
| `ParseError` | — | response body shape mismatch |

## Development

```bash
pip install -e ".[dev]"
pytest
ruff check .
```

Contract tests (`tests/contract.py`, `tests/customer_state_contract.py`,
`tests/verdict_to_envelope_contract.py`) load fixtures from
`cleanlib-contract-fixtures` and assert byte-identical behavior against
the sdk-js sister — do not skip these; a divergence is a wire-shape
regression.

## Cross-references

- [CleanLibrary docs](https://cleanlibrary.clnstrt.dev) — customer documentation portal
- Rust SDK: [`cleanlib-client`](https://crates.io/crates/cleanlib-client) — reference implementation
- Go SDK: [`cleanlib-sdk-go`](https://pkg.clnstrt.dev/cleanlib-sdk-go)
- JavaScript SDK: [`@cleanstart/cleanlib-sdk`](https://www.npmjs.com/package/@cleanstart/cleanlib-sdk)

## License

Proprietary — CleanStart.
