You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Tracks extending the project-level toolhive.lock.yaml (RFC THV-0080) with a sibling plugins: key so project-scoped thv ai-plugin installs are pinned the same way skills are: source, resolvedReference, digest, contentDigest, and Sigstore provenance, plus thv ai-plugin sync / thv ai-plugin upgrade, signature verification on consume, and signing on push.
THV-0080 owns only version and skills:; it reserved sibling keys (explicitly plugins:) as a contract proposal for a later RFC to ratify. This issue ratifies that contract without a new RFC: same file, additive plugins: key, schema version stays 1. Older binaries already round-trip unknown top-level keys via Lockfile.Extra; bumping to version 2 would hard-fail mixed-version teams that only use skills.
This is the AI-plugin counterpart of the skills track (#5899). It copies that trust model rather than inventing one, and reuses the packages skills already landed (pkg/skills/lockfile.Entry including provenance/unsigned, pkg/skills/verifier, pkg/skills/signer, cmd/thv/app/exitcode.go).
The feature is gated behind TOOLHIVE_PLUGINS_LOCK_ENABLED for the whole rollout — plugin entries must not become user-visible in the lock until verification is wired, because by then the lock file is the live skills trust document (#6139 removes TOOLHIVE_SKILLS_LOCK_ENABLED). Pinning and Sigstore are one v1, not two independently shippable halves.
Prerequisite: start Stack 1 only after #6139 (skills push-signing + ungate) is merged to main. Do not stack these PRs on skills-sig/14-push-signing-ungate.
As a team maintaining a project that consumes AI-tool plugins, I want project-scope plugin installs pinned in the same committed toolhive.lock.yaml as skills, cryptographically verified against the publisher's Sigstore identity, and safely upgradable, So that every teammate and CI run restores the exact same reviewed plugins, and tampered or re-signed artifacts are detected before they reach a developer's machine.
Agreed design
Same file, additive key, version 1.plugins: sits next to skills:. A skill and a plugin may share a name; they are separate graphs.
Full skills parity as one v1: pin on project-scope install, sync / upgrade, verify on consume, sign on push, TOFU provenance / unsigned: true.
CLI/API:thv ai-plugin sync / thv ai-plugin upgrade and POST /plugins/sync / POST /plugins/upgrade. Do not teach thv skill sync to walk plugins: — that would change existing CI --check semantics.
Lock only explicit plugin installs.requires is parsed today but not materialized; requiredBy / explicit stay unused until that Phase-3 wave. Do not record uninstalled deps.
Client-agnostic. No clients field. contentDigest is the frozen skills dirhash over the canonical plugin tree (what ExtractPlugin writes into the client plugin dir), not marketplace.json / settings.json.
Reuse, don't extract. Keep pkg/skills/lockfile; add Plugins []Entry plus GetPlugin / UpsertPlugin / RemovePlugin. Validate skills and plugins as separate name/requiredBy graphs. Import existing verifier/signer. Moving signer into toolhive-core stays a Track: Skills lock file + Sigstore signing (RFC THV-0080) #5899 follow-up.
AT: Project-scope thv ai-plugin install writes a plugins: entry in toolhive.lock.yaml pinning source, resolvedReference, digest, and contentDigest; thv ai-plugin uninstall removes it. Lock-write failure fails the entire install (rollback DB + dematerialize).
AT: thv ai-plugin sync --check detects drift between the lock file and installed plugins (missing, digest mismatch, on-disk tampering via contentDigest) and exits with code 2 on failure
AT: thv ai-plugin sync --adopt records out-of-band installs into the lock; --prune removes lock entries for manually deleted plugins
AT: thv ai-plugin upgrade --preview shows the candidate change without applying; moving to a different resolved reference requires --allow-ref-change
AT: Sync and upgrade are exposed via POST /plugins/sync and POST /plugins/upgrade with typed exit codes for CI (0 success / 2 check failure / 3 partial failure / 4 policy rejection)
Unit: a skills-only lock still loads; unknown top-level keys still round-trip via Extra; plugin names are unique within plugins: (not across skills:)
Stack 2 — Sigstore v1
AT: Project-scope install verifies OCI artifacts via the Sigstore referrers API and git plugins via gitsign; unsigned artifacts are rejected unless --allow-unsigned is passed, which records unsigned: true in the lock
AT: First verified install pins the publisher identity (provenance.signerIdentity + certIssuer, TOFU); subsequent installs/syncs fail with signer-mismatch if the identity diverges
AT: thv ai-plugin sync --check re-verifies stored Sigstore bundles offline (no registry access) against the locked provenance
AT: thv ai-plugin upgrade blocks candidates whose signer identity or cert issuer changed, with --allow-signer-change as the explicit override
AT: thv ai-plugin push signs by default with a cosign key (--key, COSIGN_PASSWORD), attaching the signature as an OCI referrer; --no-sign opts out. (plugins.PushOptions already aliases skills' Key/NoSign; this stack is what honors them.)
Unit: Verified bundles are persisted alongside installed plugins (SQLite) for offline re-verification
Lock plugin entries validate as exactly one of provenance or unsigned: true
Delivery
Work lands as the 10-PR stacked chain below, each PR within repo size guidelines and independently green
docs/arch/14-plugins-system.md documents the v1 trust model (cryptographic verification / TOFU identity / human lock-file review), with a pointer from the lock section in docs/arch/12-skills-system.md
TOOLHIVE_PLUGINS_LOCK_ENABLED is removed in the last PR, documented as the user-facing change
Stack 1 — Lock file mechanics
Branch chain: plugins-lock/01-schema → plugins-lock/05-upgrade, each PR targeting the previous one's branch. Feature gated; no provenance wiring yet (entries written only behind the gate, and Stack 2 lands before ungate).
PR10 — Push signing + trust-state display on ai-plugin info / install; docs; removes the TOOLHIVE_PLUGINS_LOCK_ENABLED gate (the point the feature is fully "landed")
Follow-ups (tracked separately once reached)
Plugin requires materialization and requiredBy locking (not installed today; a later Phase-3 wave)
Summary
Tracks extending the project-level
toolhive.lock.yaml(RFC THV-0080) with a siblingplugins:key so project-scopedthv ai-plugininstalls are pinned the same way skills are: source, resolvedReference, digest, contentDigest, and Sigstore provenance, plusthv ai-plugin sync/thv ai-plugin upgrade, signature verification on consume, and signing on push.THV-0080 owns only
versionandskills:; it reserved sibling keys (explicitlyplugins:) as a contract proposal for a later RFC to ratify. This issue ratifies that contract without a new RFC: same file, additiveplugins:key, schema version stays 1. Older binaries already round-trip unknown top-level keys viaLockfile.Extra; bumping to version 2 would hard-fail mixed-version teams that only use skills.This is the AI-plugin counterpart of the skills track (#5899). It copies that trust model rather than inventing one, and reuses the packages skills already landed (
pkg/skills/lockfile.Entryincluding provenance/unsigned,pkg/skills/verifier,pkg/skills/signer,cmd/thv/app/exitcode.go).The feature is gated behind
TOOLHIVE_PLUGINS_LOCK_ENABLEDfor the whole rollout — plugin entries must not become user-visible in the lock until verification is wired, because by then the lock file is the live skills trust document (#6139 removesTOOLHIVE_SKILLS_LOCK_ENABLED). Pinning and Sigstore are one v1, not two independently shippable halves.Prerequisite: start Stack 1 only after #6139 (skills push-signing + ungate) is merged to
main. Do not stack these PRs onskills-sig/14-push-signing-ungate.Related: #5899 (skills lock + Sigstore), #5525 (plugin lifecycle epic, RFC THV-0077).
User Story
As a team maintaining a project that consumes AI-tool plugins,
I want project-scope plugin installs pinned in the same committed
toolhive.lock.yamlas skills, cryptographically verified against the publisher's Sigstore identity, and safely upgradable,So that every teammate and CI run restores the exact same reviewed plugins, and tampered or re-signed artifacts are detected before they reach a developer's machine.
Agreed design
plugins:sits next toskills:. A skill and a plugin may share a name; they are separate graphs.sync/upgrade, verify on consume, sign on push, TOFUprovenance/unsigned: true.thv ai-plugin sync/thv ai-plugin upgradeandPOST /plugins/sync/POST /plugins/upgrade. Do not teachthv skill syncto walkplugins:— that would change existing CI--checksemantics.requiresis parsed today but not materialized;requiredBy/explicitstay unused until that Phase-3 wave. Do not record uninstalled deps.clientsfield.contentDigestis the frozen skills dirhash over the canonical plugin tree (whatExtractPluginwrites into the client plugin dir), notmarketplace.json/settings.json.pkg/skills/lockfile; addPlugins []EntryplusGetPlugin/UpsertPlugin/RemovePlugin. Validate skills and plugins as separate name/requiredBy graphs. Import existing verifier/signer. Moving signer into toolhive-core stays a Track: Skills lock file + Sigstore signing (RFC THV-0080) #5899 follow-up.Acceptance Criteria
Stack 1 — Lock file
thv ai-plugin installwrites aplugins:entry intoolhive.lock.yamlpinningsource,resolvedReference,digest, andcontentDigest;thv ai-plugin uninstallremoves it. Lock-write failure fails the entire install (rollback DB + dematerialize).thv ai-plugin sync --checkdetects drift between the lock file and installed plugins (missing, digest mismatch, on-disk tampering viacontentDigest) and exits with code 2 on failurethv ai-plugin sync --adoptrecords out-of-band installs into the lock;--pruneremoves lock entries for manually deleted pluginsthv ai-plugin upgrade --previewshows the candidate change without applying; moving to a different resolved reference requires--allow-ref-changePOST /plugins/syncandPOST /plugins/upgradewith typed exit codes for CI (0 success / 2 check failure / 3 partial failure / 4 policy rejection)Extra; plugin names are unique withinplugins:(not acrossskills:)Stack 2 — Sigstore v1
--allow-unsignedis passed, which recordsunsigned: truein the lockprovenance.signerIdentity+certIssuer, TOFU); subsequent installs/syncs fail withsigner-mismatchif the identity divergesthv ai-plugin sync --checkre-verifies stored Sigstore bundles offline (no registry access) against the locked provenancethv ai-plugin upgradeblocks candidates whose signer identity or cert issuer changed, with--allow-signer-changeas the explicit overridethv ai-plugin pushsigns by default with a cosign key (--key,COSIGN_PASSWORD), attaching the signature as an OCI referrer;--no-signopts out. (plugins.PushOptionsalready aliases skills'Key/NoSign; this stack is what honors them.)provenanceorunsigned: trueDelivery
docs/arch/14-plugins-system.mddocuments the v1 trust model (cryptographic verification / TOFU identity / human lock-file review), with a pointer from the lock section indocs/arch/12-skills-system.mdTOOLHIVE_PLUGINS_LOCK_ENABLEDis removed in the last PR, documented as the user-facing changeStack 1 — Lock file mechanics
Branch chain:
plugins-lock/01-schema→plugins-lock/05-upgrade, each PR targeting the previous one's branch. Feature gated; no provenance wiring yet (entries written only behind the gate, and Stack 2 lands before ungate).plugins:to the lock file schema (GetPlugin/UpsertPlugin/RemovePlugin, validate as its own graph) (Add plugins key to lock file schema #6303)PluginLockServiceinterface andmanagedflag oninstalled_plugins(Add PluginLockService and managed install flag #6311)thv ai-plugin sync: restore project plugins from the lock file (POST /plugins/sync,--check/--adopt/--prune, confirmation gate, exit codes 0/2/3/4) (Add plugin lock-file sync #6316)thv ai-plugin upgrade: re-resolve pinned plugins to newer content (POST /plugins/upgrade,--preview/--allow-ref-change/--fail-on-changes) (Add plugin lock-file upgrade #6317)Each PR includes its own tests (unit + E2E where applicable) and passes CI independently of the rest of the stack.
Stack 2 — Sigstore signing/verification
Branch chain:
plugins-sig/06-bundle→plugins-sig/10-push-ungate. Reusepkg/skills/verifierandpkg/skills/signer; no new Sigstore packages.--allow-unsignedsync --check--allow-signer-changeai-plugin info/ install; docs; removes theTOOLHIVE_PLUGINS_LOCK_ENABLEDgate (the point the feature is fully "landed")Follow-ups (tracked separately once reached)
requiresmaterialization andrequiredBylocking (not installed today; a later Phase-3 wave)Provenanceon plugin types)thv lock syncthat walks bothskills:andplugins:pkg/skills/lockfile/ signer into a generic package (signer move into toolhive-core is already sequenced after Track: Skills lock file + Sigstore signing (RFC THV-0080) #5899 PR14)stacklok/toolhive-skills-lock-example)Out of scope (v1)
toolhive.yamlmanifestclientsfield)Definition of done
mainbefore Stack 1 starts.TOOLHIVE_PLUGINS_LOCK_ENABLEDgate removed in PR10, documented as the user-facing change.