cleanlib-sdk 0.4.3__tar.gz → 0.4.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. cleanlib_sdk-0.4.4/CHANGELOG.md +41 -0
  2. {cleanlib_sdk-0.4.3 → cleanlib_sdk-0.4.4}/PKG-INFO +1 -1
  3. {cleanlib_sdk-0.4.3 → cleanlib_sdk-0.4.4}/cleanlib_sdk/__init__.py +1 -1
  4. {cleanlib_sdk-0.4.3 → cleanlib_sdk-0.4.4}/pyproject.toml +16 -1
  5. cleanlib_sdk-0.4.3/bitbucket-pipelines.yml +0 -55
  6. cleanlib_sdk-0.4.3/tests/__init__.py +0 -0
  7. cleanlib_sdk-0.4.3/tests/fixtures/contract-fixtures/CORPUS-README.md +0 -96
  8. cleanlib_sdk-0.4.3/tests/fixtures/contract-fixtures/EXPECTED.json +0 -50
  9. cleanlib_sdk-0.4.3/tests/fixtures/contract-fixtures/fixtures/verdict-allow-clean.json +0 -7
  10. cleanlib_sdk-0.4.3/tests/fixtures/contract-fixtures/fixtures/verdict-allow-recommended.json +0 -17
  11. cleanlib_sdk-0.4.3/tests/fixtures/contract-fixtures/fixtures/verdict-degraded-stale.json +0 -18
  12. cleanlib_sdk-0.4.3/tests/fixtures/contract-fixtures/fixtures/verdict-deny-exploitation-critical.json +0 -21
  13. cleanlib_sdk-0.4.3/tests/fixtures/contract-fixtures/fixtures/verdict-deny-kev.json +0 -21
  14. cleanlib_sdk-0.4.3/tests/fixtures/contract-fixtures/fixtures/verdict-unreachable.json +0 -16
  15. cleanlib_sdk-0.4.3/tests/fixtures/contract-fixtures/fixtures/verdict-warn-abandoned.json +0 -14
  16. cleanlib_sdk-0.4.3/tests/fixtures/contract-fixtures/fixtures/verdict-warn-has-remediation.json +0 -17
  17. cleanlib_sdk-0.4.3/tests/test_attestation.py +0 -117
  18. cleanlib_sdk-0.4.3/tests/test_client_construct.py +0 -46
  19. cleanlib_sdk-0.4.3/tests/test_error_mapping.py +0 -90
  20. cleanlib_sdk-0.4.3/tests/test_v041_contract_and_clients.py +0 -221
  21. cleanlib_sdk-0.4.3/tests/test_v042_enrich_cascade.py +0 -603
  22. cleanlib_sdk-0.4.3/tests/test_v04_rich_data_and_verbs.py +0 -205
  23. {cleanlib_sdk-0.4.3 → cleanlib_sdk-0.4.4}/.gitignore +0 -0
  24. {cleanlib_sdk-0.4.3 → cleanlib_sdk-0.4.4}/README.md +0 -0
  25. {cleanlib_sdk-0.4.3 → cleanlib_sdk-0.4.4}/cleanlib_sdk/client.py +0 -0
  26. {cleanlib_sdk-0.4.3 → cleanlib_sdk-0.4.4}/cleanlib_sdk/derive_status.py +0 -0
  27. {cleanlib_sdk-0.4.3 → cleanlib_sdk-0.4.4}/cleanlib_sdk/errors.py +0 -0
  28. {cleanlib_sdk-0.4.3 → cleanlib_sdk-0.4.4}/cleanlib_sdk/http/__init__.py +0 -0
  29. {cleanlib_sdk-0.4.3 → cleanlib_sdk-0.4.4}/cleanlib_sdk/http/enrich_client.py +0 -0
  30. {cleanlib_sdk-0.4.3 → cleanlib_sdk-0.4.4}/cleanlib_sdk/http/enrich_types.py +0 -0
  31. {cleanlib_sdk-0.4.3 → cleanlib_sdk-0.4.4}/cleanlib_sdk/http/remediation_client.py +0 -0
  32. {cleanlib_sdk-0.4.3 → cleanlib_sdk-0.4.4}/cleanlib_sdk/http/types.py +0 -0
  33. {cleanlib_sdk-0.4.3 → cleanlib_sdk-0.4.4}/cleanlib_sdk/http/verdict_client.py +0 -0
  34. {cleanlib_sdk-0.4.3 → cleanlib_sdk-0.4.4}/cleanlib_sdk/reason_codes.py +0 -0
  35. {cleanlib_sdk-0.4.3 → cleanlib_sdk-0.4.4}/cleanlib_sdk/schema/__init__.py +0 -0
  36. {cleanlib_sdk-0.4.3 → cleanlib_sdk-0.4.4}/cleanlib_sdk/schema/verdict_envelope_v1.py +0 -0
  37. {cleanlib_sdk-0.4.3 → cleanlib_sdk-0.4.4}/cleanlib_sdk/transport.py +0 -0
  38. {cleanlib_sdk-0.4.3 → cleanlib_sdk-0.4.4}/cleanlib_sdk/types.py +0 -0
