Finish the plugin RPC decorator rollout

The last 20 wire methods move onto the decorators: db.* and the three
unconditional methods (DbRpc), meta.* (MetaRpc) and the host-mediated surface
of users/broadcasts/notify/ai/oauth/scheduler (HostSurfaceRpc). That closes the
ledger at 113/113, so the legacy has() blocks in rpc-host.ts are gone and the
router is down to what was never domain-specific: build the granted subset of
the dispatch map, dispatch, audit, map errors.

PluginHostDepsFactory was the 26-argument wiring sheet behind those blocks. With
the handlers in the domains it has two dependencies left, so it is now
PluginRpcHostFactory: the plugin's own sqlite handle, the inter-plugin router
bound with this plugin as the caller, and the audit sink.

The 18 host-to-plugin hook calls get the same treatment. They used to spell out
the fn name and the timeout at each controller, which meant nothing checked that
a hook:* grant on the consent screen had a consumer behind it, that the fn
matched what the SDK documents, or that two call sites agreed on the budget.
PluginHooks declares all 18 with @PluginHook and performs them, and the
controllers call it by name.

With both halves complete, requireTotalCoverage goes on: a KNOWN_METHOD without
a handler, or a hook without a consumer, now fails app boot instead of surfacing
as a PERMISSION_DENIED nobody can explain.
This commit is contained in:
Maurice
2026-08-09 17:15:53 +02:00
committed by Maurice
parent 611f94e820
commit f72aba0c91
74 changed files with 1942 additions and 2164 deletions
@@ -23,7 +23,7 @@ import type { JourneyDomainService } from '../../../src/nest/journey/journey-dom
vi.mock('../../../src/nest/plugins/kill-switch', () => ({ pluginsEnabled }));
import { JournalEntryRowsController } from '../../../src/nest/plugins/journal-entry-rows.controller';
import type { PluginRuntimeService } from '../../../src/nest/plugins/plugin-runtime.service';
import type { PluginHooks } from '../../../src/nest/plugins/plugin-hooks.service';
import type { AddonsService } from '../../../src/nest/addons/addons.service';
const addonsStub = { isAddonEnabled } as unknown as AddonsService;
@@ -33,8 +33,8 @@ const req = (id?: number) => ({ user: id === undefined ? undefined : { id } }) a
function controller(invoke: (id: string) => unknown, providers = ['p1']) {
const runtime = {
providersOf: vi.fn(() => providers),
invokeHook: vi.fn(async (id: string) => invoke(id)),
} as unknown as PluginRuntimeService;
journalRows: vi.fn(async (id: string) => invoke(id)),
} as unknown as PluginHooks;
return { c: new JournalEntryRowsController(runtime, new DatabaseService(dbConn), addonsStub, { canAccessJourney } as unknown as JourneyDomainService), runtime };
}
const row = (over: Record<string, unknown> = {}) => ({ label: 'Distance', value: '12 km', ...over });
@@ -77,7 +77,7 @@ describe('JournalEntryRowsController', () => {
{ label: 'Steps', value: '9000', url: 'mailto:x@y.z' },
],
}]);
expect(runtime.invokeHook).toHaveBeenCalledWith('p1', 'journalEntryProvider', 'getRows', [7], 5, 5000);
expect(runtime.journalRows).toHaveBeenCalledWith('p1', 7, 5);
});
it('drops javascript:/empty/unparseable urls, caps lengths, drops label-less rows and non-objects', async () => {