a plugin's permissions become real capabilities, and survive a restart
two gaps between "a plugin can mount routes" and "a plugin is part of the platform". both closed. FIRST: nothing registered a plugin's declared permissions, so the capability gate could not resolve a plugin path at all. it resolved to null, and null is denied — the owner never noticed because isSuperAdmin short-circuits every check, which is exactly the shape of bug that reaches a member first. the registry is now rebuildable the same way the hono app is: CORE_REGISTRY holds the platform's own, CAPABILITIES is core plus whatever the installed plugins declare, and setPluginCapabilities replaces the plugin half wholesale rather than diffing it. two invariants hold by construction — DEFAULT_ROLE_- CAPABILITIES and CORE_CAPABILITIES derive from CORE_REGISTRY, so a plugin can never put itself in the fresh-install baseline and can never become `core` (every account, undeniable). a key colliding with a core one is refused and logged, because a plugin able to redefine `chat` could widen it. ownerOnly maps to admin, everything else to app. those are the only kinds a manifest can express, and it has no field for a kind at all. capabilities are registered BEFORE routes are mounted: the gate runs ahead of every router, so mounting a route whose permission is not yet registered would 403 the freshly installed plugin until something else happened to refresh. SECOND: nothing mounted plugins at boot. honoServer is built with none at import, because discovery reads disk and database and neither can be awaited at module scope, and every install verb rebuilt — so it tested perfectly and would have silently unmounted everything on the first restart. server.tsx now refreshes before serve(), so there is no window where an installed plugin 404s, and a plugin that will not load is logged rather than fatal. verified: after a restart, [plugins] mounted /example, the row survived, officer-example came back online from its ecosystem entry, capabilityForApiPath resolves /api/example/ping to the example capability at kind=app, and it appears in the owner's grantable list. 756 pass, same 10 pre-existing failures. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -112,7 +112,11 @@ export async function installPlugin(appName: string, onStep?: OnStep): Promise<P
|
||||
await step(steps, onStep, `recorded at ${plugin.manifest.version}`);
|
||||
|
||||
const { mounted } = await refreshPluginMounts();
|
||||
await step(steps, onStep, mounted.length ? `mounted: ${mounted.join(', ')}` : 'mounted: nothing (no api/router.ts)');
|
||||
await step(
|
||||
steps,
|
||||
onStep,
|
||||
mounted.length ? `mounted: ${mounted.join(', ')}` : 'mounted: nothing (no api/router.ts)',
|
||||
);
|
||||
|
||||
return { ok: true, appName, steps };
|
||||
} catch (err) {
|
||||
|
||||
Reference in New Issue
Block a user