Skip to content

feat(plugins): add sandboxed comment administration - #3170

Open
ascorbic wants to merge 3 commits into
mainfrom
codex/comment-administration
Open

ascorbic wants to merge 3 commits into
mainfrom
codex/comment-administration

Conversation

@ascorbic

@ascorbic ascorbic commented Sep 17, 2026

Copy link
Copy Markdown
Collaborator

What does this PR do?

Adds the complete sandboxed comment-administration vertical slice with comments:read and comments:moderate. Moderation implies read access.

ctx.comments can get, count, and cursor-page through non-trashed comments. The read capability explicitly exposes author names and email addresses, comment bodies, pseudonymous IP hashes, user agents, and moderation metadata, but not linked EmDash user-account IDs. Installation and updates surface that personal-data access for consent.

Authorized plugins can move comments between approved, pending, and spam through the core moderation service. Each mutation requires the previously observed status, rejects stale writes with COMMENT_STATUS_CONFLICT, identifies plugin origin, runs comment:afterModerate once, preserves approval notifications, and fences recursive moderation across native and sandbox execution. Hard deletion and bulk replacement remain out of scope.

The contract is carried through shared and core manifest validation, declared-access canonicalization, registry lexicons and generated types, the plugin CLI and generated authoring skill, native context, Cloudflare Worker Loader, Node/workerd, and runtime-backed plugin-test helpers. Public documentation and package changesets describe the same authority and result shape.

This is an independent capability from the maintainer-directed sandbox capability plan, built on the runtime-test foundation merged in #3162.

Type of change

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

This is maintainer-directed capability work; there is no separate Discussion URL.

