Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
31fbe60
chore: drop unused mypy type-ignore in format_output
aurangzaib048 Oct 21, 2026
e1a7400
feat: track negotiated TEA spec version on TeaClient
aurangzaib048 Oct 21, 2026
ec17a93
feat!: enforce TEA 1.0.0 required fields, keep 0.4.0 via legacy context
aurangzaib048 Oct 21, 2026
9f00117
feat!: reject MD5 checksums from TEA 1.x servers
aurangzaib048 Oct 21, 2026
7fde207
fix: enforce remaining TEA 1.0.0 presence rules, fail closed on CLI c…
aurangzaib048 Oct 21, 2026
de83272
fix: allow user-supplied MD5 in CLI --checksum
aurangzaib048 Oct 21, 2026
a5a3506
feat!: token-based pagination for TEA 1.0.0, offset tokens for 0.4.0
aurangzaib048 Oct 21, 2026
bbbc62e
fix: bound token pagination and key CLI header on negotiated version
aurangzaib048 Oct 21, 2026
efe54a7
feat: parse TEA 1.0.0 flattened release-with-collection responses
aurangzaib048 Oct 21, 2026
8cb33dc
feat: parse TEA 1.0.0 tei:// identifiers alongside urn:tei
aurangzaib048 Oct 21, 2026
f755d1f
fix: CLI download recognises tei:// sources
aurangzaib048 Oct 21, 2026
f331b62
feat: negotiate TEA 1.0.0 then 0.4.0, keep advertised version spelling
aurangzaib048 Oct 21, 2026
42adf3b
fix: fail over across supported TEA versions during discovery
aurangzaib048 Oct 21, 2026
acf4ca6
feat: TEA 1.0.0 error types, tolerate unknown error values
aurangzaib048 Oct 21, 2026
8f38adb
fix: parse TEA error type from a larger bounded read
aurangzaib048 Oct 21, 2026
72be6e9
fix: TEA error parsing tolerates deeply nested bodies
aurangzaib048 Oct 21, 2026
82e6c15
feat: conformance checks for TEA 1.0.0 pagination tokens and TEI URIs
aurangzaib048 Oct 21, 2026
d53e76d
feat: discovery by PURL (TEA 1.0.0)
aurangzaib048 Oct 21, 2026
68356f5
feat: download server-hosted artifacts via TEA 1.0.0 download endpoint
aurangzaib048 Oct 21, 2026
89ec29a
docs: TEA 1.0.0 alignment, upgrade notes, deferred items
aurangzaib048 Oct 21, 2026
8163d58
docs: restore sbomify 0.4.0 examples, fix runnable README snippets
aurangzaib048 Oct 21, 2026
683d90a
fix(cli): inspect renders 1.0.0 server-hosted formats and picks newes…
aurangzaib048 Oct 21, 2026
3e7f3f4
fix(discovery): prefer exact supported versions over a compatible dow…
aurangzaib048 Oct 21, 2026
058e2d1
fix: harden response handling for TEA 1.0.0 servers
aurangzaib048 Oct 21, 2026
da9c763
fix(download): reject HTTPS to HTTP redirect downgrades
aurangzaib048 Oct 21, 2026
8c7b7bc
docs: upgrade notes for Python-built models and version checks, more …
aurangzaib048 Oct 21, 2026
2cb36cb
docs: note HTTPS to HTTP download redirect rejection in upgrade notes
aurangzaib048 Oct 21, 2026
ed7ce0d
Merge remote-tracking branch 'upstream/master' into feat/tea-1.0.0-al…
aurangzaib048 Oct 22, 2026
86aa711
fix(conformance): search checks follow page tokens past empty TEA 1.x…
aurangzaib048 Oct 22, 2026
0eff4bc
fix: use the TEA 0.4.0 artifact path on legacy servers
aurangzaib048 Oct 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 11 additions & 8 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

## Project Overview

**py-libtea** is a hand-crafted Python client library for the Transparency Exchange API (TEA) v0.4.0. Consumer-focused (read-only); publisher API is not yet supported (blocked on TEA spec). Licensed Apache 2.0, maintained under sbomify.
**py-libtea** is a hand-crafted Python client library for the Transparency Exchange API (TEA) 1.0.0 with 0.4.0 server compatibility. Consumer-focused (read-only); publisher API is not yet supported (blocked on TEA spec). Licensed Apache 2.0, maintained under sbomify.

## Build & Dev Commands

Expand All @@ -27,17 +27,17 @@ uv build # Build wheel and sdist
- **Line length**: 120, ruff rules: E, F, I
- **Models**: Pydantic v2 with `frozen=True`, `extra="ignore"`, `alias_generator=to_camel`
- **HTTP mocking**: `responses` library (not `unittest.mock` for HTTP)
- **Coverage**: Branch coverage enabled, target ~97%
- **Coverage**: Branch coverage enabled, target ~95%

## Architecture

The library has a layered design with strict separation of concerns:

