Conversation
Co-authored-by: ascorbic <ascorbic@users.noreply.github.com>
🦋 Changeset detectedLatest commit: a70fab2 The changes in this PR will be included in the next version bump. This PR includes changesets to release 13 packages
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 |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
emdash-i18n | a70fab2 | Apr 29 2026, 11:15 AM |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
docs | a70fab2 | Apr 29 2026, 11:16 AM |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
emdash-perf-coordinator | a70fab2 | Apr 29 2026, 11:15 AM |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
emdash-playground | a70fab2 | Apr 29 2026, 11:17 AM |
PR template validation failedPlease fix the following issues by editing your PR description:
See CONTRIBUTING.md for the full contribution policy. |
Scope checkThis PR changes 1,369 lines across 38 files. Large PRs are harder to review and more likely to be closed without review. 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. |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
emdash-demo-cache | a70fab2 | Apr 29 2026, 11:16 AM |
@emdash-cms/admin
@emdash-cms/auth
@emdash-cms/blocks
@emdash-cms/cloudflare
emdash
create-emdash
@emdash-cms/gutenberg-to-portable-text
@emdash-cms/x402
@emdash-cms/plugin-ai-moderation
@emdash-cms/plugin-atproto
@emdash-cms/plugin-audit-log
@emdash-cms/plugin-color
@emdash-cms/plugin-embeds
@emdash-cms/plugin-forms
@emdash-cms/plugin-webhook-notifier
commit: |
Overlapping PRsThis PR modifies files that are also changed by other open PRs:
This may cause merge conflicts or duplicated work. A maintainer will coordinate. |
There was a problem hiding this comment.
Pull request overview
Implements the capability renaming/unification plan from #767 by introducing canonical capability names, adding a runtime alias/normalization layer for legacy names, and wiring deprecation warnings/errors into the plugin CLI and marketplace upgrade diffing.
Changes:
- Introduces canonical capability strings + legacy alias mapping utilities (
CAPABILITY_RENAMES,normalizeCapability(ies)) and updates runtime checks to only use canonical names. - Updates hook registration gating to the new
hooks.<family>:registernamespace and replaces:anywith:unrestricted. - Adds CLI warnings (bundle/validate) and publish-time hard-fail for deprecated capability names; normalizes capability diffs in marketplace upgrades; updates docs and tests accordingly.
Reviewed changes
Copilot reviewed 38 out of 38 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| skills/creating-plugins/references/hooks.md | Updates hook capability requirements/examples to canonical names. |
| skills/creating-plugins/references/api-routes.md | Updates external proxy example to network:request. |
| skills/creating-plugins/SKILL.md | Updates capability references and tables to canonical names. |
| packages/core/tests/unit/plugins/standard-format.test.ts | Updates expected serialized capabilities to canonical names. |
| packages/core/tests/unit/plugins/pipeline-rebuild.test.ts | Updates test plugin capabilities for hook pipeline rebuild scenarios. |
| packages/core/tests/unit/plugins/page-hooks-execution.test.ts | Updates page fragments capability naming and assertions. |
| packages/core/tests/unit/plugins/marketplace-client.test.ts | Updates manifest fixtures to canonical capabilities. |
| packages/core/tests/unit/plugins/manager.test.ts | Updates implication expectations to content:write ⇒ content:read. |
| packages/core/tests/unit/plugins/hooks.test.ts | Updates hook gating tests to canonical capabilities. |
| packages/core/tests/unit/plugins/exclusive-hooks.test.ts | Updates email hook capability fixtures to new hook-registration capabilities. |
| packages/core/tests/unit/plugins/email-pipeline.test.ts | Updates email pipeline tests to canonical + hook-registration capabilities. |
| packages/core/tests/unit/plugins/define-plugin.test.ts | Updates capability validation/implication tests and adds alias-layer tests. |
| packages/core/tests/unit/plugins/capability-normalization.test.ts | Adds unit tests for rename map + normalization helpers. |
| packages/core/tests/unit/plugins/adapt-sandbox-entry.test.ts | Updates and extends adapter tests for canonicalization + alias layer. |
| packages/core/tests/unit/api/marketplace-handlers.test.ts | Updates capability diff expectations and adds “rename is no change” coverage. |
| packages/core/tests/integration/plugins/capabilities.test.ts | Updates enforcement error matching and capability fixtures. |
| packages/core/tests/integration/comments/hooks.test.ts | Updates comment hook capability fixtures to users:read. |
| packages/core/src/plugins/types.ts | Defines canonical + deprecated capability unions, rename map, and normalization helpers. |
| packages/core/src/plugins/manifest-schema.ts | Splits current vs deprecated capability enums; keeps combined accepted list. |
| packages/core/src/plugins/hooks.ts | Updates HOOK_REQUIRED_CAPABILITY mapping to canonical + hook-registration capabilities. |
| packages/core/src/plugins/define-plugin.ts | Normalizes deprecated capability names before applying implications. |
| packages/core/src/plugins/context.ts | Updates context gating and error messages to canonical capabilities. |
| packages/core/src/plugins/adapt-sandbox-entry.ts | Normalizes deprecated capability names in sandbox adapter before implications. |
| packages/core/src/emdash-runtime.ts | Updates built-in plugin capabilities to canonical names. |
| packages/core/src/cli/commands/publish.ts | Hard-fails publish if manifest contains deprecated capability names. |
| packages/core/src/cli/commands/bundle.ts | Adds deprecated-capability warnings; updates network warning logic/messages. |
| packages/core/src/api/handlers/marketplace.ts | Normalizes both sides in diffCapabilities to avoid spurious upgrade prompts. |
| docs/src/content/docs/reference/hooks.mdx | Updates hook docs to new capability names. |
| docs/src/content/docs/plugins/sandbox.mdx | Updates sandbox docs to canonical capabilities. |
| docs/src/content/docs/plugins/overview.mdx | Updates overview docs + adds rename table and deprecation note. |
| docs/src/content/docs/plugins/installing.mdx | Updates installation audit capability labels to canonical names. |
| docs/src/content/docs/plugins/hooks.mdx | Updates content hook capability requirements to canonical names. |
| docs/src/content/docs/plugins/creating-plugins.mdx | Updates example plugin descriptor capabilities to canonical names. |
| docs/src/content/docs/plugins/api-routes.mdx | Updates external API proxy docs to network:request. |
| docs/src/content/docs/plugins/admin-ui.mdx | Updates example capability to network:request. |
| docs/src/content/docs/migration/porting-plugins.mdx | Updates migration table capability strings to canonical names. |
| docs/src/content/docs/coming-from/astro.mdx | Updates example capability to content:read. |
| .changeset/unified-plugin-capabilities.md | Adds minor changeset describing renames + deprecation behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| * Type guard: is this capability one of the deprecated legacy names? | ||
| */ | ||
| export function isDeprecatedCapability(cap: string): cap is DeprecatedPluginCapability { | ||
| return cap in CAPABILITY_RENAMES; |
There was a problem hiding this comment.
isDeprecatedCapability uses the in operator, which also matches properties on Object.prototype (e.g. "toString", "constructor"). That means normalizeCapability("toString") would incorrectly treat it as deprecated and return a non-string value from the prototype chain, violating the function contract (“unknown strings are returned as-is”) and potentially breaking callers.
Use an own-property check instead (e.g. Object.hasOwn(CAPABILITY_RENAMES, cap) / Object.prototype.hasOwnProperty.call(...)) or construct CAPABILITY_RENAMES with Object.create(null) to avoid a prototype entirely. Consider adding a test case for "toString"/"proto" to prevent regressions.
| return cap in CAPABILITY_RENAMES; | |
| return Object.prototype.hasOwnProperty.call(CAPABILITY_RENAMES, cap); |
ascorbic
left a comment
There was a problem hiding this comment.
@ask-bonk thanks for the PR. I found one runtime blocker and one smaller docs issue.
HIGH: Cloudflare sandbox still enforces the old capability names
packages/cloudflare/src/sandbox/bridge.ts and packages/cloudflare/src/sandbox/bridge-http.ts still check legacy capabilities like read:content, write:content, network:fetch, and network:fetch:any. This PR updates manifests/descriptors and core runtime checks to canonical names like content:read and network:request, but those canonical names are passed through to the Cloudflare bridge and denied there.
Trigger: publish or install a sandboxed plugin with capabilities: ["content:read", "network:request"], then call ctx.content.get() or ctx.http.fetch(). The bridge sees no read:content / network:fetch and throws a missing capability error.
Fix: normalize capabilities before passing them into the bridge and update bridge enforcement to check canonical names. Keep the normalization path so existing installed manifests with legacy names continue working.
LOW: Publish failure links to a missing docs page
packages/core/src/cli/commands/publish.ts points deprecated-capability failures at https://emdashcms.com/docs/plugins/capabilities, but this repo does not have a docs/plugins/capabilities page. Users who hit the new publish error get sent to a 404.
Trigger: run emdash plugin publish on a tarball containing read:content.
Fix: point to the existing capability section in the plugin overview docs, or add the referenced page.
|
@ascorbic Bonk workflow failed. Check the logs for details. View workflow run · To retry, trigger Bonk again. |
|
/ultrabonk address the issues in the reviews |
Co-authored-by: ascorbic <ascorbic@users.noreply.github.com>
|
All review feedback has been addressed. Below is the PR body for the new changes. What does this PR do?Addresses the three review findings on PR #816: Copilot — ascorbic HIGH — Cloudflare sandbox bridge still enforced legacy names.
To make this possible, exported ascorbic LOW — Publish error pointed at a 404. Updated the changeset to bump Type of change
These are bug fixes against the open feature PR #816 — the underlying feature itself was discussed and approved on issue #767. Checklist
Tests added
AI-generated code disclosure
Notes for reviewers
|
|
/ultrabonk tests are failing. fix pls |
Co-authored-by: ascorbic <ascorbic@users.noreply.github.com>
|
Lint clean. The fix is minimal and targeted: align the test expectation with the actual What does this PR do?Fixes the Updated the expected key list to match the actual source order: 7 canonical names followed by 7 legacy aliases. No source/runtime changes — this is a test-only realignment. The other failing check on PR #816 ( Type of change
Checklist
AI-generated code disclosure
|
The Apply workflow has been silently broken for ~2 days, so PRs that landed in that window (#820, #816, #811) merged with stale snapshots in the repo. CI's Measure on this PR shows the real per-route counts on top of current main: uniform +1 cold and +1 warm on all public routes except /pages/about and /rss.xml. This is pre-existing drift, not caused by this PR — it's the natural consequence of the Apply workflow failing to commit the auto-updated snapshots that the Measure step correctly produced on those merged PRs. Apply CI's regenerated snapshots from this PR's Measure run so main ends up in a consistent state once this lands. Future PRs will then get their own real drift signal rather than inheriting this baseline correction.
* fix(ci): query-counts-apply workflow no longer silently fails
The Apply workflow has been silently broken for ~2 days, never pushing
auto-updated snapshots to PRs that drifted. Two distinct bugs:
(1) Same-repo PRs: the artifact was unpacked into
$GITHUB_WORKSPACE/artifact, but the later actions/checkout step
defaults to clean: true and wipes the workspace, deleting our
payload before the Apply step's cp could read it. Stage under
$RUNNER_TEMP/qc-artifact instead, which sits outside the workspace.
(2) Fork PRs: the cross-check used listPullRequestsAssociatedWithCommit
against the base repo, which doesn't reliably surface fork PRs from
that view, so valid fork artifacts were rejected as 'not associated
with commit'. Replace with pulls.get(prNumber) and verify
pr.head.sha === workflow_run.head_sha — equivalent tamper-resistance
(a forged pr-number won't match), works for forks.
Add pull-requests: read so pulls.get works when the default token
permissions are restricted.
* ci: reconcile query-count snapshot baseline
The Apply workflow has been silently broken for ~2 days, so PRs that
landed in that window (#820, #816, #811) merged with stale snapshots
in the repo. CI's Measure on this PR shows the real per-route counts
on top of current main: uniform +1 cold and +1 warm on all public
routes except /pages/about and /rss.xml. This is pre-existing drift,
not caused by this PR — it's the natural consequence of the Apply
workflow failing to commit the auto-updated snapshots that the
Measure step correctly produced on those merged PRs.
Apply CI's regenerated snapshots from this PR's Measure run so main
ends up in a consistent state once this lands. Future PRs will then
get their own real drift signal rather than inheriting this baseline
correction.
Final summary: Implemented the unified plugin capability formula with deprecation aliases per the issue.
What does this PR do?
Implements the unified plugin capability formula proposed in the issue: every capability now follows
<resource>[.<sub-resource>]:<verb>[:<qualifier>], hook-registration permissions are split out into their ownhooks.<family>:registernamespace, and:anyis replaced with the conspicuous:unrestrictedqualifier. Old names keep working through a runtime alias layer for one minor; the bundle/validate CLIs warn for each deprecated name andpublishhard-fails before authentication.Closes #767
read:contentcontent:readwrite:contentcontent:writeread:mediamedia:readwrite:mediamedia:writeread:usersusers:readnetwork:fetchnetwork:requestnetwork:fetch:anynetwork:request:unrestrictedemail:providehooks.email-transport:registeremail:intercepthooks.email-events:registerpage:injecthooks.page-fragments:registerWhere the alias layer lives — and the runtime never sees the old names:
definePluginandadaptSandboxEntryrewrite vianormalizeCapabilities()before applying capability implications, sowrite:contentstill implies the (canonical)content:read.context.tsandhooks.tswere updated to check the canonical names only — no dual-checks.marketplace.tsdiffCapabilitiesnormalizes both sides before diffing, so a v1read:content→ v2content:readupgrade reports an empty diff (no spurious "capability changed" prompt).bundlewarns for each deprecated name,validate(which delegates to bundle) warns,publisherrors before authentication.Type of change
The issue itself has been the discussion (filed by the maintainer asking for implementation) —
/bonk implement thisfrom the maintainer is the implementation request.Checklist
pnpm typecheckpassespnpm lintpasses (18 baseline diagnostics, all preexisting and in unrelated files)pnpm testpasses (786 plugin/marketplace tests pass; 8 unrelated failures are admin-build infra issues)pnpm formathas been runTests added
tests/unit/plugins/capability-normalization.test.ts— 15 new tests pinningCAPABILITY_RENAMES,isDeprecatedCapability,normalizeCapability, andnormalizeCapabilities(idempotence, dedup, no-mutation).tests/unit/plugins/define-plugin.test.ts— added 3 tests for the alias layer in native plugins (rewrite all 10 deprecated names, dedup current+deprecated, normalize before applying implications).tests/unit/plugins/adapt-sandbox-entry.test.ts— added 2 tests for the same in sandboxed plugins.tests/unit/api/marketplace-handlers.test.ts— added 1 test thatdiffCapabilitiestreats an installedread:contentv1 → publishedcontent:readv2 as no change (noconfirmCapabilityChangesneeded).ResolvedPluginobjects directly were updated to use canonical names — these tests bypassdefinePlugin, so they're testing the runtime invariant that the pipeline only sees canonical names.AI-generated code disclosure
Notes for reviewers
.agents/skills/creating-plugins/**were left alone to avoid bulk drive-by changes — they'll pick up the new names through a separate template-sync flow. Old names in those template skills still work through the alias layer.packages/plugins/*and the marketplace audit fixtures still declare old names. They continue to work via the alias layer; updating them is a follow-up.email:sendunchanged per the issue's open question —sendis a genuine verb distinct from CRUD, so renaming it toemail:writewouldn't read better.Closes #767
github run