Skip to content

feat(plugin-api)!: the contract keeps what core consumes; plugins declare their own extension points - #133

Merged
zumbrunn merged 35 commits into
mainfrom
refactor/contract-split
Nov 10, 2026
Merged

zumbrunn merged 35 commits into
mainfrom
refactor/contract-split

Conversation

@zumbrunn

@zumbrunn zumbrunn commented Nov 7, 2026 •

Copy link
Copy Markdown
Member

Update: extension points are no longer declared by declare module augmentation (JSR rejects it in a published package). They are typed CapabilityKey<T> tokens instead; wherever this description says "declaration merging", read "typed key". Done in the slice-2 PR stacked on this one.

Part of #127. Stacked on #132.

  • AdminPageRegistration, AdminRouteRegistration, ContentEditorPlugin and the bar-action types leave core, with the keys admin.pages, admin.routes, editor.content, bar.actions: core never read them.
  • Capabilities and Contributions are open to declaration merging; a key is prefixed with the name of the plugin that consumes it (admin.*, bar.*; unprefixed is core's). Contributions is empty in core.
  • ResponseTransformContext gets capabilities (the reader) in place of barActions/adminPrefix.

Draft: breaking, and the sibling PRs (plugin-admin, plugin-inline-edit, testing, plugin-api, dune-docs) must move together. Slice 2 (inline-edit routes under /api/inline-edit/*, admin-contributed bar actions, webhooks from hooks) comes later.

🤖 Generated with Claude Code

zumbrunn and others added 17 commits November 6, 2026 22:47
…dmin settings

Core's history engine, job state, search analytics and public sessions use the
runtime directory, and the history engine uses max_revisions, so they cannot
live under admin: when the admin is optional. They are system.runtime_dir and
system.max_revisions now, resolved by resolveRuntimeDir() and
resolveMaxRevisions(). The old admin.* keys, in both spellings, are refused with
the new location (step 1 of the admin config plan).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ix slot) (#123, step 2)

Core no longer reads admin.path. The admin plugin provides the admin.prefix
capability slot; core reads it lazily for page-cache and transform exclusion,
onRequest short-circuit protection, plugin route reservation, hybrid static
output and the dev/serve banners. With no provider there is no admin and no
path is treated as one. resolveCapabilities() resolves slots idempotently for
callers that run before createDuneApp(). MCP stops reporting admin.path.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…st (#123, step 4)

bootstrap() stops importing @dune/plugin-admin itself. The admin runs only when site.yaml
lists it under plugins:; dune new writes the entry and dune migrate:config adds it and
removes admin.enabled. admin.enabled and admin.authz_store are removed, the authProvider
bootstrap option and the admin version guard are gone, the authz system is created when a
plugin provides auth.session or declares authzActions, plugin factories receive
(config, { root, dev }), multisite builds its island snapshot from the sites' plugins,
and dune doctor keys its admin check on admin.prefix.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…#123, step 3)

services.engines.mt/audit, PluginServicesOptions.root, SiteConfig.machine_translation and
the @dune/core/mt and @dune/core/audit entry points are removed; @dune/plugin-admin owns
them now. The response-transforms test stops importing types from the published admin.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…t core's (#123, step 5)

DuneConfig.admin and its types, defaults, schema entry and validation are removed; a system.yaml admin:
block is refused. The settings live under config: of the admin's entry in plugins: and dune migrate:config
moves them (lifting runtime_dir, max_revisions and data_dir to the top level). MCP stops reporting admin
settings and drops the audit-log resource.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ig too

site.machine_translation is the admin's setting: migrate:config moves it under config: of the admin's
entry as machine_translation, renaming apiKey/baseUrl to api_key/base_url; a site.yaml that still has it
is refused.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The authz system now exists whenever the site has a login (site.auth), a
plugin provides auth.session, or a plugin declares authzActions, and
mountDuneAuth reuses that one instance. admin, editor and author are granted
once at group level (group:<role> holds <role> on app:site), so the role
membership site auth already syncs is enough: no per-user tuple, no admin
plugin. The resource is renamed from app:admin to app:site; tuples are derived
data and re-seeded at every start.

Closes #130

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…t once (#130)

Group membership, which page gating and the admin-tier role grants read, was
only seeded at startup, so any change to roles[] waited for a restart. User
stores that core creates are now wrapped with withRoleSync: create, a roles
update and delete make member-of-group tuples match the record. The CLI role
commands sync the same way, and the payments webhook stops writing the
membership twice on a synced store.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
main squash-merged #124, #126 and #125 (rebased), so this branch's history no
longer matched its base. The tree is #128's own change replayed onto main, with
the slot renamed to editor.session and the doctor check keyed on admin.prefix.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The base was squash-merged into main (and this stack rebased), so history no
longer matched. The tree is this PR's own change replayed onto the new base.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The base was squash-merged into main (and this stack rebased), so history no
longer matched. The tree is this PR's own change replayed onto the new base.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The base was squash-merged into main (and this stack rebased), so history no
longer matched. The tree is this PR's own change replayed onto the new base.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…lare their own extension points (#127)

AdminPageRegistration, AdminRouteRegistration, ContentEditorPlugin and the bar
action types leave core's contract along with the keys admin.pages,
admin.routes, editor.content and bar.actions: core never read them. Capabilities
and Contributions are open to declaration merging, keyed by the name of the
plugin that consumes the key. ResponseTransformContext loses barActions and
adminPrefix; a bar-owning plugin reads them through MountApi.capabilities.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…mContext.capabilities

Replaces barActions and adminPrefix with the registry's reader, so a transform
asks for what it needs per request and a plugin module shared between sites keeps
no state of its own.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…admin.prefix

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
zumbrunn and others added 11 commits November 8, 2026 12:32
…mounts plugins; drop unused admin import-map entries; warn on per-environment admin.enabled

headless-smoke set admin.enabled: false, which core now refuses. The headless main.ts
imported the admin's mount subpath unversioned; it now calls mountPlugins and takes island
specifiers from the loaded plugins.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ookup, document exact membership sync, note orphaned app:admin tuples

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
# Conflicts:
#	src/cli/migrate-config.ts
#	tests/cli/migrate_config_test.ts
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Fresh's BuildOptions has no islandSpecifiers, so the option the headless
main.ts passed to the Builder constructor was silently dropped and no
plugin island (the admin's included) was registered. Register each
specifier after construction, as dev.ts and serve.ts do.

Fixes #140

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Brings in the squashed #128 and the #135 doc note. The one conflict was
createAuditTrail in plugin-api/create-services.ts: this branch moves the
audit trail out of core, so its side wins and the block stays removed.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
zumbrunn and others added 5 commits November 10, 2026 14:01
…ver write a lifted key twice

Two bugs in the admin-settings move:

- A 0.35 site with `admin.enabled: false` had its whole admin: block
  deleted, including runtime_dir, max_revisions and data_dir, which are
  core settings now lifted to the top level for every other site. They
  are lifted here too. Keys that belong to forms or webhooks (honeypot,
  notifications, incoming_webhooks) make it a manual move, as for an
  enabled admin, instead of being deleted.
- A lifted key already present at the top level of system.yaml was
  appended a second time, which @std/yaml refuses to parse. An equal
  value is skipped; a different one is reported as a manual move and the
  files are left untouched.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@zumbrunn
zumbrunn marked this pull request as ready for review November 10, 2026 13:31
zumbrunn and others added 2 commits November 10, 2026 14:31
Brings in the squashed #128-#132. The one conflict was the CHANGELOG
entry for the contract split, kept as this branch has it.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@zumbrunn
zumbrunn changed the base branch from fix/site-user-editing to main November 10, 2026 13:33
@zumbrunn
zumbrunn merged commit 32b8859 into main Nov 10, 2026
5 checks passed
zumbrunn added a commit that referenced this pull request Nov 10, 2026
Brings in the squashed #133. Its tree is identical to the contract-split
head this branch already merged, so every conflict resolves to this
branch's side and the merge changes nothing.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant