chtypes 2.0.0.dev8__tar.gz → 2.0.0.dev10__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.
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/CHANGELOG.md +6 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/PKG-INFO +1 -1
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/pyproject.toml +1 -1
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/src/chtypes/__init__.py +8 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/src/chtypes/__main__.py +31 -2
- chtypes-2.0.0.dev10/src/chtypes/_advisory.py +38 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/src/chtypes/_codes.py +2 -1
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/src/chtypes/_decode.py +3 -2
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/src/chtypes/_input.py +37 -2
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/src/chtypes/_ocifetch/__init__.py +5 -1
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/src/chtypes/_ocifetch/_channel.py +6 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/src/chtypes/_ocifetch/_constants.py +6 -1
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/src/chtypes/_ocifetch/_ensure.py +289 -89
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/src/chtypes/_ocifetch/_errors.py +23 -1
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/src/chtypes/_ocifetch/_http.py +96 -15
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/src/chtypes/_ocifetch/_referrers.py +123 -2
- chtypes-2.0.0.dev10/src/chtypes/_ocifetch/_retired.py +182 -0
- chtypes-2.0.0.dev10/src/chtypes/_ocifetch/_revocation.py +642 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/src/chtypes/errors.py +26 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/src/chtypes/library.py +1 -1
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/src/chtypes/registry.py +45 -3
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/api/test_decode.py +3 -2
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/api/test_input.py +38 -0
- chtypes-2.0.0.dev10/tests/cli/test_revocation_cli.py +62 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/ocifetch/test_conformance.py +47 -5
- chtypes-2.0.0.dev10/tests/ocifetch/test_revocation.py +334 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/uv.lock +1 -1
- chtypes-2.0.0.dev8/src/chtypes/_ocifetch/_retired.py +0 -77
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/.gitignore +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/LICENSE +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/README.md +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/src/chtypes/_abi2/__init__.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/src/chtypes/_abi2/_decls.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/src/chtypes/_abi2/_errmap.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/src/chtypes/_abi2/_errors.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/src/chtypes/_abi2/_loader.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/src/chtypes/_abi2/_vocab.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/src/chtypes/_ed25519.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/src/chtypes/_guard.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/src/chtypes/_ocifetch/_dsse.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/src/chtypes/_ocifetch/_faults.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/src/chtypes/_ocifetch/_goldens.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/src/chtypes/_ocifetch/_hold.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/src/chtypes/_ocifetch/_layout.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/src/chtypes/_ocifetch/_lock.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/src/chtypes/_ocifetch/_oci.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/src/chtypes/_ocifetch/_prune.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/src/chtypes/_ocifetch/_resolve.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/src/chtypes/_ocifetch/_unpack.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/src/chtypes/_setup.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/src/chtypes/py.typed +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/src/chtypes/results.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/abi2/__init__.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/abi2/_stub_tree.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/abi2/conftest.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/abi2/test_conformance.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/abi2/test_dev_fingerprint.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/abi2/test_goldens_runner_stub.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/abi2/test_loader.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/abi2/test_public_library.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/abi2/test_public_open.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/abi2/test_public_registry.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/abi2/test_public_server.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/abi2/test_public_setup_cases.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/abi2/test_reader_rules.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/abi2/test_registry_fetch.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/abi2/test_registry_flight.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/api/__init__.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/api/test_trusted_keys.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/api/test_vocab.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/cli/__init__.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/cli/conftest.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/cli/test_devchannel.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/cli/test_main.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/cli/test_resolve_prune.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/conftest.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/goldens_v1/__init__.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/goldens_v1/data/stub-goldens.json +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/goldens_v1/test_runner.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/ocifetch/__init__.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/ocifetch/_bundle_support.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/ocifetch/_registry_support.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/ocifetch/_sign_support.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/ocifetch/conftest.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/ocifetch/test_cache_roots.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/ocifetch/test_cache_strict.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/ocifetch/test_devchannel.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/ocifetch/test_dsse.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/ocifetch/test_ensure.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/ocifetch/test_goldens.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/ocifetch/test_http.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/ocifetch/test_layout.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/ocifetch/test_lock.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/ocifetch/test_oci.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/ocifetch/test_prodv2_smoke.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/ocifetch/test_prodv2channel.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/ocifetch/test_retired.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/ocifetch/test_seam_gate.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/ocifetch/test_unpack.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/test_abi_identity.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/test_cache_roots_api.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/test_devchannel_registry.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/test_ed25519.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/test_examples_tour.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/test_fetch_options.py +0 -0
- {chtypes-2.0.0.dev8 → chtypes-2.0.0.dev10}/tests/test_support_v2_gen.py +0 -0
|
@@ -20,6 +20,8 @@ The four bindings in this repository are released together and give one answer,
|
|
|
20
20
|
- The v2 spec's decline catalog has 49 entries, and it publishes the settings values a server refuses outright (`spec/abi-v2/where-settings/documented-pathological.tsv`).
|
|
21
21
|
- The v2 spec's decline catalog follows the producer's batch 11: its `server-profile-setting` entry now covers every settings layer the schema compiles under (the id is unchanged), the evaluation-time setting entries name only the filter's own settings, and its `stack-depth` entry lists `chs_schema_describe`.
|
|
22
22
|
- The v2 spec publishes the documented platform differences (`spec/abi-v2/documented-platform/`), and its decline catalog adds `setting-format-pair`, `string-reading` and `equals-fold` (52 reasons).
|
|
23
|
+
- The v2 spec's decline catalog adds `server-dependent-function` (a column's DEFAULT, MATERIALIZED, ALIAS or EPHEMERAL expression, or a CHECK, that calls a function whose resolver needs a server is declined at `chs_schema_create`), `function-name-as-lambda` (a bare function name written where a higher-order function takes a lambda, such as `arrayExists(isNull, arr)`, is declined at `chs_filter_create` on 26.7, 26.8 and 26.9) and `skipped-key-in-squashed-block` (a batch whose answer depends on how the INSERT's squashing step groups its chunks is unsupported), and `call-setting-session-timezone` names only a per-call `session_timezone` the call cannot attach (55 reasons).
|
|
24
|
+
- Build-level revocation for generation 2 (#625; `docs/guides/fetch-v1.md`, "Build-level revocation"). A build its signed revocation refuses is the new code `CHTYPES_ARTIFACT_REVOKED`, exit status 11 (`CODE_ARTIFACT_REVOKED`, `ArtifactRevokedError`), whose message names the advisory and whose `.revocation` carries it for programs (`Revocation`, `Advisory`; an advisory's `class` is `class_`). An install reads a revocation from the trust listing it already makes and refuses before the layer; `--frozen` makes one listing, warm or cold, so a warm frozen fetch makes 1 request instead of 0 (`--frozen --offline` stays at 0); a registry open, `preload` and a fetch that finds the build installed check it at most once per build per process, one attempt per base, no backoff and one 5 s deadline (`check_installed`); `--lock` refuses a revoked build on every platform it pins. A verified revocation is stored in the build's cache entry (`revocation.json`) and refuses it from then on with no request, offline too; `chtypes verify` reports it and exits 11. A revocation that does not verify is ignored with one warning; a status that cannot be read is one warning, or with `FetchOptions(require_revocation_check=True)` (`--require-revocation-check`) the transport's error. `FetchOptions(allow_revoked=True)` (`--allow-revoked`) uses a revoked build for one call, with a warning, never for a lock or an update. A `410` whose body's `errors[0].detail.revoked` is an object is `CHTYPES_ARTIFACT_REVOKED`; every other `410` stays `CHTYPES_SOURCE_RETIRED`. The v1 contract is unchanged.
|
|
23
25
|
- A `410 Gone` is a retired repository, and permanent (#571). Any request the fetch layer makes (a tag, an alias, a digest, a blob, `tags/list`, the referrers API or its fallback tag, after a redirect too) that answers `410` is now the new code `CHTYPES_SOURCE_RETIRED` (exit status 10), never retried and never sent to the next base, nor from the dev channel's alias to its tag. It was `CHTYPES_SOURCE_UNREACHABLE` (exit 3) with the registry's message dropped, and Go, Python and Rust moved on to the next base, so a mirror's 404 could hide the retirement. The error names the URL that answered and carries the registry's own message, with control and bidirectional-formatting characters removed and cut to 256 code points: Go `CodeSourceRetired` (`errors.Is(err, ErrSourceRetired)`), Python and TypeScript `SourceRetiredError`, Rust `Error::SourceRetired`. The rule is `docs/guides/fetch-v1.md` section 2, "A retired repository", and the `retired-410-*` and `list-tags-retired-410` conformance cases and one shared message table (`tests/fixtures/retired-message/cases.json`) hold all four bindings to it.
|
|
24
26
|
- A 2.0.0-dev SDK prefers the newest build of its own fingerprint (#511). A version request resolves `<tag>--fp-<fingerprint>` first, the alias the staging dev registry serves beside every dev tag, with the binding's own generated ABI fingerprint, and the tag itself only when every base answers the alias 404; any other failure of the alias request is that failure, with no fallback, and an alias whose index lacks the platform is `CHTYPES_ARTIFACT_UNPUBLISHED`. The dev channel's cache lookups (the installed-build lookup a registry open makes first, the offline answer, and the monotonic rule of a fetch) now see only records whose signed `abi_fingerprint` is the SDK's own, so a newer build of another fingerprint, installed by another dev SDK in the shared cache, is never returned or kept over this one's own, and is never removed or rewritten. So a dev build of a newer fingerprint no longer strands an older dev SDK until its consumers upgrade. Trust is unchanged (the signed predicate and the load checks decide), a listing never shows an alias, nothing records one, `list` and `where` are unchanged, and there is no option: both steps are the dev channel's alone, and the v1 contract and every production channel never take them. The rule is `docs/guides/fetch-v1.md` section 3, and the `dev-alias-*`, `resolve-installed-dev-alias-*` and `list-tags-dev-alias` conformance cases hold all four bindings to it.
|
|
25
27
|
|
|
@@ -65,6 +67,7 @@ The four bindings in this repository are released together and give one answer,
|
|
|
65
67
|
- **Enforcement gate lifted** for `26.7` on `darwin-arm64`.
|
|
66
68
|
- **Enforcement gate lifted** for `26.8` on `darwin-arm64`.
|
|
67
69
|
- **Enforcement gate lifted** for `26.9` on `darwin-arm64`.
|
|
70
|
+
- `docs/limitations.md` is re-checked against artifact build `20261012.142240` (v2-dev.13), the library half measured on that build: functions that need a server, in a column expression, and a body the server may read serially or in parallel under a non-zero error budget are listed under "Declined by design"; the known divergence for a column expression that calls a function needing a server is retired, since that build declines those schemas (on 26.8 and 26.9 the `AI*` and `ai*` functions compile, as the page says); and the `lossy` raw-NUL TSV gap is marked fixed on that build and still present in the 1.0 libraries. `docs/guides/filters.md` gains a section on subcolumn filters and a JSON value typed by its batch.
|
|
68
71
|
|
|
69
72
|
### Removed
|
|
70
73
|
|
|
@@ -72,6 +75,9 @@ The four bindings in this repository are released together and give one answer,
|
|
|
72
75
|
|
|
73
76
|
### Fixed
|
|
74
77
|
|
|
78
|
+
- A response body cut off part way (shorter than its Content-Length, a reset, a TLS close) is retried like a 5xx and, once the retry table is spent, is `CHTYPES_SOURCE_UNREACHABLE`; only an oversize body is `CHTYPES_ARTIFACT_CORRUPT`, and it is never retried (#641). Python took a body that ended before its Content-Length as the whole body and failed its digest check as corrupt without a retry; now a short body, or `http.client.IncompleteRead`, is retried and ends as unreachable. The rule is `docs/guides/fetch-v1.md` section 7, and the new `body-cut-off-then-ok` and `body-cut-off-exhausted` conformance cases hold every binding to it.
|
|
79
|
+
- A chunked response body cut off before its terminating zero-length chunk is held to the same rule (#644): the new `body-cut-off-chunked-then-ok` and `body-cut-off-chunked-exhausted` conformance cases cut one on the layer blob GET, and this binding already retried it and ended as `CHTYPES_SOURCE_UNREACHABLE` once the retry table was spent. No behavior change; the case now holds it.
|
|
80
|
+
- A settings or query-parameter key or value that is not valid UTF-8 is refused as a usage error; it was silently replaced with U+FFFD, so the library saw a different value. A string holding a lone surrogate (what `surrogateescape` makes of undecodable bytes) was sent as a `\udXXX` escape; the same inputs are refused now, for the per-call zone, the setup defaults and the server profile too. Valid UTF-8 serializes as before.
|
|
75
81
|
- A declined export's payload is absent, as documented: it was an empty payload when the library returned a zero-length buffer for a declined export. `BatchResult.payload` and `spans` are `None` for a declined export. Whether an export was declined is decided from the document's `export_declined`, never from the buffer's length.
|
|
76
82
|
- The layout's `index.json` never gains a second entry for one manifest (#487): concurrent installs of one build each appended an entry (7 or 8 entries for the same digest after 8 concurrent installs, measured on 1.0.2). The merge now deduplicates by digest, as Go and TypeScript do.
|
|
77
83
|
- `--frozen` with a warm, verified cache makes zero requests, and `--frozen --offline` verifies the installed build against the lock with zero network (#414). An installed build whose `verified.json` is for the platform and records exactly the lock's pinned manifest, layer and bundle digests is used as it is. `--frozen --offline` is `CHTYPES_ARTIFACT_MISSING` when the pinned build is not installed, and `CHTYPES_ARTIFACT_PINNED` when there is no lock entry or the installed record does not match the pin; `--offline` without `--frozen` is unchanged. The `frozen-warm-cache-zero-requests`, `frozen-offline-lock-match`, `frozen-offline-lock-mismatch` and `frozen-offline-lock-not-installed` conformance cases hold all four bindings to it, under both corpora. The Python frozen path also compares the record's layer and bundle digests to the pin, and looks in the system directories too.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: chtypes
|
|
3
|
-
Version: 2.0.0.
|
|
3
|
+
Version: 2.0.0.dev10
|
|
4
4
|
Summary: Python binding for the chtypes artifacts: ClickHouse's own type system, per version, behind the frozen chs_* C ABI
|
|
5
5
|
Project-URL: Homepage, https://github.com/wave-rf/chtypes
|
|
6
6
|
Project-URL: Repository, https://github.com/wave-rf/chtypes
|
|
@@ -69,6 +69,7 @@ from .errors import (
|
|
|
69
69
|
CODE_ARTIFACT_INCOMPATIBLE,
|
|
70
70
|
CODE_ARTIFACT_MISSING,
|
|
71
71
|
CODE_ARTIFACT_PINNED,
|
|
72
|
+
CODE_ARTIFACT_REVOKED,
|
|
72
73
|
CODE_ARTIFACT_UNPUBLISHED,
|
|
73
74
|
CODE_ARTIFACT_UNTRUSTED,
|
|
74
75
|
CODE_CACHE_UNUSABLE,
|
|
@@ -77,17 +78,20 @@ from .errors import (
|
|
|
77
78
|
CODE_SOURCE_RETIRED,
|
|
78
79
|
CODE_SOURCE_UNAUTHORIZED,
|
|
79
80
|
CODE_SOURCE_UNREACHABLE,
|
|
81
|
+
Advisory,
|
|
80
82
|
ArtifactCorruptError,
|
|
81
83
|
ArtifactError,
|
|
82
84
|
ArtifactIncompatibleError,
|
|
83
85
|
ArtifactMissingError,
|
|
84
86
|
ArtifactPinnedError,
|
|
87
|
+
ArtifactRevokedError,
|
|
85
88
|
ArtifactUnpublishedError,
|
|
86
89
|
ArtifactUntrustedError,
|
|
87
90
|
CacheUnusableError,
|
|
88
91
|
CallError,
|
|
89
92
|
ChtypesError,
|
|
90
93
|
InternalError,
|
|
94
|
+
Revocation,
|
|
91
95
|
SchemaError,
|
|
92
96
|
SourceForbiddenError,
|
|
93
97
|
SourceIncompatibleError,
|
|
@@ -130,6 +134,7 @@ __all__ = [
|
|
|
130
134
|
"CODE_ARTIFACT_INCOMPATIBLE",
|
|
131
135
|
"CODE_ARTIFACT_MISSING",
|
|
132
136
|
"CODE_ARTIFACT_PINNED",
|
|
137
|
+
"CODE_ARTIFACT_REVOKED",
|
|
133
138
|
"CODE_ARTIFACT_UNPUBLISHED",
|
|
134
139
|
"CODE_ARTIFACT_UNTRUSTED",
|
|
135
140
|
"CODE_CACHE_UNUSABLE",
|
|
@@ -139,11 +144,13 @@ __all__ = [
|
|
|
139
144
|
"CODE_SOURCE_UNAUTHORIZED",
|
|
140
145
|
"CODE_SOURCE_UNREACHABLE",
|
|
141
146
|
"EXPORT_NONE",
|
|
147
|
+
"Advisory",
|
|
142
148
|
"ArtifactCorruptError",
|
|
143
149
|
"ArtifactError",
|
|
144
150
|
"ArtifactIncompatibleError",
|
|
145
151
|
"ArtifactMissingError",
|
|
146
152
|
"ArtifactPinnedError",
|
|
153
|
+
"ArtifactRevokedError",
|
|
147
154
|
"ArtifactUnpublishedError",
|
|
148
155
|
"ArtifactUntrustedError",
|
|
149
156
|
"AtMergeEntry",
|
|
@@ -183,6 +190,7 @@ __all__ = [
|
|
|
183
190
|
"Reason",
|
|
184
191
|
"Registry",
|
|
185
192
|
"Resolved",
|
|
193
|
+
"Revocation",
|
|
186
194
|
"RowResult",
|
|
187
195
|
"Schema",
|
|
188
196
|
"SchemaDescription",
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
chtypes fetch <spelling>... | --all [--platform <os-arch>] [--cache <dir>] [--lock <file>]
|
|
5
5
|
[--frozen] [--offline] [--update] [--strict]
|
|
6
|
+
[--allow-revoked] [--require-revocation-check]
|
|
6
7
|
chtypes verify [--cache <dir>] [--strict] re-verify every installed build
|
|
7
8
|
chtypes list [--cache <dir>] [--offline] [--strict]
|
|
8
9
|
chtypes where [--cache <dir>] [--strict] [--all] the cache root; --all: every search dir
|
|
@@ -37,7 +38,7 @@ from collections.abc import Sequence
|
|
|
37
38
|
|
|
38
39
|
from . import __version__ as chtypes_version
|
|
39
40
|
from . import _ocifetch as fetch_layer
|
|
40
|
-
from ._codes import CODE_ARTIFACT_CORRUPT
|
|
41
|
+
from ._codes import CODE_ARTIFACT_CORRUPT, CODE_ARTIFACT_REVOKED
|
|
41
42
|
from ._ocifetch import _channel
|
|
42
43
|
from ._ocifetch import _constants as C
|
|
43
44
|
from ._ocifetch._ensure import _translate_transport_error, detect_host_platform, missing_notes
|
|
@@ -148,6 +149,19 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
148
149
|
"(or set CHTYPES_OFFLINE=1)"
|
|
149
150
|
),
|
|
150
151
|
)
|
|
152
|
+
fetch.add_argument(
|
|
153
|
+
"--allow-revoked",
|
|
154
|
+
action="store_true",
|
|
155
|
+
help=(
|
|
156
|
+
"use a build a verified revocation refuses, with one warning "
|
|
157
|
+
"(this call only; never --lock or --update)"
|
|
158
|
+
),
|
|
159
|
+
)
|
|
160
|
+
fetch.add_argument(
|
|
161
|
+
"--require-revocation-check",
|
|
162
|
+
action="store_true",
|
|
163
|
+
help="a revocation status that cannot be read is the transport's error, not a warning",
|
|
164
|
+
)
|
|
151
165
|
|
|
152
166
|
verify = sub.add_parser(
|
|
153
167
|
"verify",
|
|
@@ -249,6 +263,8 @@ def _options(args: argparse.Namespace, *, platform: str | None = None):
|
|
|
249
263
|
lock_path=lock_path,
|
|
250
264
|
lock_write=lock_write,
|
|
251
265
|
update=getattr(args, "update", False),
|
|
266
|
+
allow_revoked=getattr(args, "allow_revoked", False),
|
|
267
|
+
require_revocation_check=getattr(args, "require_revocation_check", False),
|
|
252
268
|
)
|
|
253
269
|
return fetch._to_options(platform)
|
|
254
270
|
|
|
@@ -348,10 +364,23 @@ def _cmd_verify(args: argparse.Namespace) -> int:
|
|
|
348
364
|
return EXIT_OK
|
|
349
365
|
_say_notes(options)
|
|
350
366
|
failed = 0
|
|
367
|
+
revoked = 0
|
|
351
368
|
for r in results:
|
|
352
|
-
if not
|
|
369
|
+
if r.revocation is not None:
|
|
370
|
+
# A build a stored revocation refuses (docs/guides/fetch-v1.md,
|
|
371
|
+
# "Build-level revocation"); it is reported whatever its bytes say.
|
|
372
|
+
revoked += 1
|
|
373
|
+
_say(f"REVOKED {r.version} {r.platform} {r.dir}: {r.detail}")
|
|
374
|
+
elif not r.ok:
|
|
353
375
|
failed += 1
|
|
354
376
|
_say(f"chtypes: FAILED {r.version} {r.platform} {r.dir}: {r.detail}")
|
|
377
|
+
if revoked:
|
|
378
|
+
# Exit 11 wins over a mismatch's 1: a revocation is the more specific finding.
|
|
379
|
+
_say(
|
|
380
|
+
f"chtypes: {revoked} of {len(results)} installed build(s) are revoked "
|
|
381
|
+
f"[{CODE_ARTIFACT_REVOKED}]"
|
|
382
|
+
)
|
|
383
|
+
return C.ERROR_EXIT_CODES[CODE_ARTIFACT_REVOKED]
|
|
355
384
|
if failed:
|
|
356
385
|
_say(f"chtypes: {failed} of {len(results)} installed build(s) FAILED verification")
|
|
357
386
|
return C.ERROR_EXIT_CODES[CODE_ARTIFACT_CORRUPT]
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""What a `CHTYPES_ARTIFACT_REVOKED` error carries for programs
|
|
2
|
+
(docs/guides/fetch-v1.md, "Build-level revocation"; public issue #625).
|
|
3
|
+
|
|
4
|
+
A leaf module, so the fetch layer's errors and the public ones share the two
|
|
5
|
+
types without importing each other."""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
|
|
11
|
+
__all__ = ["Advisory", "Revocation"]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True)
|
|
15
|
+
class Advisory:
|
|
16
|
+
"""One advisory a revocation names, every string made safe to print. A
|
|
17
|
+
signed revocation's carries its `revoked_at`; a 410's carries the `build` it
|
|
18
|
+
names, newest first. A field the source did not send is "". `class_` is the
|
|
19
|
+
advisory's `class` (a keyword in Python)."""
|
|
20
|
+
|
|
21
|
+
id: str = ""
|
|
22
|
+
url: str = ""
|
|
23
|
+
class_: str = ""
|
|
24
|
+
summary: str = ""
|
|
25
|
+
build: str = ""
|
|
26
|
+
superseded_by: str = ""
|
|
27
|
+
revoked_at: str = ""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass(frozen=True)
|
|
31
|
+
class Revocation:
|
|
32
|
+
"""`signed` is true for a verified, signed revocation of `manifest`, whose
|
|
33
|
+
one advisory it is, and false for a 410's body, which is unsigned, names no
|
|
34
|
+
manifest (`manifest` is "") and lists one advisory per revoked build."""
|
|
35
|
+
|
|
36
|
+
signed: bool = False
|
|
37
|
+
manifest: str = ""
|
|
38
|
+
advisories: tuple[Advisory, ...] = ()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Code generated by scripts/fetch-v1/gen-constants.py from
|
|
2
2
|
# spec/fetch-v1/constants.json. DO NOT EDIT.
|
|
3
|
-
# Source sha256:
|
|
3
|
+
# Source sha256: 5aa3d14a5d8b60399040a8242f003135fa5f6c2c402de7d4e2db4c768a3366ae
|
|
4
4
|
"""The shared error codes (docs/guides/fetch-v1.md §8), one per entry of the
|
|
5
5
|
fetch constants' `errors`, in their order. `chtypes.errors` re-exports them
|
|
6
6
|
(docs/reference/bindings-v1.md §4, the codes)."""
|
|
@@ -21,3 +21,4 @@ CODE_SOURCE_INCOMPATIBLE: Final[str] = "CHTYPES_SOURCE_INCOMPATIBLE"
|
|
|
21
21
|
CODE_ARTIFACT_INCOMPATIBLE: Final[str] = "CHTYPES_ARTIFACT_INCOMPATIBLE"
|
|
22
22
|
CODE_CACHE_UNUSABLE: Final[str] = "CHTYPES_CACHE_UNUSABLE"
|
|
23
23
|
CODE_SOURCE_RETIRED: Final[str] = "CHTYPES_SOURCE_RETIRED"
|
|
24
|
+
CODE_ARTIFACT_REVOKED: Final[str] = "CHTYPES_ARTIFACT_REVOKED"
|
|
@@ -431,8 +431,9 @@ def decode_batch(raw: bytes, export: bytes | None) -> BatchResult:
|
|
|
431
431
|
export_declined = _bytes(obj, "export_declined", where)
|
|
432
432
|
if export_declined:
|
|
433
433
|
# A declined export has no payload and no spans, whatever buffer the call
|
|
434
|
-
# returned: the library
|
|
435
|
-
#
|
|
434
|
+
# returned: the library returns a NULL out_export for it (builds up to
|
|
435
|
+
# 20261011.105019 returned a zero-length buffer), and the document's
|
|
436
|
+
# export_declined decides, never the buffer.
|
|
436
437
|
export, spans = None, None
|
|
437
438
|
return BatchResult(
|
|
438
439
|
outcome=Outcome.of(_text(obj, "outcome", where)),
|
|
@@ -45,6 +45,31 @@ def body_bytes(value: bytes, what: str = "body") -> bytes:
|
|
|
45
45
|
raise TypeError(f"{what} must be bytes, not {type(value).__name__}")
|
|
46
46
|
|
|
47
47
|
|
|
48
|
+
_NON_UTF8_HINT = (
|
|
49
|
+
"this ABI generation cannot carry a non-UTF-8 value in a JSON input; "
|
|
50
|
+
"write the bytes as unhex('<hex>') in the expression instead"
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _is_utf8(text: str) -> bool:
|
|
55
|
+
"""False for a `str` that cannot be UTF-8 encoded (a lone surrogate, as
|
|
56
|
+
`surrogateescape` makes of undecodable bytes)."""
|
|
57
|
+
try:
|
|
58
|
+
text.encode("utf-8")
|
|
59
|
+
except UnicodeEncodeError:
|
|
60
|
+
return False
|
|
61
|
+
return True
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _refuse_non_utf8(what: str, part: str, key: str) -> Exception:
|
|
65
|
+
# The key is shown escaped (ASCII only), never as the raw text.
|
|
66
|
+
return misuse(
|
|
67
|
+
f"{what}: the key {json.dumps(key)} is not valid UTF-8, and {_NON_UTF8_HINT}"
|
|
68
|
+
if part == "key"
|
|
69
|
+
else f"{what}: the value for key {json.dumps(key)} is not valid UTF-8, and {_NON_UTF8_HINT}"
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
48
73
|
def _string_map(mapping: Mapping[str, str], what: str) -> dict[str, str]:
|
|
49
74
|
out: dict[str, str] = {}
|
|
50
75
|
for key, value in mapping.items():
|
|
@@ -53,6 +78,12 @@ def _string_map(mapping: Mapping[str, str], what: str) -> dict[str, str]:
|
|
|
53
78
|
f"{what} maps strings to strings only; got {type(key).__name__} -> "
|
|
54
79
|
f"{type(value).__name__} for {key!r} (a value is never rewritten)"
|
|
55
80
|
)
|
|
81
|
+
# json.dumps would write a lone surrogate as a \udXXX escape, a value
|
|
82
|
+
# the library does not read as the bytes the caller meant: refuse.
|
|
83
|
+
if not _is_utf8(key):
|
|
84
|
+
raise _refuse_non_utf8(what, "key", key)
|
|
85
|
+
if not _is_utf8(value):
|
|
86
|
+
raise _refuse_non_utf8(what, "value", key)
|
|
56
87
|
out[key] = value
|
|
57
88
|
return out
|
|
58
89
|
|
|
@@ -75,6 +106,8 @@ def settings_json(
|
|
|
75
106
|
if session_timezone is not None:
|
|
76
107
|
if not isinstance(session_timezone, str):
|
|
77
108
|
raise TypeError(f"session_timezone must be str, not {type(session_timezone).__name__}")
|
|
109
|
+
if not _is_utf8(session_timezone):
|
|
110
|
+
raise misuse(f"the zone: the value is not valid UTF-8, and {_NON_UTF8_HINT}")
|
|
78
111
|
if _ZONE_KEY in merged:
|
|
79
112
|
raise misuse(
|
|
80
113
|
f"the {_ZONE_KEY} option and a {_ZONE_KEY} settings key were both given; "
|
|
@@ -141,10 +174,12 @@ def server_profile_json(profile: ServerProfile) -> bytes:
|
|
|
141
174
|
if profile.timezone is not None:
|
|
142
175
|
if not isinstance(profile.timezone, str):
|
|
143
176
|
raise TypeError(f"timezone must be str, not {type(profile.timezone).__name__}")
|
|
177
|
+
if not _is_utf8(profile.timezone):
|
|
178
|
+
raise misuse(f"server profile: the timezone is not valid UTF-8, and {_NON_UTF8_HINT}")
|
|
144
179
|
if profile.timezone != "":
|
|
145
180
|
doc["timezone"] = profile.timezone
|
|
146
181
|
if profile.settings is not None:
|
|
147
|
-
doc["settings"] = _string_map(profile.settings, "settings")
|
|
182
|
+
doc["settings"] = _string_map(profile.settings, "server profile settings")
|
|
148
183
|
if profile.macros is not None:
|
|
149
|
-
doc["macros"] = _string_map(profile.macros, "macros")
|
|
184
|
+
doc["macros"] = _string_map(profile.macros, "server profile macros")
|
|
150
185
|
return _dump(doc)
|
|
@@ -4,7 +4,7 @@ Not part of this package's public API: every name here is subject to change
|
|
|
4
4
|
without notice until the v1 switch. `_constants` is generated; the fetch
|
|
5
5
|
logic lives in sibling modules. The seam (docs/guides/fetch-v1.md "The seam")
|
|
6
6
|
is the only surface anything outside this package may use: `ensure`,
|
|
7
|
-
`resolve_installed`, `list_installed`, `verify_installed` and
|
|
7
|
+
`resolve_installed`, `check_installed`, `list_installed`, `verify_installed` and
|
|
8
8
|
`fetch_signed`, plus the `Request`/`Options`/`Resolved`/
|
|
9
9
|
`VerifyResult` shapes they take and return; and beside it the CLI's `resolve`
|
|
10
10
|
and `prune` and the registry's in-use `hold` (§1, "In use"). Every other module here
|
|
@@ -25,9 +25,11 @@ from chtypes._ocifetch._ensure import (
|
|
|
25
25
|
Request,
|
|
26
26
|
Resolved,
|
|
27
27
|
VerifyResult,
|
|
28
|
+
check_installed,
|
|
28
29
|
ensure,
|
|
29
30
|
fetch_signed,
|
|
30
31
|
list_installed,
|
|
32
|
+
reset_revocation_memo_for_tests,
|
|
31
33
|
resolve_installed,
|
|
32
34
|
verify_installed,
|
|
33
35
|
)
|
|
@@ -43,11 +45,13 @@ __all__ = [
|
|
|
43
45
|
"Resolved",
|
|
44
46
|
"Superseded",
|
|
45
47
|
"VerifyResult",
|
|
48
|
+
"check_installed",
|
|
46
49
|
"ensure",
|
|
47
50
|
"fetch_signed",
|
|
48
51
|
"hold",
|
|
49
52
|
"list_installed",
|
|
50
53
|
"prune",
|
|
54
|
+
"reset_revocation_memo_for_tests",
|
|
51
55
|
"resolve",
|
|
52
56
|
"resolve_each",
|
|
53
57
|
"resolve_installed",
|
|
@@ -133,6 +133,10 @@ class Channel:
|
|
|
133
133
|
# lookups see only records signed with it. "" does neither (the v1 contract
|
|
134
134
|
# and every production channel).
|
|
135
135
|
own_fingerprint: str = ""
|
|
136
|
+
# Build-level revocation (_revocation.py; docs/guides/fetch-v1.md, "Build-level
|
|
137
|
+
# revocation"): generation 2, the dev channel and the production one. The v1
|
|
138
|
+
# contract has none of it.
|
|
139
|
+
revocation: bool = False
|
|
136
140
|
|
|
137
141
|
|
|
138
142
|
# What a 2.0.0-dev SDK speaks, and what every process speaks unless a test
|
|
@@ -149,6 +153,7 @@ DEV_CHANNEL: Final = Channel(
|
|
|
149
153
|
overridable=False,
|
|
150
154
|
pinnable=False,
|
|
151
155
|
own_fingerprint=CHS_ABI_FINGERPRINT.removeprefix("sha256:"),
|
|
156
|
+
revocation=True,
|
|
152
157
|
)
|
|
153
158
|
|
|
154
159
|
# The v1 contract the fetch-v1 conformance cases specify, from the generated
|
|
@@ -183,6 +188,7 @@ PROD_V2_CHANNEL: Final = Channel(
|
|
|
183
188
|
keys=C.RELEASE_KEYS,
|
|
184
189
|
overridable=True,
|
|
185
190
|
pinnable=True,
|
|
191
|
+
revocation=True,
|
|
186
192
|
)
|
|
187
193
|
|
|
188
194
|
_lock = threading.Lock()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Code generated by scripts/fetch-v1/gen-constants.py from
|
|
2
2
|
# spec/fetch-v1/constants.json. DO NOT EDIT.
|
|
3
|
-
# Source sha256:
|
|
3
|
+
# Source sha256: 5aa3d14a5d8b60399040a8242f003135fa5f6c2c402de7d4e2db4c768a3366ae
|
|
4
4
|
"""The v1 fetch constants, generated: see docs/guides/fetch-v1.md."""
|
|
5
5
|
|
|
6
6
|
from __future__ import annotations
|
|
@@ -46,6 +46,7 @@ ARTIFACT_TYPE: Final[str] = "application/vnd.wavehouse.chtypes.artifact.v1"
|
|
|
46
46
|
GOLDENS_ARTIFACT_TYPE: Final[str] = "application/vnd.wavehouse.chtypes.goldens.v1"
|
|
47
47
|
FIXTURES_ARTIFACT_TYPE: Final[str] = "application/vnd.wavehouse.chtypes.sdk-fetch-fixtures.v1"
|
|
48
48
|
CHANNEL_ARTIFACT_TYPE: Final[str] = "application/vnd.wavehouse.chtypes.channel.v1"
|
|
49
|
+
REVOCATION_ARTIFACT_TYPE: Final[str] = "application/vnd.wavehouse.chtypes.revocation.v1"
|
|
49
50
|
|
|
50
51
|
DSSE_PAYLOAD_TYPE: Final[str] = "application/vnd.in-toto+json"
|
|
51
52
|
DSSE_MAX_SIGNATURES: Final[int] = 4
|
|
@@ -56,6 +57,7 @@ PREDICATE_TYPE_ARTIFACT: Final[str] = "https://artifacts.wavehouse.dev/spec/arti
|
|
|
56
57
|
PREDICATE_TYPE_GOLDENS: Final[str] = "https://artifacts.wavehouse.dev/spec/goldens/v1"
|
|
57
58
|
PREDICATE_TYPE_FIXTURES: Final[str] = "https://artifacts.wavehouse.dev/spec/sdk-fetch-fixtures/v1"
|
|
58
59
|
PREDICATE_TYPE_CHANNEL: Final[str] = "https://artifacts.wavehouse.dev/spec/channel/v1"
|
|
60
|
+
PREDICATE_TYPE_REVOCATION: Final[str] = "https://artifacts.wavehouse.dev/spec/revocation/v1"
|
|
59
61
|
|
|
60
62
|
KEYID_ALGORITHM: Final[str] = "sha256-first16hex"
|
|
61
63
|
RELEASE_KEYS: Final[tuple[dict[str, str], ...]] = (
|
|
@@ -87,6 +89,7 @@ RETIRED_STATUSES: Final[tuple[int, ...]] = (410,)
|
|
|
87
89
|
|
|
88
90
|
CONNECT_TIMEOUT_S: Final[float] = 30.0
|
|
89
91
|
IDLE_READ_TIMEOUT_S: Final[float] = 60.0
|
|
92
|
+
REVOCATION_CHECK_S: Final[float] = 5.0
|
|
90
93
|
|
|
91
94
|
MANIFEST_MAX_BYTES: Final[int] = 4194304
|
|
92
95
|
BUNDLE_MAX_BYTES: Final[int] = 1048576
|
|
@@ -109,6 +112,7 @@ PROD_V2_BASES: Final[tuple[str, ...]] = ("https://registry.wavehouse.dev/chtypes
|
|
|
109
112
|
PROD_V2_SYSTEM_DIRS: Final[tuple[str, ...]] = ("/usr/local/share/chtypes/v2", "/opt/chtypes/v2")
|
|
110
113
|
CACHE_UNPACKED_DIR: Final[str] = "unpacked/sha256"
|
|
111
114
|
CACHE_VERIFIED_RECORD: Final[str] = "verified.json"
|
|
115
|
+
CACHE_REVOCATION_RECORD: Final[str] = "revocation.json"
|
|
112
116
|
CACHE_ANNOTATION_PREFIX: Final[str] = "dev.wavehouse.chtypes."
|
|
113
117
|
|
|
114
118
|
LOCK_SCHEMA: Final[int] = 3
|
|
@@ -132,4 +136,5 @@ ERROR_EXIT_CODES: Final[dict[str, int]] = {
|
|
|
132
136
|
"CHTYPES_ARTIFACT_INCOMPATIBLE": 8,
|
|
133
137
|
"CHTYPES_CACHE_UNUSABLE": 9,
|
|
134
138
|
"CHTYPES_SOURCE_RETIRED": 10,
|
|
139
|
+
"CHTYPES_ARTIFACT_REVOKED": 11,
|
|
135
140
|
}
|