Skip to content

feat(plugins): add content discovery capabilities - #3171

Merged
ascorbic merged 5 commits into
mainfrom
feat/plugin-content-discovery
Sep 19, 2026
Merged

ascorbic merged 5 commits into
mainfrom
feat/plugin-content-discovery

Conversation

@ascorbic

Copy link
Copy Markdown
Collaborator

What does this PR do?

Adds the first Stack A capability slice for sandboxed plugin content discovery.

Plugins can declare schema:read to inspect batched public collection and field definitions. Existing content:read access gains safe content identity fields, translation discovery, and public URL resolution shared with menus, sitemaps, and hreflang output. Retained revision snapshots require the separate content:revisions:read capability, omit revision-author identity, and cannot read trashed entries.

The contract and consent boundary are implemented across shared/core manifest validation, registry lexicons, plugin CLI generation, native execution, Cloudflare Worker Loader, Node.js workerd, @emdash-cms/plugin-test, public docs, and canonical/generated authoring guidance.

Closes: n/a

Type of change

  • Bug fix
  • Feature (requires maintainer-approved Discussion)
  • Refactor (no behavior change)
  • Translation
  • Documentation
  • Performance improvement
  • Tests
  • Chore (dependencies, CI, tooling)

Checklist

  • I have read CONTRIBUTING.md
  • pnpm typecheck passes
  • pnpm lint passes
  • pnpm test passes (or targeted tests for my change)
  • pnpm format has been run
  • I have added/updated tests for my changes (if applicable)
  • User-visible strings in the admin UI are wrapped for translation (if applicable). Do not include messages.po changes except in translation PRs — a workflow extracts catalogs on merge to main.
  • I have added and reviewed the user-facing changeset (if this PR changes a published package)
  • New features link to an approved Discussion: n/a — this implements the maintainer-provided sandboxed capability expansion plan.
  • I have included screenshots below if this PR changes the UI

AI-generated code disclosure

  • This PR includes AI-generated code — model/tool: OpenAI Codex (GPT-5)

Screenshots / test output

Not applicable. The admin change adds localized consent copy without changing layout.

Validated with:

  • pnpm build
  • pnpm typecheck
  • pnpm lint:quick
  • pnpm lint:json (zero diagnostics)
  • pnpm --dir docs build
  • focused native, Cloudflare Worker Loader, Node.js workerd, plugin-test, registry, CLI, and admin test suites
  • independent adversarial review against origin/main, including a material-fix re-check

@changeset-bot

changeset-bot Bot commented Sep 17, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 2771851

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 18 packages
Name Type
@emdash-cms/plugin-types Minor
emdash Minor
@emdash-cms/cloudflare Minor
@emdash-cms/sandbox-workerd Minor
@emdash-cms/plugin-test Minor
@emdash-cms/plugin-cli Minor
@emdash-cms/registry-lexicons Minor
@emdash-cms/admin Minor
@emdash-cms/registry-verification Patch
@emdash-cms/registry-client Patch
@emdash-cms/registry-loader Patch
@emdash-cms/auth Minor
@emdash-cms/blocks Minor
create-emdash Minor
@emdash-cms/gutenberg-to-portable-text Minor
@emdash-cms/x402 Minor
@emdash-cms/auth-atproto Patch
@emdash-cms/plugin-embeds Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions

Copy link
Copy Markdown
Contributor

Scope check

This PR changes 1,605 lines across 55 files. Large PRs are harder to review and more likely to be closed without review.
This PR spans 4 different areas (area/core, area/admin, area/docs, area/cloudflare). Consider breaking it into smaller, focused PRs.

If this scope is intentional, no action needed. A maintainer will review it. If not, please consider splitting this into smaller PRs.

See CONTRIBUTING.md for contribution guidelines.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Sep 17, 2026 •

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
docs 2771851 Sep 19 2026, 10:30 AM

@pkg-pr-new

pkg-pr-new Bot commented Sep 17, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

@emdash-cms/admin

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/admin@3171

@emdash-cms/auth

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/auth@3171

@emdash-cms/auth-atproto

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/auth-atproto@3171

@emdash-cms/blocks

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/blocks@3171

@emdash-cms/cloudflare

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/cloudflare@3171

@emdash-cms/contentful-to-portable-text

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/contentful-to-portable-text@3171

emdash

