Plugins: operate
Verified against v0.21.0 · cmd/parley/main.go, cmd/parley/plugincmd.go, internal/api/catalog.go, internal/api/plugins.go, internal/api/router.go, internal/plugin/pin.go, internal/plugin/bundle/bundle.go, web/src/pages/PluginsPage.tsx, web/src/pages/CatalogPage.tsx, sdk/abi/bundle-v1.md
This is the operator’s side of the plugin system: who is trusted, where bundles live, who may install them, and what to do when a key changes. The bundle format is in Plugin bundle format; what a plugin may do is in Plugin sandbox; writing one is Build your first plugin.
How the pieces fit
Section titled “How the pieces fit”A plugin is a signed .parley bundle. Bundles are stored in Postgres, in an
instance-wide catalog, and every replica re-verifies a stored bundle against
its own trust set before using it. An org admin installs a catalog version
into their org; the install is pinned to that bundle’s digest, so it runs
exactly those bytes. Nothing is fetched at run time.
The org’s Plugins page describes only that org’s installs and grants. If the catalog or installed-plugin list cannot be loaded, Try again retries the request without changing any install or capability grant.
Trust a publisher
Section titled “Trust a publisher”Parley runs only bundles signed by a key you list in PLUGIN_TRUSTED_KEYS
(comma-separated base64 Ed25519 public keys; unset trusts none). A malformed
entry stops the boot.
- The publisher generates a pair with
parley plugin keygen, which prints the key id and the public key, and sends you the public key (the.pubfile). - Get the key id out of band — from the publisher’s release notes, a signed
message, a call — and compare it with the id you compute yourself. Do not take
both from the same message you received the key in. The key id is the first 8
bytes of sha256 of the public key, in hex;
keygenprints it, andparley plugin verifyprints the id of whoever signed a bundle. - Add the public key to
PLUGIN_TRUSTED_KEYSand restart every replica. The trust set is read at boot. - Check a bundle before you upload it:
parley plugin verify -key BASE64_PUBLIC_KEY bundle.parleyprints the digest and key id or the reason for refusal.
The Parley project’s own key id will be published in SECURITY.md; it is not in this page, because a page you fetched from the same place as the bundle proves nothing.
Removing a key revokes it. Take it out of PLUGIN_TRUSTED_KEYS and restart each
replica; trust is checked on every call, so the bundle stops running on that
replica’s next call after it restarts. PLUGIN_ALLOW_UNSIGNED=true also accepts
unsigned bundles. Leave it off unless you build every bundle yourself.
Upload to the catalog
Section titled “Upload to the catalog”An admin of the default org uploads on the Plugin catalog page
(/catalog), several files at a time if wanted, or with
POST /api/catalog/bundles (one bundle per request). Admins of other orgs cannot
upload. Every org member can browse the catalog, and each version shows its
signer, digest, publish date, and what it adds or drops against the previous
version.
A bundle is stored only if its key is trusted. The first bundle stored for a name and version holds it: a different bundle for the same name and version is refused, so a fix ships as a new version. Stored bundles are never deleted (see known limits).
Install per org
Section titled “Install per org”An org admin opens Plugins under the org’s admin area
(/o/ORG/admin/plugins, or Install in beside a version on /catalog) and
picks a version. The install is per org: enabling it in one org changes nothing
in another.
- Consent. The screen lists what the version may do, in wording generated by the host. Installing needs an explicit acknowledgement; the server refuses to act without one.
- Approvals. An upgrade that asks for wider grants than the ones in force waits. The old version keeps running on the old grants until an admin approves the new ones; an upgrade that asks for nothing new applies directly.
- Disable and uninstall. Disabling is reversible. Uninstall deletes the plugin’s stored data and secrets and is refused while any session of a kind it provides still exists.
Every install, upgrade, approval, rollback, enable, disable and uninstall writes
a row to org_audit_log.
Settings and secrets
Section titled “Settings and secrets”A plugin may declare settings. They appear on the install’s card and are
validated by the host; the schema subset is in
Plugin bundle format. A field marked
format: "secret" is write-only: it is shown on the consent screen as a secrets
grant, stored encrypted, and never returned by the admin API, which reports only
whether it is set and whether a configured key can open it. Secrets need
PLUGIN_SECRET_KEY; without it a plugin asking for one fails to install.
Upgrade, roll back, re-pin
Section titled “Upgrade, roll back, re-pin”An install can move to any trusted catalog version of the same plugin, chosen
from the install chooser (the Plugins page, or an Install link on /catalog).
The install’s own card offers only the versions it has already run. Moving to an older one is a rollback: it takes two clicks and is audited
with the version and bundle it moved from and to. Installing an older version
directly is refused. Saved settings must fit the target version’s schema, or the
move is refused with 409 naming the fields.
A name and version holds one signed bundle. An unsigned bundle is stored only
when nothing holds that name and version, but a signed one is still accepted
after it, so one version can have both. Moving an install to another bundle of
the same version re-pins it. The one case that matters: moving a running
signed install onto the unsigned bundle of the same version, which is possible
only with PLUGIN_ALLOW_UNSIGNED=true. It lowers
the trust the install runs under and today has no extra warning
(#737). Leave
PLUGIN_ALLOW_UNSIGNED off and that move is not available.
Removing a kind in an upgrade or rollback leaves its existing rooms in place but degraded; the room shows the “kind unavailable” state.
Several replicas
Section titled “Several replicas”The catalog, the installs and their pins are all in Postgres, so a change made through any replica applies on every one without a restart: a running host re-checks which bundle answers at most every 5 seconds. Two things are per-replica: the trust set and the secret keys, both read at boot, so change them with a rolling restart of every replica.
Avoid plugin admin actions during a rollout. An older binary can change an install’s version without moving its pin. The newer binary then runs only the trusted stored bundle for the new version and re-pins to it, and with none stored it fails closed.
Rotating the secret key
Section titled “Rotating the secret key”Plugin secrets and standup webhook secrets are encrypted under
PLUGIN_SECRET_KEY. To rotate without losing them:
- Roll out
PLUGIN_SECRET_KEY= the old key andPLUGIN_SECRET_KEY_PREVIOUS= the new key to every replica. - Roll out
PLUGIN_SECRET_KEY= the new key andPLUGIN_SECRET_KEY_PREVIOUS= the old key to every replica. - Run
parley secrets resealonce, with the same environment (a one-off Job orkubectl execon Kubernetes). - Remove
PLUGIN_SECRET_KEY_PREVIOUSonly after a run that exits 0 and logssecrets reseal finishedwithremaining0.
Rotating without the previous key makes every stored secret undecryptable. The
full behavior, including the bound and unbound forms, is under
PLUGIN_SECRET_KEY_PREVIOUS.
Observability
Section titled “Observability”GET /api/catalog/loaded, for default-org admins, lists the bundles the answering replica has compiled, by digest and key id. Call it once per replica to confirm they all run the same bytes.- A plugin card shows its health and last error, and the bundle digest it is pinned to.
- Plugin admin actions are in
org_audit_log. A loose-file read logs a warning, at most once every few seconds per plugin version, which is the signal that something has not been moved to the catalog. - At boot Parley logs how many secrets are not yet under the current key.
Moving from loose PLUGIN_DIR files to the catalog
Section titled “Moving from loose PLUGIN_DIR files to the catalog”Before the catalog, a plugin was <name>-<version>.wasm (and .ui.js,
.slots.json) in PLUGIN_DIR, installed from a package.json. Those files are
legacy and unsigned. They keep working, and moving off them is a few steps:
- Get a signed bundle of the same plugin and version, from the publisher, or pack and sign one with the plugin SDK.
- Trust the signer. Add the public key to
PLUGIN_TRUSTED_KEYSas above and restart every replica. - Put the bundle in the catalog. Either upload it on
/catalog, or drop the.parleyfile intoPLUGIN_DIR: at boot every*.parleyfile there is verified and stored, and one that is refused is logged and skipped. - Restart once. After the import, every install with no pin, or a pin that no longer matches the install’s version, is pinned to the trusted stored bundle for its own name and version (a signed bundle is preferred over an unsigned one), under its own advisory lock so replicas booting together agree. The install is not disabled or changed; it now runs the bundle by digest.
- Check the plugins page. An install with no matching bundle is never disabled: it keeps running from the loose files and shows Not in catalog — still runs from PLUGIN_DIR. Upload the matching bundle, or move the install to a catalog version from its card.
- Remove the loose files once no install shows that status. An install that has ever been pinned never runs a loose file again, and a loose file is read only when no bundle at all is stored for that name and version.
A catalog bundle is the only way to install or upgrade from now on; there is no
upload of a bare package.json. Keeping PLUGIN_DIR set is harmless: it is only
a place to import .parley files from. It is no longer needed to run the plugin
host, which runs either way.
Known limits
Section titled “Known limits”- No garbage collection of stored bundles. Bundles are append-only, and the database holds every one you have ever uploaded. Retention and a way to delete an unreferenced one are not built (#737).
- A trust-lowering re-pin has no warning, as above.
- Revoking a key needs a restart of every replica.
- The catalog has one curator group, the admins of the default org. A marketplace with several publishers is a roadmap item.