fix(plugins): boot enabled plugins after the rpc-registry scan, not before

PluginRuntimeService booted enabled plugins from onModuleInit, but Nest fires
same-module onModuleInit hooks in providers-array order, where the runtime
precedes PluginRpcRegistryService — so every boot-activated plugin's rpc host
was bound from a still-empty registry and its first RPC (typically db.migrate
in onLoad) came back PERMISSION_DENIED on every cold boot, until an admin
deactivated and reactivated it by hand. Move the boot reconcile to
onApplicationBootstrap, which is guaranteed to run after every module's
onModuleInit, registry scan included.

Also fix the denial message, which interpolated the plugin id where the
permission belonged ('db.migrate requires a permission "featured-guides" was
not granted'); it now names both the missing permission and the plugin.
This commit is contained in:
jubnl
2026-08-25 08:06:50 +02:00
parent 4548743874
commit decc0e1a12
5 changed files with 169 additions and 13 deletions
+7 -2
View File
@@ -76,9 +76,14 @@ describe('PluginRpcHost — capability enforcement', () => {
expect((res as RpcError).error.message).toBe('unknown method fs.readFile');
});
it('RPCHOST-003 a denied known method names the plugin, so the log says who asked', async () => {
it('RPCHOST-003 a denied known method names the missing permission AND the plugin', async () => {
// The old template interpolated the plugin id where the permission belonged
// ('tags.list requires a permission "p" was not granted'), so an operator read
// their plugin's NAME as an unknown permission. The message must name both.
const res = await makeHost([]).dispatch(req('tags.list'), 42);
expect((res as RpcError).error.message).toBe('tags.list requires a permission "p" was not granted');
expect((res as RpcError).error.message).toBe(
'tags.list requires the "db:read:tags" permission, which was not granted to plugin "p"',
);
});
it('RPCHOST-004 with no permissions at all, every permissioned method is denied', async () => {