npm i https://pkg.pr.new/emdash-cms/emdash@3171

create-emdash

npm i https://pkg.pr.new/emdash-cms/emdash/create-emdash@3171

@emdash-cms/gutenberg-to-portable-text

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/gutenberg-to-portable-text@3171

@emdash-cms/plugin-cli

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-cli@3171

@emdash-cms/plugin-test

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-test@3171

@emdash-cms/plugin-types

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-types@3171

@emdash-cms/registry-client

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/registry-client@3171

@emdash-cms/registry-lexicons

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/registry-lexicons@3171

@emdash-cms/registry-loader

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/registry-loader@3171

@emdash-cms/registry-moderation

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/registry-moderation@3171

@emdash-cms/registry-verification

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/registry-verification@3171

@emdash-cms/sandbox-workerd

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/sandbox-workerd@3171

@emdash-cms/x402

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/x402@3171

@emdash-cms/plugin-ai-moderation

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-ai-moderation@3171

@emdash-cms/plugin-atproto

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-atproto@3171

@emdash-cms/plugin-audit-log

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-audit-log@3171

@emdash-cms/plugin-color

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-color@3171

@emdash-cms/plugin-embeds

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-embeds@3171

@emdash-cms/plugin-field-kit

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-field-kit@3171

@emdash-cms/plugin-forms

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-forms@3171

@emdash-cms/plugin-webhook-notifier

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-webhook-notifier@3171

commit: 2771851

@github-actions

Copy link
Copy Markdown
Contributor

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Sep 17, 2026 •

Copy link
Copy Markdown

🚀 Deploying Preview to Cloudflare 🚀

Preview URL: https://feat-plugin-content-discovery.try.emdashcms.com, https://feat-plugin-content-discovery-emdash-playground.emdash-cms.workers.dev (commit 2771851)

This URL reflects your latest Preview deployment

Preview Deployments by commit

Status Deployment URL Commit Updated (UTC) See this deployment's details
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://fc9a7261.try.emdashcms.com, https://fc9a7261-emdash-playground.emdash-cms.workers.dev 2771851 2026-09-19T10:33:04.956Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://d5f5203b.try.emdashcms.com, https://d5f5203b-emdash-playground.emdash-cms.workers.dev 4b5e49e 2026-09-19T08:12:02.887Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://a98986c7.try.emdashcms.com, https://a98986c7-emdash-playground.emdash-cms.workers.dev 95cfe6e 2026-09-17T13:31:49.995Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://bc0f8712.try.emdashcms.com, https://bc0f8712-emdash-playground.emdash-cms.workers.dev 069e264 2026-09-17T13:13:58.799Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://b025c5b8.try.emdashcms.com, https://b025c5b8-emdash-playground.emdash-cms.workers.dev 7cf55da 2026-09-17T12:31:09.451Z Visit the dashboard ↗

@ascorbic ascorbic added the bot:review Trigger an emdashbot code review on this PR label Sep 17, 2026

@emdashbot emdashbot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a well-scoped slice of the Stack A sandboxed-plugin capability expansion. The approach is sound: schema:read, content:revisions:read, and the extended content:read surface (getTranslations, getPublicUrl) are capability-gated consistently across the manifest/lexicon trust contract, core PluginContextFactory, the Cloudflare PluginBridge, the Node.js/workerd bridge, and the plugin-test runtime. The shared resolveLocalizedContentRoutePath helper correctly unifies locale-aware public URL resolution between menus, sitemaps, hreflang, and the new plugin API. Revision snapshots correctly omit author identity and require the owning content row to be visible. Admin consent copies, CLI schema, registry lexicons, and docs are updated in step.

I checked:

  • Manifest → declaredAccess → capability round-trip in plugin-types and core.
  • Core context creation (createContentAccess, createSchemaAccess) and implication closure (definePlugin, adaptSandboxEntry).
  • Cloudflare bridge/wrapper and workerd bridge/wrapper parity for the new methods.
  • RevisionRepository.findVisibleBy* SQL safety and author stripping.
  • resolveLocalizedContentRoutePath usage in sitemap, hreflang, and menus.
  • Admin marketplace consent labels (Lingui descriptors present).
  • Documentation against implementation (capabilities.mdx, installing.mdx, SKILL.md).
  • Changeset against .changeset/README.md.
  • Query-count impact: the new methods are plugin-context only; EmDashHead/sitemap/menus already existed or only gained the existing trailingSlash option, with no new logged-out round-trips.

