Repository navigation
setup: a failed open before the first step 7 unlocks setup (#468) - #469
Merged
Merged
Conversation
…on 1.0.2) The shared setup case now also covers the ruling on #468: - a second, different setup before any open is attempted is a UsageError; - an open the loader refuses before step 7 (the stub's fingerprint-other variant: an incompatible artifact, reason "fingerprint") must clear the setup record while no image has completed step 7, so the next, different setup is accepted. An open step may now name its own stub variant; each runner copies every variant a case opens once, and checks a loader refusal by its class and reason. The variant, its reason and its class come from _stubshared.plan and sdk.json's loader.refusals, not from a list here. This commit carries no fix: on 1.0.2 code the case fails at step 3, where the setup after the refused load is still a UsageError. refs #468 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1.0.2 settled the setup around load step 7 only, so a setup stayed recorded after an open that failed earlier (the fetch, the signature, an incompatible artifact, a missing symbol), and a different setup was refused until the process restarted. Option 2 as ruled: while no image has completed step 7, ANY failed open clears the record; once one has, today's rule holds; and a second, different setup before any open is attempted is still a UsageError. Each binding now settles around the whole public open, not step 7: - an image that completes step 7 latches the setup, under the image lock that already holds its commit and load; - every public open (Go Registry.For/OpenUnverified/OpenLinked, Python Registry.for_version/open_unverified, TS registry.for/openUnverified, Rust Registry::for_version/Library::open_unverified) reads a generation when it begins and, on any failure, clears the record under that same lock, unless the setup has latched or a setup was recorded after the open began. The step-7-only callbacks from 1.0.2 are gone (Python's loader settle=, TS ImageSetup.settle). TS moves the spelling check inside the settled open, so a refused spelling clears the record there as it does in the other three. docs/reference/bindings-v1.md section 6 rules 1, 2 and 4 and the settings guide say so, including that an open with no setup after a failure records the empty setup. Each CHANGELOG has an [Unreleased] Fixed entry; no version bump. refs #468 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…head) The ruling on #468 changed before merge from "clear" to "unlock": after an open that attempted a load and failed, the setup record is KEPT but becomes replaceable, so a plain retry runs under the recorded zone, never the empty setup; and the binding's own input checks (a refused spelling, an unverified open without the caller's opt-in) unlock nothing. The shared cases now pin that: - setup.latches_only_on_success: a refused spelling and a withheld opt-in each leave a different setup refused; a refused load makes the record replaceable; a replaced record is locked again; a plain retry after a step 7 refusal is refused again under the recorded zone; the corrected setup opens, and the image reports that zone. - setup.a_retry_after_a_failed_open_keeps_the_recorded_setup: a refused load, then a plain retry with no new setup, which must load under the recorded zone. The stub gains a zone probe (_stubshared.ZONE_PROBE): chs_type_validate of exactly "!Z:" returns the zone the image holds, so each runner reads it back through its public validate_type. Open steps may now carry "request" (a registry over an empty cache) and "allow" (the caller's opt-in). This commit carries no binding change: on the clear head both cases fail, at the setup after the refused spelling and at the retry's image zone. refs #468 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The parent used to stop at the first failing case, so a later case's result was never seen. It now runs each case in its own process, reports every failure, then fails once. Still no binding change: on the clear head both cases must fail in Rust too. refs #468 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The lead refined the ruling before merge: clearing the record failed open, because a plain retry after a transient failure silently loaded under the empty setup (UTC) instead of the zone the caller chose. Now, while no image has completed load step 7: - an open that attempted a load and failed (resolve or fetch, signature, an incompatible artifact, a missing symbol, step 7) KEEPS the record but marks it replaceable: a retry with no new setup runs under the recorded zone and defaults, and a different setup replaces the record; - a replaced record is locked again, and so is any record an open commits for its load, so no setup replaces a record under a load; - the binding's own input checks fail before anything is attempted and unlock nothing: a refused version spelling (checked before the attempt begins) and an unverified open without both opt-ins (checked before the commit, through a gate the loader now exposes); - two different setups before any open is attempted are still a UsageError. After the first successful step 7 nothing changes. The generation guard stays: a failed open still never unlocks a setup that was recorded or replaced after it began (another caller's setup, replacing an unlocked record during a slow fetch, must stay locked). - Go: setup.go replaceable; failedOpen unlocks; Registry.open treats an ocifetch.SpellingError as misuse; OpenUnverified asks the new abi1.CheckUnverifiedAllowed first. - Python: _setup.py _replaceable; open_failed unlocks; Registry._open parses the request before the attempt; open_unverified checks opt-ins first. - TypeScript: setup.ts replaceable; settleFailedOpen unlocks; the spelling check is back before the settled opening; openUnverified asks the loader's exported checkUnverifiedAllowed first. - Rust: setup.rs replaceable and unlock_after_failed_open; Registry::open parses the spelling first; open_unverified checks opt-ins first. Section 6 rules 1, 2 and 4, the settings guide and the four [Unreleased] CHANGELOG entries say so. refs #468 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
EricAndrechek
marked this pull request as ready for review
October 6, 2026 07:49
This was referenced Oct 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
refs #468
Option 2 as refined on #468 before merge ("unlock", not "clear"), in all four bindings, with no name or signature changes.
The rule
setupruns under the recorded zone and defaults, never the empty setup.setupis accepted and replaces the record.setupcan replace a record underneath a load.UsageError(the Q6 ruling).The generation guard is still needed, and it stays. A failed open never unlocks a setup that was recorded or replaced after the open began. Without the guard, a slow fetch that fails could unlock a setup that another caller recorded meanwhile, by replacing a record that was already unlocked. That would reopen the silent last-wins this rule exists to prevent.
What each binding does
go/chtypes/setup.go:replaceable.Setupreplaces only a replaceable record.commitSetupandlatchSetuplock it, andfailedOpen(underimages.mu, generation-guarded) unlocks it.registry.gotreats anocifetch.SpellingErroras misuse.library.goOpenUnverifiedasks the newabi1.CheckUnverifiedAllowed(go/internal/abi1/loader.go) before anything is attempted.python/src/chtypes/_setup.py:_replaceable,open_failed(underLOCK).registry.pyRegistry._openparses the request before the attempt begins.library.pyopen_unverifiedchecks the opt-ins before it.ts/src/setup.ts:replaceable,settleFailedOpen.registry.ts: the spelling check is back before the settled opening.library.tsopenUnverifiedasks the loader's newly exportedcheckUnverifiedAllowed(ts/src/abi1/loader.ts) before the commit.rust/src/setup.rs:replaceable,unlock_after_failed_open.registry.rsRegistry::openparses the spelling (VersionRequest::parse) before the attempt.library.rsLibrary::open_unverifiedchecks the opt-ins before it.The shared cases (
tests/fixtures/abi-v1/setup-cases.json)setup.latches_only_on_success:UsageError.v26.8, through a registry over an empty cache), then a different setup, which is still aUsageError.UsageError.fingerprint-other,artifact_incompatible) unlocks the record.setup.a_retry_after_a_failed_open_keeps_the_recorded_setup: setupAsia/Tokyo; a refused load; a plain retry with no new setup, which must load underAsia/Tokyo; then latched._stubshared.ZONE_PROBE):chs_type_validateof exactly!Z:returns the zone the image holds. Each runner reads the zone back through its publicvalidate_type, so the retry assertion is the image's own zone, not an inference.request(a registry open) andallow. The Rust runner now runs every case before its verdict.api_v1_setup_failed_open.rs):Proof
v1-abirun 37430039241, headdbbf4b2: the new cases and runners, with binding code exactly as at6c11397;measured): all 12v1-abi-conformancelegs failed, on both new paths, in every binding.setup.latches_only_on_success, step 3 (setup "Europe/Berlin"after the refused spelling):want a usage error, got success. The refused spelling had cleared the record.setup.a_retry_after_a_failed_open_keeps_the_recorded_setup, step 2 (the plain retry):the image was set up with zone "", want "Asia/Tokyo". The retry had loaded under the empty setup.TestSetupCases/setup.latches_only_on_successandTestSetupCases/setup.a_retry_after_a_failed_open_keeps_the_recorded_setup; locally, Go printed the same two step messages.b8c3e6b) shows the same, except that Rust stopped at the first case.069a6ec,v1-abirun 37430600288,success): all 12 legs pass, and each one ran both cases.281 passed, 4 skipped, 0 failed, and the census requiresTestSetupCases.189 passed, 1 skipped(the other OS's ctor-marker case).✓ … setup.latches_only_on_successand✓ … setup.a_retry_after_a_failed_open_keeps_the_recorded_setup.…: passed in its own processfor both cases.cijobs ran the new unit tests: Rusta_failed_open_before_step_7_unlocks_the_setup_and_misuse_does_not ... ok, and TypeScript✓ setup: a failed open before the first step 7 unlocks the record×3.go test -race ./...with stubs andgo test ./...without them;uv run pytest -qwith and without stubs; golangci-lint; ruff;tsc --noEmit; biome 2.5.13;cargo fmt;clippy -D warnings;gen.py --checkand--selftest;check-no-hand-decls; every always-gate; markdownlint and dprint. All green.v1: all 21 pass.measuredfrom their own output):docs — support matrix: release tag drift at 1.0.1.security (report only):pnpm audit (ts).🤖 Generated with Claude Code