Skip to content

fix(marketplace): honour producer tag_pattern in consumer semver range resolution - #2366

Open
sergio-sisternes-epam wants to merge 2 commits into
mainfrom
sergio-sisternes-epam-fix-2319-semver-tag-pattern
Open

fix(marketplace): honour producer tag_pattern in consumer semver range resolution#2366
sergio-sisternes-epam wants to merge 2 commits into
mainfrom
sergio-sisternes-epam-fix-2319-semver-tag-pattern

Conversation

@sergio-sisternes-epam

Copy link
Copy Markdown
Collaborator

fix(marketplace): honour producer tag_pattern in consumer semver range resolution

TL;DR

When a consumer installs a marketplace dependency with a semver range (e.g. version: "~2.1.0"), APM now uses the tag-naming pattern declared by the marketplace producer instead of silently hardcoding {name}--v{version}. The bug caused silent install failures for any marketplace that ships tags under a different convention. The fix threads effective_tag_pattern from apm pack through marketplace.json to apm install.

Note

Closes #2319. Backward-compatible: old marketplace.json files without tag_pattern fall back to {name}--v{version} (unchanged behaviour).

Problem (WHY)

  • resolve_marketplace_plugin() called resolve_version_constraint() with no tag_pattern kwarg, so the consumer always resolved tags using the hardcoded default "{name}--v{version}" — regardless of what pattern the producer declared in build.tagPattern. Any marketplace using a different convention (e.g. {name}/{version}, releases/{name}-v{version}) would produce zero tag matches and fail silently.
  • apm pack already honoured build.tagPattern when producing marketplace.json, but the resulting file never carried the resolved pattern forward. The producer-to-consumer contract had a one-field gap.
  • [!] The discrepancy between the producer default (v{version}) and the consumer default ({name}--v{version}) meant that even the built-in defaults were asymmetric — a marketplace built with no explicit tagPattern would produce tags like v1.0.0 but the consumer would search for my-plugin--v1.0.0.

Approach (WHAT)

# Fix
1 Add effective_tag_pattern to ResolvedPackage; compute it in _resolve_entry() as entry.tag_pattern or yml.build.tag_pattern.
2 Emit "tag_pattern" in the remote source dict of marketplace.json (producer side).
3 Parse tag_pattern from the source dict into MarketplacePlugin (consumer model).
4 Pass plugin.tag_pattern or DEFAULT_TAG_PATTERN to resolve_version_constraint() (consumer resolver).

Implementation (HOW)

  • src/apm_cli/marketplace/builder.py — adds effective_tag_pattern: str = "" to the frozen ResolvedPackage dataclass; _resolve_entry() computes the pattern from the entry or build-level config and forwards it through _resolve_explicit_ref() (4 return sites) and _resolve_version_range() (1 return site). Local-path packages retain the default "".
  • src/apm_cli/marketplace/output_mappers.py — emits "tag_pattern" in the remote source object only when pkg.effective_tag_pattern is non-empty, keeping the diff minimal and old marketplace files stable.
  • src/apm_cli/marketplace/models.py — adds tag_pattern: str | None = None to MarketplacePlugin; _parse_plugin_entry() reads it from the source dict at parse time.
  • src/apm_cli/marketplace/resolver.py — imports DEFAULT_TAG_PATTERN from version_resolver and passes tag_pattern=plugin.tag_pattern or DEFAULT_TAG_PATTERN to resolve_version_constraint(), closing the call-site gap.
  • Tests — updated golden fixture (tests/fixtures/marketplace/golden.json) and three existing tests that asserted tag_pattern must NOT appear in source dicts; added 5 new tests covering the full producer→consumer closure.
  • Docs / CHANGELOGpackages/apm-guide/.apm/skills/apm-usage/dependencies.md corrects the stale {name}--v{version} reference; docs/src/content/docs/consumer/manage-dependencies.md notes pattern propagation; CHANGELOG adds [Fixed] entry.

Diagrams

The sequence below shows the old (broken) and new (fixed) resolution path for a marketplace semver range.

sequenceDiagram
    participant C as apm install
    participant R as marketplace/resolver.py
    participant VR as marketplace/version_resolver.py
    participant Git as Git remote

    C->>R: resolve_marketplace_plugin("my-plugin", "acme-mkt", "~2.1.0")
    R->>R: fetch marketplace.json
    rect rgb(255, 247, 200)
        Note over R,VR: NEW: read tag_pattern from plugin source dict
        R->>VR: resolve_version_constraint(..., tag_pattern="v{version}")
        VR->>Git: list refs matching "v*"
        Git-->>VR: ["v2.0.0", "v2.1.3"]
        VR-->>R: "v2.1.3"
    end
    R-->>C: resolved ref = "v2.1.3"
Loading

The flow below shows the producer path that now emits tag_pattern into marketplace.json.

