Skip to content

fix(bundler): enforce version pin on bundled preset/extension installs#3377

Merged
mnriem merged 2 commits into
github:mainfrom
jawwad-ali:fix/bundler-bundled-version-pin
Jul 9, 2026
Merged

fix(bundler): enforce version pin on bundled preset/extension installs#3377
mnriem merged 2 commits into
github:mainfrom
jawwad-ali:fix/bundler-bundled-version-pin

Conversation

@jawwad-ali

Copy link
Copy Markdown
Contributor

Description

Bundle manifests pin component versions for reproducibility, enforced by _assert_pinned_version. But the bundled install branch of _PresetKindManager/_ExtensionKindManager returns before that check:

bundled = _locate_bundled_preset(component.id)
if bundled is not None:
    self._manager.install_from_directory(bundled, speckit_version, priority)
    return   # <-- _assert_pinned_version (below) never runs for bundled assets

So a bundle pinning e.g. 2.0.0 silently installs whatever version the bundled asset ships (1.0.0) — the pin is only enforced on the catalog path. Reproduced on main @ 92b7cf7: a ComponentRef(version="2.0.0") against a bundled extension.yml of 1.0.0 installs with no error. (_WorkflowKindManager already enforces the pin unconditionally, so this is an internal inconsistency.)

Fix

Read the bundled asset's own declared version from its manifest (best-effort — None on unreadable/invalid, which _assert_pinned_version treats as "cannot enforce", matching the catalog "advertises no version" escape hatch) and call _assert_pinned_version before install_from_directory in both bundled branches.

Testing

New tests (mirror the existing _assert_pinned_version / bundled-workflow test style):

  • bundled extension & preset with a 2.0.0 pin vs 1.0.0 manifestBundlerError "pinned to version 2.0.0", and install_from_directory is not called;
  • matching pin and unpinned (version=None) → install proceeds.

Mismatch tests fail-before ("DID NOT RAISE", verified by source-stash); full test_bundler_primitives.py: 15 passed. uvx ruff check clean.

AI Disclosure

  • I did use AI assistance (describe below)

Found and fixed with Claude Code (Claude Fable 5) under my direction. AI spotted the early-return bypassing the pin in the bundled branch vs the workflow manager's unconditional enforcement; I reproduced the silent pin violation, verified fail-before/pass-after, and reviewed the diff.

The bundled install branch of _PresetKindManager/_ExtensionKindManager
called install_from_directory and returned before _assert_pinned_version,
so a bundle manifest pinning e.g. 2.0.0 would silently install the
bundled asset's own version (1.0.0) — the pin was only enforced on the
catalog path. _WorkflowKindManager already enforces it unconditionally.
Read the bundled asset's declared version from its manifest (best-effort;
None => cannot enforce, matching the catalog 'advertises no version'
escape hatch) and call _assert_pinned_version before install, in both
bundled branches.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a reproducibility gap in the bundler’s primitive installers by enforcing bundle-manifest version pins even when presets/extensions are installed from locally bundled assets (previously the bundled early-return path bypassed pin validation, unlike the catalog path and workflow manager).

Changes:

  • Add _bundled_manifest_version() to best-effort read a bundled preset/extension manifest’s declared version.
  • Enforce _assert_pinned_version() in the bundled install branches for presets and extensions before installing from directory.
  • Add unit tests covering bundled extension/preset pin enforcement behavior.
Show a summary per file
File Description
tests/unit/test_bundler_primitives.py Adds unit tests to ensure bundled preset/extension installs respect bundle-manifest version pins and don’t proceed on mismatches.
src/specify_cli/bundler/services/primitives.py Reads bundled manifest versions and enforces version pins for bundled preset/extension installs to match catalog-path behavior.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 2/2 changed files
  • Comments generated: 3
  • Review effort level: Low

Comment thread src/specify_cli/bundler/services/primitives.py
Comment thread src/specify_cli/bundler/services/primitives.py
Comment thread tests/unit/test_bundler_primitives.py
- Make _assert_pinned_version's error source-agnostic ('resolved version'
  / 'the source') so bundled preset.yml/extension.yml mismatches read
  correctly, not just catalog ones.
