feat(security): seed Yerin agent + registry-selectable MCP server

- migration 011_yerin.sql: seed read-only 'yerin' agent ({read:true}, kind claude,
  model NULL = server default; switch to local Ollama via agents.model anytime)
- companion-stdio.js: select the toolset from VOID_TOOL_REGISTRY ('security' →
  Yerin's securityRegistry; default → Dross's companionRegistry)
- tests/mcp/registry_select.test.js

Remaining for Yerin (left for review): an entry point (route or cron) + persona
prompt — see docs/yerin-security-agent.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
root
2026-06-02 00:17:53 +10:00
parent c45246b918
commit a3eb5a58f0
3 changed files with 62 additions and 4 deletions

View File

@@ -0,0 +1,40 @@
import { describe, it, expect, beforeAll } from 'vitest';
import { pool } from '../../lib/db/pool.js';
import { resetDb } from '../helpers/db.js';
import { migrateUp } from '../../lib/db/migrate.js';
import { listMcpTools, callMcpTool } from '../../lib/mcp/companion-stdio.js';
// The stdio MCP server must be able to expose Yerin's securityRegistry instead
// of Dross's companionRegistry, selected by VOID_TOOL_REGISTRY at launch.
describe('MCP registry selection', () => {
it('defaults to the companion registry when VOID_TOOL_REGISTRY is unset', () => {
const names = listMcpTools({}).map(t => t.name).sort();
expect(names).toEqual(['context', 'propose_change', 'read', 'search']);
});
it('selects the security registry when VOID_TOOL_REGISTRY=security', () => {
const names = listMcpTools({ VOID_TOOL_REGISTRY: 'security' }).map(t => t.name).sort();
expect(names).toContain('audit_log');
expect(names).toContain('agent_inventory');
expect(names).not.toContain('propose_change'); // Yerin cannot propose mutations
});
it('callMcpTool routes to the selected registry', async () => {
await resetDb(); await migrateUp();
const out = await callMcpTool('agent_inventory', {}, {}, { VOID_TOOL_REGISTRY: 'security' });
expect(Array.isArray(out.agents)).toBe(true);
// an unknown tool for the default registry must not be reachable in security mode
await expect(callMcpTool('propose_change', {}, {}, { VOID_TOOL_REGISTRY: 'security' }))
.rejects.toThrow(/unknown tool/i);
});
});
describe('migration 011 seeds Yerin', () => {
it('creates a read-only yerin agent', async () => {
await resetDb(); await migrateUp();
const { rows: [y] } = await pool.query(`SELECT * FROM agents WHERE slug='yerin'`);
expect(y).toBeTruthy();
expect(y.capabilities).toEqual({ read: true, suggest: false, write: false });
});
});