flowchart LR
    subgraph Producer["apm pack"]
        E["_resolve_entry()"]
        RP["ResolvedPackage\n(effective_tag_pattern)"]:::new
        OM["output_mappers.py\nemit tag_pattern"]:::new
        MJ["marketplace.json\nsource.tag_pattern"]:::new
    end
    subgraph Consumer["apm install"]
        MP["MarketplacePlugin\n(tag_pattern field)"]:::new
        RS["resolver.py\npass tag_pattern kwarg"]:::new
        VC["resolve_version_constraint"]
    end
    E --> RP --> OM --> MJ --> MP --> RS --> VC
    classDef new stroke-dasharray: 5 5;
    class RP,OM,MJ,MP,RS new;
Loading

Trade-offs

  • Emit tag_pattern for all non-local packages, not only when it differs from the default. Chosen because the consumer has no way to know what the original default was at pack time; always emitting the resolved value is unambiguous. Rejected "emit only when non-default" because it would require the mapper to know the consumer's fallback, coupling two modules.
  • Consumer falls back to DEFAULT_TAG_PATTERN when plugin.tag_pattern is None. Ensures old marketplace.json files (produced before this fix) continue to work with the existing {name}--v{version} convention — no forced migration.
  • Golden fixture updated rather than suppressing the field. The golden file is the reference for correctness; suppressing a correct field would defeat its purpose. Tests that asserted tag_pattern must not appear in source dicts were incorrect post-fix and have been updated.

Benefits

  1. apm install with a semver range now resolves to the correct git tag regardless of the tag-naming convention the marketplace producer chose — eliminating silent install failures.
  2. marketplace.json carries a self-describing tag_pattern field that future tooling (CLI marketplace inspect, drift detection) can use without re-reading apm.yml.
  3. Old marketplace files without tag_pattern continue to install unchanged — zero forced migration.
  4. The producer-to-consumer data contract is now explicit and tested end-to-end.

Validation

pytest (1522 tests, 0 failures)
uv run --extra dev pytest tests/unit/marketplace/ tests/integration/marketplace/ \
  -q --ignore=tests/integration/marketplace/test_live_e2e.py

1522 passed in 17.07s
Lint (ruff + pylint R0801 + auth-signal check)
uv run --extra dev ruff check src/ tests/
All checks passed!

uv run --extra dev ruff format --check src/ tests/
1549 files already formatted

uv run --extra dev python -m pylint --disable=all --enable=R0801 \
  --min-similarity-lines=10 --fail-on=R0801 src/apm_cli/
Your code has been rated at 10.00/10

bash scripts/lint-auth-signals.sh
[+] auth-signal lint clean

Scenario Evidence