```
__init__.py Public API re-exports (all models, exceptions, client, discovery)
client.py TeaClient — high-level consumer API, checksum verification
client.py TeaClient — high-level consumer API, spec version resolution, checksum verification
↓ uses
_validation.py Input validation helpers (path segments, page size/offset, Pydantic wrappers)
_validation.py Input validation helpers (path segments, page size, Pydantic wrappers)
_http.py TeaHttpClient — low-level requests wrapper, auth, streaming downloads
Also: probe_endpoint() for endpoint failover
_security.py SSRF protection (_validate_download_url, DNS rebinding checks, internal IP detection)
Expand All @@ -52,7 +52,7 @@ _cli_entry.py Entry point wrapper that handles missing CLI extras gracefu

conformance/ Conformance test suite (optional dependency: pytest)
__init__.py Public API: run_conformance, CheckResult, CheckStatus, ConformanceResult
_checks.py 27 check functions organized by category, CheckContext shared state
_checks.py 28 check functions organized by category, CheckContext shared state
_runner.py Orchestrates checks, collects results into ConformanceResult
_types.py CheckStatus (StrEnum), CheckResult (frozen dataclass), ConformanceResult
plugin.py pytest plugin — --tea-base-url options, parametrized test generation
Expand All @@ -65,9 +65,10 @@ conformance/ Conformance test suite (optional dependency: pytest)
- Downloads follow redirects manually with SSRF validation at each hop
- Discovery redirects are validated against internal networks (SSRF protection via `_security._validate_download_url`)
- `_validation._validate()` wraps Pydantic `ValidationError` into `TeaValidationError` so all client errors are `TeaError` subclasses
- Endpoint failover: `from_well_known()` probes candidates in priority order, skipping unreachable ones
- Endpoint failover: `from_well_known()` probes candidates in priority order, skipping unreachable ones. With `version=None` it negotiates 1.0.0 first, then 0.4.0, and the candidates span both versions
- `download_artifact_content()` (server-hosted artifact bytes) sends the token only on the first hop, to the API base; every redirect hop goes through the unauthenticated session with SSRF validation
- `probe_endpoint()` lives in `_http.py` (not `client.py`) to maintain the HTTP layer boundary
- `_raise_for_status()` uses bounded reads (201 bytes) for error body snippets to avoid memory issues on streaming responses
- `_raise_for_status()` reads 4xx bodies once, bounded to 4 KiB, to parse the TEA `error` value (`_tea_error_type`); the message snippet is cut to 200 characters. This avoids memory issues on streaming responses
- CLI formatters in `_cli_fmt.py` escape all server-controlled strings with `rich.markup.escape()` to prevent Rich markup injection
- Conformance checks run sequentially; earlier checks populate `CheckContext` with discovered UUIDs for later checks
- `from __future__ import annotations` is safe in conformance modules (no Pydantic models, only dataclasses)
Expand All @@ -83,6 +84,8 @@ conformance/ Conformance test suite (optional dependency: pytest)
- `ChecksumAlgorithm` values may arrive as `SHA_256` (underscore) or `SHA-256` (hyphen) from servers — the `@field_validator` in `Checksum` normalizes both
- BLAKE3 is in the enum for spec completeness but NOT supported at runtime (not in stdlib `hashlib`) — raises `TeaChecksumError`
- `Identifier.id_type` is typed as `str` (not `IdentifierType` enum) so unknown types from future spec versions pass through
- **Behaviour follows the negotiated version, never response-shape sniffing.** `TeaClient` resolves `spec_version` once (explicit argument, then the base URL's `/v<semver>` suffix, then `TEA_SPEC_VERSION` = 1.0.0) and exposes `is_legacy`. Pagination parameters, page-size limits, the artifact metadata path (`/artifact/{uuid}` on 0.4.0, `/latest` on 1.x), artifact versions, discovery by PURL and server-hosted downloads all branch on it. Never infer the version from what a response looks like
- **0.4.0 shims run only under the validation context `{"tea_legacy": True}`.** `_validation._validate(..., legacy=client.is_legacy)` supplies it; no context means 1.0.0 rules. Fields required by both versions are non-Optional model types; fields only 1.0.0 requires stay `X | None` and are rejected when missing outside the legacy context (`_v1_required` / `_v1_problems()` on `_TeaModel`). Building an MD5 `Checksum` by hand therefore needs `Checksum.model_validate({...}, context={"tea_legacy": True})`
- CGNAT range (100.64.0.0/10, RFC 6598) is checked separately in SSRF protection because `ipaddress.is_private` misses it on Python 3.11+

## TEA Spec Reference
Expand All @@ -93,7 +96,7 @@ The TEA spec repo should be cloned to `/tmp/transparency-exchange-api/` for cros
git clone https://github.com/CycloneDX/transparency-exchange-api /tmp/transparency-exchange-api
```

Key spec files: `spec/openapi.yaml`, `discovery/readme.md`, `auth/readme.md`
Key spec files: `spec/openapi.yaml`, `discovery/readme.md`, `auth/readme.md`. The 0.4.0 baseline is `git -C /tmp/transparency-exchange-api show e0c986b:spec/openapi.yaml`.

## Design Docs

Expand Down
Loading
Loading