@@ -0,0 +1,41 @@
1
+ # cleanlib-sdk changelog
2
+
3
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
4
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
5
+
6
+ ## [0.4.4] — 2026-06-04
7
+
8
+ ### Fixed
9
+
10
+ - `cleanlib_sdk.__version__` synchronized with the package manifest version.
11
+ Previously the in-code `__version__` reported `0.4.2` while the published
12
+ artifact was `0.4.3` — telemetry that relied on `__version__` for User-Agent
13
+ composition was under-reporting deployment penetration.
14
+ - Wheel + sdist now include `CHANGELOG.md`. Customers running `pip download`
15
+ + tar inspection (or browsing the PyPI release files page) see the version
16
+ history alongside the package code.
17
+
18
+ ## [0.4.3] — 2026-06-03
19
+
20
+ ### Fixed
21
+
22
+ - `pyproject.toml` `[project.urls]` table sanitized: `Homepage`,
23
+ `Documentation`, and `Bug Tracker` now point at `cleanlibrary.clnstrt.dev`
24
+ rather than the internal source-of-truth URLs that previously rendered on
25
+ the PyPI listing page.
26
+ - `README.md` cross-references brand-clean: repo-internal links replaced with
27
+ the customer-facing `cleanlibrary.clnstrt.dev` documentation site and
28
+ cross-language SDK registry pages (crates.io, pkg.clnstrt.dev, npmjs.com).
29
+
30
+ ### Notes
31
+
32
+ - No runtime API or wire-protocol changes; this is a metadata + docs ship.
33
+ - Verified via `python -m build` against the resulting `cleanlib_sdk-0.4.3-py3-none-any.whl`
34
+ `METADATA` + `cleanlib_sdk-0.4.3.tar.gz` `PKG-INFO`: zero
35
+ internal-source URL references.
36
+
37
+ ## 0.4.2 — enrich verb cascade + F4 default-flip to cleanlib-enrich
38
+
39
+ ## 0.4.1 — per-domain HTTP client split (HttpRemediationClient) + schema/reason-codes mirror + DeriveStatus + Client @deprecated
40
+
41
+ ## 0.4.0 — verb cascade + rich-data
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cleanlib-sdk
3
- Version: 0.4.3
3
+ Version: 0.4.4
4
4
  Summary: CleanLibrary Python SDK — HttpVerdictClient + HttpRemediationClient + HttpEnrichClient triad (sister-shape with @cleanstart/cleanlib-sdk v0.4.4)
5
5
  Project-URL: Homepage, https://cleanlibrary.clnstrt.dev
6
6
  Project-URL: Documentation, https://cleanlibrary.clnstrt.dev/docs/sdk-python
@@ -99,7 +99,7 @@ from cleanlib_sdk.types import (
99
99
  Verdict,
100
100
  )
101
101
 
102
- __version__ = "0.4.2"
102
+ __version__ = "0.4.4"
103
103
 