# Scenario (user promise) Principle(s) Test(s) proving it Type
1 apm install resolves a semver range against tags matching the marketplace's declared tagPattern, not a hardcoded default Vendor-neutral, Governed by policy tests/unit/marketplace/test_version_resolver.py::TestResolveVersionConstraint::test_custom_slash_tag_pattern_resolves
tests/unit/marketplace/test_version_resolver.py::TestResolveVersionConstraint::test_custom_slash_pattern_caret_picks_highest (regression-trap for #2319)
unit
2 A marketplace built with a non-default tagPattern passes that pattern to the consumer resolver Vendor-neutral tests/unit/marketplace/test_versioned_resolver.py::TestResolveMarketplacePlugin::test_semver_range_uses_custom_tag_pattern unit
3 apm pack embeds tag_pattern in marketplace.json source dicts Governed by policy tests/integration/marketplace/test_build_integration.py::TestTagPatternClosure::test_producer_emits_tag_pattern_in_source integration
4 Consumer parses tag_pattern from marketplace.json into MarketplacePlugin Governed by policy tests/integration/marketplace/test_build_integration.py::TestTagPatternClosure::test_consumer_parses_tag_pattern_from_source integration
5 Full producer→consumer closure: pack with custom pattern, install resolves against matching tags Vendor-neutral, Governed by policy tests/integration/marketplace/test_build_integration.py::TestTagPatternClosure::test_consumer_resolver_uses_tag_pattern_from_marketplace_json integration
6 Existing marketplace files without tag_pattern continue to resolve using the original default Governed by policy, DevX tests/unit/marketplace/test_version_resolver.py::TestResolveVersionConstraint::test_default_pattern_unaffected_by_fix unit

How to test

  • git checkout sergio-sisternes-epam-fix-2319-semver-tag-pattern && uv run --extra dev pytest tests/unit/marketplace/ tests/integration/marketplace/ -q --ignore=tests/integration/marketplace/test_live_e2e.py — expect 1522 passed, 0 failed.
  • Open tests/fixtures/marketplace/golden.json — each plugin's source object should contain "tag_pattern": "v{version}".
  • In tests/integration/marketplace/test_build_integration.py, search for TestTagPatternClosure — three integration tests exercise the producer-to-consumer closure with a custom {name}/{version} pattern.
  • Open packages/apm-guide/.apm/skills/apm-usage/dependencies.md lines 211-224 — the stale {name}--v{version} reference is replaced with the correct dynamic description.
  • Review CHANGELOG.md under [Unreleased] for the [Fixed] entry describing this change.

Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

When a consumer resolves a marketplace dependency with a semver range
(e.g. version: "~2.1.0"), APM now reads the tag naming pattern declared
by the marketplace producer instead of always falling back to the
hardcoded "{name}--v{version}" convention.

Changes:
- builder.py: add effective_tag_pattern to ResolvedPackage; thread it
  from _resolve_entry through _resolve_explicit_ref and
  _resolve_version_range.
- output_mappers.py: emit tag_pattern in the remote source dict so
  marketplace.json carries the producer's chosen pattern.
- models.py: parse tag_pattern from the source dict into
  MarketplacePlugin.
- resolver.py: pass plugin.tag_pattern (or DEFAULT_TAG_PATTERN
  fallback) to resolve_version_constraint.
- Update golden fixture and existing unit tests to reflect the new
  tag_pattern field in source dicts.
- Add unit and integration tests covering the full producer-to-consumer
  closure.
- Update docs and CHANGELOG.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 25, 2026 11:45
@sergio-sisternes-epam sergio-sisternes-epam added the panel-review Trigger the apm-review-panel gh-aw workflow label Jul 25, 2026

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 marketplace dependency semver-range resolution by propagating the producer-declared tag naming pattern (tagPattern / tag_pattern) into marketplace.json and ensuring apm install uses it when resolving version constraints, restoring producer/consumer symmetry and preventing “no matching tag” failures for non-default tag layouts.

Changes:

  • Producer: thread an effective_tag_pattern through the marketplace build pipeline and emit source.tag_pattern into marketplace.json.
  • Consumer: parse tag_pattern into MarketplacePlugin and pass it into resolve_version_constraint() during version constraint resolution.
  • Tests/docs: extend unit + integration coverage for end-to-end closure, update golden fixture, and refresh dependency docs + changelog.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/apm_cli/marketplace/builder.py Tracks and carries an effective tag pattern alongside resolved package data.
src/apm_cli/marketplace/output_mappers.py Emits tag_pattern into marketplace JSON source objects.
src/apm_cli/marketplace/models.py Parses tag_pattern from marketplace.json into the consumer model.
src/apm_cli/marketplace/resolver.py Uses parsed tag_pattern when resolving semver ranges to git tags.
tests/unit/marketplace/test_version_resolver.py Adds unit coverage for custom slash tag patterns and default behavior.
tests/unit/marketplace/test_versioned_resolver.py Ensures resolver forwards tag_pattern into version constraint resolution.
tests/unit/marketplace/test_marketplace_resolver.py Updates expected call args for version constraint resolution.
tests/unit/marketplace/test_builder.py Updates assertions around which keys may appear in emitted source dicts.
tests/integration/marketplace/test_build_integration.py Adds producer→consumer integration tests validating tag_pattern closure.
tests/fixtures/marketplace/golden.json Updates golden fixture to include emitted tag_pattern.
packages/apm-guide/.apm/skills/apm-usage/dependencies.md Updates dependency docs to describe tag-pattern-based resolution.
docs/src/content/docs/consumer/manage-dependencies.md Notes that marketplace dict dependencies resolve using marketplace tag naming patterns.
CHANGELOG.md Adds an Unreleased Fixed entry describing the bugfix.
Comments suppressed due to low confidence (1)

src/apm_cli/marketplace/resolver.py:989

  • The fallback debug log still hardcodes the old "%s--v*" tag layout, but this resolver now tries plugin.tag_pattern which can be something else (e.g. {name}/{version}). Updating the message to reflect the actual pattern attempted would make debugging tag resolution failures less misleading.
                    tag_pattern=plugin.tag_pattern or DEFAULT_TAG_PATTERN,
                    host=source.host,
                    token=token,
                    auth_scheme=auth_scheme,
                    auth_resolver=auth_resolver,

Comment thread packages/apm-guide/.apm/skills/apm-usage/dependencies.md Outdated
Comment thread CHANGELOG.md Outdated
Comment thread src/apm_cli/marketplace/builder.py Outdated
…eld comment

- CHANGELOG: condense to one line, reference PR #2366 (not issue #2319),
  resolve merge conflict with main's #2069 entry
- dependencies.md: distinguish producer default (v{version}) from legacy
  consumer fallback ({name}--v{version}) per reviewer feedback
- builder.py: clarify effective_tag_pattern comment -- it is for consumer-side
  semver resolution closure, not used for explicit ref entries

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

panel-review Trigger the apm-review-panel gh-aw workflow

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Consumer-side semver range resolution ignores marketplace's declared tag_pattern, hardcodes {name}--v{version}

2 participants