the plugin system says permissions, not the other word
i named the field permissions and then narrated in the overloaded one anyway, which is worse than either — the whole reason for the rename was that the word already means three things here. renamed what was mine: setPluginCapabilities -> setPluginPermissions, pluginCapabilities -> pluginPermissions, and the prose throughout. what remains is the platform's own vocabulary, not the plugin system's: the type it imports, and the field on a dock manifest, which is the shape the shell already renders. renaming those is a separate change to a separate system and is the owner's to make. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { listPluginInstalls, type PluginInstall } from 'officerdb';
|
||||
import type { MountedPlugin } from '../hono';
|
||||
import { rebuildHonoApp } from '../hono';
|
||||
import { setPluginCapabilities, type Capability } from '../capabilities/registry';
|
||||
import { setPluginPermissions, type Capability } from '../capabilities/registry';
|
||||
import { discoverPlugins } from './discover';
|
||||
import { mountPrefix, type DiscoveredPlugin } from './manifest';
|
||||
import { generatePluginsModule, rebuildFrontend } from './generate';
|
||||
@@ -87,18 +87,18 @@ export async function mountablePlugins(snapshot: PluginsSnapshot): Promise<Mount
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn a plugin's declared permissions into capabilities the platform's own machinery understands.
|
||||
* Turn a plugin's declared permissions into the entries the platform's own registry understands.
|
||||
*
|
||||
* ALWAYS `app`, because a plugin permission is always grantable: the same read/write/none per role that
|
||||
* every core capability gets, on the same page. `core`, `execution`, `confined` and `admin` stay the
|
||||
* platform's to assign, and a plugin cannot name any of them — its manifest has no field for a kind, so
|
||||
* the escalation question is removed rather than answered.
|
||||
* ALWAYS `app`, because a plugin permission is always grantable: the same read/write/none per role the
|
||||
* platform's own permissions get, on the same page. The restricted levels stay the platform's to assign
|
||||
* and a plugin cannot name any of them — its manifest has no field for one, so the escalation question is
|
||||
* removed rather than answered.
|
||||
*
|
||||
* The FIRST permission gates the plugin's whole surface. Later ones are registered as grantable keys that
|
||||
* claim no path — a plugin wanting genuinely separate surfaces needs sub-path claims in the manifest, and
|
||||
* that can be added when something needs it rather than guessed at now.
|
||||
*/
|
||||
function pluginCapabilities(state: PluginState): Capability[] {
|
||||
function pluginPermissions(state: PluginState): Capability[] {
|
||||
const { plugin } = state;
|
||||
const prefix = mountPrefix(plugin);
|
||||
|
||||
@@ -133,11 +133,11 @@ export type RefreshResult = {
|
||||
export async function refreshPluginMounts(options: { skipFrontend?: boolean } = {}): Promise<RefreshResult> {
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user