104
104
  __all__ = [
105
105
  # v0.4.2 per-domain HTTP client triad (primary API)
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "cleanlib-sdk"
7
- version = "0.4.3"
7
+ version = "0.4.4"
8
8
  description = "CleanLibrary Python SDK — HttpVerdictClient + HttpRemediationClient + HttpEnrichClient triad (sister-shape with @cleanstart/cleanlib-sdk v0.4.4)"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -39,6 +39,21 @@ Documentation = "https://cleanlibrary.clnstrt.dev/docs/sdk-python"
39
39
  [tool.hatch.build.targets.wheel]
40
40
  packages = ["cleanlib_sdk"]
41
41
 
42
+ # v0.4.4 CLEANLIB-49: ship CHANGELOG.md inside the package so customers
43
+ # reading the PyPI listing (or running pip download + tar inspection) see
44
+ # the version history.
45
+ [tool.hatch.build.targets.wheel.force-include]
46
+ "CHANGELOG.md" = "cleanlib_sdk/CHANGELOG.md"
47
+
48
+ [tool.hatch.build.targets.sdist]
49
+ include = [
50
+ "cleanlib_sdk/**",
51
+ "README.md",
52
+ "CHANGELOG.md",
53
+ "LICENSE",
54
+ "pyproject.toml",
55
+ ]
56
+
42
57
  [tool.pytest.ini_options]
43
58
  asyncio_mode = "auto"
44
59
  testpaths = ["tests"]
@@ -1,55 +0,0 @@
1
- # CleanLibrary Python SDK CI — per workspace §C.5 + §C.6 + pen #16 forward fix.
2
- #
3
- # Mirrors the cleanlib repo's commit-prefix discipline + rebase-required
4
- # detector. Uses a separate CLEANLIB-N counter per sdk-substrate-rev1.md §8.
5
-
6
- image: python:3.12-slim
7
-
8
- pipelines:
9
- default:
10
- - step:
11
- name: Validate commit message prefix
12
- script:
13
- # Each commit on the PR branch (since divergence from main) must start with
14
- # a CLEANLIB-N prefix per workspace §6.9 discipline. --no-merges per pen #16
15
- # avoids false-positives on BB-synthesized merge commits.
16
- - |
17
- apt-get update -qq && apt-get install -y -qq git >/dev/null
18
- BASE_REF=${BITBUCKET_PR_DESTINATION_BRANCH:-main}
19
- git fetch origin "$BASE_REF" || true
20
- git log --no-merges "origin/$BASE_REF..HEAD" --pretty=format:"%H %s" | while read -r line; do
21
- MSG=${line#* }
22
- case "$MSG" in
23
- CLEANLIB-*) ;;
24
- *) echo "❌ commit missing CLEANLIB-N prefix: $line"; exit 1;;
25
- esac
26
- done
27
- echo "✅ commit prefixes OK"
28
-
29
- - step:
30
- name: Rebase-required detector
31
- script:
32
- # Mirrors cleanlib repo §C.6: if merge-base is not on main's HEAD, ask for rebase.
33
- - |
34
- apt-get update -qq && apt-get install -y -qq git >/dev/null
35
- BASE_REF=${BITBUCKET_PR_DESTINATION_BRANCH:-main}
36
- git fetch origin "$BASE_REF"
37
- MB=$(git merge-base "origin/$BASE_REF" HEAD)
38
- HEAD_REMOTE=$(git rev-parse "origin/$BASE_REF")
39
- if [ "$MB" != "$HEAD_REMOTE" ]; then
40
- echo "❌ Branch is behind origin/$BASE_REF; rebase before merging."
41
- echo " merge-base: $MB"
42
- echo " origin/$BASE_REF: $HEAD_REMOTE"
43
- exit 1
44
- fi
45
- echo "✅ branch current with origin/$BASE_REF"
46
-
47
- - step:
48
- name: Lint + test
49
- caches:
50
- - pip
51
- script:
52
- - pip install --quiet --upgrade pip
53
- - pip install --quiet -e ".[dev]"
54
- - ruff check .
55
- - pytest -q
File without changes
@@ -1,96 +0,0 @@
1
- # cleanlib-contract-fixtures
2
-
3
- **Cross-SDK contract fixture corpus.** Source-of-truth fixtures that every CleanLibrary SDK consumer asserts identical `DeriveStatus` results against. The corpus locks against the **`VerdictEnvelopeV1`** schema published in `@cleanstart/cleanlib-sdk@0.4.1`.
4
-
5
- ## Consumers (4)
6
-
7
- | SDK | Repo | Test harness | Schema validator |
8
- |---|---|---|---|
9
- | sdk-js | `triamsec/cleanlib-sdk-js` (npm `@cleanstart/cleanlib-sdk`) | vitest | ajv |
10
- | sdk-py | `triamsec/cleanlib-sdk-py` (PyPI `cleanlib-sdk`) | pytest | `jsonschema` |
11
- | sdk-go | `triamsec/cleanlib-sdk-go` (Go modules; same path) | `go test` | `github.com/xeipuuv/gojsonschema` |
12
- | cleanlib-client (Rust) | `triamsec/cleanlib` workspace member | `cargo test` | `jsonschema` crate |
13
-
14
- Each consumer pulls this corpus as a git submodule (or vendors under `test/fixtures/`); CI fails on `DeriveStatus(fixture) != expected_per_EXPECTED.json` for any fixture.
15
-
16
- ## Schema lock
17
-
18
- v1.0.0 of this corpus locks against:
19
- - Tarball: `@cleanstart/cleanlib-sdk@0.4.1` (sha-1 `b5f00c160907a6ea1f490f14a9bda6f6de34b8b6`)
20
- - Schema: `https://cleanstart.com/schemas/verdict-envelope.v1.json` (`VerdictEnvelopeV1Schema` exported from `dist/schema/verdict-envelope.v1.js`)
21
- - ReasonCode registry: `dist/reason-codes.js` (15 codes)
22
- - Freshness-precedence rule: ratified 2026-05-28; documented in schema `reason_code.description` + `reason-codes.js` comment
23
-
24
- ## VerdictEnvelopeV1 — top-level shape
25
-
26
- ```
27
- required: [status, reason_code, human_message, as_of]
28
- optional: rich_data, remediation, exploitability, availability{kev,epss,exploitation_fusion}
29
- status enum: [ALLOW, WARN, DENY] ← server-side tri-state only
30
- ```
31
-
32
- **`UNREACHABLE` and `LIVE_DEGRADED` are NOT envelope status values.** They are client-tier states:
33
- - `UNREACHABLE` — CLI exit-code 3 on SDK transport-error; no envelope
34
- - `LIVE_DEGRADED` — VS Code extension status-bar state on cache-fallback; surfaces as `reason_code=VERDICT_DEGRADED_STALE` when an envelope IS assembled from a stale cache hit, else as an out-of-band transport state
35
-
36
- The corpus's `verdict-unreachable.json` documents the transport-error shape (NOT a VerdictEnvelopeV1) for SDK transport-tier contract tests.
37
-
38
- ## Fixture → (status, reason_code) contract
39
-
40
- Per the freshness-precedence rule (App dispatch §4 + Client dispatch §2.3 binding contract):
41
- - **Substance precedence** — signals top-to-bottom (exploitation_critical > kev > obfuscation > has_remediation > abandonment > clean); `unavailable` substrate falls through to the next rule
42
- - **Freshness override** — `degraded_stale` substrate KEEPS the substance-derived status tier but rewrites `reason_code` → `VERDICT_DEGRADED_STALE`
43
- - **Block tie-break in remediation** — `fix > recommended_version > other blocks`
44
- - **Server-side `VERDICT_DEGRADED_STALE` ≠ client-side `LIVE_DEGRADED`** — distinct concepts; never conflated
45
-
46
- | Fixture | Status | Reason code | Driving fields |
47
- |---|---|---|---|
48
- | `verdict-allow-clean.json` | ALLOW | VERDICT_CLEAN | (no sub-objects) |
49
- | `verdict-allow-recommended.json` | ALLOW | VERDICT_RECOMMENDED_VERSION_NEWER | `rich_data.recommended_version` (benign upgrade hint, no remediation block) |
50
- | `verdict-warn-abandoned.json` | WARN | VERDICT_ABANDONED | `rich_data.abandonment_score >= 0.7` |
51
- | `verdict-warn-has-remediation.json` | WARN | VERDICT_HAS_REMEDIATION | `remediation.fix` or `remediation.recommended_version` (under `remediation`, NOT `rich_data`) |
52
- | `verdict-deny-kev.json` | DENY | VERDICT_KEV_LISTED | `exploitability.in_kev=true` + `availability.kev='available'` + `exploitability.exploit_risk_score >= 70` |
53
- | `verdict-deny-exploitation-critical.json` | DENY | VERDICT_EXPLOITATION_CRITICAL | `exploitability.exploitation_likelihood='CRITICAL'` |
54
- | `verdict-degraded-stale.json` | WARN | VERDICT_DEGRADED_STALE | substance: HAS_REMEDIATION; freshness override applies (`remediation.fix.availability='degraded_stale'`) |
55
- | `verdict-unreachable.json` | _(out-of-band)_ | _(out-of-band)_ | transport-error synthetic; not a VerdictEnvelopeV1; CLI exit-3 / extension LIVE_DEGRADED |
56
-
57
- See `EXPECTED.json` for the machine-readable lookup.
58
-
59
- ## DeriveStatus precedence (canonical spec; SDKs implement independently)
60
-
61
- ```
62
- if exploitability.exploitation_likelihood == "CRITICAL" → DENY + VERDICT_EXPLOITATION_CRITICAL
63
- if exploitability.in_kev && availability.kev == "available" && exploit_risk_score >= 70 → DENY + VERDICT_KEV_LISTED
64
- if rich_data.has_obfuscation → DENY + VERDICT_OBFUSCATED
65
- if remediation.fix != null || remediation.recommended_version != null → WARN + VERDICT_HAS_REMEDIATION
66
- if rich_data.abandonment_score >= 0.7 → WARN + VERDICT_ABANDONED
67
- if rich_data.recommended_version != null → ALLOW + VERDICT_RECOMMENDED_VERSION_NEWER
68
- default → ALLOW + VERDICT_CLEAN
69
-
70
- # Then apply the freshness-precedence override:
71
- if the substance-driving signal's availability == "degraded_stale"
72
- → status unchanged; reason_code := VERDICT_DEGRADED_STALE
73
- ```
74
-
75
- `DeriveStatus` is **not exported from sdk-js** — each SDK implements this algorithm independently against the binding spec. The cross-SDK contract test verifies all 4 implementations agree byte-identically on every fixture's `(status, reason_code)`.
76
-
77
- ## Drift handling
78
-
79
- Schema changes (new envelope field, renamed key, etc.) MUST land here first; then all 4 SDKs receive a coordinated PR pulling the updated corpus + adjusting derivation cases. Monthly cron CI in each SDK fetches the latest corpus + diffs locally (cycle-8 scope; for cycle-7, manual sync during S1+S2 publish events).
80
-
81
- ## Layout
82
-
83
- ```
84
- cleanlib-contract-fixtures/
85
- ├── README.md # this file
86
- ├── EXPECTED.json # machine-readable fixture → (status, reason_code) lookup
87
- └── fixtures/
88
- ├── verdict-allow-clean.json
89
- ├── verdict-allow-recommended.json
90
- ├── verdict-warn-abandoned.json
91
- ├── verdict-warn-has-remediation.json
92
- ├── verdict-deny-kev.json
93
- ├── verdict-deny-exploitation-critical.json
94
- ├── verdict-degraded-stale.json
95
- └── verdict-unreachable.json # OUT-OF-BAND transport-error; not a VerdictEnvelopeV1
96
- ```
@@ -1,50 +0,0 @@
1
- {
2
- "_doc": "Machine-readable fixture → (status, reason_code) lookup, schema-locked against sdk-js v0.4.1 published surface (VerdictEnvelopeV1Schema + ReasonCode registry). Each SDK consumer (sdk-js, sdk-py, sdk-go, cleanlib-client Rust) loads this and asserts DeriveStatus(fixtures[N]) == EXPECTED[N] for every fixture. v1.0.0 of this corpus locks against sdk-js v0.4.1.",
3
- "_schema_lock": {
4
- "sdk_js_version": "@cleanstart/cleanlib-sdk@0.4.1",
5
- "tarball_sha1": "b5f00c160907a6ea1f490f14a9bda6f6de34b8b6",
6
- "schema_id": "https://cleanstart.com/schemas/verdict-envelope.v1.json",
7
- "envelope_status_enum": ["ALLOW", "WARN", "DENY"],
8
- "freshness_precedence_rule_ratified": "2026-05-28"
9
- },
10
- "fixtures": {
11
- "verdict-allow-clean.json": {
12
- "status": "ALLOW",
13
- "reason_code": "VERDICT_CLEAN"
14
- },
15
- "verdict-allow-recommended.json": {
16
- "status": "ALLOW",
17
- "reason_code": "VERDICT_RECOMMENDED_VERSION_NEWER"
18
- },
19
- "verdict-warn-abandoned.json": {
20
- "status": "WARN",
21
- "reason_code": "VERDICT_ABANDONED"
22
- },
23
- "verdict-warn-has-remediation.json": {
24
- "status": "WARN",
25
- "reason_code": "VERDICT_HAS_REMEDIATION"
26
- },
27
- "verdict-deny-kev.json": {
28
- "status": "DENY",
29
- "reason_code": "VERDICT_KEV_LISTED"
30
- },
31
- "verdict-deny-exploitation-critical.json": {
32
- "status": "DENY",
33
- "reason_code": "VERDICT_EXPLOITATION_CRITICAL"
34
- },
35
- "verdict-degraded-stale.json": {
36
- "status": "WARN",
37
- "reason_code": "VERDICT_DEGRADED_STALE",
38
- "_note": "substance:HAS_REMEDIATION + freshness:degraded_stale → status preserved (WARN), reason_code overridden (VERDICT_DEGRADED_STALE) per freshness-precedence rule"
39
- }
40
- },
41
- "_out_of_band": {
42
- "verdict-unreachable.json": {
43
- "_kind": "transport_error_synthetic",
44
- "_note": "NOT a VerdictEnvelopeV1. SDK transport-failure result; CLI maps to UNREACHABLE+exit3; extension maps to LIVE_DEGRADED status-bar (client-tier). No envelope status assigned (schema enum is [ALLOW,WARN,DENY] only).",
45
- "cli_exit_code": 3,
46
- "cli_status_label": "UNREACHABLE",
47
- "extension_status_bar": "LIVE_DEGRADED"
48
- }
49
- }
50
- }
@@ -1,7 +0,0 @@
1
- {
2
- "_doc": "DeriveStatus contract: no remediation/exploitability/rich-data alerts → status=ALLOW, reason_code=VERDICT_CLEAN. Minimum required envelope shape per VerdictEnvelopeV1Schema. All optional sub-objects omitted.",
3
- "status": "ALLOW",
4
- "reason_code": "VERDICT_CLEAN",
5
- "human_message": "left-pad 1.3.0 — no findings. Allowed.",
6
- "as_of": "2026-05-28"
7
- }
@@ -1,17 +0,0 @@
1
- {
2
- "_doc": "DeriveStatus contract: benign envelope with recommended_version under rich_data only (NOT under remediation.recommended_version or remediation.fix) → status=ALLOW, reason_code=VERDICT_RECOMMENDED_VERSION_NEWER (specific reason from published ReasonCode registry; F2 ratification supersedes the earlier draft VERDICT_CLEAN mapping). Sister of the cycle-6 cors clean-shape probe.",
3
- "status": "ALLOW",
4
- "reason_code": "VERDICT_RECOMMENDED_VERSION_NEWER",
5
- "human_message": "cors 2.8.4 — clean, but a newer recommended version (2.8.6) is available.",
6
- "as_of": "2026-05-28",
7
- "rich_data": {
8
- "recommended_version": {
9
- "version": "2.8.6",
10
- "source": "l1_package_master",
11
- "detected_at": "",
12
- "total_versions": 0
13
- },
14
- "verdict_source": "llm_anthropic",
15
- "terminal_state": "completed"
16
- }
17
- }
@@ -1,18 +0,0 @@
1
- {
2
- "_doc": "DeriveStatus contract (freshness-precedence rule ratified 2026-05-28 + binding via App dispatch §4 + Client dispatch §2.3 + verdict-envelope.v1 schema reason_code doc-comment): when underlying substrate availability='degraded_stale' on a block driving a substance-derived reason, the substance-derived tier is PRESERVED and the reason_code OVERRIDES to VERDICT_DEGRADED_STALE. This fixture's substance signal is HAS_REMEDIATION (remediation.fix present); freshness=degraded_stale → status=WARN (preserved) + reason_code=VERDICT_DEGRADED_STALE (overridden). Server-side VERDICT_DEGRADED_STALE is architecturally distinct from client-side LIVE_DEGRADED cache-fallback state.",
3
- "status": "WARN",
4
- "reason_code": "VERDICT_DEGRADED_STALE",
5
- "human_message": "stale-cache-fixture 1.0.0 — remediation data is stale (since 2026-05-21); WARN preserved, reason overridden to freshness.",
6
- "as_of": "2026-05-28",
7
- "remediation": {
8
- "fix": {
9
- "availability": "degraded_stale",
10
- "as_of": "2026-05-21"
11
- }
12
- },
13
- "availability": {
14
- "kev": "available",
15
- "epss": "available",
16
- "exploitation_fusion": "available"
17
- }
18
- }
@@ -1,21 +0,0 @@
1
- {
2
- "_doc": "DeriveStatus contract: exploitability.exploitation_likelihood='CRITICAL' → status=DENY, reason_code=VERDICT_EXPLOITATION_CRITICAL. Highest substance precedence — fires before KEV / obfuscation / remediation / abandonment checks.",
3
- "status": "DENY",
4
- "reason_code": "VERDICT_EXPLOITATION_CRITICAL",
5
- "human_message": "log4j-style-fixture 2.14.0 — CRITICAL active exploitation; immediate action required.",
6
- "as_of": "2026-05-28",
7
- "exploitability": {
8
- "exploitation_likelihood": "CRITICAL",
9
- "in_kev": true,
10
- "exploit_risk_score": 99,
11
- "kev": {
12
- "cve_id": "CVE-2021-FIXTURE",
13
- "date_added": "2021-12-15"
14
- }
15
- },
16
- "availability": {
17
- "kev": "available",
18
- "epss": "available",
19
- "exploitation_fusion": "available"
20
- }
21
- }
@@ -1,21 +0,0 @@
1
- {
2
- "_doc": "DeriveStatus contract: exploitability.in_kev=true AND exploitability.availability.kev='available' AND exploitability.exploit_risk_score>=70 → status=DENY, reason_code=VERDICT_KEV_LISTED. All three conditions required; ablating any one drops to lower precedence.",
3
- "status": "DENY",
4
- "reason_code": "VERDICT_KEV_LISTED",
5
- "human_message": "kev-listed-fixture 1.0.0 — listed in CISA KEV (CVE-2024-FIXTURE); high exploit-risk.",
6
- "as_of": "2026-05-28",
7
- "exploitability": {
8
- "in_kev": true,
9
- "exploit_risk_score": 85,
10
- "exploitation_likelihood": "HIGH",
11
- "kev": {
12
- "cve_id": "CVE-2024-FIXTURE",
13
- "date_added": "2024-08-15"
14
- }
15
- },
16
- "availability": {
17
- "kev": "available",
18
- "epss": "available",
19
- "exploitation_fusion": "available"
20
- }
21
- }
@@ -1,16 +0,0 @@
1
- {
2
- "_doc": "OUT-OF-BAND transport-error result — NOT a VerdictEnvelopeV1. This fixture documents the SDK's transport-failure shape (`HttpRemediationClient.getRemediation → {kind:'transient', error}` per sdk-js types/remediation.d.ts). The CLI maps this to UNREACHABLE + exit 3; the VS Code extension maps to its client-tier LIVE_DEGRADED status-bar state (architecturally distinct from server-side VERDICT_DEGRADED_STALE per dispatch §2.3 ratification + verdict-envelope.v1 schema doc-comment). The envelope schema's status enum is [ALLOW, WARN, DENY] only — UNREACHABLE has no envelope; it is by definition the absence-of-envelope path.",
3
- "_kind": "transport_error_synthetic",
4
- "_envelope": null,
5
- "transport_error": {
6
- "kind": "transient",
7
- "message": "connection refused / DNS NXDOMAIN / 5xx after retry budget exhausted",
8
- "endpoint": "https://cleanlib-enrich.clnstrt.dev"
9
- },
10
- "_expected_client_state": {
11
- "cli_exit_code": 3,
12
- "cli_status_label": "UNREACHABLE",
13
- "extension_status_bar": "LIVE_DEGRADED",
14
- "reason_code_when_envelope_assembled_from_cache_hit": "CLIENT_NETWORK_UNREACHABLE"
15
- }
16
- }
@@ -1,14 +0,0 @@
1
- {
2
- "_doc": "DeriveStatus contract: rich_data.abandonment_score >= 0.7 → status=WARN, reason_code=VERDICT_ABANDONED. Score field MUST drive the derivation; reason text is descriptive only.",
3
- "status": "WARN",
4
- "reason_code": "VERDICT_ABANDONED",
5
- "human_message": "stale-lib-fixture 0.9.0 — abandoned (no commits since 2022; 0 active maintainers).",
6
- "as_of": "2026-05-28",
7
- "rich_data": {
8
- "abandonment_score": 0.85,
9
- "repo_health": {
10
- "last_commit_at": "2022-01-15T00:00:00Z",
11
- "maintainer_count": 0
12
- }
13
- }
14
- }
@@ -1,17 +0,0 @@
1
- {
2
- "_doc": "DeriveStatus contract: remediation.fix OR remediation.recommended_version present (under remediation block, not rich_data) → status=WARN, reason_code=VERDICT_HAS_REMEDIATION. Distinction vs verdict-allow-recommended: this fixture's recommended_version sits under remediation (signals active vuln with fix path); the allow-recommended fixture has rich_data.recommended_version (benign upgrade hint, no active vuln). Per dispatch §3.2 block tie-break: fix > recommended_version > other blocks.",
3
- "status": "WARN",
4
- "reason_code": "VERDICT_HAS_REMEDIATION",
5
- "human_message": "vuln-with-fix-fixture 1.0.0 — vulnerable; upgrade available.",
6
- "as_of": "2026-05-28",
7
- "remediation": {
8
- "fix": {
9
- "availability": "available",
10
- "as_of": "2026-05-20"
11
- },
12
- "recommended_version": {
13
- "availability": "available",
14
- "as_of": "2026-05-20"
15
- }
16
- }
17
- }
@@ -1,117 +0,0 @@
1
- """Cycle-5 SDK ripple — typed attestation field deserialization tests.
2
-
3
- Asserts that the new `Verdict.attestation: Optional[SignedAttestation]` field
4
- deserializes correctly from:
5
- 1. Live cleanapp.clnstrt.dev prod response (PR #87 / prod-v0.1.9 shape)
6
- 2. cycle-4 + cycle-5 pre-PR-#87 responses that omit the field (backwards-compat)
7
- """
8
-
9
- from __future__ import annotations
10
-
11
- from cleanlib_sdk import Attestation, SignedAttestation, Verdict
12
-
13
- # Empirical wire-shape captured from cleanapp.clnstrt.dev 2026-05-25 against
14
- # /v1/customer/verdicts/npm/cors/2.8.4 (PR #87 / prod-v0.1.9 deployed shape).
15
- LIVE_PROD_RESPONSE = {
16
- "verdict_id": "01KDVDNA0000000000000001YK",
17
- "verdict": "VECTOR_VERDICT",
18
- "source": "VECTOR_VERDICT",
19
- "confidence": 0.98,
20
- "composite_score": 78,
21
- "severity": "HIGH",
22
- "reasoning": "Prototype pollution vulnerability in cors 2.8.4; upgrade to 2.8.5 patched release",
23
- "similar_to": [],
24
- "evidence_gaps": [],
25
- "suggested_actions": [
26
- "Upgrade to cors@2.8.5 or later",
27
- "Audit downstream consumers for prototype pollution exposure",
28
- ],
29
- "data_freshness_at": "2026-05-20T10:00:00Z",
30
- "data_oldest_signal_at": "2026-05-19T10:00:00Z",
31
- "stale_since_at": None,
32
- "staleness_reason": None,
33
- "previous_verdict": None,
34
- "computed_at": "2026-05-20T10:30:00Z",
35
- "attestation": {
36
- "attestation": {
37
- "artifact_hash": "8d7c47dc708c15e9ca9a2b1ab9f7360bad7876a3d67cf8d6757d9d652d8176db",
38
- "verdict_id": "01KDVDNA0000000000000001YK",
39
- "verdict_source": "VECTOR_VERDICT",
40
- "verdict_evaluated_at": "2026-05-20T10:30:00Z",
41
- "policy_decision": "ALLOW",
42
- "policy_rule_id_matched": "",
43
- "risk_acceptance_status": "none",
44
- "organization_id": "org_dev_anonymous",
45
- "app_version": "0.1.0",
46
- "served_at": "2026-05-25T02:54:28.199721580Z",
47
- },
48
- "signature_b64": "MEUCIQDyz1ImG33JyKRfmtYD8BuEqxuVm0eNR7WFx+lqq/vNXAIgKokBPl58yWYScWXaGzaaRW9VFrYUeRVCodI7WBD047g=",
49
- "key_id": "local-ecdsa-p256-test",
50
- },
51
- }
52
-
53
-
54
- def test_verdict_deserializes_attestation_field_from_live_prod_response():
55
- v = Verdict.model_validate(LIVE_PROD_RESPONSE)
56
- assert v.attestation is not None
57
- assert isinstance(v.attestation, SignedAttestation)
58
- assert v.attestation.key_id == "local-ecdsa-p256-test"
59
- assert v.attestation.signature_b64.startswith("MEUCI")
60
-
61
-
62
- def test_attestation_inner_payload_binds_to_verdict_id():
63
- """Per PR #87 verdict-id binding correctness test mirror."""
64
- v = Verdict.model_validate(LIVE_PROD_RESPONSE)
65
- inner = v.attestation.attestation
66
- assert isinstance(inner, Attestation)
67
- assert inner.verdict_id == v.verdict_id
68
- assert inner.verdict_source == "VECTOR_VERDICT"
69
-
70
-
71
- def test_attestation_artifact_hash_is_64_char_hex():
72
- """Sha256 hex shape — sister-assertion to PR #87 test layer."""
73
- v = Verdict.model_validate(LIVE_PROD_RESPONSE)
74
- h = v.attestation.attestation.artifact_hash
75
- assert len(h) == 64
76
- assert all(c in "0123456789abcdef" for c in h.lower())
77
-
78
-
79
- def test_attestation_policy_decision_is_allow_for_verdict_attestation_layer():
80
- """Per PR #87 Path β Option (a): verdict-attestation layer is
81
- policy-agnostic; policy_decision = ALLOW (neutral) is the canonical
82
- marker that this is a verdict-attestation, NOT a byte-serve attestation."""
83
- v = Verdict.model_validate(LIVE_PROD_RESPONSE)
84
- assert v.attestation.attestation.policy_decision == "ALLOW"
85
- assert v.attestation.attestation.policy_rule_id_matched == ""
86
-
87
-
88
- def test_verdict_backwards_compat_when_attestation_absent():
89
- """cycle-4 mock fixtures + cycle-5-pre-PR-#87 responses omitted the field.
90
- SDK MUST deserialize cleanly without it (Optional[...] default None)."""
91
- response_without_attestation = {k: v for k, v in LIVE_PROD_RESPONSE.items() if k != "attestation"}
92
- v = Verdict.model_validate(response_without_attestation)
93
- assert v.attestation is None
94
- # All other fields still parse correctly
95
- assert v.verdict_id == "01KDVDNA0000000000000001YK"
96
- assert v.confidence == 0.98
97
-
98
-
99
- def test_verdict_handles_null_attestation_value_gracefully():
100
- """If App ships an explicit null (e.g. signing failure with explicit None
101
- on the wire instead of skip_serializing_if), SDK still deserializes."""
102
- response_with_null = dict(LIVE_PROD_RESPONSE)
103
- response_with_null["attestation"] = None
104
- v = Verdict.model_validate(response_with_null)
105
- assert v.attestation is None
106
-
107
-
108
- def test_signed_attestation_roundtrips_through_model_dump():
109
- """Verify the SDK can serialize the attestation back to wire shape.
110
- Useful for SDK consumers that want to forward or store the attestation."""
111
- v = Verdict.model_validate(LIVE_PROD_RESPONSE)
112
- dumped = v.model_dump(mode="json", exclude_none=True)
113
- assert dumped["attestation"]["signature_b64"] == LIVE_PROD_RESPONSE["attestation"]["signature_b64"]
114
- assert (
115
- dumped["attestation"]["attestation"]["artifact_hash"]
116
- == LIVE_PROD_RESPONSE["attestation"]["attestation"]["artifact_hash"]
117
- )
@@ -1,46 +0,0 @@
1
- """Construction smoke tests — verify Client + endpoint validation behavior."""
2
-
3
- from __future__ import annotations
4
-
5
- import pytest
6
-
7
- from cleanlib_sdk import Client
8
- from cleanlib_sdk.errors import TransportError
9
-
10
-
11
- def test_client_constructs_with_https_endpoint():
12
- c = Client(endpoint="https://cleanapp.clnstrt.dev", api_key="clk_test")
13
- assert c.base_url == "https://cleanapp.clnstrt.dev"
14
- assert c.api_version == "v1"
15
- assert c.api_key == "clk_test"
16
-
17
-
18
- def test_client_strips_trailing_slash_from_endpoint():
19
- c = Client(endpoint="https://cleanapp.clnstrt.dev/", api_key="clk_test")
20
- assert c.base_url == "https://cleanapp.clnstrt.dev"
21
-
22
-
23
- def test_client_rejects_http_remote_endpoint():
24
- with pytest.raises(TransportError, match="TLS required"):
25
- Client(endpoint="http://cleanapp.clnstrt.dev", api_key="clk_test")
26
-
27
-
28
- def test_client_allows_http_localhost():
29
- c = Client(endpoint="http://localhost:8080", api_key="clk_test")
30
- assert "localhost" in c.base_url
31
-
32
-
33
- def test_client_allows_http_127_loopback():
34
- c = Client(endpoint="http://127.0.0.1:8080", api_key="clk_test")
35
- assert c.base_url == "http://127.0.0.1:8080"
36
-
37
-
38
- def test_client_constructs_without_api_key():
39
- """API key optional at construction; unauthenticated calls will fail server-side."""
40
- c = Client(endpoint="https://cleanapp.clnstrt.dev")
41
- assert c.api_key is None
42
-
43
-
44
- def test_client_custom_api_version():
45
- c = Client(endpoint="https://cleanapp.clnstrt.dev", api_key="k", api_version="v2")
46
- assert c.api_version == "v2"