diff --git a/src/tools/diagnostics.ts b/src/tools/diagnostics.ts index b519424..c916d20 100644 --- a/src/tools/diagnostics.ts +++ b/src/tools/diagnostics.ts @@ -56,7 +56,7 @@ export const diagnosticsTools: PaperclipTool[] = [ { name: 'diag_agents_missing_permissions', description: - 'Agentes sem dangerouslySkipPermissions=true em adapter_config (check 11 health).', + 'Agentes sem dangerouslySkipPermissions=true em adapter_config (NOTA: NÃO é RBAC — é flag Claude Code/CLI para correr sem prompts. Aplicar conscientemente por agent, não em massa).', inputSchema: { type: 'object', properties: {} }, handler: async () => { const rows = await query( @@ -441,4 +441,85 @@ export const diagnosticsTools: PaperclipTool[] = [ return ok({ updated: rows.length, rows }); }, }, + + // 17 — WRITE + { + name: 'ensure_agent_membership', + description: + 'WRITE: garante membership active em company_memberships para um agent (idempotente). Fix achado #2 sessão 5.', + inputSchema: { + type: 'object', + properties: { + agent_id: { type: 'string', description: 'UUID do agente' }, + membership_role: { type: 'string', description: 'Default "member"' }, + }, + required: ['agent_id'], + }, + handler: async (args) => { + const agentId = String(args.agent_id ?? ''); + const role = String(args.membership_role ?? 'member'); + if (!agentId) throw new Error('agent_id obrigatório'); + const existing = await query( + `SELECT id, status, membership_role FROM company_memberships + WHERE company_id = $1 AND principal_type = 'agent' AND principal_id = $2`, + [COMPANY_ID, agentId] + ); + if (existing.length > 0) { + const rows = await query( + `UPDATE company_memberships + SET status = 'active', membership_role = $1, updated_at = NOW() + WHERE id = $2 + RETURNING id, status, membership_role`, + [role, existing[0].id] + ); + return ok({ action: 'updated', row: rows[0] }); + } + const rows = await query( + `INSERT INTO company_memberships + (company_id, principal_type, principal_id, status, membership_role) + VALUES ($1, 'agent', $2, 'active', $3) + RETURNING id, status, membership_role`, + [COMPANY_ID, agentId, role] + ); + return ok({ action: 'inserted', row: rows[0] }); + }, + }, + + // 18 — WRITE + { + name: 'grant_agent_permission', + description: + 'WRITE: insere row em principal_permission_grants para um agent (idempotente por permission_key). Baseline RBAC. Fix achado #4.', + inputSchema: { + type: 'object', + properties: { + agent_id: { type: 'string', description: 'UUID do agente' }, + permission_key: { type: 'string', description: 'Ex: tasks:assign, agents:create, runs:read' }, + scope: { type: 'string', description: 'Scope opcional (default null)' }, + }, + required: ['agent_id', 'permission_key'], + }, + handler: async (args) => { + const agentId = String(args.agent_id ?? ''); + const permissionKey = String(args.permission_key ?? ''); + const scope = args.scope ? String(args.scope) : null; + if (!agentId || !permissionKey) throw new Error('agent_id e permission_key obrigatórios'); + const existing = await query( + `SELECT id FROM principal_permission_grants + WHERE company_id = $1 AND principal_type = 'agent' AND principal_id = $2 AND permission_key = $3`, + [COMPANY_ID, agentId, permissionKey] + ); + if (existing.length > 0) { + return ok({ action: 'noop', existing: existing[0] }); + } + const rows = await query( + `INSERT INTO principal_permission_grants + (company_id, principal_type, principal_id, permission_key, scope, granted_by_user_id) + VALUES ($1, 'agent', $2, $3, $4, 'mcp-bootstrap') + RETURNING id, permission_key, scope`, + [COMPANY_ID, agentId, permissionKey, scope] + ); + return ok({ action: 'inserted', row: rows[0] }); + }, + }, ];