Marketplace Discussion #296
Replies: 15 comments 16 replies
-
Beta Was this translation helpful? Give feedback.
-
|
sounds good! |
Beta Was this translation helpful? Give feedback.
-
|
#303 is the first PR in the series. |
Beta Was this translation helpful? Give feedback.
-
|
Looking good ❤️ For the ratings though, I suggest looking into how Steam does reviews with a binary ‘recommended/not’ signal to go along with a review, which others can in turn upvote as relevant or not. This is an easier signal for reviewers to work with as opposed to an arbitrary 5/5 star system that assumes a uniformly internalized framework for quality tiers. |
Beta Was this translation helpful? Give feedback.
-
|
I think the API and UI should always have the same URL. Changing something like the content-type header would yield a different response JSON or HTML |
Beta Was this translation helpful? Give feedback.
-
|
#366 The next PR is ready (Reviews) |
Beta Was this translation helpful? Give feedback.
-
|
#368 enables the marketplace in the other templates (not just |
Beta Was this translation helpful? Give feedback.
-
|
I appreciate your work here, but I think we need to nail down a few things before we can start merging PRs. I'm unsure of how it would interact with the proposal of federated package distribution, which I like a lot. I'd much rather allow multiple marketplaces that interoperate than forcing users to choose which ones to use. Can you think about how this might work with that? With ratings and install counts we need to think hard about how we'd handle manipulation and abuse. It's currently unclear what we'd do for trusted plugins that can't be installed via the admin UI. |
Beta Was this translation helpful? Give feedback.
-
Decentralized plugin distribution + reviewsThanks @ascorbic for flagging the FAIR discussion in #307 and @erlend-sh for the detailed writeup there. I spent some time with the FAIR protocol spec, the ATProto labeler architecture, and Andrew Nesbitt's analysis of where FAIR stands today. There's a lot EmDash could borrow, and I want to keep the conversation here so we have one place for marketplace direction. I'm pretty excited about this. EmDash has a real shot at getting decentralized plugin distribution right in a way WordPress never could. Here's where my head is at. EmDash's advantageFAIR was built for a world where plugins have full database and filesystem access. The entire trust model has to be external: signatures, labelers, SBOM verification, community vetting. All of it because you fundamentally cannot trust the code. EmDash already solves the hardest part at runtime. The sandbox enforces capability boundaries. A plugin declaring
Signing in EmDash shouldn't be about proving code safety. It should be about proving provenance: this bundle was produced by this publisher and hasn't been modified in transit. The sandbox handles the rest. If that holds, EmDash could ship a lighter decentralized model than FAIR with fewer trust layers needed to reach the same level of confidence. A possible approachFive phases, each independently useful. The marketplace UI work from the OP could be built as part of this effort, informed by the decisions we make here. Phase 1: Signed bundles + multi-source Publishers sign bundles with ED25519 during No new identity infrastructure. Just tamper detection and the ability to pull plugins from multiple registries. Phase 2: Decentralized identifiers Publishers and packages each get a DID. Support both The install flow would become: resolve DID, get public key, verify signature. The marketplace would no longer be the authority on identity -- just a host. Publisher identity becomes portable across registries. Package DIDs would also enable direct install without a marketplace: resolve the DID, find the repository, download, verify. Phase 3: Aggregator The official marketplace becomes an aggregator that crawls external repositories alongside its own. Deduplicates by package DID. Multiple aggregators could coexist -- a hosting company runs one, an enterprise runs an internal one. For aggregator discovery, I'd suggest starting with a directory model: a well-known list of aggregators maintained in the EmDash repo or at a known URL (e.g. Note that a compromised or malicious aggregator can't serve tampered bundles -- signatures prevent that. An aggregator can only influence metadata and curation (search ranking, featured placement, install counts). The directory model gives the community a way to delist bad actors without needing protocol-level enforcement. Phase 4: Labelers ATProto-style trust overlays. Independent services that attach signed labels to packages: EmDash could build and operate the first labelers (the AI audit pipeline is already most of a security scanner labeler). Third parties build their own for compliance, community trust, etc. More on what a first labeler might look like below. Phase 5: Protocol spec + FAIR interop Document the full system as a formal spec. FAIR is already platform-agnostic and exploring non-WordPress implementations (TYPO3 is in progress at CloudFest). EmDash could be the second ecosystem to implement a compatible protocol. What this could look like in configemdash({
marketplace: {
sources: [
"https://marketplace.emdashcms.com",
"https://plugins.mycompany.internal",
],
pinned: [
"did:web:somepublisher.dev:seo-meta",
],
labelers: [
"did:web:labeler.emdashcms.com",
"did:web:security.mycompany.internal",
],
},
})Review systemThis is where the labeler architecture gets practical. WordPress and FAIR don't really address reviews. Steam does, and there's a lot worth borrowing. Design principle: if you can install a plugin, you can review it. No extra accounts, no signup, no barriers. EmDash site owners are already authenticated with their own site via passkeys. That's the identity. We're building a WordPress replacement -- many of those users are non-technical and shouldn't need a GitHub account or a DID to leave a rating. How it could work The plugin manager in the admin panel shows a "Rate this plugin" option for any installed plugin. Stars, optional text, submit. The review goes to the marketplace the plugin was installed from, tagged with the site's anonymous hash (already generated for install tracking) and install metadata. Install-gated: no install record for your site hash, no review form. Same idea as Steam -- you can only review what you own. Prevents drive-by reviews from people who never used the plugin. Each review would carry context that the marketplace can display alongside it:
"Active for 3 months, version 1.2.0" is a very different trust signal than "installed 10 minutes ago." Other marketplace users (website or admin panel) could upvote or downvote reviews as helpful or unhelpful. Anonymous, low-friction. Helpful reviews surface over time. Two rating windows on the plugin detail page: last 30 days and all-time. A plugin that was rough at launch but improved shouldn't be permanently punished by early reviews. Publishers can reply to any review, clearly labeled as the developer response. Each review would be a self-contained portable record (site hash, plugin ID, rating, text, install duration, version, active status, timestamp) so aggregators could carry reviews across sources in the future. When DIDs land, a signature field gets added without changing the record structure. First labeler: review qualityBuilt and operated by EmDash. Watches reviews across the marketplace and publishes labels that aggregators use to downrank (never delete) low-quality reviews. Spam and low-effort detection Flags reviews matching common abuse patterns:
Flagged reviews would be downranked in display order, not removed. Still visible if you want them. Review bombing detection Borrowing from Steam's playbook: the labeler monitors the rate and sentiment of incoming reviews per plugin. Anomalous spike in volume with skewed sentiment? The time window gets flagged. Reviews within a flagged window would be excluded from the aggregate score by default but remain visible with an annotation. Both positive and negative reviews in the window get excluded to prevent counter-bombing from inflating the score. Vote manipulation detection If a cluster of helpful/unhelpful votes comes from site hashes that only ever vote on one plugin's reviews, those votes could be downweighted. Labels the review labeler could publish:
The marketplace (or any aggregator) subscribes to these labels and applies them to display logic. Default behavior would be to downrank labeled reviews and exclude bombing-window reviews from aggregates. Aggregators could adjust their response per label type. This would be the first labeler. The architecture would support anyone building more: a community trust labeler weighing reviewer history, an enterprise labeler surfacing only reviews from verified internal deployments, a language-specific labeler filtering by locale. Same protocol -- subscribe to the feed, publish labels, let aggregators decide what to do with them. The argument for starting early: the API surface is still v0.1 and malleable. Designing for multi-source and signatures while everything is still taking shape is cheap. Retrofitting after the ecosystem ossifies around a centralized model is exactly what made FAIR's job impossible with WordPress. If we're directionally aligned, I'd like to put together a more detailed spec and implementation plan as a next step. Would love to hear thoughts -- especially on DID method preferences and how deep into FAIR compatibility the team wants to go. |
Beta Was this translation helpful? Give feedback.
-
|
@BenjaminPrice what you've described is basically the FAIR model. And by "basically" I mean almost exactly, so this path could be fully compatible with FAIR: DIDs, aggregators, labellers, and all. (Disclosure: I'm deeply involved in FAIR; not speaking formally on behalf of the project here, but this is all reliably consistent with internal discussion.) I can offer a few small corrections about FAIR's trust model and its inherent design considerations, along with some additional background. FAIR doesn't necessarily need to have every package pass a deep scan, as that could differ based on package type, based on ecosystem or based on specific a policy applied by a host or site manager, or baked into a CI/CD pipeline. FAIR supports adding a private repo by design, so a deployment pipeline could include repos that bypass any inspection or labelling systems. What FAIR is building is an ATProto-inspired labelling system which can provide a "Trust Score" based on a variety of factors (more flexible than trusted/not) and will offer insight into how the score was calculated. Some of this is necessary because of security shortfalls in the WordPress plugin supply chain, but it can take the corrective much further. For example, Bluesky specifically added Ed25519 support for FAIR, as package signing is crucial for the federated/distributed system we're building - because yes, provenance! Basically, there's flexibility in what can be distributed, so one aggregator doesn't necessarily need to apply the same rules or strict checks on packages, much like you don't get the same moderation terms from one Mastodon server to the next. To your point above, this keeps the actual FAIR Protocol very light on its own. While FAIR was born out of a need for technical independence (and governance) in the WordPress supply chain, it's a common and understandable misconception that it was designed for WordPress. At the outset, the FAIR Protocol was designed to be platform-agnostic to the point where you could use it to distribute epub or mp3 files, basically any digital goods, not just software. WordPress was obviously the initial application of the FAIR Protocol, which actually has all of the WordPress-centric parts of the spec as extensions to and not part of the core protocol. It was always designed for more than WordPress. A couple of weeks ago at CloudFest, FAIR & TYPO3 teams worked together to add TYPO3 support to FAIR, and vice-versa, so that TYPO3's upcoming release can have FAIR support in its core. This was a 3-day hackathon project that didn't take all 3 days - the upshot is that FAIR has definitely moved beyond WordPress, and has several other platforms interested. One of the key points is that FAIR moved quickly past thinking about WordPress to thinking about supply chain security more generally, and working on a distributed model that could also improve upon the security we've known. The design gives us the ability to add more (or less) stringent requirements where appropriate, along with labeller support including third-party labels. On that front, we worked with Patchstack to create a proof-of-concept labeller in November that could put CVE information in front of site owners at the time they were making install decisions. Miscellaneous thoughts - FAIR has been vetted and tested by a few web hosts, who have chosen not to make any public statements about it, but who affirmed the security of the architecture. On labelling, a small heads-up that you won't be able to say whether something is CRA-compliant, only that it isn't. This is something I've been looking at in recent weeks for our own purposes, but boils down to not being able to apply the right tests without knowing the use case for the software. There is however a baseline list of requirements (SBOM, Security info, etc) without which it would not be CRA-compliant, so the wording of the label will matter significantly. Sorry for the lengthy post, but you won't see a lot of that on the FAIR website or on GitHub without a lot of digging. :) We'd be happy to collaborate, and would be very open to discuss what that could look like and where the collaboration could help propel the marketplace forward as well, as that's an aspect we're discussing as well, with the FAIR Protocol having roughed-in support for commercial participation in monetizing an ecosystem. |
Beta Was this translation helpful? Give feedback.
-
|
@toderash thank you for the detailed reply. As you've laid out, what I proposed is pretty close to what FAIR has spec'd out. I think the biggest gaps are around formatting of things like manifests (easy to match FAIR) and the API contracts defined in FAIR (this is the bigger bit). Since we're early stage, making the API contracts compatible is something that could be tackled now. Either by reworking the current ones or adding a facade that maps the endpoints. As we move forward with more detailed specifications, I'll aim to match FAIR as closely as possible and call out if/why anything differs. |
Beta Was this translation helpful? Give feedback.
-
|
Over the coming weekend I'll work on a more detailed specification for review. |
Beta Was this translation helpful? Give feedback.
-
|
Great discussion. I’ve been experimenting with building a frontend on top of the existing headless marketplace pieces in the repo, mainly to understand how discovery and distribution feel in practice. Some of that work landed in #384 (admin-configurable marketplace settings). One thing that became clear pretty quickly while building:
As soon as you support different ingestion paths (GitHub releases, npm, ZIPs), you start needing:
A few other observations from working against the current model:
So from actually building on top of it, the distributed/federated direction being discussed here feels less like a future extension and more like a natural continuation of how the system already behaves in practice. Happy to share more concrete examples if helpful. Still early, but building a frontend against it made some of these tradeoffs much clearer. |
Beta Was this translation helpful? Give feedback.
-
Proposed EmDash Decentralized Marketplace SpecStatus: Draft / Proposal 1. OverviewThis document proposes a decentralized plugin distribution system for EmDash. It draws from the FAIR protocol (Federated and Independent Repositories) and the AT Protocol's identity, labeler, and data distribution architecture. Where those systems were designed for platforms without runtime safety guarantees, this spec takes advantage of EmDash's sandbox model to build a lighter trust layer that achieves similar outcomes with less infrastructure. The goals are:
This spec describes the full system as a single deliverable. Nothing ships until the complete architecture is operational. 2. Design PrinciplesThe sandbox is the primary trust layer. EmDash plugins run in isolated V8 workers with declared capability manifests. A plugin requesting No barriers for end users. Site owners install plugins and leave reviews using the identity they already have: their EmDash site authenticated via passkeys. No GitHub account, no ATProto login, no manual DID setup. Identity is derived transparently from their domain. Publishers (who are developers) interact with DIDs, signing keys, and PDS tooling via the CLI. Aggregators are convenience, not authority. An aggregator indexes plugins from multiple repositories and provides search. It cannot modify bundles (signatures prevent it). It cannot forge publisher identity (DIDs prevent it). If an aggregator misbehaves, sites remove it. Labelers are additive, not load-bearing. Labelers provide useful trust signals but the system is safe without them. A site owner can install a plugin with zero labeler subscriptions and still be protected by the sandbox. Distribution is decoupled from discovery. A plugin can be installed by DID alone, without any marketplace or aggregator. The DID document points to the repository. The repository serves the bundle. The signature verifies integrity. Discovery through aggregators is a convenience layer on top. 3. Architecture3.1 Layers3.2 Site Configuration// astro.config.mjs
emdash({
marketplace: {
// Repositories and aggregators to search for plugins.
// Searched in parallel. Results merged in the admin UI.
sources: [
"https://marketplace.emdashcms.com",
"https://plugins.mycompany.internal",
],
// Plugins installed directly by DID, bypassing discovery.
// The DID document points to the repository.
pinned: [
"did:web:somepublisher.dev:seo-meta",
],
// Labelers whose labels are fetched and displayed.
// Site owner configures per-label behavior.
labelers: [
"did:web:labeler.emdashcms.com",
"did:web:security.mycompany.internal",
],
// Mirrors for offline / air-gapped / non-Cloudflare bundle distribution.
mirrors: [
"https://mirror.hostingcompany.com/emdash-plugins",
],
},
})Backward compatibility: If 4. Identity4.1 DID MethodsTwo DID methods are supported. DID is required for all participants from day one.
Both methods are first-class. 4.2 Publisher IdentityPublishers register a DID via the CLI. Their DID document contains:
{
"@context": "https://www.w3.org/ns/did/v1",
"id": "did:web:benprice.dev",
"verificationMethod": [{
"id": "did:web:benprice.dev#atproto",
"type": "Multikey",
"controller": "did:web:benprice.dev",
"publicKeyMultibase": "zDnaef5rGMoatrSj1f..."
}],
"service": [
{
"id": "#atproto_pds",
"type": "AtprotoPersonalDataServer",
"serviceEndpoint": "https://pds.benprice.dev"
},
{
"id": "#emdash_repo",
"type": "EmDashRepository",
"serviceEndpoint": "https://marketplace.emdashcms.com"
}
]
}4.3 Package IdentityEach plugin gets a DID derived from the publisher's DID:
The package DID document contains the publisher DID (ownership link), the repository service endpoint, and the signing key for this package's bundles. 4.4 Site Identity (Transparent)EmDash sites get a
The site's DID is This gives every EmDash site a verifiable, globally unique identity without any user action. Reviews signed by 4.5 DID Resolution and CachingDID documents change rarely. EmDash core caches resolved DID documents with a 6-hour TTL (matching ATProto client conventions). Cache is stored in the site's local database. A failed signature verification triggers an immediate re-resolve to check for key rotation. 5. Bundle Signing5.1 Signing Flow
5.2 Verification Flow
A compromised repository or mirror cannot serve a tampered bundle. It would need the publisher's private key to produce a valid signature. 5.3 Bundle Manifest Additions{
"id": "seo-meta",
"version": "1.0.0",
"publisher": "did:web:benprice.dev",
"capabilities": ["read:content"],
"sbom": {
"format": "cyclonedx",
"url": "sbom.json"
},
"security": {
"contact": "security@benprice.dev",
"policy": "https://benprice.dev/.well-known/security.txt"
},
...
}The 6. Publisher PDS6.1 Plugin Listings as ATProto RecordsPublishers host plugin listings as records in a Personal Data Server. The EmDash marketplace indexes these records rather than being the source of truth. A custom ATProto lexicon defines the record schema: Each listing record contains:
When a publisher runs
The marketplace aggregator subscribes to publisher PDS repositories (via the ATProto firehose or polling) and indexes new listings automatically. 6.2 Why PDSThe publisher's catalog is portable. If they switch repositories, the listing data travels with the PDS. The marketplace can re-index from the PDS without the publisher re-uploading anything. Plugin metadata is owned by the publisher, not by the marketplace. A marketplace operator cannot unilaterally alter a listing's description, capabilities, or metadata. They can choose not to index it, but they can't modify it. Aggregators can discover new plugins by subscribing to the ATProto network rather than relying on publishers to manually register with each aggregator. 6.3 PDS HostingPublishers can:
The CLI handles PDS setup and record management. 7. Repositories and Mirrors7.1 RepositoriesA repository hosts plugin metadata and signed bundles. It implements the EmDash Marketplace API. All JSON responses include JSON-LD
Example response envelope: {
"@context": "https://emdashcms.com/ns/marketplace/v1",
"id": "seo-meta",
"name": "SEO Meta Tags",
"publisher": "did:web:benprice.dev",
"_links": {
"self": { "href": "/api/v1/plugins/seo-meta" },
"versions": { "href": "/api/v1/plugins/seo-meta/versions" },
"latest-bundle": { "href": "/api/v1/plugins/seo-meta/versions/1.0.0/bundle" },
"publisher": { "href": "/api/v1/publishers/did:web:benprice.dev" }
},
...
}Anyone can run a repository. The existing marketplace Worker at 7.2 MirrorsA mirror replicates bundles from upstream repositories for local, offline, or non-Cloudflare distribution. Mirrors serve bundles but don't need to implement the full marketplace API. Mirror sync protocol: A mirror fetches a bundle manifest from the upstream repository: Returns a list of all plugin bundles with their SHA-256 checksums and signatures: {
"bundles": [
{
"pluginId": "seo-meta",
"version": "1.0.0",
"bundleUrl": "/api/v1/plugins/seo-meta/versions/1.0.0/bundle",
"sha256": "abc123...",
"signature": "ed25519sig...",
"publisherDid": "did:web:benprice.dev",
"updatedAt": "2026-04-12T10:00:00Z"
}
],
"cursor": "..."
}The mirror downloads bundles it doesn't have (or where the checksum has changed), stores them locally, and serves them directly. Since bundles are signed by the publisher, a mirror cannot tamper with them. Verification works identically whether the bundle comes from the origin repository, a CDN, or an air-gapped mirror behind a corporate firewall. Mirror configuration: Sites add mirrors to their config. During install, EmDash checks mirrors before the origin repository: mirrors: [
"https://mirror.hostingcompany.com/emdash-plugins",
"file:///mnt/plugin-mirror", // local filesystem for air-gapped
],7.3 Why MirrorsEmDash runs on Node.js as well as Cloudflare Workers. A Node.js deployment behind a corporate firewall with no internet access can't reach 8. Aggregator8.1 EmDash AggregatorEmDash operates a first-party aggregator at
8.2 Third-Party AggregatorsAnyone can run an aggregator. A hosting company indexes plugins relevant to its customer base. An enterprise indexes only internally approved plugins. A regional aggregator surfaces plugins with documentation in a specific language. 8.3 Aggregator DiscoveryTwo models for how aggregators discover each other are under consideration. See Section 17 (Decisions) for the tradeoffs. Directory model: A well-known JSON file lists known aggregators: {
"aggregators": [
{
"url": "https://marketplace.emdashcms.com",
"name": "EmDash Official",
"did": "did:web:marketplace.emdashcms.com",
"tier": "official"
},
{
"url": "https://plugins.hostingcompany.com",
"name": "HostCo Plugin Index",
"did": "did:web:plugins.hostingcompany.com",
"tier": "verified"
}
]
}The EmDash project maintains this file. Aggregator operators request inclusion. The project curates the list. Gossip model: Each aggregator maintains its own peer list. When a new aggregator comes online, it introduces itself to one or more known peers. Those peers share their own peer lists. Over time, every aggregator knows about every other aggregator. No central directory required. Trust tiers (applicable to either model):
In either model, a compromised aggregator can't serve tampered bundles (signatures prevent it). It can only influence metadata and curation. 8.4 Aggregator APIAn aggregator exposes the same EmDash Marketplace API as a repository, plus:
A site can add an aggregator URL to its 9. Labelers9.1 ArchitectureLabelers are independent services that subscribe to events (new plugin versions, new reviews, new votes) and publish signed labels. The labeler protocol aligns with ATProto's labeler specification. A labeler has a DID, a signing key, and a set of label definitions. 9.2 ATProto AlignmentLabelers implement ATProto-compatible endpoints:
Label format: {
"src": "did:web:labeler.emdashcms.com",
"uri": "did:web:benprice.dev:seo-meta",
"val": "security-audit:pass",
"cts": "2026-04-12T10:00:00Z",
"sig": "ed25519signaturebase64..."
}
This format is directly compatible with ATProto labelers. Bluesky's Ozone moderation tool could consume EmDash labels and vice versa. 9.3 Label DefinitionsEach labeler publishes a definition document declaring its label vocabulary: {
"did": "did:web:labeler.emdashcms.com",
"labels": [
{
"id": "security-audit:pass",
"description": "Plugin passed automated security analysis",
"severity": "info"
},
{
"id": "sbom:verified",
"description": "SBOM present and dependencies checked against advisory databases",
"severity": "info"
},
{
"id": "cra:compliant",
"description": "Meets EU Cyber Resilience Act requirements",
"severity": "info"
}
]
}Site owners configure per-label behavior:
9.4 Potential EmDash-Operated LabelersEmDash could build and operate some or all of the following first-party labelers. See Section 17 (Decisions) for which to prioritize. Security scanner labeler -- extracted from the existing AI audit pipeline in the marketplace Worker. Subscribes to new plugin version events. Runs Workers AI code analysis and image analysis. Publishes SBOM/vulnerability labeler -- checks for SBOM presence, validates format, cross-references dependencies against vulnerability databases (e.g. GitHub Advisory DB, OSV). Publishes Publisher identity labeler -- verifies that a publisher's DID resolves correctly, the signing key matches, and the PDS is accessible. Publishes Review quality labeler -- detailed in Section 12. 9.5 Third-Party LabelersAnyone can build a labeler. Examples:
10. CRA Compliance ReadinessThe EU Cyber Resilience Act (arriving December 2027) requires software supply chain integrity, vulnerability disclosure, and dependency transparency. EmDash doesn't mandate these today, but the architecture supports them. SBOM: The manifest Security contact: The manifest Vulnerability disclosure: When a labeler detects a known vulnerability in a plugin's dependencies (via SBOM cross-referencing), it publishes a Provenance chain: Every bundle is signed by the publisher's DID-anchored key. The DID document is independently resolvable. The complete chain from "this person published this code at this time" is cryptographically verifiable. This positions EmDash ahead of most CMS ecosystems for CRA readiness. Compliance is enabled by the architecture and incentivized by labeler visibility, without being enforced as a gate. 11. Review System11.1 PrinciplesIf you can install a plugin, you can review it. No extra accounts. No signup. No barriers. The site owner's identity is 11.2 Submitting a ReviewThe plugin manager in the admin panel shows "Rate this plugin" for any installed plugin. The review form: star rating (1-5, required) and text (optional). On submit, EmDash core:
Review record: {
"reviewer": "did:web:theirsite.com",
"plugin": "did:web:benprice.dev:seo-meta",
"rating": 4,
"text": "Works well, easy to configure.",
"installDuration": "P92D",
"pluginVersion": "1.2.0",
"isActive": true,
"timestamp": "2026-04-12T10:00:00Z",
"sig": "ed25519signaturebase64..."
}11.3 Install-GatedA review can only be submitted if the marketplace has an install record for this site's DID on this plugin. No install, no review form. Same principle as Steam: you can only review what you own. 11.4 Install ContextThe marketplace displays install context alongside each review:
"Active for 3 months, version 1.2.0" is a very different trust signal than "installed 10 minutes ago." 11.5 Helpful / Unhelpful VotingAnyone browsing the marketplace (website or admin panel) can upvote or downvote a review. Helpful reviews surface to the top of the display order. 11.6 Rating WindowsTwo rating windows on the plugin detail page:
11.7 Publisher ReplyPublishers can respond to any review via their PDS or the CLI. Replies are visually distinct. One reply per review. 11.8 Portable Review RecordsEach review is a signed, self-contained record with a DID-based reviewer identity. Aggregators can carry reviews across sources. Deduplication is by 12. Review Quality LabelerBuilt and operated by EmDash. Inspired by the 12.1 ArchitectureThe review quality labeler follows the same pattern as
12.2 Categories
12.3 Llama Guard IntegrationFor R7 (toxic content), the labeler runs the review text through Llama Guard 3 8B (same model as the The Llama Guard result feeds into the decision as an additional signal alongside the pattern-matching categories. 12.4 Bombing DetectionThe labeler maintains a rolling baseline of review volume and sentiment per plugin (average reviews per day, average rating over 30 days). When incoming reviews deviate significantly from baseline (configurable threshold, default 3x volume with >1.5 standard deviation sentiment shift), the time window is flagged. Reviews within a flagged window are labeled 12.5 Labels Published
12.6 Default Aggregator Behavior
Aggregators can adjust these defaults. Labels are informational -- the aggregator decides how to act on them. 13. CLI Tooling13.1 Existing Commands (Unchanged)
13.2 Modified Commands
Currently: uploads tarball to marketplace, marketplace assigns author from GitHub auth. Changes:
Currently: produces a tarball with manifest, backend.js, admin.js, README, icon, screenshots. Changes:
13.3 New Commands
Generates an ED25519 keypair and registers a DID for the publisher. # did:web -- requires owning a domain
emdash plugin keygen --method web --domain benprice.dev
# Generates keypair, creates did.json, prompts to upload to domain
# did:plc -- registers via PLC directory
emdash plugin keygen --method plc
# Generates keypair, registers DID, stores credentials locallyStores private key in
Configures PDS connectivity for the publisher. # Use a hosted PDS
emdash plugin pds setup --host https://pds.provider.com
# Self-hosted PDS
emdash plugin pds setup --host https://pds.benprice.dev --self-hostedRegisters the publisher's DID with the PDS and updates the DID document's
Manually syncs plugin listings from the local bundle history to the PDS. Useful for initial migration or if the PDS was unavailable during publish. emdash plugin pds sync
# Reads all published bundles from local history, creates/updates PDS records
Syncs bundles from a repository to a local mirror directory. # Sync all bundles to a local directory
emdash plugin mirror --from https://marketplace.emdashcms.com --to ./mirror
# Sync specific plugins
emdash plugin mirror --from https://marketplace.emdashcms.com --to ./mirror --plugins seo-meta,sitemap
# Serve the mirror locally
emdash plugin mirror serve --dir ./mirror --port 8788
Inspect a published plugin's provenance and verification status. emdash plugin inspect did:web:benprice.dev:seo-meta
# Resolves DID, fetches metadata from repository, verifies signature,
# checks labeler status, displays trust summary
Generate a CycloneDX SBOM from the plugin's dependencies. emdash plugin sbom generate
# Reads package.json / pnpm-lock.yaml, produces sbom.json13.4 Credential StorageExtends the existing 14. Changes to EmDash Core14.1 MarketplaceClientThe existing
14.2 Site DID GenerationOn first boot, EmDash core:
This is automatic and invisible to the site owner. 14.3 Install FlowUpdated install flow:
14.4 Review SubmissionNew API route in EmDash core:
14.5 Admin UI
14.6 Database Migrations
15. Divergences from FAIRThese are active choices, not oversights. 15.1 Trust model weightFAIR: Signatures, labelers, SBOMs, and community vetting are all critical trust infrastructure. The trust model is load-bearing because WordPress plugins run with full system access. EmDash: The Dynamic Workers sandbox enforces capability boundaries at runtime. Signatures prove provenance. Labelers provide supplementary signals. SBOM and security metadata are available for compliance but not required for installation. The system is safe without any external trust infrastructure. Why: EmDash's sandbox is a genuinely different security model. Replicating FAIR's full trust stack as mandatory gates would add friction without proportional safety benefit. If adopted: Making labeler checks mandatory before installation would increase supply chain rigor and CRA readiness. It would also require labeler infrastructure to be operational and responsive before any plugin can be installed, creating an availability dependency. Site owners could configure this via the 15.2 WordPress API compatibilityFAIR: A large portion of FAIR's complexity is replacing WordPress.org's specific API endpoints as a drop-in replacement. EmDash: No legacy API to maintain. The marketplace API is designed for federation from the start. 15.3 Aggregator discoveryFAIR: AspireCloud is the initial aggregator, with architecture supporting fully independent aggregators from day one. Aggregator participation is permissionless -- anyone can stand one up without requesting inclusion in a central list. EmDash: Aggregator discovery model is pending decision (Section 17). If directory-based, this is a divergence from FAIR: aggregator participation is curated rather than permissionless. If gossip-based, EmDash would be closely aligned with FAIR's vision of fully decentralized aggregator participation. 15.4 Data distributionFAIR: Does not use ATProto's PDS/firehose model. Repositories are standalone servers with their own data stores. EmDash: Publishers host listings in a PDS. The marketplace indexes from PDS records. This is a deeper ATProto integration than FAIR has pursued. Why: EmDash is a greenfield CMS with no legacy data distribution to be compatible with. ATProto's PDS model provides data portability and real-time discoverability that standalone repositories don't. Since publishers are developers comfortable with CLI tooling, the PDS setup friction is acceptable. 16. Divergences from ATProto16.1 No ATProto login for site ownersATProto: Users have a DID and a PDS. All data lives in the PDS. EmDash: Site owners use a transparent Why: Requiring an ATProto account to rate a plugin is a non-starter for WordPress-replacement users. Many site owners are non-technical. Their hosting provider set up their site. They authenticate with passkeys. That's enough. If adopted: If reviews were ATProto records in the reviewer's PDS, they would be fully owned by the reviewer and portable. Reviews couldn't be deleted by the marketplace operator. The reviewer's entire review history would be cryptographically their own. The cost is that every site owner needs a PDS, which is infrastructure non-technical users should not be expected to manage. 16.2 PDS for publishers onlyATProto: All participants in the network use a PDS. EmDash: Only publishers use a PDS. Site owners have a Why: Publishers are developers who benefit from data portability and can handle CLI-driven PDS setup. Site owners need zero friction. Splitting the identity model between these user types is the pragmatic path. 16.3 No relay / firehose dependencyATProto: Data distribution relies on relays that aggregate the firehose from all PDSes. EmDash: The aggregator polls publisher PDSes directly or subscribes to their individual event streams. No centralized relay infrastructure. Why: The ATProto relay model is optimized for high-volume social networking workloads (millions of small records per second). EmDash plugin publishing is low-volume (maybe hundreds of new versions per day across the entire ecosystem). Direct polling or per-PDS subscription is sufficient and avoids depending on relay infrastructure. If adopted: Using the ATProto relay/firehose would give aggregators real-time, network-wide visibility into all plugin publishing activity from a single subscription point. It would also enable non-EmDash ATProto applications to discover and surface EmDash plugins. The cost is a hard dependency on relay availability and the operational overhead of firehose processing. 16.4 Custom lexicon, limited scopeATProto: Applications define lexicons for all their record types. A full ATProto social app has lexicons for posts, follows, likes, blocks, lists, etc. EmDash: Defines a single lexicon ( Why: The lexicon covers the one record type that benefits most from PDS portability: the plugin listing. Reviews don't need PDS portability (the site owner's If adopted: Full lexicon coverage (reviews, labels, aggregator metadata) would make the entire EmDash marketplace a native ATProto application. Any ATProto client could browse plugins, read reviews, and subscribe to labelers. The cost is significantly more protocol work and a tighter coupling to ATProto's evolution. 17. Open DecisionsThe following decisions need to be resolved before implementation can proceed. 17.1 Aggregator Discovery ModelHow do aggregators find each other? Option A: Directory A curated list maintained by the EmDash project (e.g. a JSON file in the repo or at a well-known URL). Aggregator operators request inclusion.
Option B: Gossip Aggregators discover peers by exchanging peer lists. A new aggregator introduces itself to one or more known peers and learns about others organically.
Option C: Directory now, gossip later Start with a directory for simplicity. Define a gossip protocol in the spec but don't require it. Migrate when the aggregator count justifies it.
17.2 EmDash-Operated LabelersWhich labelers should EmDash build and operate as first-party infrastructure? The architecture supports any number, but each is a service to build, deploy, and maintain. Security scanner labeler Extracted from the existing AI audit pipeline. Lowest-effort to build since most of the code exists in
Review quality labeler Described in detail in Section 12. Protects the review system from abuse.
SBOM/vulnerability labeler Cross-references plugin SBOMs against vulnerability databases.
Publisher identity labeler Verifies DID resolution, signing key validity, PDS accessibility.
Recommendation to discuss: At minimum, the security scanner labeler (already mostly built) and the review quality labeler (essential for review credibility) seem necessary. The publisher identity labeler is cheap to add. The SBOM labeler is high-value but may be premature until SBOM adoption among publishers reaches critical mass. 17.3 FAIR Extension SpecAPI responses include JSON-LD Option A: Publish alongside launch Coordinate with the FAIR team during implementation. Ship the extension spec as part of this deliverable.
Option B: Publish as a fast-follow Ship the marketplace without a FAIR extension spec. JSON-LD/HAL responses mean FAIR aggregators can partially index EmDash plugins (basic metadata works, EmDash-specific fields are opaque). Publish the extension spec once the marketplace is stable and the FAIR extension framework has matured via the TYPO3 implementation.
Option C: No formal extension spec Rely on JSON-LD/HAL for basic interop. Don't publish a FAIR extension. Let FAIR aggregators handle EmDash-specific fields however they choose.
17.4 PDS Hosting for PublishersWho operates the PDS infrastructure that publishers use? Option A: Bring your own PDS Publishers self-host or use an existing ATProto PDS provider. EmDash CLI handles the setup but doesn't provide infrastructure.
Option B: EmDash-operated PDS EmDash runs a shared PDS instance for plugin publishers (similar to how
Option C: Both EmDash operates a default PDS. Publishers can migrate to their own PDS at any time. Same model as Bluesky: start on the hosted PDS, self-host when you're ready.
18. Next StepsIf we're directionally aligned:
Feedback welcome on all of it. |
Beta Was this translation helpful? Give feedback.
-
|
@toderash @chuckadams there’s now an RFC ready for review here: #694 It’s not based on FAIR but it borrows a lot of ideas from it and still builds on atproto. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
I've renamed this discussion to be more general around the Marketplace itself rather than just my original ideas around building out the UI. The bits about the UI may still be useful later so I'm leaving the original body here.
--- Previous Body (4/18/2026) ---
The marketplace backend is solid but there's no public-facing layer yet. I'd like to build it out. Posting here to align on approach before I start opening PRs.
Gaps
No public website.
marketplace.emdashcms.comserves only the API. No web UI for discovering plugins outside the admin panel, nothing to land on from a search engine.No reviews or ratings. Install counts exist but there's no qualitative signal.
No categories.
keywordsJSON column but no structured taxonomy, no browse-by-category.Search is LIKE-based.
searchPlugins()won't scale. Should be FTS5.No publisher dashboard. CLI-only for publishing. No web UI for managing listings or viewing stats.
First-party plugins aren't seeded. 7+ plugins in the repo but only
marketplace-testis packaged for marketplace publishing.No marketplace docs.
Plan
Six PRs, ordered by dependency:
PR 1: Categories + FTS5 Search
Only touches
packages/marketplace.categories+plugin_categoriestablessearchPlugins()to use FTS5GET /categoriesroute,?category=filterPR 2: Reviews & Ratings
Touches
packages/marketplace+packages/admin.reviewstable, denormalizedrating_avg/rating_countonpluginssort=ratingMarketplacePluginDetailPR 3: Marketplace Website
New package. Flexible on approach (separate Astro site vs SSR in the existing Worker).
PR 4: Publisher Dashboard
Extends the website behind GitHub auth.
PR 5: Seed Plugins + Template Config
marketplaceURL tomarketing-cloudflare,portfolio-cloudflare,starter-cloudflare(already inblog-cloudflare)PR 6: Documentation
Questions
Website deployment. What's the preferred pattern for serving the website alongside the API Worker on
marketplace.emdashcms.com? Route splitting? SSR in the Hono app? Service bindings?PR ordering. Does this sequence make sense? Could also seed plugins (PR 5) before the website (PR 3) so there's content ready when the UI ships.
Design direction. Any preferences for the public website? Match the docs site, or its own thing? Leaning towards using
kumoalready.Review auth. Proposing GitHub auth (same as publisher flow). Right level of friction?
Categories. Fixed set of ~12, moderator-managed. Or something more dynamic?
Can start on PR 1 right away.
Detailed PRD with data models, architecture decisions, and scope estimates: https://gist.github.com/BenjaminPrice/270411fd4e2516331184ee4f8784a060
Beta Was this translation helpful? Give feedback.
All reactions