Files
wehub-resource-sync 070959e133
landing-page-staging / Deploy landing page to staging (push) Has been skipped
landing-page-ci / Validate landing page (push) Failing after 4s
visual-baseline / Capture visual baselines (push) Has been cancelled
bake-plugin-previews / Bake plugin previews (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:00:47 +08:00

608 lines
30 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`](../plugins-spec.md) · zh-CN
[`docs/plugins-spec.zh-CN.md`](../plugins-spec.zh-CN.md).
Sibling plan: [`plugins-implementation.md`](./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](https://documentation.openclaw.ai/clawhub) — agent-era plugin
catalog with GitHub-PR submission and a single rendered index site.
- [skills.sh](https://skills.sh) — community skill registry with `id:vendor/name`
addressing and copy-to-clipboard install commands.
- [npm registry](https://docs.npmjs.com/cli) — 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.
- [x] **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).
- [x] **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`.)
- [x] **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.
- [x] **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:
```text
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:
```text
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:
```text
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:
```text
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
```text
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):
```ts
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.
```text
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`](../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.
- [x] **P0.1 Unify trust tier vocabulary.** In `packages/contracts/src/plugins/marketplace.ts`,
rename `MarketplaceTrust` from `official|trusted|untrusted`
`official|trusted|restricted`. Migrate daemon, CLI, UI, fixtures.
- [x] **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.).
- [x] **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.
- [x] **P0.4 Default trust inheritance.** Installs from `official`/`trusted`
catalogs default to `trusted`; installs from `restricted` catalogs stay
`restricted`. Document in spec §6 patch.
- [x] **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.
- [x] **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.
- [x] **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.
- [x] **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.
- [x] **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.
- [x] **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.
- [x] **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.
- [x] **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"
- [x] **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.
- [x] **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.
- [x] **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.
- [x] **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
- [x] **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.
- [x] **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.
- [x] **P3.3 Submission guide.** `docs/publishing-a-plugin.md` + zh-CN. The
guide must be runnable end-to-end with `od plugin init` →
`od plugin pack` → `od plugin publish`, no manual JSON editing.
- [x] **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.
- [x] **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`.
- [x] **P3.6 Registry doctor.** `od marketplace doctor` validates every entry
is downloadable, manifest parseable, checksum match, permissions present.
Surface in web Sources tab too.
- [x] **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
- [x] **P4.1 DB-backed RegistryBackend.** Same interface, SQLite or Postgres.
Validates R2.
- [x] **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.
- [x] **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.
- [x] **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)
- [ ] `R1``R6` 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`](./plugins-implementation.md); this plan does
not modify any of those modules.