Skip to content

fix(marketplace): repo-scoped plugin ids — one contract for browse, install, and update - #43

Merged
tajanthind merged 1 commit into
mainfrom
fix/marketplace-repo-scoped-plugin-ids
Sep 10, 2026
Merged

tajanthind merged 1 commit into
mainfrom
fix/marketplace-repo-scoped-plugin-ids

Conversation

@tajanthind

Copy link
Copy Markdown
Owner

Problem

Two halves of the same API disagreed about what a plugin id means: browse de-duplicated by id (last repo in query order wins, docstring claimed "newest"), install picked the first repository that had the id, and update/latest_versions() scanned every repo. Repo insertion order was an implicit side effect; install requests carried no provenance (source_repo_id was ignored).

The contract (now single, documented in docs/architecture.md → "Marketplace identity")

  • Browse = one entry per (repository, plugin). Duplicate ids across enabled repos are never merged; every card names its repo, lists other publishers (also_in_repos), and is flagged installed_elsewhere when the id is installed from a different repo.
  • Installs carry provenance. install(plugin_id, version, source_repo_id) records the source on InstalledPlugin.source_repo_id. Without a source, an id published by >1 enabled repo is refused as ambiguous (candidates listed) — never "first repo that has it". A version/id the chosen repo doesn't publish is refused.
  • Updates follow provenance. update() re-installs from the repo the plugin was installed from; latest_versions() is source-aware, so a duplicate id in a second repo never drives update badges or pulls in a different fork.
  • Duplicates are detected, not ignored. Add/refresh-repository responses return conflict warnings; marketplace cards render inline "Also in: …" warnings.
  • Switching sources is explicit — a labelled UI action, never an ordering artifact.
  • Config-as-code export records source_repo_url; import re-installs from the same repository URL.

Files

  • plugins/registry.py — list_available() per-repo; install(..., source_repo_id); _repos_publishing(); repository_conflicts(); source-aware update()/latest_versions().
  • plugins/api.py — /install accepts source_repo_id; add/refresh return conflict warnings; export/import provenance.
  • static/app.js — install/update/switch buttons send provenance; "Also in" + "installed from another repo" UI; warning toasts.
  • docs/architecture.md — new "Marketplace identity" section.
  • tests/architecture/test_marketplace_repo_identity.py — new: per-repo browse, ambiguity refusal, provenance, source-aware update, conflict detection.

Verification

  • Architecture suite 25/25 pass (24 existing + new); smoke test passes.

…nstall, and update

Multi-repo duplicate-id handling was incoherent: browse de-duplicated by id
("newest/whatever-wins"), install picked the FIRST repository that had the
id, and update flags scanned every repo. Ordering (repo insertion time) was
an implicit side effect and there was no provenance in install requests.

New, documented contract (docs/architecture.md, "Marketplace identity"):

- Browse returns ONE entry per (repository, plugin) — duplicate ids across
  enabled repositories are never merged; every entry lists the other
  publishers (`also_in_repos`) and is labelled `installed_elsewhere` when
  the id is installed from a different repository.
- install(plugin_id, version, source_repo_id) — provenance is explicit and
  recorded on InstalledPlugin.source_repo_id. Without a source, an id
  published by >1 enabled repository is REFUSED as ambiguous (candidates
  listed); a version/id the chosen repo does not publish is refused.
- update() re-installs from the repository the plugin was INSTALLED from —
  never from whichever repo is checked first. latest_versions() is
  source-aware so duplicate ids elsewhere never drive update badges.
- Duplicates are detected, not ignored: add/refresh-repository responses
  carry conflict warnings; marketplace cards show "Also in: <repo>" inline.
- Config-as-code export now records source_repo_url per plugin; import
  re-installs from the same repository URL.

UI: install/update/switch buttons pass source_repo_id; switching an id
installed from another repository is an explicit labelled action; conflict
warnings render as toasts + inline card warnings.

New arch test tests/architecture/test_marketplace_repo_identity.py pins the
contract (per-repo browse, ambiguity refusal, provenance recording, source-
aware update, conflict detection).
@tajanthind
tajanthind merged commit d848253 into main Sep 10, 2026
2 checks passed
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.

1 participant