feat: implement minimum host version checks for plugin installation and updates - #355
Conversation
There was a problem hiding this comment.
ℹ️ No critical issues — one sequencing observation in the body.
Reviewed changes
Reviewed the initial diff adding minCoreVersion enforcement across the plugin lifecycle.
- Added
PluginManifest.MinCoreVersionfield and strict semver validation in the domain layer (entity.go, newversion.go). plugin_service.gonow enforces the minimum host version inInstallPluginandUpdatePlugin, returningPLUGIN_INCOMPATIBLE_HOST_VERSIONwith structured details.- Extended
apierr.Errorwith aDetailsmap and surfaced it in HTTP error envelopes aserror_details. - The web marketplace panel now maps plugin install/upgrade errors — especially incompatible-host-version — to localized per-card messages with version interpolation.
- Added translations for all supported locales and updated plugin-system docs.
ℹ️ Incompatible-host checks run after side effects in marketplace upgrades
In UpgradeMarketplacePlugin, the new manifest is downloaded, migrations run, and the WASM runtime is reloaded before svc.UpdatePlugin enforces CheckMinCoreVersion. If the host is too old, cleanupArtifacts removes the downloaded files, but the DB migrations and runtime load have already occurred. Consider calling manifest.CheckMinCoreVersion(hostVersion) immediately after downloading and validating the manifest so the request fails fast before any side effects.
This does not block merge — the current cleanup path prevents persisted inconsistency — but moving the gate earlier removes a window where an incompatible plugin briefly mutates the instance.
Kimi K2 (free via Pullfrog for OSS) | 𝕏
…tion and upgrades
There was a problem hiding this comment.
Important
The host-version gate in the marketplace-upgrade handler runs after the installer has already overwritten the currently installed plugin's artifacts.
Reviewed changes
Reviewed the delta since the prior pullfrog review (64e5fa0).
- Renamed the strict semver parser to exported
ParseSemverand addedCompareSemverin the plugin domain, with coverage inentity_test.go. - Added
CheckHostCompatibilityto the domainServiceinterface and the concrete plugin service, centralizingPLUGIN_INCOMPATIBLE_HOST_VERSIONerror construction. - Updated
InstallPlugin/UpdatePluginto callCheckHostCompatibility;plugin_handler.gonow uses the shared domainCompareSemverfor upgrade ordering. - Moved the host-version gate in
UpgradeMarketplacePluginso it runs before migrations and WASM runtime reload, addressing the sequencing concern from the previous review. - Updated the HTTP error envelope to resolve
*apierr.Erroronce and emiterror_details. - Added handler-level mock method and tests for the standalone
CheckHostCompatibilitycall.
⚠️ Marketplace upgrade still overwrites installed artifacts before the host-version gate
installer.Install inside UpgradeMarketplacePlugin removes and replaces backend/frontend/migration files in-place. If CheckHostCompatibility fails a few lines later, the cleanup Uninstall removes the newly downloaded files, but the old files were already deleted by Install. The plugin record remains in the database while its artifacts are gone, leaving the installed plugin broken.
Move the host-version gate so it runs before any destructive install step, or change the installer to stage downloads in a temporary directory and only swap them in once all pre-replacement checks pass.
Technical details
# Marketplace upgrade artifact safety
## Affected sites
- `services/api/internal/transport/http/handler/plugin_handler.go:383` — `h.installer.Install` overwrites backend/frontend/migration files before compatibility is checked.
- `services/api/internal/transport/http/handler/plugin_handler.go:404` — `h.svc.CheckHostCompatibility` is called after the destructive install.
## Required outcome
- An incompatible marketplace upgrade must leave the currently installed plugin's artifacts intact.
- The existing plugin must keep running after the API returns `PLUGIN_INCOMPATIBLE_HOST_VERSION`.
## Suggested approach
Fetch only the manifest tarball from the marketplace entry before downloading the full artifact set; run `CheckHostCompatibility` on that manifest, or refactor `installer.Install` to stage all artifacts in a temp directory and only replace the real directories once every pre-replacement gate has passed.Note: 1 inline comment(s) dropped because they did not anchor to lines inside the PR diff:
services/api/internal/transport/http/handler/plugin_handler.go:383-404(RIGHT) — start_line 383 (RIGHT) is not inside a diff hunk
Kimi K2 (free via Pullfrog for OSS) | 𝕏
… upgrade processes
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
Reviewed the delta since prior pullfrog review (6002a04).
- Moved the host-version gate inside
Installer.Installby adding acheckManifestcallback that runs on the downloaded manifest immediately after parsing/validation and before any existing installed backend/frontend/migration/MCP/skills directory is removed or overwritten, fixing the prior marketplace-upgrade sequencing concern. - Added
installer_test.goregression coverage: a rejected callback leaves existing artifacts untouched, an accepted callback lets writes proceed, and a nil callback is still allowed. InstallMarketplacePluginandUpgradeMarketplacePluginnow passh.svc.CheckHostCompatibilitytoinstaller.Installand map installer errors throughinstallArtifactsError, preserving*apierr.Errordetails such asPLUGIN_INCOMPATIBLE_HOST_VERSIONinstead of flattening them toCodeBadRequest.- Removed the now-redundant explicit
CheckHostCompatibility+ cleanup block inUpgradeMarketplacePluginand the localcompareSemver/parseSemverhelpers; upgrade ordering now uses the exportedplugindom.CompareSemver. presenter.Errorresolves*apierr.Erroronce and emitserror_details;PLUGIN_INCOMPATIBLE_HOST_VERSIONmaps to HTTP 409 Conflict.- Extended tests in
plugin_handler_test.go,response_test.go, andplugin_service_test.gofor the callback wiring, error-detail envelope behavior, and directCheckHostCompatibilityusage. - Added web marketplace per-card error display keyed by plugin name/install ID, with localized interpolation of
required_version/host_versionfromerror_detailsand fallbacks when details are absent.
Local go test passed for the affected API packages (internal/domain/plugin, internal/apierr, internal/transport/http/presenter, internal/service/plugin, internal/platform/plugin, internal/transport/http/handler).
Kimi K2 (free via Pullfrog for OSS) | 𝕏

