Files
nexu-io-open-design/docs/plans/plugin-registry.md
PerishFire a0afc584bb [codex] centralize daemon data directory docs (#4222)
* docs: centralize daemon data directory contract

* fix(e2e): allow slower artifact consistency navigation

Generated-By: looper 0.9.5 (runner=fixer, agent=codex)

* docs: localize daemon data directory pointers

Generated-By: looper 0.9.5 (runner=fixer, agent=codex)

---------

Co-authored-by: Looper <looper@noreply.github.com>
2026-06-15 02:52:05 +00:00

30 KiB
Raw Permalink Blame History

Open Design Plugin Registry — Plan (living)

One sentence: Turn the existing open-design-marketplace.json federation into a real npm-/clawhub-/skills.sh-style registry: GitHub repo as the v1 storage backend, od CLI as the canonical client, official site as one rendered consumer, and the whole thing pluggable so a third party can stand up their own Open Design plugin source with one config line.

Source spec: docs/plugins-spec.md · zh-CN docs/plugins-spec.zh-CN.md. Sibling plan: plugins-implementation.md — that file owns the per-plugin runtime/apply/snapshot pipeline. This file owns the registry / distribution / website / multi-source story.

References (shape, not API):

  • Clawhub — agent-era plugin catalog with GitHub-PR submission and a single rendered index site.
  • skills.sh — community skill registry with id:vendor/name addressing and copy-to-clipboard install commands.
  • npm registry — versioned packages, scoped names, publish/yank lifecycle, lockfiles, integrity hashes.

0. Invariants (lock first; never violate without a spec patch)

  • R1. CLI is the canonical client. Every UI action and every website feature must be expressible as one od subcommand. UI / site are renderers.
  • R2. Storage backend is swappable. GitHub repo is the v1 backend, but daemon code talks to a RegistryBackend interface. Replacing GitHub with a managed DB later must be a one-file swap, not a refactor.
  • R3. SKILL.md floor stays portable. A plugin published to OD's registry must still install cleanly as a plain agent skill in Claude Code / Cursor / Codex / Gemini CLI / OpenClaw / Hermes. open-design.json remains an additive sidecar (per spec §1).
  • R4. Trust vocabulary is one set, everywhere. Contracts, daemon, CLI, UI, and website all use official / trusted / restricted. (Today marketplace.ts ships official|trusted|untrusted and the runtime ships bundled|trusted|restricted — P0 unifies these and normalizes legacy untrusted rows to restricted.)
  • R5. Federation is the default, not the exception. OD's own registry is one source among many. Adding a third-party registry URL is symmetric to adding ours; no special-casing in code paths.
  • R6. Provenance is preserved end-to-end. Every installed plugin carries sourceMarketplaceId + marketplaceTrust + resolved transport (github / url / local / bundled), so UI and audit can answer "where did this come from?" without re-resolving.

1. Target architecture

1.0 Product surface map

The registry architecture has three stable layers:

Registry supply network
  GitHub repo in v1, database backend later.
  Owns package identity, versions, trust, integrity, yanking, provenance.

od CLI
  Canonical client and automation API.
  Owns login/whoami, publish, pack, doctor, search, install, upgrade.

Product UI
  Human operating console.
  Owns fast use, discovery, source management, provenance/risk display.

Concrete relationship:

Plugin source repo
  open-design.json includes plugin.repo
        |
        | od plugin validate / pack / publish
        v
Plugin artifact
  GitHub ref, GitHub Release .tgz, HTTPS archive, or local folder
        |
        v
Registry index
  v1: open-design/plugin-registry or this repo
      community/**/open-design.json
      generated open-design-marketplace.json
  future: DatabaseRegistryBackend
        |
        | od marketplace search / od plugin install
        v
Installed plugin
  local runnable record with sourceMarketplaceId, trust, integrity, resolved ref

Product surface semantics:

  • Home / Official starters is not a separate registry. It is a curated shortcut shelf for already-installed bundled/official workflows so users can immediately click Use. Bundled official plugins are the preinstalled cache of the official registry source, not a separate distribution model.
  • Plugins / Installed is the full local runnable inventory: bundled official plugins, user-created plugins, direct GitHub/URL/local installs, and marketplace-installed plugins.
  • Plugins / Available is the registry discovery layer: entries from configured Sources that are not installed yet or have upgrades available.
  • Plugins / Sources is the registry source manager: official, community, self-hosted, and enterprise catalogs; trust tier; refresh; remove; auth/cache status later.
  • Plugins / Team is the enterprise governance layer: private catalogs, organization allowlists, approvals, audit, and policy.
  • open-design.ai/plugins is the public renderer of the official and community registry sources, not a separate source of truth.

Agent consumption boundary:

User-added registry
  Sources -> Available -> Install -> Installed -> agent context/runtime

Packaged official plugins
  official registry -> bundled preinstall cache -> Installed/bundled
  -> Home Official starters -> agent

Default community registry
  community source -> Available by default -> user installs intentionally
  -> installed plugin record -> Installed -> agent

Available is not directly consumable by the agent. It is a supply pool. The agent consumes the installed set: bundled official plugins, user-created plugins, direct GitHub/URL/local installs, and marketplace-installed plugins. A future "Use from Available" convenience action may auto-install first, but it must still create an installed record before execution.

User-created and user-installed plugins are persisted through daemon-managed storage. This plan MUST NOT define daemon data paths; read the root AGENTS.md section Daemon data directory contract before changing or documenting plugin storage. Daemon startup reloads installed records from SQLite and resolves those user-state plugin folders; a packaged runtime upgrade should not overwrite them. Bundled official plugins stay inside the runtime image and are re-registered on boot as official-source preinstalls.

Authoring and publishing mirror the consumption loop:

Create plugin
  -> agent-assisted authoring
  -> od plugin scaffold / validate / local install / pack
  -> od plugin login/whoami through gh
  -> od plugin publish
  -> GitHub registry PR
  -> generated open-design-marketplace.json
  -> Available for downstream users after refresh

The Create plugin button should therefore launch an agent workflow that helps the user describe the plugin, writes SKILL.md and open-design.json, adds examples/preview metadata, validates locally, installs a test copy, packs it, and then drives the GitHub-backed publish PR. The CLI remains canonical; the agent is the product wrapper around the CLI workflow.

v1 registry scope is intentionally simple: a GitHub repo with reviewable source entries plus a generated open-design-marketplace.json. The JSON is what daemon/CLI/UI fetch; the source entries are what humans review in PRs. This can start in the main Open Design repo, but the code path must still be expressed as RegistryBackend so moving to open-design/plugin-registry or a database later does not change the product model.

1.1 Storage abstraction

packages/registry-protocol/              ← NEW pure TS, schema + interface
  ├── backend.ts                         ← RegistryBackend interface
  ├── types.ts                           ← RegistryEntry, RegistryVersion,
  │                                        PublishRequest, SearchResult, …
  └── tests/

apps/daemon/src/registry/                ← side-effect zone
  ├── github-backend.ts                  ← v1: PR-based publish, raw.git read
  ├── http-backend.ts                    ← already-exists shape (marketplace.json)
  ├── local-backend.ts                   ← dev/test fixture
  ├── cache.ts                           ← on-disk cache + ETag refresh
  ├── lockfile.ts                        ← od-plugin-lock.json writer/reader
  └── publish.ts                         ← orchestrates pack → fork → PR

RegistryBackend (sketch — exact shape lands in P0):

interface RegistryBackend {
  id:        string;          // e.g. "official" | "acme-private"
  kind:      'github' | 'http' | 'local' | 'db';
  trust:     MarketplaceTrust;
  // read
  list(filter?: ListFilter):    Promise<RegistryEntry[]>;
  search(q: string):            Promise<SearchResult[]>;
  resolve(name: string, range?: string): Promise<ResolvedEntry>;
  manifest(name: string, version: string): Promise<PluginManifest>;
  // write (optional — http/db may be read-only from CLI perspective)
  publish?(req: PublishRequest):    Promise<PublishOutcome>;
  yank?(name: string, version: string, reason: string): Promise<void>;
  // health
  doctor(): Promise<DoctorReport>;
}

1.2 GitHub-backed v1 layout

A dedicated public repo — proposed open-design/plugin-registry — owns the canonical official catalog. Third parties fork the same shape and point their own marketplace.json URL at it.

open-design/plugin-registry/
├── plugins/
│   └── <vendor>/<plugin-name>/
│       ├── manifest.json              ← latest copy of open-design.json
│       ├── versions/
│       │   ├── 0.1.0.json             ← frozen manifest snapshot per version
│       │   └── 0.2.0.json
│       ├── README.md                  ← rendered on the website
│       ├── OWNERS                     ← GitHub usernames with publish rights
│       └── tarball.txt                ← canonical archive URL (GitHub release)
├── marketplace.json                   ← generated index; what daemons fetch
├── schema/
│   └── open-design.marketplace.v1.json
├── .github/workflows/
│   ├── validate-pr.yml                ← schema + manifest + license + a11y
│   └── publish-index.yml              ← rebuild + commit marketplace.json
└── docs/                              ← "how to publish" guide

Why a separate repo: keeps the main monorepo green, gives publishers a clear contribution target, lets us mirror the same shape for third-party registries verbatim.

Tarball storage: plugins ship as .tgz (per existing od plugin pack). v1 uses GitHub Releases of the plugin's own source repo as the canonical archive host; the registry only stores a URL + integrity hash, never the bytes. This keeps the registry repo small and makes the storage layer trivially swappable.

Namespace policy: published package ids are always vendor/plugin-name. The vendor is the GitHub org/user or enterprise org namespace. The full id is stable after publish; rename means new id plus alias/deprecation metadata.

Source repo policy: accept "anything that packs". The source repo does not need a special layout if od plugin validate and od plugin pack pass. The manifest must include plugin.repo in open-design.json, pointing to the canonical source repository or subdirectory.

Tarball fallback: GitHub Releases are the default archive host, but raw HTTPS/object-storage archive URLs are accepted when Releases are unavailable. Fallback archives require integrity hashes.

1.3 Publish flow (PR-based)

od plugin publish end-to-end:

  1. Run od plugin pack → produces <vendor>-<name>-<version>.tgz + manifest digest.
  2. Read or create a GitHub release on the plugin's own source repo, upload the tarball, capture release asset URL + SHA-256.
  3. gh auth status to confirm login (no token persisted by od).
  4. Fork (or reuse fork of) open-design/plugin-registry via gh repo fork.
  5. Check out a branch publish/<vendor>-<name>-<version>.
  6. Write/refresh plugins/<vendor>/<name>/manifest.json and versions/<version>.json, append entry to a per-plugin index, run pnpm run validate locally.
  7. gh pr create with a templated body containing manifest summary, capability/permission list, tarball URL, integrity hash, changelog link.
  8. CI in the registry repo runs validate-pr.yml — schema, license header, tarball download + checksum, manifest replay, optional preview render.
  9. On merge, publish-index.yml regenerates marketplace.json and pushes it to main. GitHub Pages / CDN serves it as the fetchable marketplace JSON, while https://open-design.ai/plugins/ renders the public browser and detail pages over that same source data.

Yanking uses the same PR shape with a yanked: true, reason: "..." patch to the version JSON. Daemons treat yanked versions as unresolvable but preserve them for od plugin info <name>@<v>.

Yanking never hard-deletes bytes or metadata. New installs refuse yanked versions; exact lockfile replay may still install with a warning as long as the archive remains reachable and integrity matches.

1.4 Install / resolve flow

od plugin install <name>[@<range>]
  → daemon iterates configured backends in trust order
  → first match: download tarball, verify SHA-256, extract to
    daemon-managed plugin storage, write InstalledPluginRecord with
    full provenance (sourceMarketplaceId, marketplaceTrust, source URL,
    sourceDigest, resolved ref)
  → record the lockfile through daemon-managed storage for reproducibility

od plugin upgrade [--policy latest|pinned] re-resolves against lockfile and applies new versions atomically (no in-place mutation; new version directory, swap symlink, rollback on failure).

1.5 Login model

  • gh is a first-class dependency of od registry workflows. Installing od should ensure gh is present when the platform channel can bootstrap it; otherwise the installer fails with exact remediation.
  • od plugin login wraps gh auth login with Open Design copy, scopes, and host guidance. od plugin whoami wraps gh auth status plus gh api user.
  • od plugin logout may wrap gh auth logout, but only after explicit confirmation because it affects the user's global GitHub CLI session.
  • The daemon never stores GitHub tokens. GitHub.com and GitHub Enterprise auth stay in gh; daemon code goes through a narrow GhClient abstraction.
  • Generic bearer/header/basic/mTLS auth profiles are reserved for future non-GitHub HTTPS or database backends. They must not leak into marketplace.json.
  • Database backend (future) defines its own auth in RegistryBackend.kind=db; CLI surface stays unchanged.

1.6 Website renderers

Two consumers of the same marketplace.json:

  • Official site (open-design.ai/plugins) — static, SSG against repo-owned plugins/registry/*/open-design-marketplace.json sources. Browse, search, copy install command, render plugin details, preview asset, capability & permission summary, version history, publisher links, and canonical SEO pages. open-design.ai/marketplace can be kept as an alias once routes are finalized.
  • Self-hosted third-party site — out of the box, anyone running the same registry repo template gets the static site as a copy-paste GitHub Pages action. They publish their own catalog URL, users add it with od marketplace add <url>.

UI never special-cases the official catalog beyond the trust tier; the website is one rendering of a generic data source.

1.7 Web app surface (apps/web)

Update apps/web/src/components/PluginsView.tsx from "installed list" into a multi-source registry browser:

  • Tabs: Installed · Available · Sources · Team
  • Available: cards grouped by sourceMarketplaceId and trust tier. Filter by capability, tag, publisher. Each card: title, version, publisher, trust badge, install button.
  • Sources: Add URL · Refresh · Remove · Trust toggle · auth status for private catalogs · "view manifest" drawer.
  • Plugin detail drawer: provenance line ("from official · trusted · github.com/foo/bar@v0.2.0 · sha256:abcd…"), permissions, README, install command, available versions, yanked-version warning.

The first UI slice has already unlocked Available and Sources using cached marketplace manifests and existing /api/marketplaces endpoints. Follow-up work should move large-catalog browsing to typed paginated APIs and add provenance-aware --from <marketplace-id> install semantics.

1.8 Current implementation slice (2026-05-13 / 2026-05-14)

This repo now has the first registry closure in place:

  • Contracts and JSON schema use official / trusted / restricted marketplace trust, and marketplace entries can carry versions, dist, distTags, integrity, manifestDigest, publisher, homepage, license, capabilitiesSummary, deprecated, and yanking metadata while staying passthrough-friendly.
  • Daemon install and upgrade flows preserve marketplace provenance: sourceMarketplaceId, entry name/version, marketplace trust, resolved source/ref, manifest digest, and archive integrity. Snapshot records carry the same audit trail for agent/runtime replay.
  • The packaged daemon seeds built-in official and community registry sources from plugins/registry/*/open-design-marketplace.json. official is verified and can also hydrate bundled preinstalls; community is restricted by default and feeds Available entries for user-initiated installs.
  • Bundled official plugins now carry sourceMarketplaceId=official and sourceMarketplaceEntryName=open-design/<plugin-id>, so they are modeled as preinstalled official registry entries while keeping offline first-run bytes in the runtime image.
  • od plugin login and od plugin whoami now delegate to gh, and registry publishing now has three paths: --to open-design produces the human review target/link, --to marketplace-json --catalog <path> upserts a self-hosted static catalog entry, and GithubRegistryBackend.publish/yank produces deterministic PR mutation payloads for a real GitHub mutator.
  • packages/registry-protocol defines the backend interface and schemas; daemon now has static, GitHub, and SQLite database-backed implementations sharing the same list/search/resolve/publish/yank/doctor contract.
  • Version resolution supports exact versions, dist-tags, ^/~ ranges, and yanking behavior. Archive downloads verify/store SHA-256 integrity, and a lockfile helper records resolved marketplace provenance for reproducible installs.
  • od marketplace plugins, od marketplace doctor, od marketplace login, versioned od plugin install, policy-aware od plugin upgrade, marketplace od plugin info, and od plugin yank are implemented as headless surfaces with JSON output where automation needs it.
  • Home now presents official plugins as Official starters with a Browse registry path into /plugins; /plugins remains the registry console (Installed / Available / Sources / Team).
  • apps/landing-page now exposes the public SEO renderer at /plugins/ plus static per-plugin detail pages. It reads plugins/registry/official, plugins/registry/community, and bundled official manifests at build time, so open-design.ai can show the ecosystem without calling daemon APIs.
  • The Create plugin product prompt is agent-assisted and explicitly drives scaffold/validate/local install/pack/login/whoami/publish expectations.
  • Registry evaluation cases now live in docs/testing/plugin-registry-eval-cases.md. The first covered set locks raw open-design-marketplace.json source input, populated official seed loading, default community seed loading, provenance/trust inheritance, bundled official Use behavior in Available, direct GitHub imports, the Create/Publish agent handoff surfaces, version resolution/yanking, backend parity, registry doctor, archive integrity, lockfiles, and static marketplace-json publishing.

2. Phased plan

P0 — Contract closure (unblocks everything else)

Goal: every later phase can assume one trust vocabulary, full provenance, and a versioned marketplace entry.

  • P0.1 Unify trust tier vocabulary. In packages/contracts/src/plugins/marketplace.ts, rename MarketplaceTrust from official|trusted|untrustedofficial|trusted|restricted. Migrate daemon, CLI, UI, fixtures.
  • P0.2 Marketplace entry v1.1 fields. Extend MarketplacePluginEntrySchema with optional versions[], integrity (sha256), publisher, homepage, license, capabilitiesSummary, yanked. Stay .passthrough() for community extensions (clawhub tags etc.).
  • P0.3 Install provenance. Plumb sourceMarketplaceId, marketplaceTrust, marketplacePluginName, and the resolved transport source through apps/daemon/src/server.ts install path (currently around line 3880). The InstalledPluginRecord already has these fields — make sure they actually get populated when install comes via marketplace.
  • P0.4 Default trust inheritance. Installs from official/trusted catalogs default to trusted; installs from restricted catalogs stay restricted. Document in spec §6 patch.
  • P0.5 Registry protocol package. New packages/registry-protocol/ with the RegistryBackend interface, shared zod schemas, and pure tests. No runtime deps.

P1 — Headless / CLI completeness

Goal: every registry action you'd want from the website works on the CLI first, headless, JSON-emitting.

  • P1.1 New CLI subcommands.
    • od marketplace plugins <id> [--json]
    • od marketplace search <query> [--json]
    • od marketplace doctor [<id>]
    • od plugin install <name>@<version|range>
    • od plugin upgrade [--policy latest|pinned]
    • od plugin yank <name>@<version> --reason "..."
    • od plugin info <name> [--version <v>] [--json]
    • od plugin login -> wraps gh auth login with OD-specific host/scope guidance.
    • od plugin whoami -> wraps gh auth status plus gh api user. These now cover marketplace plugins/search/doctor/login, versioned install, policy-aware upgrade, marketplace info, yanking, and gh-backed login/whoami.
  • P1.2 GitHub backend module. apps/daemon/src/registry/github-backend.ts implements RegistryBackend against open-design/plugin-registry. Uses raw HTTPS/static reads and a narrow mutation client for PR creation, keeping gh/GitHub auth outside daemon persistence.
  • P1.3 Publish orchestrator, first mutation-capable slice. GithubRegistryBackend.publish/yank builds deterministic registry files and PR payloads; od plugin publish --to marketplace-json --catalog <path> covers self-hosted static catalogs. The release-upload/fork/branch executor remains an adapter on top of the tested mutation contract.
  • P1.4 Lockfile. The daemon-managed plugin lockfile records resolved name@version + integrity + sourceMarketplaceId. od plugin install honors lock on second run; od plugin upgrade rewrites it.
  • P1.5 Private GitHub catalog auth. od marketplace login <id> delegates to gh auth login for the catalog host. GitHub credentials stay in gh, never in marketplace.json or installed_plugins. Generic token profiles are reserved for future non-GitHub HTTPS or database backends.
  • P1.6 Integrity verification. Verify SHA-256 of downloaded tarball against the marketplace entry's integrity field before extracting. Fail closed if mismatch. Store digest on the install record.
  • P1.7 Headless e2e tests under apps/daemon/tests/:
    • add marketplace → search → install by name → run → provenance asserted in installed_plugins row.
    • lockfile reproduces same install on a fresh daemon data dir.
    • integrity-mismatch tarball is rejected.

P2 — Web app: from "installed page" to "registry browser"

  • P2.1 Unlock source management. /plugins now has Installed / Available / Sources / Team tabs. Sources can add URL, refresh, remove, and change trust tier through existing /api/marketplaces endpoints.
  • P2.2 Available tab entry slice. Available cards are built from configured marketplace manifests and show Install / Use / Upgrade states. The bare-name install path now persists marketplace provenance; explicit --from <marketplace-id> remains follow-up work.
  • P2.3 Home official shelf semantics. Rename the Home page Official plugin shelf copy to Official starters or Official installed, and add a lightweight Browse registry path into /plugins. Home remains fast-use; /plugins remains registry discovery/management.
  • P2.4 Agent-assisted Create plugin flow. The Create plugin action should start an agent workflow that gathers intent, scaffolds the plugin, writes SKILL.md/open-design.json, validates, installs a local test copy, packs, checks gh login/whoami, and publishes by opening a GitHub registry PR through od plugin publish. Current slice upgrades the product prompt, CLI wrapper, marketplace-json self-host publish, and tested GitHub PR mutation payload contract.
  • P2.5 Plugin detail drawer. Provenance line, permissions, capability summary, version dropdown, install command (copy-to-clipboard, matches od plugin install <name>@<version>).
  • P2.6 Team / Private marketplace UI. Private URL field, auth status pill, default trust, org allowlist, refresh schedule, audit log link. Wired to /api/marketplaces/:id/auth (new).
  • P2.7 Trust badge consistency. Same official / trusted / restricted visual language across cards, drawer, Sources tab, install confirm modal.

P3 — Official website + ecosystem

  • P3.1 Stand up open-design/plugin-registry repo shape. Schema, validation workflow, index-publishing workflow, OWNERS, contribution guide. Seed with the bundled plugins currently shipped in plugins/_official/. The local repo now carries the source shape and generated registry inputs; creating the external GitHub repo is an operational launch step, not a code blocker.
  • P3.2 Static site renderer. apps/landing-page now statically generates open-design.ai/plugins and per-plugin detail routes from plugins/registry/*/open-design-marketplace.json plus bundled official manifests, with SEO metadata, search JSON, and od:// detail links.
  • P3.3 Submission guide. docs/publishing-a-plugin.md + zh-CN. The guide must be runnable end-to-end with od plugin initod plugin packod plugin publish, no manual JSON editing.
  • P3.4 Self-host kit. docs/self-hosting-a-registry.md — copy the plugin-registry repo template, point od marketplace add at it, done. Also documents the future DB-backend swap path so enterprises know the exit option exists.
  • P3.5 od plugin publish --to marketplace-json. Lets third-party catalog owners accept submissions from their own users using the same CLI by writing/upserting their own static open-design-marketplace.json.
  • P3.6 Registry doctor. od marketplace doctor validates every entry is downloadable, manifest parseable, checksum match, permissions present. Surface in web Sources tab too.
  • P3.7 Publisher verification hooks. Lightweight GitHub-org publisher metadata is generated for marketplace-json entries and protocol schemas now support verified publishers/signatures. Heavier sigstore/cosign enforcement remains deferred — open question §4.

P4 — Future / non-blocking

  • P4.1 DB-backed RegistryBackend. Same interface, SQLite or Postgres. Validates R2.
  • P4.2 Search index. Static open-design.ai/plugins/search.json exposes the website search index; CLI still works against marketplace.json directly. Typesense/Meilisearch can replace the static file later without changing registry semantics.
  • P4.3 Web-of-trust badges data hooks. Protocol schemas include optional metrics for downloads, installs, stars, and activity timestamps. Real collection/display stays opt-in per spec privacy stance.
  • P4.4 Marketplace signing data hooks. Protocol schemas include optional signature records for GitHub OIDC/cosign/minisign/custom signing. Client-side enforcement is intentionally deferred until the registry service starts emitting signed catalogs.

3. Resolved decisions

  1. Namespace policy. Registry package ids are vendor/plugin-name. Flat ids remain a compatibility path for existing local/bundled plugins, but public publish requires the namespaced id.
  2. Plugin source-of-truth repo. The source repo can be any shape that survives od plugin validate and od plugin pack. Registry publish requires a plugin.repo field in open-design.json pointing to source.
  3. Tarball hosting fallback. If GitHub Releases are unavailable (enterprise / mirror), raw HTTPS or object-storage archive URLs are accepted with mandatory integrity hash.
  4. Signing. sigstore/cosign is deferred to P4. Start with PR-author identity + SHA-256, escalate if ecosystem grows.
  5. Yanking vs deletion. Never hard-delete a version. Only mark it yanked: true with reason; new installs refuse, exact existing locks can still replay with warning when integrity matches.
  6. Plugin ID stability. A published package id is immutable. A renamed plugin gets a new id plus alias/deprecation metadata; old id stays traceable for historical installs and lockfiles.

4. Definition of done (registry v1)

  • R1R6 invariants each have a regression test that fails when violated.
  • A third party can fork open-design/plugin-registry, change two config values, run one workflow, and have a working OD plugin source at their own URL — verified with an e2e fixture catalog.
  • Every UI action in PluginsView.tsx Sources/Available tabs is expressible as one od CLI invocation, verified by a script that drives both surfaces against the same fixture.
  • One real third-party publisher (target: a Claude-skills-community contributor) has published a plugin via od plugin publish end-to-end without writing JSON by hand.

5. Cross-references

  • Trust tier rename touches packages/contracts/src/plugins/marketplace.ts, apps/daemon/src/cli.ts (marketplace subcommands), apps/daemon/src/server.ts (install path), apps/web/src/components/PluginsView.tsx.
  • Provenance plumbing touches apps/daemon/src/server.ts (~line 3880) and apps/daemon/src/cli.ts (~line 1183).
  • Existing per-plugin runtime work (apply, snapshot, pipeline) is owned by plugins-implementation.md; this plan does not modify any of those modules.