Repository navigation
Description
Follow-up to #123. Steps 1 to 5 of #123 make the admin an ordinary plugin as far as loading and config go. This issue is about what is left in the plugin contract after that. Counts below are from src/ at the head of #126; a file with many mentions is not necessarily coupling (the string "admin" is also a role name).
1. Slots and types whose only consumer is the admin
src/plugin-api/capabilities.ts documents these with the admin as their consumer:
admin.pages("Consumer: the admin")admin.routes("Consumer: the admin, which applies authentication, CSRF and permission checks")editor.content("A replacement page editor. Consumer: the admin")
src/hooks/types.ts (the file with the most mentions, 74) carries the matching types: AdminPageRegistration, AdminRouteRegistration, ContentEditorPlugin, AdminBarAction*.
If the admin were a normal plugin, these would be its extension API, defined in its own package, not part of the @dune/plugin-api contract we are about to freeze. Today core ships and freezes an extension API for a single plugin.
2. The bar knows the admin's URL layout
adminPrefix is a field of ResponseTransformContext and AdminBarActionContext (hooks/types.ts). plugin-inline-edit (src/bar.ts) builds ${adminPrefix}/pages/edit?path=... and ${adminPrefix}/api/content/<path>/commit and /fields from it. So the dependency is the admin's page URL and content API, not only its prefix.
3. Admin-shaped code in the request path
src/cli/response-transforms.ts (31 mentions), src/runtime/server.ts (43), src/runtime/register-middleware.ts (21), src/runtime/bootstrap.ts (59). Part of this goes away with #123; the rest should be checked file by file.
Left alone on purpose: identity vocabulary
The admin role, the pages.* / users.* / config.* permissions, DUNE_ADMIN_* and the users:* commands (authz-schema.ts, bootstrap-admin.ts, users-role.ts, users-create.ts). The identity-unification decision made users and RBAC core. Listed so it is a decision and not an oversight; say if it should be revisited.
To decide
- Which slots and contribution points are core's contract (core consumes them:
auth.session,scheduler.executor,bar.actions, theeditor.inlineWebSocket side) and which are the admin's own extension API (admin.pages,admin.routes,editor.content)? - If the admin owns its extension points, how does it declare them? The 1.0 contract currently says first-party keys only and third-party keys are unsupported, so this needs either declaration merging or a plain plugin-to-plugin dependency on the admin's types.
- How does the bar get its links without knowing the admin's routes (contributions with absolute URLs, or an
editor.inline-side API)?
Timing
These change the surface @dune/plugin-api freezes, so they should be decided before it is published.
Not in scope: more than one admin per site (decided: no; auth.session stays exclusive, #125).
Activity
zumbrunn commentedon Nov 6, 2026
Additions after going through how
auth.sessionand authz are actually used (read fromorigin/main; not reproduced at runtime).4.
auth.sessionis the editor session, under a general-sounding nameCore uses it for: the admin bar and whether editing markers survive in public HTML (
register-middleware.ts), the/api/inline-edit/wsfallback (server.ts), and deciding which requests bypass the shared page cache (sessionCookies). Public visitors sign in through the site auth (mountDuneAuth,siteUser), a different path. The contract text says so too ("Who is the admin on this request", "an admin-tier session"). Decide whether the slot should be named for what it is (e.g.editor.session) before the freeze, or whether it should disappear (see 6).5. The authz system is tied to the admin or an
auth.sessionproviderbootstrap.tscreatesctx.authzonly whenadmin.enabled !== falseor some plugin providesauth.session. The authz system should exist because something uses it: the admin, another plugin, or the site's own login page.Consequence, from the code: a site with public auth (
site.auth, e.g. the password login in #117),admin.enabled: false, noauth.sessionprovider and the inline-edit plugin ends up withctx.authz === undefined.mountDuneAuththen builds its own private bundle (auth/mount.ts, used for page gating) and never assigns it back.checkInlineEditPermissionForSiteUser(ctx.authz, ...)inserver.tsfails closed, so every site user gets 403 on/api/inline-edit/ws. Worth a reproduction.Smaller: the resource that
pages.updateis checked against is{ type: "app", id: "admin" }(server.ts), even for a site user with no admin in sight.6. Acceptance for the decoupling
A site without the admin plugin and with
plugin-inline-editshould work: log in through the site's login page, edit inline, save. The only thing missing should be the bar buttons that link to the admin's page editor.What stops that today:
${adminPrefix}/api/content/<path>/commitand/fields, which are the admin's routes (see 2);ctx.authzgap in 5;adminPrefixstring even when there is no admin.So the commit/fields endpoints (and their permission check) need to live in core or in
plugin-inline-edit, driven byauthz.check("pages.update")on the signed-in site user, and the admin's page-editor links should be an optional bar action the admin contributes. That would also settle question 1: the extension points the admin owns are the ones other plugins reach the admin through.zumbrunn commentedon Nov 6, 2026
The authz part of point 5 is now reproduced and filed separately as #130 (it has a second cause: a site user's role does not grant
pages.updatewithout the admin plugin'sapp:admintuple).feat!: export extension points as typed capability keys, not module a…
zumbrunn commentedon Nov 10, 2026
Decided and done. (1) Core's contract keeps only what core consumes (
editor.session,editor.inline,admin.prefix,scheduler.executor);admin.pages,admin.routes, the page-editor slot and the bar-action types left it (#133). (2) The plugin that owns an extension point exports it as a typedCapabilityKey<T>(ADMIN_PAGES,ADMIN_ROUTES,ADMIN_EDITOR,BAR_ACTIONS), because module augmentation is refused bydeno publish(#134). (3) The bar no longer knows the admin's URLs: the inline-edit provider serves its own/api/inline-edit/*routes behind core's gate and the admin contributes its ownbar.actions(#134, plugin-admin#21, plugin-inline-edit#7). Point 4 becameeditor.session(#125); the authz part of point 5 was #130 (#132). The admin-shaped code left in the request path is the genericadmin.prefixhandling (which paths core keeps out of the page cache and away from plugin routes);adminContextand friends are gone. The identity vocabulary (adminrole, permissions,DUNE_ADMIN_*,users:*) stays, as decided.