Summary
Adds a
minCoreVersionfield to the plugin manifest so a plugin can declare the minimum Paca (host) version it requires. The host now enforces this on every path that persists a manifest and rejects incompatible installs/upgrades with a newPLUGIN_INCOMPATIBLE_HOST_VERSIONerror, surfaced in the marketplace UI as a friendly, localized, per-card message.Backend
plugindom.PluginManifest.MinCoreVersion: optional strictX.Y.Z(orvX.Y.Z) semver string; validated inValidate().plugindom.PluginManifest.CheckMinCoreVersion(hostVersion)(version.go): strict-parsesMinCoreVersion, leniently parses the running build's version (toleratesvprefix and pre-release/build suffixes), and compares major.minor.patch. A host version with no numeric core (e.g. the"dev"default for local/unreleased builds) is treated as unconstrained rather than rejected.plugin_service.Servicenow takes ahostVersionviaWithHostVersion(...)and callsCheckMinCoreVersionin bothInstallPluginandUpdatePlugin, returningapierr.CodePluginIncompatibleHostVersion(mapped to HTTP 409) when the running build is older. Wired up in bootstrap viapluginsvc.New(pluginRepo).WithHostVersion(cfg.Release.Version).apierr.Errorgained aDetails map[string]stringfield andNewWithDetails(...)constructor for structured, non-localized error context. The HTTP error envelope now includes an optionalerror_detailsfield (omitted when empty) alongsideerror_code/error. ForPLUGIN_INCOMPATIBLE_HOST_VERSIONthis carriesplugin_id,required_version, andhost_versionso clients can build their own localized message instead of showing the English-onlyerrorstring.Frontend
PluginMarketplacePanel: install/upgrade failures are now shown inline on the affected plugin card instead of failing silently. Errors are tracked per-plugin (installErrors/upgradeErrorsmaps) and cleared on the next attempt or on success.resolveMutationErrorMessagemaps known plugin error codes to translated copy;PLUGIN_INCOMPATIBLE_HOST_VERSIONinterpolatesrequired_version/host_versionfromerror_detailswhen present, falling back to a generic translated message otherwise.lib/api-error.ts: addedPluginNotFound,PluginNameTaken,PluginAlreadyUpToDate,PluginDowngradeNotAllowed,PluginIncompatibleHostVersioncodes, plusgetApiErrorMessage/getApiErrorDetailshelpers and theerror_detailsfield onApiErrorEnvelope.marketplace.card.errors.*added to all locales (en, es, fr, ja, ko, pt-BR, ru, vi, zh-CN).Docs
minCoreVersion)" section documenting enforcement points and theerror_detailscontract.minCoreVersionadded to the example manifest, an explainer section, a semver-bump callout, and an updated pre-release checklist item.Test plan
go test ./...inservices/api— new coverage forPluginManifest.Validate/CheckMinCoreVersion(entity_test.go), service-level install/update gating (plugin_service_test.go),apierr.NewWithDetails(codes_test.go), anderror_detailspresence/omission in the response envelope (response_test.go).vitestinapps/web— new PluginMarketplacePanel.test.tsx covering install success, the interpolated incompatible-host-version message, the generic fallback whenerror_detailsis missing, a generic message for unrecognized errors, and error clearing on retry.