项目文件夹

文件
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 行
30 KiB
Markdown

# 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.