Headline: code is clean and the surface is consistent. The only gap I found is in the exhaustive round-trip test, which did not get updated for the new capabilities.


Findings

  • [suggestion] packages/plugin-types/tests/capabilities.test.ts:176

    The exhaustive declaredAccess ↔ capabilities round-trip test was not updated for the new capabilities added in this PR. contentChoices should include content:revisions:read states (e.g. ["content:read", "content:revisions:read"] and ["content:read", "content:write", "content:revisions:read"]) and singletonFacets should include "schema:read".

    The separate unit tests above do cover these mappings, but the comment here claims this enumeration is “the only states that can reach a published manifest,” so leaving them out makes the round-trip guard incomplete for the very surface this PR introduces. Update the choices and the expected count at line 222.

@emdashbot emdashbot Bot removed the bot:review Trigger an emdashbot code review on this PR label Sep 17, 2026
@github-actions github-actions Bot added review/awaiting-author Reviewed; waiting on the author to respond and removed review/needs-review No maintainer or bot review yet labels Sep 17, 2026
@ascorbic

Copy link
Copy Markdown
Collaborator Author

Addressed the exhaustive round-trip coverage gap in 95cfe6e: revision-read states and schema-read singleton combinations are now included, with the matrix updated to 9,600 implication-closed states. The focused test and format/lint checks pass.

@ascorbic ascorbic added the bot:review Trigger an emdashbot code review on this PR label Sep 17, 2026
@github-actions github-actions Bot added review/needs-rereview Author pushed changes since the last review and removed review/awaiting-author Reviewed; waiting on the author to respond labels Sep 17, 2026

@emdashbot emdashbot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review of the Stack A sandboxed-plugin content discovery slice. The only finding from the previous review — the incomplete exhaustive declaredAccess ↔ capabilities round-trip enumeration in packages/plugin-types/tests/capabilities.test.ts — has been addressed. contentChoices now covers the content:revisions:read states and singletonFacets includes "schema:read", with the expected count updated to 9600.

I re-checked the full surface introduced by this PR:

  • Trust contract: schema:read and content:revisions:read are present in plugin-types, core, plugin-cli, admin consent labels, registry lexicons, and the release-action bundle. Implication closure (revisionsRead → read) is applied consistently in definePlugin, adaptSandboxEntry, and declared-access.ts.
  • Runtime gating: PluginContextFactory populates ctx.schema, ctx.content.getTranslations, ctx.content.getPublicUrl, and revision methods only when the corresponding capabilities are held. The Cloudflare PluginBridge and workerd bridge-handler enforce the same capability checks before delegating.
  • Security boundaries: RevisionRepository.findVisibleBy* uses Kysely's tagged sql template with parameterized values and sql.ref() after validateIdentifier, checks the owning content row is not trashed, and the context strips authorId from revision snapshots. getPublicUrl only resolves published, routable rows with valid slugs.
  • URL resolution parity: The new resolveLocalizedContentRoutePath helper is shared by menus, sitemaps, hreflang output, and the plugin API, and now receives the site trailingSlash setting consistently.
  • Tests: New integration/unit tests cover schema discovery, translation discovery, public URL resolution, revision access, and the updated round-trip enumeration. The plugin-test fixture exercises the new methods end-to-end through the sandbox.
  • Docs: capabilities.mdx, installing.mdx, api-routes.mdx, and skills/creating-plugins/SKILL.md describe the new capabilities accurately and match the implementation.
  • Changeset: The changeset explains the user-facing capability additions and the separate consent required for revision history.

No remaining logic bugs, security gaps, or AGENTS.md convention violations were found.

@emdashbot emdashbot Bot removed the bot:review Trigger an emdashbot code review on this PR label Sep 17, 2026
@github-actions github-actions Bot added review/approved Approved; no new commits since and removed review/needs-rereview Author pushed changes since the last review labels Sep 17, 2026
@github-actions github-actions Bot mentioned this pull request Sep 17, 2026
11 of 19 tasks

@emdashbot emdashbot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review of the Stack A plugin content-discovery slice. The prior review’s only finding — the incomplete contentChoices/singletonFacets enumeration in packages/plugin-types/tests/capabilities.test.ts — has been addressed: contentChoices now covers the content:revisions:read states, singletonFacets includes "schema:read", and the expected count was updated to 9600.

