Skip to content

v2: resolve the parity gate's #499/#500 surface before the lock (codes generated in all four; one shape per concept) - #608

Merged
EricAndrechek merged 5 commits into
v2from
v2-surface-499-500
Oct 10, 2026
Merged

EricAndrechek merged 5 commits into
v2from
v2-surface-499-500

Conversation

@EricAndrechek

Copy link
Copy Markdown
Member

Summary

The parity gate (#436) carried each binding's one-binding conveniences and shape differences as allowlist entries citing #499, #500 and #487. 2.0.0 is the last window before the v2 lock for removing or renaming public binding API, so this PR resolves every one of them, following the ruled table. A convenience exists in every binding or in none (docs/reference/bindings-v1.md §1, principle 2). Every removed allowlist entry goes with its item, and no entry cites #499, #500 or #487 any more. Each binding's CHANGELOG has a migration note under ### Changed, marked Breaking, old → new.

Error codes, in all four, generated

  • scripts/fetch-v1/gen-constants.py generates the codes from spec/fetch-v1/constants.json into every binding, so --check covers them and no binding types a code by hand:
    • Go: ErrorCode plus Code…, as a type of the public package's own (go/chtypes/codes_gen.go, no methods), and ocifetch's own Code….
    • Python: CODE_*, in the private chtypes._codes.
    • TypeScript: CODE_* and the ErrorCode union.
    • Rust: chtypes::code::*, which Error::code() returns.
  • bindings-v1.md §4 gains a codes table, and the gate reads it.
  • A code's exit status is now the command's, never the library's. Go's ErrorCode.ExitCode() and .Sentinel() are removed, and so is TypeScript's ArtifactError.exitStatus. The CLIs keep the exit status internally.

Go

  • ArtifactError no longer exports Msg, Err, Request, Platform or Source. Use Error() and errors.Unwrap.
  • Every fetch error's message now names the request, the platform and the source it concerns, wherever its text did not already. A source URL's password is redacted. TestFetchErrorMessageCarriesRequestPlatformSource pins this, including the "layer: %v" case.
  • FetchOptions.ConnectTimeout and .IdleReadTimeout are removed.
  • Format.CHName() is renamed Format.ChName(), through the generator.

Python

Rust

  • Refusal.detail is removed. A one-sided finding is now in got, as the other three already did, and Refusal is #[non_exhaustive].
  • The fetch layer's own corruption now has an empty reason (it was "fetch"), as Go's has.
  • FetchOptions.platform is removed.
  • reason::is_lossy is renamed reason::lossy, through the generator.

TypeScript

  • ArtifactError now carries reason, path, want and got, as Go and Python do.
  • LoaderCorruptError and LoaderErrorFields are removed. The loader's corrupt refusal is an ArtifactCorruptError.
  • isChtypesError is removed. Since v2 fetch: zero-request frozen paths (#414), Go frozen re-download, TS lock platforms and error class, Python index duplicates (#487) #606, e instanceof ChtypesError is the same test.
  • retryAfterRefused, BatchOutcome and batchOutcomeKnown are removed.
  • FetchOptions is now an interface of its own, so the gate reads its fields; an allowlist entry covers them, like the other three bindings' entries.
    • trustedKeys takes hex keys.
    • platform and lockAllPlatforms are removed. A registry opens libraries for this host only, and CHTYPES_TARGET stays the CLI's.
  • Python and TypeScript now ignore a trusted-keys option, without validating it, on a channel that honors no trust override, as Go and Rust already do.

Documentation

  • bindings-v1.md §3 gains the discover_query_param row; all four bindings have that vocabulary on v2.
  • §3 also states the rule that a TypeScript fact is a free function. The gate's §3 reader does not read free-function spellings (it reads a TypeScript fact as a member), so the four fact spellings stay allowlisted as the documented idiom, with no issue.
  • §4 says where each binding carries a loader refusal's fields.
  • §6 says the trusted keys are hex, and that no option names a platform or a timeout.
  • The per-language reference pages, the Go tour and the TypeScript tour are updated to match.

Test plan

Run locally, each gate on its own and unpiped, with its exit status:

  • go vet ./...: 0. go test ./... with the ABI v2 stubs (CHTYPES_ABI2_STUBS): 0. The Go fetch conformance (TestConformance(V1|ProdV2), TestDevChannel*) against tests/fixtures/fetch-v1 and fetch-v2: 0.
  • uv run pytest -q with the stubs and both conformance corpora: 0 (712 passed, 3 skipped by name).
  • ruff check and ruff format --check: 0.
  • scripts/fetch-v1/gen-constants.py --selftest and --check: 0. scripts/abi-v1/gen.py --check, --major 2 --check and --selftest: 0. check-no-hand-decls.py --scope all: 0.
  • scripts/parity-surface.py --selftest: 0. scripts/parity-surface.py run --only go python: 0, green, with no findings and no allowlist problems.
  • policy-merge-check.py --check-ci-names and --check-guide: 0. lint-public.sh, lint-spelling.sh, lint-cited-paths.sh, check-quoting-passthrough.py, check-no-error-code-table.py, check-reserved-test-names.sh and check-selftests-wired.py: 0.
  • check-cli-parity.sh go and python: 0.
  • markdownlint-cli2 and dprint check: 0. biome lint (ts): 0. rustfmt --check on every changed Rust file: 0.
  • TypeScript and Rust build, test, clippy and the parity gate's TypeScript and Rust surfaces: CI only, since there is no local cargo or pnpm build.
  • golangci-lint: CI only. The local binary was built with an older Go and cannot read Go 1.27.2's export data.

Related issues

Closes #499
Closes #500

🤖 Generated with Claude Code

EricAndrechek and others added 5 commits October 10, 2026 09:14
- Error codes are generated from spec/fetch-v1/constants.json in every
  binding (scripts/fetch-v1/gen-constants.py): Go's public ErrorCode is a
  type of the package's own (go/chtypes/codes_gen.go) with no ExitCode or
  Sentinel method; Python's CODE_* come from the generated chtypes._codes;
  Rust gains chtypes::code::*, which Error::code returns.
- Go: ArtifactError keeps Code and the refusal and cache-fault fields; the
  message, the cause and the request, platform and source fields are no
  longer exported (Error(), errors.Unwrap). Every fetch error's message
  names the request, the platform and the source it concerns, and a test
  pins it. FetchOptions.ConnectTimeout and IdleReadTimeout are removed.
  Format.CHName is Format.ChName.
- Python: chtypes.errors.internal and misuse are private (#499);
  SourceUnreachableError loses retryable and retry_after; TrustedKey is no
  longer public and FetchOptions.trusted_keys takes hex keys.
- Rust: Refusal.detail is removed (a one-sided finding is in got, as in the
  other bindings) and Refusal is #[non_exhaustive]; the fetch layer's own
  corruption has an empty reason; FetchOptions.platform is removed;
  reason::is_lossy is reason::lossy.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…tion notes

- TypeScript: ArtifactError carries reason, path, want and got, as Go's and
  Python's do; LoaderCorruptError and LoaderErrorFields go (the loader's
  corrupt refusal is an ArtifactCorruptError); isChtypesError, exitStatus,
  retryAfterRefused, BatchOutcome and batchOutcomeKnown are removed; the
  CODE_* constants and ErrorCode are exported, generated from the fetch
  constants. FetchOptions is an interface of its own, so the gate reads its
  fields: trustedKeys takes hex keys, and platform and lockAllPlatforms are
  gone (a registry opens for this host; CHTYPES_TARGET stays the CLI's).
- Python and TypeScript ignore the trusted keys where the channel honors no
  trust override, without validating them, as Go and Rust do.
- Rust registry unit tests open for the host, now that FetchOptions has no
  platform.
- bindings-v1.md: §3 gains the discover_query_param row and the TypeScript
  free-function rule for facts; §4 gains the codes table and says where each
  binding carries a loader refusal's fields; §6 says the trusted keys are hex
  and that no option names a platform or a timeout. The per-language pages
  and the Go and TypeScript tours follow.
- parity-surface.py reads §4's codes table (header `code`) as it reads an
  operation table; the allowlist drops every entry #499, #500 and #487 cited
  and keeps the four TypeScript fact spellings as the documented idiom.
- Each binding's CHANGELOG carries a migration note, old to new.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant