Skip to content

v2: a fetch-only registry call (#492), chtypes resolve (#493) and chtypes prune (#494), in all four bindings - #609

Merged
EricAndrechek merged 4 commits into
v2from
v2-fetch-only-resolve-prune
Oct 10, 2026
Merged

EricAndrechek merged 4 commits into
v2from
v2-fetch-only-resolve-prune

Conversation

@EricAndrechek

Copy link
Copy Markdown
Member

Pre-lock items #492, #493 and #494, in all four bindings, on v2. None of them needs a C-ABI entry or argument: fetch and resolve run entirely in each binding's own OCI layer (the library is first touched at load, which neither reaches), and the in-use signal is a file lock on the cache, not an ABI call. The fingerprint and the header are unchanged (gen.py --check, --major 2 --check).

What it adds

A fetch-only call (#492). The registry's fetch is the library counterpart of chtypes fetch: it resolves, fetches, verifies and installs the build a request names, and opens nothing (no dlopen, no setup, nothing in the memo or libraries()). It honors the registry's fetch options, its errors are the fetch codes, and it returns the fetch layer's Resolved (version, build, digests, library path).

binding spelling
Go (*Registry).Fetch(ctx context.Context, request string) (Resolved, error)
Python Registry.fetch(request: str) -> Resolved
TypeScript registry.fetch(request: string): Promise<Resolved>
Rust Registry::fetch(&self, request: &str) -> Result<Resolved>

A concurrent fetch and open of one request share one fetch: the open's fetch now runs through a per-request table of fetches in progress that fetch uses too (#491's single flight, extended).

chtypes resolve <spelling> [--json] [--offline] (#493). What the spelling resolves to on every platform the index offers: the index (through the dev channel's alias first), then each platform manifest by digest and its signed statement, verified exactly as a fetch verifies it. No layer is requested and nothing is written. Output, byte-identical in all four CLIs:

resolved <version> <platform> <build> <manifest>

or, with --json, one line: [{"platform":"linux-amd64","version":"26.8.15.10","build":"20261001.183455","manifest":"sha256:…"},…]. --offline answers from the cache (each platform's resolve_installed); nothing installed is CHTYPES_ARTIFACT_MISSING.

chtypes prune [--line <line>] [--keep <n>] [--dry-run] (#494). Removes each (platform, line)'s installed builds older than its newest n (default 1): the entry and its record, its index.json entries, and its blobs (manifest, config, layer, and its referrers with their layers), never a blob a remaining build names. Cache only, never a system directory; on the dev channel only its own fingerprint's builds, so another SDK's are neither removed nor counted. Output: pruned|would-prune|in-use <version> <platform> <dir> lines, then a summary on stderr.

The in-use signal (new; the cache had none). A registry holds every build it opens or fetches with flock(LOCK_SH) on the entry's verified.json, taken before the library is read and kept until the process exits; prune takes flock(LOCK_EX|LOCK_NB) on the same file and keeps any build it cannot lock, renaming a claimed entry aside before it drops the lock, so a hold taken after finds it gone and the registry looks once more. flock is per open file description, so the same rule holds within one process and across all four bindings; the kernel drops a dead process's locks. TypeScript reaches flock through the generated libc declarations (scripts/abi-v1/emit/ts.py), Rust through the libc crate.

Tests

  • Conformance, both corpora (genfixtures, --abi=2): resolve-build-{line,exact,partial-index,untrusted,unpublished,offline,offline-miss,dev-alias} (every online case forbids each layer GET in requests.none_matching) and prune-{keep-newest,keep-two,line,dry-run,in-use,system-dirs-never,foreign-fingerprint-untouched,v1-channel-sees-all}. The schema gains setup.held, request.prune, expect.resolutions and expect.prune.
  • Per binding: fetch installs without opening (bytes that are no library: fetch succeeds, nothing loaded, setup not latched), and a fetch and an open of one request make one tag GET and one layer GET (the fixture server's gate and log); the CLI's resolve and prune over the fixtures' basic tree; Go also holds a build from a second process and shows prune keeps it until that process exits.

Gates

Run locally on darwin-arm64, each alone and unpiped, every one exit 0 (measured):

  • Go: go test ./...; go test -race on internal/ocifetch and chtypes (also with the ABI v2 stubs built by build-stubs.sh); TestConformanceV1 and TestConformanceProdV2, 325 results over 183 cases each, the 24 new results among them passing.
  • Python: uv run pytest -q (487 passed, 244 skipped without stubs; tests/abi2 with the stubs: 285 passed); the conformance suite on both corpora, 325/325 each, 24/24 new; ruff check, ruff format --check.
  • genfixtures --check (v1 and --abi=2) and --selftest; server.py --selftest; schema_check.py; staging.py --selftest; parity.py --selftest.
  • gen.py --check and --major 2 --check (fingerprint sha256:d60a681e… and header unchanged); gen-constants.py --check; check-no-hand-decls.py --scope all.
  • check-cli-parity.sh --selftest (the new planted --keep 0 is caught), and go and python match expected.txt (34 cases); parity-surface.py --selftest, run --only go python (0 findings), --check-issues.
  • lint-public, lint-spelling, lint-cited-paths, lint-actions, check-selftests-wired.py, markdownlint, dprint check, rustfmt --edition 2024 --check; policy-merge-check.py --check-ci-names and --check-guide.

TypeScript and Rust were not built or tested locally (CI only, per the lane rules); rust/Cargo.lock gains only libc in the crate's own dependency list (cargo metadata --offline, no build).

Refs #492
Refs #493
Refs #494

🤖 Generated with Claude Code

EricAndrechek and others added 4 commits October 10, 2026 10:22
…egistry.Fetch and CLI

- go/internal/ocifetch: Resolve (what a request resolves to on every
  platform, verified, no layer), Prune (superseded builds of a line, kept
  newest N per platform, never a held build, never another fingerprint's),
  and Hold, the shared flock on an entry's verified.json a registry takes
  before it loads a build (prune takes the exclusive lock without waiting).
- go/chtypes: Registry.Fetch(ctx, request) installs without opening, shares
  one fetch with a concurrent open, and holds what it returns.
- go/cmd/chtypes: chtypes resolve and chtypes prune.
- genfixtures: resolve-build-* and prune-* cases in both corpora, with the
  case-schema fields they need (setup.held, request.prune,
  expect.resolutions, expect.prune).
- scripts/abi-v1/emit/ts.py: flock in the generated TS libc declarations
  (fingerprint and header unchanged).

Refs #492, #493, #494

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d CLI parity

- python: Registry.fetch(request) installs without opening and shares one
  fetch with a concurrent open; the open path holds what it loads; the fetch
  layer's resolve, prune and hold (_resolve.py, _prune.py, _hold.py); the
  chtypes resolve and chtypes prune commands; the conformance runner runs
  the resolve-build-* and prune-* cases.
- docs: fetch-v1.md gains "In use", "Pruning", "What a request resolves to",
  "The fetch-only call" and "The command line", and the new case kinds in
  section 10; bindings-v1.md gains the registry's fetch row and the hold in
  section 6; the per-language references and install notes name the two new
  commands.
- scripts/check-cli-parity.sh: resolve and prune rows, and a planted
  --keep 0 the selftest must catch.
- CHANGELOG: Go and Python.

Refs #492, #493, #494

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hold

- ts: registry.fetch(request) installs without opening and shares one fetch
  with a concurrent open; the open path holds what it loads; the fetch
  layer's resolveBuilds, prune and hold (flock through the generated libc
  declaration, which now never reports a failure as 0); chtypes resolve and
  chtypes prune; a refused spelling exits with the usage status; the
  conformance runner runs the resolve-build-* and prune-* cases and installs
  each installed.json build under its own index entry's platform.
- rust: Registry::fetch(&self, request) with the same single flight and
  holds (libc::flock; libc added as a direct dependency, Cargo.lock
  refreshed); the fetch layer's resolve, prune and hold; chtypes resolve and
  chtypes prune; the conformance runner and unit tests.
- CHANGELOG (TypeScript, Rust) and the Rust README's command list; a
  scratch prefix the public-repo lint reads as a repository name.

Refs #492, #493, #494

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…andContext

- examples/rust/Cargo.lock lists libc in the chtypes package's dependencies,
  as rust/Cargo.lock does (cargo metadata --offline; the tour runs --locked).
- The Go registry fetch tests skip loudly when the fetch fixtures' test key or
  scripts/fetch-v1/server.py are not beside the module (the bare standalone
  copy has neither).
- hold_test.go starts its helper process with exec.CommandContext (noctx).

Refs #492, #493, #494

Co-Authored-By: Claude Opus 5.5 <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