Checklist

  • I have read CONTRIBUTING.md
  • pnpm typecheck passes
  • pnpm lint passes
  • pnpm test passes (targeted tests for this 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 (capability consent labels use Lingui; no messages.po files included)
  • I have added and reviewed the user-facing changeset
  • New features link to an approved Discussion (not applicable; maintainer-directed sandbox capability plan)
  • I have included screenshots below if this PR changes the UI (not applicable; no rendered UI change)

AI-generated code disclosure

  • This PR includes AI-generated code — model/tool: GPT-5.6 Sol in Codex

Screenshots / test output

Not applicable; this PR does not change rendered UI.

Validated locally:

  • full repository build and typecheck;
  • type-aware lint with zero diagnostics and git diff --check;
  • 226 focused core capability, comment, consent, concurrency, recursion, and runtime tests;
  • 54 Node/workerd bridge and wrapper tests;
  • real Cloudflare Worker Loader runtime-host journeys, including personal-data shape, invalid-value denial, slow-hook concurrency, recursive moderation, notifications, and origin;
  • Cloudflare bridge/wrapper, admin consent, shared contract, registry lexicon, CLI schema/generated-skill, and documentation tests;
  • real admin comment-approval HTTP integration journey; and
  • documentation build (85 pages).

A separate adversarial-review agent reviewed the exact main...6ebf5b4988dea0a399434b7bb0690713e54dd1f4 diff. Its three findings were fixed and the exact-SHA re-check passed with no remaining blockers.

@changeset-bot

changeset-bot Bot commented Sep 17, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 6ebf5b4

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/admin Minor
@emdash-cms/cloudflare Minor
@emdash-cms/plugin-cli Minor
@emdash-cms/plugin-test Minor
@emdash-cms/plugin-types Minor
@emdash-cms/registry-lexicons Minor
@emdash-cms/sandbox-workerd Minor
emdash 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,993 lines across 59 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.

@ascorbic ascorbic changed the title feat(plugins): add comment administration capability feat(plugins): add sandboxed comment administration Sep 17, 2026
@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 03c2e00 Sep 17 2026, 12:24 PM

@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@3170

@emdash-cms/auth

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

@emdash-cms/auth-atproto

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

@emdash-cms/blocks

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

@emdash-cms/cloudflare

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

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

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

emdash

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

create-emdash

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

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

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

@emdash-cms/plugin-cli

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

@emdash-cms/plugin-test

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

@emdash-cms/plugin-types

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

@emdash-cms/registry-client

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

@emdash-cms/registry-lexicons

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

@emdash-cms/registry-loader

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

@emdash-cms/registry-moderation

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

@emdash-cms/registry-verification

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

@emdash-cms/sandbox-workerd

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

@emdash-cms/x402

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

@emdash-cms/plugin-ai-moderation

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

@emdash-cms/plugin-atproto

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

@emdash-cms/plugin-audit-log

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

@emdash-cms/plugin-color

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

@emdash-cms/plugin-embeds

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

@emdash-cms/plugin-field-kit

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

@emdash-cms/plugin-forms

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

@emdash-cms/plugin-webhook-notifier

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

commit: 6ebf5b4

@cloudflare-workers-and-pages

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

Copy link
Copy Markdown

🚀 Deploying Preview to Cloudflare 🚀

Preview URL: https://codex-comment-administration.try.emdashcms.com, https://codex-comment-administration-emdash-playground.emdash-cms.workers.dev (commit 6ebf5b4)

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://89598272.try.emdashcms.com, https://89598272-emdash-playground.emdash-cms.workers.dev 6ebf5b4 2026-09-17T13:33:55.083Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://b9366d21.try.emdashcms.com, https://b9366d21-emdash-playground.emdash-cms.workers.dev 4f887d1 2026-09-17T13:19:01.766Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://2a625786.try.emdashcms.com, https://2a625786-emdash-playground.emdash-cms.workers.dev 03c2e00 2026-09-17T12:26:22.648Z 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.

The approach is sound and well-scoped: it extends EmDash’s existing capability model to comments with clear separation between read (comments:read) and moderation (comments:moderate, which implies read), uses expected-status preconditions for safe concurrent moderation, and carries the same contract through native plugin contexts, the Cloudflare sandbox bridge, the workerd bridge, manifest/lexicon declarations, and admin consent labels. It fits the existing architecture without touching logged-out hot paths.

I traced the core path end-to-end: CommentRepository.updateStatusIf correctly atomically guards on the expected status; EmDashRuntime fences recursive and overlapping moderation through both an AsyncLocalStorage guard and an in-progress set; the admin route delegates through handleCommentModerate with the observed previous status so admins keep the same restore-from-trash behavior; and both the Cloudflare PluginBridge and workerd bridge-handler validate plugin-provided statuses before touching the runtime. The plugin-test host, bridge tests, and repository tests cover status conflicts, recursion blocking, personal-data shape, and runtime bridge round-trips.

I found only two small issues: a stale comment that no longer matches the awaited behavior, and a changeset paragraph that describes an internal test helper rather than user-facing release impact.

Comment thread packages/core/src/comments/service.ts Outdated
Comment thread .changeset/calm-comments-moderate.md Outdated
@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 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 cla: signed and removed review/awaiting-author Reviewed; waiting on the author to respond labels Sep 17, 2026
@ascorbic ascorbic added bot:review Trigger an emdashbot code review on this PR and removed bot:review Trigger an emdashbot code review on this PR 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 sandboxed comment-administration slice.

Approach judgment: The change is still the right, well-scoped capability extension. It gives plugins explicit, consent-gated comments:read/comments:moderate access, keeps moderation behind an observed-status precondition, and routes both plugin and admin transitions through the same core service so notifications and hooks behave identically. The architecture matches the existing content/media capability model and does not touch logged-out routes.

What I checked:

  • Read the full diff and the prior review context.
  • Verified the two prior findings are addressed: the comment:afterModerate hook contract and comments now describe the awaited (not fire-and-forget) behavior, and the changeset now describes the user-facing release impact instead of an internal test helper.
  • Traced the moderation path end-to-end:
    • CommentRepository.updateStatusIf atomically guards on expectedStatus and returns clear updated/unchanged/conflict/not_found states.
    • EmDashRuntime fences recursion with AsyncLocalStorage and overlaps with a per-runtime commentModerationInProgress set, returning COMMENT_MODERATION_IN_PROGRESS or COMMENT_STATUS_CONFLICT as appropriate.
    • The admin route delegates through handleCommentModerate with the observed prior status, preserving restore-from-trash behavior, and still checks comments:moderate authorization.
    • Plugin contexts, the Cloudflare PluginBridge, the workerd bridge-handler, and both generated wrappers validate comments:moderate vs comments:read and canonicalize comments:moderatecomments:read.
  • Checked that PluginComment omits authorUserId while exposing the documented personal-data fields, and that documented shapes/capabilities match code and tests.
  • Reviewed SQL usage in the changed repository code: Kysely builders, no raw interpolation, existing idx_comments_status index covers the new status filters.
  • Confirmed the changeset package list matches the changed packages and that the prose meets the .changeset/README.md guidance.

Headline conclusion: This is a clean, complete vertical slice. The prior blockers are resolved, and I found no new logic bugs, security issues, or AGENTS.md convention violations. LGTM.

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