The change is the right one for the stated capability plan: it adds schema:read for collection/field introspection, extends content:read with safe identity fields, translation discovery, and public URL resolution, and adds a separately consented content:revisions:read for revision history. The trust-contract plumbing (implication closure, consent labels, manifest schemas, registry lexicons, and all three runtimes) is consistent. SQL is parameterized; identifier interpolation goes through validateIdentifier; revision reads are scoped to non-trashed owning content; and public URL resolution correctly requires published, a valid slug, and a routable collection.

I did find one remaining input-validation bug introduced by the new revision-list surface. createContentAccess.listRevisions and RevisionRepository.findVisibleByEntry both derive LIMIT values without coercing/validating the caller-provided limit. A non-numeric (or NaN) limit reaches Kysely parameterized SQL as LIMIT NaN, which will produce a runtime SQL error from a sandboxed plugin. The contentList bridge handler already has the correct safe pattern (Number(opts.limit) || 50); the new revision path should match it. A test exercising the malformed-limit boundary is also missing.

Everything else — docs accuracy for the new capabilities, changeset prose, test coverage of the happy paths, runtime gating, and i18n routing parity — looks clean.

Comment thread packages/core/src/plugins/content-access.ts
Comment thread packages/core/src/database/repositories/revision.ts
@emdashbot emdashbot Bot removed the bot:review Trigger an emdashbot code review on this PR label Sep 19, 2026
@ascorbic
ascorbic force-pushed the feat/plugin-content-discovery branch from 4b5e49e to 2771851 Compare September 19, 2026 10:27
@github-actions github-actions Bot added review/needs-rereview Author pushed changes since the last review and removed review/approved Approved; no new commits since labels Sep 19, 2026
@ascorbic ascorbic added the bot:review Trigger an emdashbot code review on this PR label Sep 19, 2026

@emdashbot emdashbot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This re-review confirms that the Stack A plugin content-discovery slice is the right change for the stated capability plan and that the only prior finding has been addressed.

Approach. The PR adds schema:read for collection/field introspection, extends content:read with safe identity fields, translation discovery, and public URL resolution, and introduces a separately consented content:revisions:read for revision history. The trust-contract plumbing is consistent across native plugins, the Cloudflare Worker Loader bridge, and the Node.js workerd bridge: capability normalization adds the content:revisions:read → content:read implication, manifest schemas and registry lexicons include the new authorities, and wrappers generate the matching ctx.schema / ctx.content methods.

Prior finding — resolved. The previous review called out that createContentAccess.listRevisions and RevisionRepository.findVisibleByEntry passed unvalidated limit values through to Kysely. This is now fixed: normalizeRevisionLimit coerces non-numeric/NaN/infinite values and clamps the result to 1–100, and packages/core/tests/integration/plugins/capabilities.test.ts exercises the malformed-limit boundary for both the access surface and the repository.

What I checked. Capability implication closure in definePlugin, adaptSandboxEntry, PluginContextFactory, and @emdash-cms/plugin-types declared-access conversion; manifest schemas in packages/core and plugin-cli; the registry lexicon; SQL in revision reads (parameterized values, validateIdentifier for identifiers, deleted_at IS NULL scoping); public URL and menu/hreflang/sitemap resolution with the new shared resolveLocalizedContentRoutePath; per-runtime bridge gating in Cloudflare and workerd; wrapper code generation; docs accuracy against implementation; changeset prose per .changeset/README.md; and the new admin consent labels.

Conclusion. I found no blocking issues, regressions, or AGENTS.md violations. The diff is clean and the tests cover the new boundaries.

@emdashbot emdashbot Bot removed the bot:review Trigger an emdashbot code review on this PR label Sep 19, 2026
@github-actions github-actions Bot added review/approved Approved; no new commits since and removed review/needs-rereview Author pushed changes since the last review labels Sep 19, 2026
@ascorbic
ascorbic merged commit 80ccfaf into main Sep 19, 2026
65 of 66 checks passed
@ascorbic
ascorbic deleted the feat/plugin-content-discovery branch September 19, 2026 13:08
@emdashbot emdashbot Bot mentioned this pull request Sep 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant