diff --git a/plugins/example/manifest.ts b/plugins/example/manifest.ts index 1ea71cdc..26ded1eb 100644 --- a/plugins/example/manifest.ts +++ b/plugins/example/manifest.ts @@ -24,7 +24,7 @@ export const manifest: PluginManifest = { color: '#94a3b8', // One permission gating the whole surface. `ownerOnly: false` means a role can be granted it — which is - // the interesting case, because it is the one the capability gate actually has to resolve. + // the interesting case, because it is the one the permission gate actually has to resolve. permissions: [ { key: 'example', diff --git a/src/servers/capabilities/registry.ts b/src/servers/capabilities/registry.ts index 1c06088c..81a1e504 100644 --- a/src/servers/capabilities/registry.ts +++ b/src/servers/capabilities/registry.ts @@ -99,7 +99,7 @@ export type Capability = { }; /** - * The PLATFORM's own capabilities. A plugin's are added on top at runtime — see `setPluginCapabilities`. + * The PLATFORM's own capabilities. A plugin's are added on top at runtime — see `setPluginPermissions`. * * Kept separate from the live `CAPABILITIES` below so two invariants hold by construction rather than by * anyone remembering them: a plugin can never become `core` (granted to everyone, undeniable), and a @@ -428,7 +428,7 @@ const CORE_REGISTRY: Capability[] = [ // exports are live bindings, so an importer holding `CAPABILITY_BY_KEY` sees the reassignment — every // consumer reads these inside a function, never at module scope, which is what makes that safe. // -// Nothing mutates the arrays in place. `setPluginCapabilities` replaces them, for the same reason +// Nothing mutates the arrays in place. `setPluginPermissions` replaces them, for the same reason // `buildHonoApp` replaces the app rather than adding routes to it: a half-updated lookup is worse than a // stale one, and replacement has no half. @@ -480,7 +480,7 @@ export const CORE_CAPABILITIES = CORE_REGISTRY.filter((c) => c.kind === 'core'); * question. A key colliding with a core one is REFUSED here rather than silently overriding — a plugin * that could redefine `chat` or `terminal` could widen it. */ -export function setPluginCapabilities(pluginCapabilities: Capability[]): { rejected: string[] } { +export function setPluginPermissions(pluginCapabilities: Capability[]): { rejected: string[] } { const coreKeys = new Set(CORE_REGISTRY.map((c) => c.key)); const seen = new Set(); const accepted: Capability[] = []; diff --git a/src/servers/plugins/generate.ts b/src/servers/plugins/generate.ts index 5d77f956..41ab4492 100644 --- a/src/servers/plugins/generate.ts +++ b/src/servers/plugins/generate.ts @@ -19,8 +19,8 @@ import type { PluginState } from './mount'; // ── What is NOT here ── // // The dock tile. It is permission-filtered, and permission is a runtime question: a grant takes effect on -// the next request, not on the next build. So the tile comes from `/api/user/capabilities` like every -// other plugin manifest, and this file carries only what the BUNDLER needs. +// the next request, not on the next build. So the tile is served by the self endpoint at request time, +// and this file carries only what the BUNDLER needs. const GENERATED_PATH = join(PLATFORM_DIR, 'src/apps/officer-web/Plugins.gen.tsx'); const BUILD_DIR = join(PLATFORM_DIR, 'build'); diff --git a/src/servers/plugins/manifest.ts b/src/servers/plugins/manifest.ts index a8764d62..b624203d 100644 --- a/src/servers/plugins/manifest.ts +++ b/src/servers/plugins/manifest.ts @@ -15,9 +15,10 @@ /** * A permission the plugin adds to the platform's permission system. * - * Called `permissions` and NOT `capabilities`: that word already means three different things in this - * codebase — the permission registry, the file-based item store under `$OFFICER_ROOT/capabilities`, and - * the routing keys a sidecar registers with. A fourth would be one too many. + * Called `permissions`, and that word is used throughout the plugin system deliberately. The other one + * already means three different things here — the permission registry, the file-based item store under + * `$OFFICER_ROOT/capabilities`, and the routing keys a sidecar registers with — and a fourth meaning + * would be one too many. Nothing in this system uses it. */ export type PluginPermission = { /** Stable identifier, stored as the grant's subject. Renaming one is a data change. */ @@ -26,10 +27,10 @@ export type PluginPermission = { description: string; // There is deliberately NO `ownerOnly`, and no kind of any sort. // - // Every plugin permission is grantable, per role, at read or write — the same configuration as every - // core capability, on the same page, with no special cases to learn. A plugin that wanted to be - // owner-only would be a plugin the owner cannot delegate, and "which members may reach this" is a - // decision that belongs to the person running the server rather than to the person who wrote the code. + // Every plugin permission is grantable, per role, at read or write — the same configuration the + // platform's own permissions get, on the same page, with no special cases to learn. A plugin that + // wanted to be owner-only would be a plugin the owner cannot delegate, and "which members may reach + // this" is a decision belonging to whoever runs the server rather than whoever wrote the code. // // Finer visibility — whose rows a member sees, what a read means for this plugin's data — is the // PLUGIN's business and lives in its own queries. The platform's answer is uniform: read, write, or @@ -151,9 +152,9 @@ export function manifestProblems(appName: string, manifest: Partial { const snapshot = await snapshotPlugins(); - // Capabilities BEFORE routes. The capability gate runs ahead of every router, so a route mounted without + // Permissions BEFORE routes. The permission gate runs ahead of every router, so a route mounted without // its permission registered is a route the gate cannot resolve — and an unresolvable path is denied, // which would make a freshly installed plugin 403 until something else happened to refresh. const enabled = snapshot.states.filter((s) => s.install?.enabled); - const { rejected } = setPluginCapabilities(enabled.flatMap(pluginCapabilities)); + const { rejected } = setPluginPermissions(enabled.flatMap(pluginPermissions)); if (rejected.length) { console.error(`[plugins] permission keys refused (already taken by the platform): ${rejected.join(', ')}`); } @@ -170,7 +170,7 @@ export async function refreshPluginMounts(options: { skipFrontend?: boolean } = * * Presentation comes from the manifest and the route from `mountPrefix`, so there is one source for both * and nothing to keep in step. `capability` is the plugin's first permission when it has one, which is - * what lets `/api/user/capabilities` filter the tile out for an account that cannot reach the screen — + * what lets the self endpoint filter the tile out for an account that cannot reach the screen — * a member must not be handed the manifest of a feature they may not use, even to hide it. * * Built here rather than baked into the generated frontend module on purpose: WHO sees a tile is a