fix(marketplace): propagate marketplace --ref to string sources; fix GitLab slash-ref proxy filenames - #1880
Conversation
Mirrors the native GitLab resolver pattern -- but uses HEAD on Artifactory archive URLs as the existence signal, so no separate metadata API is needed. Covers both routing modes: Mode 1 -- explicit FQDN deps (host/artifactory/key/owner/repo/...). Mode 2 -- bare shorthand under PROXY_REGISTRY_URL+PROXY_REGISTRY_ONLY. The resolver walks candidate (owner, repo, virtual_path) splits shallow-first and locks in the first one whose archive responds 2xx-3xx. For unambiguous paths it returns the parse-time ref unchanged. When every candidate is rejected it raises -- distinguishing "missing repo" from auth (401/403) errors so users get an actionable hint instead of silently anchoring on a wrong guess. ``allow_redirects=False`` on the HEAD call keeps the Bearer token from leaking cross-host. Removes the parse-time marker-segment heuristic (_VIRTUAL_PATH_ROOT_SEGMENTS / _ARTIFACTORY_VIRTUAL_MARKERS / _ARTIFACTORY_VIRTUAL_FILE_EXTENSIONS) that previously decided the boundary based on hard-coded directory names like ``skills/`` or ``prompts/``. Parse-time now defaults to all-as-repo with a structural file-extension rule on the last segment; the install-time resolver is the authoritative source of the boundary. Also fixes a pre-existing URL-roundtrip bug where ``to_github_url`` -> ``parse`` folded the ``artifactory/<key>`` prefix into ``repo_url``, causing the downloader to build double-prefixed archive URLs and 404. ``_validate_url_repo_path`` now strips the prefix before returning the bare ``owner/repo`` slug.
- parse_artifactory_path: reject ``owner//virtual`` (empty segment before the explicit boundary) instead of falling through and returning an empty ``repo`` slug. - _proxy_routing_target: thread the proxy ``scheme`` (https/http) through the resolver into ``build_artifactory_archive_url`` so installs that intentionally route through an ``http://`` proxy (under ``PROXY_REGISTRY_ALLOW_HTTP=1``) probe over the same transport. - _CandidateStatus.EXISTS: update inline comment to say ``2xx or 3xx`` (redirect-inclusive), matching the actual classification in ``_candidate_archive_status``. - CHANGELOG: split the parse-time-behavior note into explicit-FQDN vs bare-shorthand modes (the all-as-repo default applies only under ``PROXY_REGISTRY_ONLY``; Mode 1 stays shallow). - package_resolution: clarify the ``direct_virtual_resolved`` docstring -- GitLab path gates on ``is_virtual``, Artifactory path gates on probe rebuild; both signal "persist as structured ``git:`` + ``path:`` entry". - Add two regression tests for the empty-segment edge case (parser + iterator).
…h-support # Conflicts: # CHANGELOG.md # src/apm_cli/models/dependency/reference.py
Adds focused regression-trap tests for the new install-pipeline boundary resolver (microsoft#1472) covering the contracts a future refactor could silently break: - allow_redirects=False on every HEAD probe (token-leak guard against proxy-issued cross-host redirects) - Mode 2 Authorization header is sourced from RegistryConfig (proxy bearer), never from AuthResolver (which returns the github.com PAT and would leak it to the proxy host) - 403 (like 401) classifies as AUTH; mixed 401/non-auth-4xx demotes the candidate set to MISSING so the user-facing error stays accurate (avoids misreporting a missing repo as auth failure) - 429 and other non-{401,403} 4xx are MISSING, not AUTH (guards against accidental broadening of the AUTH set) - _split_owner_repo subgroup folding for 3+ segment GitLab boundaries (group/sub1/sub2/repo correctly folds to owner=group, repo=sub1/sub2/repo) Both mutation-break gates were exercised: flipping allow_redirects to True and rewiring Mode 2 to AuthResolver each made the corresponding test fail; restoring the source code made them pass. Also applies ruff format pass to bring three files in line with the canonical formatter (pre-existing drift on the PR branch that would have failed CI lint check). Co-authored-by: chkp-roniz <chkp-roniz@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Conflict resolution: - CHANGELOG.md: combined the [Unreleased] entries from origin/main (semver ranges, ``apm deps why``, install exit-code BREAKING change, pinned-constraint fix) with this PR's Artifactory entries. Panel follow-ups addressed inline: - doc-writer: added ``(microsoft#1472)`` suffix to every CHANGELOG entry belonging to this PR, per ``.github/instructions/changelog.instructions.md``. - supply-chain-security-expert / devx-ux-expert (convergent): the boundary probe used to silently swallow transport errors (``requests.RequestException``), which could lock in a wrong owner/repo split or surface a misleading "missing repo" error during a transient DNS/TLS/timeout outage. Added a fourth candidate status ``_CandidateStatus.INCONCLUSIVE`` that fires when every URL shape for a candidate raised a transport error (no HTTP response observed at all). The resolver now fails closed on all-INCONCLUSIVE: raises a network-specific ``ValueError`` carrying the underlying exception and the proxy host, instead of mis-classifying as MISSING. Per-URL-shape transport errors are still tolerated as long as at least one URL shape per candidate produced an HTTP response. Two regression-trap tests landed in ``tests/unit/install/test_artifactory_resolver.py``: * ``TestInconclusiveFailsClosed::test_all_transport_errors_raise_network_specific_error`` * ``TestInconclusiveFailsClosed::test_partial_transport_failure_does_not_fail_closed``
…GitLab slash-ref proxy filenames Two related fixes for marketplace ref handling: 1. `apm install plugin@marketplace` now fetches from the registered --ref branch instead of silently falling back to the repo default branch. `resolve_marketplace_plugin` was not propagating `source.ref` downstream. Covers GitHub-family hosts (ref appended to canonical) and GitLab hosts (ref injected into DependencyReference). Guards skip main/HEAD and respect explicit version_spec overrides. Mirrors microsoft#1824. 2. GitLab archive URLs with slash-containing branch names (e.g. feat/my-feature) now produce Artifactory-proxyable filenames: slash preserved in the path segment, replaced with '-' in the filename. Previously PROXY_REGISTRY_ONLY=1 installs returned HTTP 404 because the filename contained a literal slash. This is the proxy scenario absent from microsoft#1824. 9 regression tests added; 0 pre-existing failures introduced. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR improves dependency resolution in two main areas: (1) propagating a marketplace-registered --ref through to plugin resolution so installs don’t silently fall back to the default branch, and (2) making Artifactory/GitLab archive URL handling more robust (including slash-containing refs and nested subgroup repo paths) by introducing an install-time Artifactory boundary probe and updating parsing/orchestration accordingly.
Changes:
- Propagate marketplace
source.refinto downstream plugin canonical/dependency-reference resolution (GitHub-family#refsuffix; GitLab viaDependencyReference.reference). - Add deterministic Artifactory boundary probing (
HEADarchive URLs) and wire it into install-time dependency resolution, including support for nested GitLab subgroup repos behind a proxy. - Normalize GitLab archive filenames for slash-containing refs and adjust Artifactory URL building and parsing to avoid malformed proxy paths.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
src/apm_cli/marketplace/resolver.py |
Threads marketplace --ref into plugin resolution and adds guard rails around ref injection/override. |
src/apm_cli/utils/github_host.py |
Adds Artifactory boundary candidate enumeration, refines Artifactory parsing, and fixes GitLab archive filename construction (basename + slash-ref normalization). |
src/apm_cli/install/artifactory_resolver.py |
Introduces install-time Artifactory boundary resolver that probes archive URLs to confirm the correct owner/repo/virtual split. |
src/apm_cli/install/package_resolution.py |
Plumbs the Artifactory boundary resolver into dependency reference resolution and persistence decisions. |
src/apm_cli/models/dependency/reference.py |
Adds Artifactory probe ref constructor and adjusts shorthand parsing/URL validation for proxy/nested paths. |
src/apm_cli/deps/artifactory_orchestrator.py |
Updates owner/repo splitting to preserve nested subgroup repo slugs. |
src/apm_cli/commands/install.py |
Wires Artifactory boundary resolution into apm install and updates persistence condition naming. |
tests/unit/marketplace/test_marketplace_resolver.py |
Adds regression tests for marketplace registered-ref propagation (GitHub-family + GitLab). |
tests/unit/test_artifactory_support.py |
Expands Artifactory parsing/probing/URL-construction tests, including nested subgroup and slash-ref behaviors. |
tests/unit/install/test_artifactory_resolver.py |
Adds focused regression traps for redirect/token-audience/error-classification invariants in the Artifactory resolver. |
tests/unit/deps/test_artifactory_orchestrator.py |
Adds tests ensuring subgroup folding in _split_owner_repo is preserved. |
docs/src/content/docs/enterprise/registry-proxy.md |
Documents nested-group repo behavior behind the registry proxy and the deterministic boundary probe. |
CHANGELOG.md |
Adds Unreleased notes for the new Artifactory boundary probing behavior and the marketplace/GitLab fixes. |
…h ASCII Addresses Copilot review comments on PR microsoft#1880. Repo encoding rules require source files to stay within printable ASCII to avoid Windows cp1252 UnicodeEncodeError in some environments. - github_host.py: U+2192 arrow (->) - resolver.py: two U+2014 em-dashes (--) - test_marketplace_resolver.py: U+2014 em-dash and two U+2500 separators - test_artifactory_support.py: U+2014 em-dash and three U+2500 separators Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
_candidate_archive_status() was returning AUTH whenever any URL shape returned 401/403, even if other shapes returned non-auth 4xx (e.g. 404). This misreported a boundary-resolution failure as an authentication failure, giving users a misleading error message. Per the module spec: AUTH is returned only when *every* observed response was 401/403 with no non-auth 4xx seen. A mix means at least one URL shape was definitively absent -- the candidate is MISSING and the unresolved- boundary error path is triggered instead. Added saw_non_auth_4xx flag; AUTH gate now requires `saw_auth and not saw_non_auth_4xx`. Addresses Copilot review comment on PR microsoft#1880. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Resolution strategy:
- CHANGELOG.md: kept both HEAD's Unreleased entries (Artifactory boundary probe,
semver ranges, nested GitLab groups, slash-ref filename fix, marketplace --ref)
and main's entries (org-wide policy discovery, GitLab host preservation,
pack version field, audit orphaned, codex install); preserved all new release
sections (0.16-0.21) from main.
- install.py: took main's refactored normalize_and_merge_skill_subset() call.
- artifactory_resolver.py: kept HEAD's saw_non_auth_4xx mixed-401+404 demote-to-MISSING
logic; took main's logger parameter, verbose_detail routing, and improved
error message with // notation hint.
- package_resolution.py: took main's logger parameter pass-through.
- marketplace/resolver.py: took main's cleaner effective_ref / split propagation
blocks for GitLab and version-spec overrides.
- github_host.py: kept HEAD's slash-in-ref filename fix (ref.replace('/', '-')).
- test_marketplace_resolver.py: kept HEAD's more comprehensive
TestMarketplaceRegisteredRefPropagation class (7 tests vs main's 4).
- test_artifactory_support.py: kept HEAD's slash-ref filename tests; resolved
add/add conflict by taking main's extended docstring and new test classes
(TestRegistryOnlyNestedShorthand, TestArtifactoryBoundaryResolver,
TestArtifactoryOrchestratorNestedRepo) once.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
CHANGELOG only: absorbed two new [Unreleased] Added items (microsoft#1842 gh-aw apm-version, microsoft#1881 apm config set target) and a new Removed section (microsoft#1865 --trust-canvas-extensions replaced by allowExecutables gate). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
APM Review Panel:
|
| Persona | B | R | N | Takeaway |
|---|---|---|---|---|
| Python Architect | 0 | 1 | 2 | Correct bug fixes with good test coverage; marketplace resolver function continues growing and would benefit from extraction of ref-propagation logic. |
| Supply Chain Security | 0 | 0 | 2 | No security regression: slash-to-dash is filename-only, ref source is local user config, AUTH/MISSING logic is correct for single-URL case. |
| Auth Expert | 0 | 0 | 0 | Auth classification change is correct: single-host 401-only still returns AUTH; allow_redirects=False and token-audience routing preserved. |
| Test Coverage Expert | 0 | 1 | 1 | Unit tests are strong mutation-break traps for all three changes; integration-tier gap for marketplace --ref propagation flow is the only substantive finding. |
| Doc Writer | 0 | 0 | 1 | CHANGELOG fix in progress (shepherd fold pass); registry-proxy.md additions are clean. |
| DevX UX Expert | 0 | 0 | 2 | Solid bug-fix UX: error messages distinguish AUTH vs MISSING clearly; ref propagation guards are sensible; no user-facing regressions. |
| CLI Logging Expert | 0 | 0 | 0 | No CLI output regressions; error reclassification is correct; marketplace debug logs follow conventions. |
| OSS Growth Hacker | 0 | 0 | 2 | Community fix unblocks GitLab enterprise segment; consider amplifying in release notes and contributor shout-out. |
| Performance Expert | 0 | 0 | 2 | No perf regression in artifactory_resolver (same HTTP call count). ADO REST fast-path removal adds ~200-500ms/source for ADO marketplace fetches -- acceptable simplification. |
B = blocking-severity findings, R = recommended, N = nits.
Counts are signal strength, not gates. The maintainer ships.
Top 4 follow-ups
- [Test Coverage Expert] Add integration test for apm install plugin@marketplace with non-default --ref -- Missing integration-tier coverage on a user-facing promise surface; unit tests prove logic but not the full install flow wiring.
- [Python Architect] Extract ref-propagation helpers from resolve_marketplace_plugin (~200-line function) -- Five sequential ref-propagation blocks accumulating; extraction improves testability and readability without changing behavior.
- [DevX UX Expert] Add logger.debug when marketplace ref guard fires on non-main default branch -- Silent ref-drop is confusing for users whose default branch is not named main; debug log aids troubleshooting.
- [Supply Chain Security] URL-encode ref values before interpolation into archive URLs (pre-existing tech debt) -- Not introduced by this PR but noted; special characters in ref names could produce malformed URLs.
Recommendation
Ship after the shepherd fold pass restores CHANGELOG entries. Two follow-up issues: integration test for marketplace --ref flow, and resolver refactor to extract ref-propagation helpers.
Full per-persona findings
Python Architect
-
[recommended] resolve_marketplace_plugin is accumulating ref-logic blocks that should be extracted at
src/apm_cli/marketplace/resolver.py:842
The function is ~200 lines with five sequential ref-propagation/override blocks. Extracting_build_url_source_dep_refand_resolve_version_spec_canonicalas standalone helpers would improve testability and readability without changing behavior.
Suggested: Extract_build_url_source_dep_ref(plugin, source, version_spec)and_resolve_version_spec_canonical(canonical, source, version_spec, plugin_name, marketplace_name, auth_resolver)as standalone helpers. -
[nit] Underscore-prefixed locals inside url-source block are unconventional for non-throwaway values at
src/apm_cli/marketplace/resolver.py:846
Python convention reserves leading underscores for unused/private names._url,_ref,_effective_ref,_entryare all actively used. -
[nit] The main/HEAD exclusion set is duplicated across two blocks; use a module-level frozenset constant at
src/apm_cli/marketplace/resolver.py:914
Suggested:_DEFAULT_BRANCH_REFS = frozenset({"main", "HEAD"})thensource.ref not in _DEFAULT_BRANCH_REFS
Supply Chain Security Expert
-
[nit] Ref value is URL-interpolated without encoding at
src/apm_cli/utils/github_host.py:781
Pre-existing; not introduced by this PR. A ref containing#,?, or%would corrupt the URL. Git rejects most problematic characters in ref names so practical risk is low. Considerquote(ref, safe='/')for path segment in a follow-up. -
[nit] source.ref guard pattern differs slightly between two sites at
src/apm_cli/marketplace/resolver.py:916
Minor inconsistency in defensive pattern -- both are safe in practice.
Auth Expert
No findings.
Test Coverage Expert
-
[recommended] Marketplace --ref propagation has no integration-tier test exercising the full install flow
7 unit tests mockfetch_or_cacheandget_marketplace_by_name, proving the resolver logic in isolation. No integration test exercisesapm install plugin@marketplacewhere marketplace was added with non-default--ref. The unit tests are solid and mutation-breaking, so this is recommended not blocking.
Proof (missing):tests/integration/marketplace/test_ref_propagation_e2e.py::test_install_from_marketplace_with_non_default_ref_downloads_correct_branch-- proves: apm install plugin@marketplace respects the --ref registered on the marketplace source -
[nit] saw_non_auth_4xx regression trap is well-crafted and mutation-breaking at
tests/unit/install/test_artifactory_resolver.py
Proof (passed):tests/unit/install/test_artifactory_resolver.py::TestErrorDiscrimination::test_mixed_401_and_404_demotes_to_unresolved-- proves: Mixed 401+404 responses are classified as MISSING not AUTH, preventing user-facing misdiagnosis
Doc Writer
CHANGELOG [Unreleased] restoration in progress via shepherd fold pass. registry-proxy.md additions are accurate and well-structured with no issues.
- [nit] registry-proxy.md new sections are accurate and well-structured; no issues
DevX UX Expert
-
[nit] Error message uses RST backticks in CLI output at
src/apm_cli/install/artifactory_resolver.py:48
_ARTIFACTORY_BOUNDARY_UNRESOLVEDand_ARTIFACTORY_BOUNDARY_AUTHconstants use double-backtick RST notation. Use single backticks or no backticks for CLI-facing error strings. -
[nit] Silent ref-drop when default branch is not 'main' at
src/apm_cli/marketplace/resolver.py:836
The guard silently drops the ref when the user explicitly pinned--ref mainbut the repo's default has a different name. Consider alogger.debugwhen the guard fires for easier troubleshooting.
CLI Logging Expert
No findings.
OSS Growth Hacker
-
[nit] CHANGELOG entries deserve a 'Enterprise/GitLab' section header or callout in release narrative
The two CHANGELOG bullets are excellent -- they name the symptom, affected persona, and lineage. When cutting the next release, these deserve a dedicated callout. -
[nit] Add troubleshooting FAQ entry: 'My marketplace plugin installs from the wrong branch' pointing to verbose debug log
The new debug log 'Propagated marketplace ref ... to string source canonical' is exactly the breadcrumb an enterprise user would grep for.
Performance Expert
-
[nit] No extra HTTP calls from saw_non_auth_4xx guard -- classification-only change
The_candidate_archive_statuschange adds zero additional HTTP requests. The loop still iterates the same URL set and exits early on the first 2xx/3xx. The new flag is a pure classification refinement post-loop. -
[nit] ADO REST fast-path removal trades ~200-500ms/source for code simplification -- acceptable
The deleted_fetch_ado_restpath was a single HTTP GET. The replacement git-subprocess path adds process-spawn overhead. Acceptable trade-off as marketplace fetches are not in the install hot loop.
This panel is advisory. It does not block merge. Re-apply the
panel-review label after addressing feedback to re-run.
- Restore CHANGELOG.md from current main (refs microsoft#1902, microsoft#1873, microsoft#1897, etc.) - Add two PR-specific Fixed entries: marketplace --ref propagation and GitLab slash-ref proxy filename normalization - Apply ruff format to test_marketplace_resolver.py and test_artifactory_support.py (CI Lint gate fix) - All four lint checks pass: ruff check, ruff format, pylint R0801, auth-signals Co-authored-by: chkp-roniz <chkp-roniz@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…pass) Resolve CHANGELOG conflict: restore all main [Unreleased] entries, preserve the two PR-specific Fixed bullets for marketplace --ref propagation and GitLab slash-ref proxy filename normalization. Co-authored-by: chkp-roniz <chkp-roniz@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
APM Review Panel:
|
| Persona | B | R | N | Takeaway |
|---|---|---|---|---|
| Python Architect | 0 | 0 | 2 | Surgically correct fixes; auth-classification logic is sound, ref.replace('/','-') matches GitLab archive scheme, priority chain is well-guarded. |
| CLI Logging Expert | 0 | 0 | 1 | AUTH vs MISSING classification is semantically correct and actionable; one nit on RST double-backtick notation in error message constants. |
| DevX UX Expert | 0 | 0 | 2 | Solid UX fix restoring predictable --ref behavior; CHANGELOG is clear; AUTH vs MISSING reclassification improves recovery guidance. |
| Supply Chain Security Expert | 0 | 0 | 2 | No blocking security issues; filename normalization is scoped correctly; auth reclassification improves accuracy without weakening posture. |
| OSS Growth Hacker | 0 | 0 | 1 | Strong community contribution from a repeat GitLab enterprise contributor; 9 regression tests, CI green, clear CHANGELOG. |
| Auth Expert | 0 | 0 | 1 | _candidate_archive_status logic is sound; allow_redirects=False prevents token leakage; no auth regressions. |
| Doc Writer | 0 | 1 | 2 | Entries accurately describe regressions; primary concern is verbosity embedding implementation internals not needed in a user-facing CHANGELOG. |
| Test Coverage Expert | 0 | 1 | 1 | Both behavioral changes have unit-tier regression traps (9 pass); integration-tier gap for marketplace --ref propagation tracked in #1918. |
B = blocking-severity findings, R = recommended, N = nits.
Counts are signal strength, not gates. The maintainer ships.
Top 3 follow-ups
- [Test Coverage Expert] Add integration-tier test for marketplace --ref propagation (tracked in test(marketplace): add integration-tier coverage for marketplace --ref end-to-end flow #1918) -- Marketplace install is a critical user-promise surface; unit mocks alone cannot catch HTTP-layer regressions in ref injection.
- [Doc Writer] Trim CHANGELOG entries to user-observable impact; drop implementation internals -- APM CHANGELOG convention is one concise user-facing 'so what' per fix; current entries embed code mechanics.
- [Python Architect] Extract _DEFAULT_REFS = frozenset({'main', 'HEAD'}) to eliminate dual-site drift risk (tracked in refactor(marketplace): extract ref-propagation helpers from resolve_marketplace_plugin #1917) -- Two-site duplication is a latent maintenance trap; a module-level constant makes the contract explicit.
Architecture
classDiagram
direction LR
class MarketplaceSource {
<<ValueObject>>
+name str
+owner str
+repo str
+host str
+ref str
}
class MarketplacePlugin {
<<ValueObject>>
+name str
+source str | dict
}
class DependencyReference {
<<ValueObject>>
+reference str
+virtual_path str
+to_canonical() str
}
class resolve_marketplace_plugin {
<<IOBoundary>>
+resolve(name, mkt, version_spec) ResolvedPlugin
}
class build_artifactory_archive_url {
<<Pure>>
+build(host, prefix, owner, repo, ref, scheme) tuple
}
class _candidate_archive_status {
<<IOBoundary>>
+probe(host, prefix, owner, repo, ref) tuple
}
class _CandidateStatus {
<<EnumLike>>
+EXISTS str
+MISSING str
+AUTH str
+INCONCLUSIVE str
}
resolve_marketplace_plugin ..> MarketplaceSource : reads ref
resolve_marketplace_plugin ..> MarketplacePlugin : reads source
resolve_marketplace_plugin ..> DependencyReference : builds
_candidate_archive_status ..> _CandidateStatus : returns
_candidate_archive_status ..> build_artifactory_archive_url : calls
note for _candidate_archive_status "saw_auth AND NOT saw_non_auth_4xx => AUTH"
note for resolve_marketplace_plugin "Priority: version_spec > path_ref > source.ref > None"
class build_artifactory_archive_url:::touched
class _candidate_archive_status:::touched
class resolve_marketplace_plugin:::touched
classDef touched fill:#fff3b0,stroke:#d47600
flowchart TD
A["apm install plugin@marketplace"] --> B["resolve_marketplace_plugin()"]
B --> C{"string source?"}
C -->|Yes| D{"GitLab host?"}
C -->|dict source| E["dict ref preserved"]
D -->|Yes| F["dep_ref.ref = version_spec or path_ref or source.ref"]
D -->|GitHub-family| G{"version_spec provided?"}
G -->|Yes| H["canonical = base#version_spec"]
G -->|No| I{"source.ref not in main/HEAD AND no # in canonical?"}
I -->|Yes| J["canonical = canonical#source.ref"]
I -->|No| K["canonical unchanged"]
M["_candidate_archive_status()"] --> N["build_artifactory_archive_url()"]
N --> O["HEAD probe each URL shape"]
O --> P{"2xx?"}
P -->|Yes| Q["return EXISTS"]
P -->|No 401/403| R["saw_auth=True"]
P -->|No other 4xx| S["saw_non_auth_4xx=True"]
R --> T{"all URLs exhausted?"}
S --> T
T --> U{"saw_auth AND NOT saw_non_auth_4xx?"}
U -->|Yes| V["return AUTH"]
U -->|No| W["return MISSING"]
Recommendation
Zero blocking findings across 9 panelists. All three triage reservations confirmed resolved with evidence from auth-expert, supply-chain-security-expert, and python-architect. CI is green (13/13), 9 new regression tests pass, deferred work is tracked in #1917 and #1918. This is a clean, bounded community bug fix from a valued repeat contributor that restores correct behavior for GitLab enterprise users. Ship it.
Full per-persona findings
Python Architect
-
[nit] Hardcoded default-ref guard could drift if more defaults are added at
src/apm_cli/marketplace/resolver.py
The set ('main', 'HEAD') appears in two places. If a third default ref (e.g. 'master') is ever added, both sites must be updated. A module-level constant _DEFAULT_REFS = frozenset({'main', 'HEAD'}) would make the contract explicit and searchable.
Suggested: Extract _DEFAULT_REFS = frozenset({'main', 'HEAD'}) at module level and reference it in both guards. -
[nit] _CandidateStatus uses class attributes instead of enum at
src/apm_cli/install/artifactory_resolver.py
A stdlib enum.Enum would give typo-safety and IDE autocomplete. Not worth a refactor in this PR.
Suggested: Future: migrate _CandidateStatus to enum.StrEnum for type safety.
CLI Logging Expert
- [nit] RST double-backtick notation in user-facing error messages renders as literal backticks in terminal at
src/apm_cli/install/artifactory_resolver.py
_ARTIFACTORY_BOUNDARY_UNRESOLVED and _ARTIFACTORY_BOUNDARY_AUTH use RST-style double-backtick quoting. In a terminal, the user sees literal backticks instead of clean quoting.
Suggested: Replace double backticks with single backticks or bare quotes in error message constants.
DevX UX Expert
-
[nit] CHANGELOG entry for the marketplace --ref fix includes implementation detail that users do not need at
CHANGELOG.md
Phrases like 'resolve_marketplace_plugin did not propagate source.ref' and 'Guards prevent double-injection' describe code mechanics, not user-observable behavior.
Suggested: Trim to: 'apm install plugin@marketplace now correctly fetches from the marketplace registered --ref branch. Explicit per-plugin refs still override the marketplace-level ref.' -
[nit] Artifactory error message strings use RST double-backtick notation in a terminal context at
src/apm_cli/install/artifactory_resolver.py
Same issue as cli-logging-expert.
Suggested: Use single backticks or plain quotes in CLI error messages.
Supply Chain Security Expert
-
[nit] Pre-existing: ref not URL-encoded in Artifactory archive URL builder at
src/apm_cli/utils/github_host.py
build_artifactory_archive_url uses raw ref in URL path segments without urllib.parse.quote, unlike other URL builders in the same module. Pre-existing issue; not introduced by this PR. Low attack surface since ref originates from local user config.
Suggested: Consider URL-encoding the ref in path segments in a follow-up PR for consistency with build_raw_content_url and build_ado_api_url. -
[nit] allow_redirects=False is good token-leak prevention at
src/apm_cli/install/artifactory_resolver.py
The HEAD probe correctly uses allow_redirects=False to prevent the Bearer token from leaking to redirect targets. Positive security pattern worth preserving.
Suggested: No change needed -- noting as positive security practice.
OSS Growth Hacker
- [nit] Release note should call out GitLab enterprise unblock explicitly
GitLab+Artifactory proxy users are a high-value enterprise cohort. The CHANGELOG entry is terse for this audience.
Suggested: In release notes, surface GitLab enterprise proxy fix as a standalone bullet and shoutout Ron Izraeli (@chkp-roniz) as repeat community contributor.
Auth Expert
- [nit] Comment could clarify the mixed-signal rationale for future readers at
src/apm_cli/install/artifactory_resolver.py:122
The inline comment explains the 401+404 mixed case but does not cover the 404-first-then-401 ordering. An operator debugging a misconfigured proxy might wonder why they see MISSING instead of AUTH.
Suggested: Expand comment: 'If any shape is definitively absent, the boundary is wrong regardless of auth errors on other shapes.'
Doc Writer
-
[recommended] Both CHANGELOG entries are over-verbose with implementation internals at
CHANGELOG.md
Phrases like 'resolve_marketplace_plugin did not propagate source.ref', 'Guards prevent double-injection', 'ref injected into DependencyReference' are code mechanics. APM CHANGELOG convention is one concise user-facing 'so what' per fix.
Suggested: Trim entry 1 to: 'apm install plugin@marketplace now respects the marketplace registered --ref branch. Previously, installs silently fell back to the repository default branch. Affects both GitHub-family and GitLab-hosted marketplaces. (Fix marketplace --ref not propagated to relative plugin sources (#1811) #1824)'. Trim entry 2 to: 'GitLab archive installs via PROXY_REGISTRY_ONLY=1 no longer return HTTP 404 when the branch name contains a slash (e.g. feat/my-feature). The proxy filename now uses a hyphen separator to match GitLab archive naming.' -
[nit] 'mirrors Fix marketplace --ref not propagated to relative plugin sources (#1811) #1824' is non-standard and ambiguous cross-reference at
CHANGELOG.md
Standard format uses '(#NNNN)' or 'fixes #NNNN'. 'mirrors' implies a duplicate rather than a related fix.
Suggested: Replace with '(Fix marketplace --ref not propagated to relative plugin sources (#1811) #1824, by Ron Izraeli (@chkp-roniz))' or 'follow-on to Fix marketplace --ref not propagated to relative plugin sources (#1811) #1824 (by Ron Izraeli (@chkp-roniz))'. -
[nit] PROXY_REGISTRY_ONLY=1 cited in CHANGELOG without a docs pointer at
CHANGELOG.md
Referencing an undocumented env var in a CHANGELOG creates an implicit docs gap.
Suggested: Add a brief inline gloss or link to the relevant reference page; if undocumented, file a follow-on issue.
Test Coverage Expert
-
[recommended] Marketplace --ref propagation lacks integration-tier test (floor gap); tracked in test(marketplace): add integration-tier coverage for marketplace --ref end-to-end flow #1918 at
src/apm_cli/marketplace/resolver.py
The marketplace install pipeline is a critical user-promise surface. 7 unit tests mock the boundary but no integration-with-fixtures test invokes apm install end-to-end with a non-default --ref. Follow-up tracked in test(marketplace): add integration-tier coverage for marketplace --ref end-to-end flow #1918. Given thorough unit coverage, green CI, and tracked follow-up -- advisory not blocking.
Proof (missing):tests/integration/marketplace/test_live_e2e.py-- proves: apm install plugin@marketplace fetches from registered --ref branch, not repo default [portability-by-manifest,devx] -
[nit] GitLab slash-ref filename fix has unit coverage only; integration probe would cement proxy scenario at
src/apm_cli/utils/github_host.py
Unit test verifies URL string construction but not the full HEAD-probe pipeline with a slash-ref. Low-risk since URL construction is pure string logic and unit assertion is strong.
Proof (passed at unit):tests/unit/test_artifactory_support.py::test_gitlab_slash_ref_filename_normalised-- proves: GitLab archive filename replaces slashes with dashes for slash-containing branch refs [portability-by-manifest,devx]
assert any(u.endswith('/-/archive/feat/my-slash-branch/repo-feat-my-slash-branch.zip') for u in urls)
Performance Expert -- inactive
Not active: no performance-relevant files touched; PR is a correctness bug fix with no hot-path changes, no perf claims, and no measurements.
This panel is advisory. It does not block merge. Re-apply the
panel-review label after addressing feedback to re-run.
e045e88
into
microsoft:main
Summary
apm install plugin@marketplacenow correctly fetches from the marketplace's registered--refbranch instead of silently falling back to the repository's default branch.resolve_marketplace_pluginwas not propagatingsource.refto downstream resolution calls. Covers both GitHub-family hosts (ref appended to canonical as#ref) and GitLab-hosted marketplaces (ref injected intoDependencyReference). Guards prevent double-injection when a plugin's dict source already declares its ownref, and skipmain/HEADas implicit defaults. Mirrors #1824.feat/my-feature) now produce correctly-formed Artifactory-proxyable filenames. The slash is preserved in the path segment (as the GitLab archive API requires) but replaced with-in the archive filename, matching GitLab's own naming convention. Previously,PROXY_REGISTRY_ONLY=1installs from such branches returned HTTP 404 because the generated filename contained a literal slash. This is the proxy scenario that #1824 did not cover.What #1824 was missing
#1824 fixed ref propagation for the resolution path but left a gap in the Artifactory proxy path:
build_artifactory_archive_urlemitted GitLab archive filenames with a literal/when the branch contained a slash. Artifactory rejects such filenames with HTTP 404 underPROXY_REGISTRY_ONLY=1. This PR completes the fix.Test plan
TestMarketplaceRegisteredRefPropagation— GitHub-family and GitLab ref propagation,main/HEADexclusion,version_specoverride precedence, dict-source non-overwriteTestBuildArtifactoryArchiveUrl— slash-ref filename normalisation and single-component ref stabilitymainare unrelated)[Unreleased]>FixedFollow-ups (not blockers, carried from #1824 review panel)
_validate_ref()call on injected refsHEAD-exclusion regression test (covered here intest_github_string_source_head_ref_not_appended)🤖 Generated with Claude Code