mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 13:36:48 +00:00
14b348581a
Every plugin now runs in its own forked child process with a scrubbed env (no JWT_SECRET, no db path, nothing inherited). It talks to TREK only over a JSON-RPC channel, and the host's capability router registers ONLY the methods a plugin's granted permissions unlock — so an ungranted call is unreachable, not merely refused. The plugin's own data lives in a separate sqlite file it can never open directly; core reads (trips/users) go through membership-checked, column-projected host methods; ws broadcasts are force-namespaced. - protocol/envelope: the wire types + method→permission map (pure, shared by host and the isolated child) - host/rpc-host: the capability router = the enforcement point (dispatch, BAD_PARAMS / PERMISSION_DENIED / RESOURCE_FORBIDDEN / UNKNOWN_METHOD) - host/plugin-data: the per-plugin sqlite file (db:own), guarded against ATTACH/PRAGMA escape, idempotent migrations - host/create-rpc-host: wires the router to the real db/websocket (host-only) - runtime/plugin-sdk + plugin-host-entry: the child bootstrap + definePlugin ctx; turns each ctx call into an RPC, never imports a privileged module - supervisor: spawn on activate, heartbeat/reap, crash backoff + auto-disable, graceful shutdown — a plugin crash/OOM/hang can never reach the Nest loop - paths: code/data layout, dist-vs-tsx child entry resolution Nothing is wired into activation yet (that's the next slice); exercised by unit tests for the router/sdk/data and an integration test that forks a real child.
54 lines
2.0 KiB
TypeScript
54 lines
2.0 KiB
TypeScript
/**
|
|
* The read-side plugin service + controller (#plugins, M0). Lists installed
|
|
* plugins and reports whether the runtime is enabled (TREK_PLUGINS_ENABLED).
|
|
*/
|
|
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
|
|
const { testDb } = vi.hoisted(() => {
|
|
const Database = require('better-sqlite3');
|
|
const db = new Database(':memory:');
|
|
db.exec(`CREATE TABLE plugins (
|
|
id TEXT PRIMARY KEY, name TEXT, description TEXT, type TEXT, icon TEXT, version TEXT,
|
|
status TEXT, reviewed_at TEXT, source_repo TEXT, sort_order INTEGER DEFAULT 0)`);
|
|
return { testDb: db };
|
|
});
|
|
vi.mock('../../../src/db/database', () => ({ db: testDb }));
|
|
|
|
import { PluginsService } from '../../../src/nest/plugins/plugins.service';
|
|
import { PluginsController } from '../../../src/nest/plugins/plugins.controller';
|
|
|
|
beforeEach(() => {
|
|
testDb.exec('DELETE FROM plugins');
|
|
delete process.env.TREK_PLUGINS_ENABLED;
|
|
});
|
|
afterEach(() => {
|
|
delete process.env.TREK_PLUGINS_ENABLED;
|
|
});
|
|
|
|
describe('PluginsService.list', () => {
|
|
it('returns the installed plugins and the runtime-enabled flag', () => {
|
|
testDb
|
|
.prepare('INSERT INTO plugins (id, name, description, type, status, version) VALUES (?,?,?,?,?,?)')
|
|
.run('flight', 'Flight', 'desc', 'widget', 'inactive', '1.0.0');
|
|
process.env.TREK_PLUGINS_ENABLED = 'true';
|
|
|
|
const out = new PluginsService().list();
|
|
expect(out.enabled).toBe(true);
|
|
expect(out.plugins).toHaveLength(1);
|
|
expect(out.plugins[0]).toMatchObject({ id: 'flight', name: 'Flight', status: 'inactive' });
|
|
});
|
|
|
|
it('reports disabled when the kill switch is off (default)', () => {
|
|
const out = new PluginsService().list();
|
|
expect(out.enabled).toBe(false);
|
|
expect(out.plugins).toEqual([]);
|
|
});
|
|
|
|
it('controller delegates to the service', () => {
|
|
const svc = { list: vi.fn(() => ({ enabled: false, plugins: [] })) } as unknown as PluginsService;
|
|
const res = new PluginsController(svc).list();
|
|
expect(svc.list).toHaveBeenCalled();
|
|
expect(res).toEqual({ enabled: false, plugins: [] });
|
|
});
|
|
});
|