Repository navigation
fix(bundles): retrieve pinned component releases when a catalog advertises a newer version - #4753
Conversation
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Derived downloads can install the wrong manifest version, and valid v-prefixed versions produce incorrect URLs.
Review effort: Balanced
Findings: 1
Open (2)
What changed in this PR
Fixes stale bundle pins by deriving and downloading historical extension/preset release URLs.
Changes:
- Adds explicit-URL catalog download APIs.
- Adds pin-aware URL derivation and error handling.
- Documents and tests pinned-release retrieval.
| File | Description |
|---|---|
src/specify_cli/bundles/primitives.py |
Implements pinned-release resolution. |
src/specify_cli/extensions/__init__.py |
Adds explicit extension URL downloads. |
src/specify_cli/presets/_catalog.py |
Adds explicit preset URL downloads. |
tests/specify_cli/bundles/test_primitives.py |
Tests pin resolution and failures. |
tests/test_extensions.py |
Tests extension URL downloads. |
tests/specify_cli/presets/test_catalog.py |
Tests preset URL downloads. |
docs/reference/bundles.md |
Documents historical-release behavior. |
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
Please address Copilot feedback and fix test & lint errors |
|
Please address Copilot feedback |
|
@muhammadumer-waheed Thanks for the work and regression tests here. I think we should pause this PR until the extension and preset version-history work under #4719 lands, then use its exact release URLs and checksums for the bundle fix rather than deriving URLs. |
|
Sorry for interrupting here — we stumbled across this while digging into #4712, and wanted to check before doing anything. Context Bundle manifests pin each component to an exact version for reproducibility. Catalog entries advertise a current release and optionally keep older ones under a Gap: the catalog can resolve the exact release, but the bundler doesn't ask for it The failure is in
Since #4726 landed, the catalog can already select and download an exact historical release: info = catalog.get_extension_info(id, version="0.4.12") # exact record, or None if absent
path = catalog.download_extension_info(info) # downloads + verifies that record's own url/sha256That is the same shared API the CLI's Question (@KSchlobohm @mnriem): now that #4726 provides exact-release selection on f this PR's URL-derivation approach is still the intended one, we're happy to work within it instead; just want to avoid duplicating effort. Scope would stay extensions-only (presets #4823 and workflows stay separate slices). |
|
This PR addresses retrieving pinned component releases, but it also highlights a few related versioning gaps:
These may be outside this PR’s scope, but it would be useful to track them alongside the broader catalog-versioning work. |
|
Thanks—yes, these belong with the broader catalog-versioning work tracked in github/spec-kit#4719, which is intentionally being delivered as separate catalog-family and bundler slices rather than expanding this PR:
The interaction between a preset’s So I’d keep github/spec-kit#4753 focused on github/spec-kit#4712 and track these broader semantics and remaining delivery slices under github/spec-kit#4719. |
|
@markuswondrak thanks for digging into this, and good catch. Now that #4726 (extensions) and #4823 (presets) are on Plan for #4753:
@KSchlobohm @mnriem: this follows the direction you suggested earlier. |
…tion Bundle manifests pin component versions, but extension and preset installs compared the pin against the version the catalog currently advertises and refused the install when they differed, even when the catalog still listed the pinned release (github#4712). The bundler now selects the pinned release from the winning catalog entry with the exact-release support added in github#4726 (extensions) and github#4823 (presets), downloads that record with its own URL and SHA-256 via download_extension_info / download_pack_info, and passes the pin as expected_id / expected_version so the archive must declare the pinned component. A pin the winning entry does not list fails with an error naming the pinned and advertised versions, without substituting the advertised release or falling through to another catalog. Unpinned components, entries advertising no version, workflows and bundled assets keep their existing behavior. Fixes github#4712
23d5ccb to
4be71da
Compare
|
Pushed the rework described above (4be71da). The branch is now a single commit on top of One deviation from that plan: I kept archive verification, but through the catalog's own path rather than the bespoke check. The pin is passed to Full suite passes locally and the PR description is updated. @KSchlobohm @mnriem: please take a look when you get the chance. |
There was a problem hiding this comment.
Copilot review overview
🟢 Approval recommended
The implementation matches the stated resolution contract and includes focused positive and negative regression coverage, though tests were not independently executed during review.
Review effort: Balanced
Findings: None
|
@mnriem I'll take the remaining Bundler work for workflow and step component pins. Bundle installs will select the pinned release through the exact-release support from #4788/#4840, rather than comparing against the advertised version. Steps will enforce their pin, which they currently don't. Online bundle validate will check the exact pinned version, not just the ID. This doesn't touch the Bundle catalog slice (@markuswondrak). |


Fixes #4712.
Description
Bundle manifests pin component versions for reproducibility, but
specify bundle installcompared each extension/preset pin against the version the catalog currently advertises and refused the install when they differed — even when the catalog still listed the pinned release.This PR builds the fix on the exact-release catalog support from #4726 (extensions) and #4823 (presets), as suggested in review, instead of deriving historical URLs:
select_release, the same pathspecify extension add --version/specify preset add --versionuse) and downloads that record withdownload_extension_info/download_pack_info, so the release's owndownload_urlandsha256are used.install_from_zipasexpected_id/expected_version, so the archive must declare the pinned component and version.docs/reference/bundles.mddocuments the behavior.Testing
uv sync && uv run pytest—9450 passed, 264 skippeduvx ruff@0.15.0 check src tests— all checks passedmarkdownlint-cli2 docs/reference/bundles.md— 0 issuesNew tests in
tests/specify_cli/bundles/test_primitives.py(fail without theprimitives.pychange):Existing bundle tests that mocked
download_extension/download_packnow mock the*_infovariants.AI Disclosure
AI disclosure: Reworked with Claude Code (model: Claude Opus 5.5), human-directed: I chose the approach and reviewed the result before pushing. Extent: code, tests, and docs were generated by the agent. The original revision of this PR was implemented with opencode (model: Qwen3.8-27B, local).