Skip to content

fix(bundles): retrieve pinned component releases when a catalog advertises a newer version - #4753

Open
muhammadumer-waheed wants to merge 1 commit into
github:mainfrom
muhammadumer-waheed:fix/4712-fetch-pinned-release
Open

muhammadumer-waheed wants to merge 1 commit into
github:mainfrom
muhammadumer-waheed:fix/4712-fetch-pinned-release

Conversation

@muhammadumer-waheed

Copy link
Copy Markdown

Fixes #4712.

Summary

Bundle manifests pin component versions for reproducibility, but specify bundle install compared each pin against the version the catalog currently advertises and refused the install when they differed — even when the pinned release remained available at its original download URL. Catalog entries advertise a single release, so the pinned release was never even attempted.

This change makes the bundler retrieve the pinned release when a catalog entry has moved on:

  • Pin-aware retrieval. When the bundle pin differs from the advertised version (extensions and presets), the bundler derives the pinned release's URL from the catalog entry's own download_url by substituting the advertised version token in the URL path (bounded token matching: a v-prefixed tag, a versioned asset filename, or a version glued to an archive suffix) and retrieves that release through the catalog's existing download pipeline. When the pinned release cannot be identified from the catalog URL, or the retrieval fails, the error names the pinned and advertised versions instead of a bare pin mismatch or network error.
  • Explicit-URL downloads. The fetch stage of ExtensionCatalog.download_extension and PresetCatalog.download_pack is now download_extension_url / download_pack_url (HTTPS validation, size-limited fetch, optional SHA-256, archive-format detection, safe cache path), which the ID-based methods delegate to without behavior change.
  • Digest scope. The catalog's SHA-256 covers only the advertised release, so a retrieved pinned release is verified against no digest; the same host and the HTTPS rule still apply.

Intentionally out of scope: workflows keep the hard pin check (their URL install path carries an interactive untrusted-source confirmation that a derived-URL fetch would bypass), bundled assets keep the hard check (no alternative release exists), and steps perform no pin check today.

Reproduction (the issue's scenario)

A local catalog server advertising specassay-check v0.5.1 while the 0.4.12 release artifact stays available at its original URL; the bundle pins 0.4.12:

  • Before (main): Error: Extension 'specassay-check' is pinned to version 0.4.12 in the bundle manifest, but the resolved version is 0.5.1. Update the bundle's pinned version or the source before installing.
  • After (this branch): ✓ Installed 'test-bundle' (1 added, 0 already present). — the registry records version 0.4.12 and the installed extension.yml declares version: 0.4.12.

Test plan

New regression tests fail on main (the pin mismatch raised before any retrieval attempt) and pass with this change; the full suite is otherwise unchanged — the 10 pre-existing *_python_parity "composed" failures reproduce identically on clean main:

$ .venv/bin/python -m pytest tests -q
10 failed, 8367 passed, 211 skipped    # with this change
10 failed, 8350 passed, 211 skipped    # clean main (same 10 failures; 8367 = 8350 + 17 new tests)
  • uvx ruff@0.15.0 check src tests → All checks passed.
  • markdownlint-cli2 docs/reference/bundles.md → 0 issues.

Manual test results

Agent: n/a (CLI-level change; verified by running specify bundle install directly) | OS/Shell: macOS/zsh

Command tested Notes
specify bundle install <bundle.yml> --integration copilot catalog advertises 0.5.1, bundle pins 0.4.12 → pinned release retrieved and installed, exit 0
specify bundle install <bundle.yml> (stale pin, non-derivable catalog URL) fails with a pin-aware error naming the pinned and advertised versions

AI disclosure

Implemented with opencode (model: Qwen3.8-27B, running locally; autonomous within the session under user direction, no human line-by-line review before this PR was opened). Extent: issue triage, source analysis, code, tests, end-to-end reproduction, and docs were all generated by the agent.

…tises a newer version

Bundle manifests pin component versions for reproducibility, but
extension/preset bundle installation compared the pin against the version
the catalog currently advertises and refused the install when they
differed, even when the pinned release remained available at its original
download URL (github#4712). Catalog entries advertise a single release, so the
resolver never attempted to retrieve the pinned one.

When the pin differs from the advertised version, the bundler now derives
the pinned release's URL from the catalog entry's own download_url by
substituting the advertised version token in the URL path (bounded token
matching: a v-prefixed tag, a versioned asset filename, or a version glued
to an archive suffix) and retrieves that release through the catalog's
existing download pipeline (HTTPS validation, size limits, safe cache
path). The catalog's SHA-256 covers only the advertised release, so a
retrieved pinned release is verified against no digest; the same host and
the HTTPS rule still apply. When the pinned release cannot be identified
from the catalog URL, or the retrieval fails, the error names the pinned
and advertised versions instead of a bare pin mismatch or network error.

To expose explicit-URL retrieval without duplicating the download
pipeline, the fetch stage of ExtensionCatalog.download_extension and
PresetCatalog.download_pack is now download_extension_url /
download_pack_url, which the ID-based methods delegate to without behavior
change (a catalog entry with a null version now names the cached archive
"unknown" instead of erroring).

Workflows and bundled-asset installs keep their existing hard pin check:
a workflow's URL install path carries an interactive untrusted-source
confirmation that a derived-URL fetch would bypass, and a bundled asset
has no alternative release to retrieve.

New regression tests in tests/specify_cli/bundles/test_primitives.py fail
on main (the pin mismatch raised before any retrieval attempt) and pass
with this change, plus URL-derivation unit tests and explicit-URL download
coverage in tests/test_extensions.py and
tests/specify_cli/presets/test_catalog.py. Verified end-to-end with a
local catalog advertising 0.5.1 while a 0.4.12 release stays available:
`specify bundle install` now installs the pinned 0.4.12 release (on main
it fails with the reported error).

Fixes github#4712

Assisted-by: opencode (model: Qwen3.8-27B (local), autonomous)
@mnriem mnriem added the triage-can-wait Verdict: valid and in-scope but deprioritized; held behind the evidence gate label Sep 26, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

triage-can-wait Verdict: valid and in-scope but deprioritized; held behind the evidence gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Bundle pins can become unresolvable after component catalog updates

2 participants