- Type-guard _bundled_manifest_version: only a non-empty string version is
  usable; missing/non-string/whitespace -> None ('cannot enforce').
- Add bundled-preset success-path test (matching pin + version=None both
  proceed to install_from_directory), mirroring the extension test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

  • Files reviewed: 2/2 changed files
  • Comments generated: 0 new
  • Review effort level: Low

@mnriem mnriem merged commit 7b1065d into github:main Jul 9, 2026
12 checks passed
@mnriem

mnriem commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

Thank you!

kanfil added a commit to tikalk/agentic-sdlc-spec-kit that referenced this pull request Jul 11, 2026
Upstream merge (30 commits, 3 releases 0.12.9-0.12.11):
- invoke_separator parse-success fix (github#3304)
- Windows Store python3 stub skip + _interpreter_runs() probe (github#3385)
- SKILL.md frontmatter control char escape via yaml_quote() (github#3399)
- chained expression filters left-to-right refactor (github#3339)
- refresh_shared_templates preserves recovered files (github#3378)
- Goose yaml skill placeholder resolution (github#3374)
- bundled version pin enforcement (github#3377)
- integration test home isolation (github#3144)
- py: script type in command templates (github#3403)
- configurable shell step timeout (github#3404)
- find plans in nested spec directories (github#3405)
- plan.md phase numbering fix (github#3416)
- PowerShell -Number 0 honor via ContainsKey (github#3412)
- workflow.yml non-string scalar validation (github#3421)
- plan-template.md self-referencing path fix (github#3417)
- pre-commit config + trailing whitespace cleanup (github#3430)
- malformed URL error handling (github#3433/github#3435/github#3437)
- agent-context nested plan.md discovery (github#3301)
- community catalog additions (EARS, Figma) (github#3407/github#3408)

9 conflicts resolved: pyproject.toml, integrations/base.py, agents.py,
forge/__init__.py, hermes/__init__.py, create-new-feature-branch.ps1,
test_git_extension.py, test_base.py, test_integration_devin.py.

Template-to-preset alignment: added py: script lines to 6 preset commands,
removed stale Phase 1 agent context line from plan preset, fixed phase
numbering. Pre-merge fix: wrapped bare make_typer import with fallback.

Assisted-by: opencode (model: glm-5.2, autonomous)
kanfil added a commit to tikalk/agentic-sdlc-spec-kit that referenced this pull request Jul 11, 2026
Upstream merge (30 commits, 3 releases 0.12.9-0.12.11):
- invoke_separator parse-success fix (github#3304)
- Windows Store python3 stub skip + _interpreter_runs() probe (github#3385)
- SKILL.md frontmatter control char escape via yaml_quote() (github#3399)
- chained expression filters left-to-right refactor (github#3339)
- refresh_shared_templates preserves recovered files (github#3378)
- Goose yaml skill placeholder resolution (github#3374)
- bundled version pin enforcement (github#3377)
- integration test home isolation (github#3144)
- py: script type in command templates (github#3403)
- configurable shell step timeout (github#3404)
- find plans in nested spec directories (github#3405)
- plan.md phase numbering fix (github#3416)
- PowerShell -Number 0 honor via ContainsKey (github#3412)
- workflow.yml non-string scalar validation (github#3421)
- plan-template.md self-referencing path fix (github#3417)
- pre-commit config + trailing whitespace cleanup (github#3430)
- malformed URL error handling (github#3433/github#3435/github#3437)
- agent-context nested plan.md discovery (github#3301)
- community catalog additions (EARS, Figma) (github#3407/github#3408)

9 conflicts resolved: pyproject.toml, integrations/base.py, agents.py,
forge/__init__.py, hermes/__init__.py, create-new-feature-branch.ps1,
test_git_extension.py, test_base.py, test_integration_devin.py.

Template-to-preset alignment: added py: script lines to 6 preset commands,
removed stale Phase 1 agent context line from plan preset, fixed phase
numbering. Pre-merge fix: wrapped bare make_typer import with fallback.

Assisted-by: opencode (model: glm-5.2, autonomous)
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.

3 participants