# Plano de Melhorias - MCP Outline PostgreSQL v1.2.1 **Data:** 2026-01-31 **Projecto:** MCP Outline PostgreSQL **Versão Actual:** 1.2.1 **Versão Alvo:** 2.0.0 (Production-Ready) --- ## 🎯 Objectivo Transformar o MCP Outline PostgreSQL de um protótipo funcional (7.2/10) num servidor production-ready (9.5/10) através de correcções de segurança críticas, optimizações de performance e melhorias de qualidade. --- ## 📋 Resumo das Melhorias | Fase | Foco | Duração | Prioridade | Tarefas | |------|------|---------|------------|---------| | **Fase 1** | Segurança Crítica | 2 semanas | 🔴 P0 | 12 | | **Fase 2** | Performance | 1 semana | 🟡 P1 | 10 | | **Fase 3** | Qualidade | 2 semanas | 🟢 P2 | 15 | | **Fase 4** | Funcionalidades | Ongoing | 🟢 P3 | 15 | **Total:** 52 tarefas | 5 semanas de trabalho core --- ## 🔴 FASE 1: Segurança Crítica (P0) **Duração:** 2 semanas **Prioridade:** CRÍTICA **Bloqueante:** SIM - Não deploy sem esta fase completa ### Objectivos - ✅ Eliminar 100% das vulnerabilidades de SQL Injection - ✅ Implementar transacções em todas as operações críticas - ✅ Adicionar validação robusta de inputs - ✅ Implementar audit logging básico --- ### Tarefas Detalhadas #### 1.1 Corrigir SQL Injection (Semana 1) **Problema:** 164 tools com potencial SQL injection via string concatenation. **Solução:** Converter todas as queries para prepared statements parametrizados. ##### 1.1.1 Auditar e Catalogar Queries Vulneráveis ```bash # Script para identificar queries vulneráveis grep -r "pool.query(\`" src/tools/ > vulnerable-queries.txt ``` **Ficheiros Prioritários:** - [ ] `documents.ts` (19 tools) - 2 dias - [ ] `collections.ts` (14 tools) - 1.5 dias - [ ] `users.ts` (9 tools) - 1 dia - [ ] `advanced-search.ts` (6 tools) - 1 dia - [ ] `analytics.ts` (6 tools) - 1 dia - [ ] Restantes 27 ficheiros - 2 dias **Total:** 8.5 dias ##### 1.1.2 Criar Função Helper para Queries Seguras **Ficheiro:** `src/utils/query-builder.ts` ```typescript /** * Query builder seguro com prepared statements */ export class SafeQueryBuilder { private params: any[] = []; private paramIndex = 1; /** * Adiciona parâmetro e retorna placeholder */ addParam(value: any): string { this.params.push(value); return `$${this.paramIndex++}`; } /** * Constrói WHERE clause com ILIKE seguro */ buildILike(column: string, value: string): string { return `${column} ILIKE ${this.addParam(`%${sanitizeInput(value)}%`)}`; } /** * Constrói IN clause seguro */ buildIn(column: string, values: any[]): string { const placeholders = values.map(v => this.addParam(v)).join(', '); return `${column} IN (${placeholders})`; } getParams(): any[] { return this.params; } } ``` **Estimativa:** 0.5 dias ##### 1.1.3 Refactoring de Queries **Antes (VULNERÁVEL):** ```typescript const query = ` SELECT * FROM documents WHERE title ILIKE '%${args.query}%' AND collectionId = '${args.collection_id}' `; const result = await pool.query(query); ``` **Depois (SEGURO):** ```typescript const qb = new SafeQueryBuilder(); const query = ` SELECT * FROM documents WHERE title ILIKE ${qb.addParam(`%${sanitizeInput(args.query)}%`)} AND collectionId = ${qb.addParam(args.collection_id)} `; const result = await pool.query(query, qb.getParams()); ``` **Estimativa:** 8 dias (todos os ficheiros) ##### 1.1.4 Adicionar Linting Rule **Ficheiro:** `.eslintrc.json` ```json { "rules": { "no-template-curly-in-string": "error", "no-restricted-syntax": [ "error", { "selector": "TemplateLiteral[parent.callee.property.name='query']", "message": "Use parameterized queries to prevent SQL injection" } ] } } ``` **Estimativa:** 0.5 dias --- #### 1.2 Implementar Transacções (Semana 2) **Problema:** Operações multi-write sem atomicidade. **Solução:** Envolver operações críticas em transacções. ##### 1.2.1 Identificar Operações Críticas **Ficheiros Afectados:** - [ ] `bulk-operations.ts` - 6 tools - [ ] `desk-sync.ts` - 2 tools - [ ] `export-import.ts` - 2 tools - [ ] `collections.ts` - memberships (4 tools) - [ ] `documents.ts` - create/update com memberships (2 tools) **Total:** 16 tools ##### 1.2.2 Criar Transaction Helper **Ficheiro:** `src/utils/transaction.ts` ```typescript import { Pool, PoolClient } from 'pg'; /** * Executa operação em transacção com retry automático */ export async function withTransaction( pool: Pool, callback: (client: PoolClient) => Promise, maxRetries = 3 ): Promise { let lastError: Error | null = null; for (let attempt = 1; attempt <= maxRetries; attempt++) { const client = await pool.connect(); try { await client.query('BEGIN'); const result = await callback(client); await client.query('COMMIT'); return result; } catch (error) { await client.query('ROLLBACK'); lastError = error as Error; // Retry apenas em deadlocks if (error instanceof Error && error.message.includes('deadlock')) { await new Promise(resolve => setTimeout(resolve, 100 * attempt)); continue; } throw error; } finally { client.release(); } } throw lastError!; } ``` **Estimativa:** 0.5 dias ##### 1.2.3 Refactoring de Operações Bulk **Antes (SEM TRANSACÇÃO):** ```typescript handler: async (args, pgClient) => { const pool = pgClient.getPool(); for (const id of args.document_ids) { await pool.query('UPDATE documents SET archivedAt = NOW() WHERE id = $1', [id]); } } ``` **Depois (COM TRANSACÇÃO):** ```typescript handler: async (args, pgClient) => { const pool = pgClient.getPool(); return await withTransaction(pool, async (client) => { const results = []; for (const id of args.document_ids) { const result = await client.query( 'UPDATE documents SET archivedAt = NOW() WHERE id = $1 RETURNING *', [id] ); results.push(result.rows[0]); } return results; }); } ``` **Estimativa:** 2 dias (16 tools) ##### 1.2.4 Testes de Rollback **Ficheiro:** `tests/transactions.test.ts` ```typescript describe('Transaction Rollback', () => { it('should rollback on error in bulk operations', async () => { const invalidIds = ['valid-uuid', 'INVALID']; await expect( bulkArchiveDocuments({ document_ids: invalidIds }, pgClient) ).rejects.toThrow(); // Verificar que nenhum documento foi arquivado const result = await pool.query( 'SELECT * FROM documents WHERE archivedAt IS NOT NULL' ); expect(result.rows).toHaveLength(0); }); }); ``` **Estimativa:** 1 dia --- #### 1.3 Validação de Inputs (Semana 2) ##### 1.3.1 Implementar Validação Automática **Ficheiro:** `src/utils/validation.ts` ```typescript import Ajv from 'ajv'; import addFormats from 'ajv-formats'; const ajv = new Ajv({ allErrors: true }); addFormats(ajv); /** * Valida args contra inputSchema */ export function validateToolInput( args: unknown, schema: object ): T { const validate = ajv.compile(schema); if (!validate(args)) { const errors = validate.errors?.map(e => `${e.instancePath} ${e.message}`).join(', '); throw new Error(`Invalid input: ${errors}`); } return args as T; } /** * Middleware para validação automática */ export function withValidation( tool: BaseTool ): BaseTool { const originalHandler = tool.handler; return { ...tool, handler: async (args, pgClient) => { const validatedArgs = validateToolInput(args, tool.inputSchema); return originalHandler(validatedArgs, pgClient); } }; } ``` **Estimativa:** 1 dia ##### 1.3.2 Adicionar Validações Específicas **Melhorias em `src/utils/security.ts`:** ```typescript /** * Valida array de UUIDs */ export function validateUUIDs(uuids: string[]): void { const invalid = uuids.filter(uuid => !isValidUUID(uuid)); if (invalid.length > 0) { throw new Error(`Invalid UUIDs: ${invalid.join(', ')}`); } } /** * Valida enum value */ export function validateEnum( value: string, allowedValues: T[], fieldName: string ): T { if (!allowedValues.includes(value as T)) { throw new Error( `Invalid ${fieldName}: ${value}. Allowed: ${allowedValues.join(', ')}` ); } return value as T; } /** * Valida tamanho de string */ export function validateStringLength( value: string, min: number, max: number, fieldName: string ): void { if (value.length < min || value.length > max) { throw new Error( `${fieldName} must be between ${min} and ${max} characters` ); } } ``` **Estimativa:** 0.5 dias --- #### 1.4 Audit Logging Básico (Semana 2) ##### 1.4.1 Criar Sistema de Audit Log **Ficheiro:** `src/utils/audit.ts` ```typescript import { Pool } from 'pg'; export interface AuditLogEntry { userId?: string; action: string; resourceType: string; resourceId: string; metadata?: Record; } /** * Regista operação em audit log */ export async function logAudit( pool: Pool, entry: AuditLogEntry ): Promise { await pool.query( `INSERT INTO events (name, actorId, modelId, data, createdAt) VALUES ($1, $2, $3, $4, NOW())`, [ entry.action, entry.userId || null, entry.resourceId, JSON.stringify({ resourceType: entry.resourceType, ...entry.metadata }) ] ); } /** * Middleware para audit logging automático */ export function withAuditLog( tool: BaseTool, getResourceInfo: (args: T) => { type: string; id: string } ): BaseTool { const originalHandler = tool.handler; return { ...tool, handler: async (args, pgClient) => { const result = await originalHandler(args, pgClient); // Log apenas operações de escrita if (['create', 'update', 'delete'].some(op => tool.name.includes(op))) { const resource = getResourceInfo(args); await logAudit(pgClient.getPool(), { action: tool.name, resourceType: resource.type, resourceId: resource.id }); } return result; } }; } ``` **Estimativa:** 1 dia --- ### Checklist Fase 1 - [ ] **SQL Injection** - [ ] Auditar 164 tools - [ ] Criar SafeQueryBuilder - [ ] Refactoring completo - [ ] Adicionar linting rule - [ ] Testar manualmente cada módulo - [ ] **Transacções** - [ ] Identificar operações críticas - [ ] Criar transaction helper - [ ] Refactoring bulk operations - [ ] Implementar testes de rollback - [ ] **Validação** - [ ] Implementar validação automática - [ ] Adicionar validações específicas - [ ] Aplicar a todos os tools - [ ] **Audit Log** - [ ] Criar sistema de logging - [ ] Integrar com tools de escrita - [ ] Testar logging ### Métricas de Sucesso Fase 1 - ✅ 0 queries com string concatenation - ✅ 100% queries parametrizadas - ✅ 16 operações críticas com transacções - ✅ 100% tools com validação de input - ✅ Audit log funcional em operações de escrita --- ## 🟡 FASE 2: Performance (P1) **Duração:** 1 semana **Prioridade:** ALTA **Bloqueante:** NÃO - Mas recomendado antes de produção ### Objectivos - ✅ Eliminar N+1 queries - ✅ Criar índices necessários - ✅ Optimizar connection pool - ✅ Implementar cursor-based pagination --- ### Tarefas Detalhadas #### 2.1 Eliminar N+1 Queries (2 dias) ##### 2.1.1 Identificar N+1 Queries **Ficheiros Afectados:** - `collections.ts:1253-1280` - export_all_collections - `documents.ts:530-577` - list_drafts - `analytics.ts` - várias queries ##### 2.1.2 Refactoring com JOINs **Antes (N+1):** ```typescript const collections = await pool.query('SELECT * FROM collections'); for (const collection of collections.rows) { const docs = await pool.query( 'SELECT * FROM documents WHERE collectionId = $1', [collection.id] ); collection.documents = docs.rows; } ``` **Depois (JOIN):** ```typescript const result = await pool.query(` SELECT c.*, json_agg( json_build_object( 'id', d.id, 'title', d.title, 'createdAt', d.createdAt ) ) FILTER (WHERE d.id IS NOT NULL) as documents FROM collections c LEFT JOIN documents d ON d.collectionId = c.id AND d.deletedAt IS NULL GROUP BY c.id `); ``` **Estimativa:** 2 dias --- #### 2.2 Criar Índices (1 dia) ##### 2.2.1 Documentar Índices Necessários **Ficheiro:** `migrations/001_indexes.sql` ```sql -- Full-text search CREATE INDEX IF NOT EXISTS idx_documents_search ON documents USING gin(to_tsvector('english', title || ' ' || COALESCE(text, ''))); -- Queries comuns CREATE INDEX IF NOT EXISTS idx_documents_collection_id ON documents(collectionId) WHERE deletedAt IS NULL; CREATE INDEX IF NOT EXISTS idx_documents_published ON documents(publishedAt DESC) WHERE deletedAt IS NULL AND publishedAt IS NOT NULL; CREATE INDEX IF NOT EXISTS idx_documents_created ON documents(createdAt DESC) WHERE deletedAt IS NULL; -- Memberships CREATE INDEX IF NOT EXISTS idx_collection_memberships_lookup ON collection_memberships(collectionId, userId); CREATE INDEX IF NOT EXISTS idx_group_memberships_lookup ON group_memberships(groupId, userId); -- Stars, Pins, Views CREATE INDEX IF NOT EXISTS idx_stars_user_document ON stars(userId, documentId) WHERE deletedAt IS NULL; CREATE INDEX IF NOT EXISTS idx_pins_collection_document ON pins(collectionId, documentId) WHERE deletedAt IS NULL; CREATE INDEX IF NOT EXISTS idx_views_document_user ON views(documentId, userId, createdAt DESC); -- Events (audit log) CREATE INDEX IF NOT EXISTS idx_events_actor_created ON events(actorId, createdAt DESC); CREATE INDEX IF NOT EXISTS idx_events_model_created ON events(modelId, createdAt DESC); ``` **Estimativa:** 0.5 dias ##### 2.2.2 Documentar em SPEC **Adicionar secção em `SPEC-MCP-OUTLINE.md`:** ```markdown ## Índices Recomendados Para performance optimal, execute as migrations em `migrations/001_indexes.sql`. ### Índices Críticos | Índice | Tabela | Tipo | Impacto | |--------|--------|------|---------| | idx_documents_search | documents | GIN | Full-text search 10-100x faster | | idx_documents_collection_id | documents | B-tree | List documents 5-10x faster | | idx_collection_memberships_lookup | collection_memberships | B-tree | Permission checks 10x faster | ``` **Estimativa:** 0.5 dias --- #### 2.3 Optimizar Connection Pool (1 dia) ##### 2.3.1 Tuning de Pool **Ficheiro:** `src/config/database.ts` ```typescript export interface DatabaseConfig { // ... existing fields // Pool tuning max?: number; // Default: 20 min?: number; // Default: 5 idleTimeoutMillis?: number; // Default: 30000 connectionTimeoutMillis?: number; // Default: 5000 maxUses?: number; // Default: 7500 (recycle connections) // Performance statementTimeout?: number; // Default: 30000 (30s) queryTimeout?: number; // Default: 10000 (10s) } export function getDefaultConfig(): DatabaseConfig { return { max: parseInt(process.env.DB_POOL_MAX || '20', 10), min: parseInt(process.env.DB_POOL_MIN || '5', 10), idleTimeoutMillis: 30000, connectionTimeoutMillis: 5000, maxUses: 7500, statementTimeout: 30000, queryTimeout: 10000 }; } ``` **Estimativa:** 0.5 dias ##### 2.3.2 Adicionar Pool Monitoring **Ficheiro:** `src/utils/monitoring.ts` ```typescript import { Pool } from 'pg'; export function monitorPool(pool: Pool): void { setInterval(() => { logger.info('Pool stats', { total: pool.totalCount, idle: pool.idleCount, waiting: pool.waitingCount }); // Alert se pool saturado if (pool.waitingCount > 5) { logger.warn('Pool saturation detected', { waiting: pool.waitingCount, total: pool.totalCount }); } }, 60000); // A cada minuto } ``` **Estimativa:** 0.5 dias --- #### 2.4 Cursor-Based Pagination (1 dia) ##### 2.4.1 Implementar Cursor Pagination **Ficheiro:** `src/utils/pagination.ts` ```typescript export interface CursorPaginationArgs { limit?: number; cursor?: string; // Base64 encoded timestamp ou ID } export interface CursorPaginationResult { items: T[]; nextCursor?: string; hasMore: boolean; } /** * Cursor-based pagination (mais eficiente que OFFSET) */ export async function paginateWithCursor( pool: Pool, baseQuery: string, cursorField: string, args: CursorPaginationArgs ): Promise> { const limit = Math.min(args.limit || 25, 100); let query = baseQuery; const params: any[] = [limit + 1]; // +1 para detectar hasMore if (args.cursor) { const cursorValue = Buffer.from(args.cursor, 'base64').toString(); query += ` AND ${cursorField} < $2`; params.push(cursorValue); } query += ` ORDER BY ${cursorField} DESC LIMIT $1`; const result = await pool.query(query, params); const hasMore = result.rows.length > limit; const items = hasMore ? result.rows.slice(0, -1) : result.rows; const nextCursor = hasMore && items.length > 0 ? Buffer.from(String((items[items.length - 1] as any)[cursorField])).toString('base64') : undefined; return { items, nextCursor, hasMore }; } ``` **Estimativa:** 1 dia --- ### Checklist Fase 2 - [ ] **N+1 Queries** - [ ] Identificar todas as ocorrências - [ ] Refactoring com JOINs - [ ] Testar performance - [ ] **Índices** - [ ] Criar migrations/001_indexes.sql - [ ] Documentar em SPEC - [ ] Testar impacto - [ ] **Connection Pool** - [ ] Tuning de configuração - [ ] Adicionar monitoring - [ ] Testar sob carga - [ ] **Pagination** - [ ] Implementar cursor-based - [ ] Migrar tools principais - [ ] Benchmark vs OFFSET ### Métricas de Sucesso Fase 2 - ✅ 0 N+1 queries em hot paths - ✅ Queries < 100ms (p95) - ✅ Índices criados e documentados - ✅ Pool utilization < 80% - ✅ Cursor pagination em listagens principais --- ## 🟢 FASE 3: Qualidade (P2) **Duração:** 2 semanas **Prioridade:** MÉDIA **Bloqueante:** NÃO - Melhoria contínua ### Objectivos - ✅ Implementar testes unitários - ✅ Adicionar testes de integração - ✅ Configurar CI/CD - ✅ Refactoring de código duplicado - ✅ Melhorar documentação --- ### Tarefas Detalhadas #### 3.1 Testes Unitários (1 semana) ##### 3.1.1 Setup de Testing **Ficheiro:** `package.json` ```json { "devDependencies": { "vitest": "^1.0.0", "@vitest/coverage-v8": "^1.0.0", "testcontainers": "^10.0.0" }, "scripts": { "test": "vitest", "test:coverage": "vitest --coverage", "test:ui": "vitest --ui" } } ``` **Ficheiro:** `vitest.config.ts` ```typescript import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { globals: true, environment: 'node', coverage: { provider: 'v8', reporter: ['text', 'json', 'html'], exclude: ['dist/', 'tests/', '**/*.test.ts'] } } }); ``` **Estimativa:** 0.5 dias ##### 3.1.2 Testes de Utils **Ficheiro:** `tests/utils/security.test.ts` ```typescript import { describe, it, expect } from 'vitest'; import { isValidUUID, isValidEmail, sanitizeInput, validatePagination } from '../../src/utils/security'; describe('Security Utils', () => { describe('isValidUUID', () => { it('should validate correct UUIDs', () => { expect(isValidUUID('123e4567-e89b-12d3-a456-426614174000')).toBe(true); }); it('should reject invalid UUIDs', () => { expect(isValidUUID('not-a-uuid')).toBe(false); expect(isValidUUID('')).toBe(false); }); }); describe('sanitizeInput', () => { it('should remove null bytes', () => { expect(sanitizeInput('test\0data')).toBe('testdata'); }); it('should trim whitespace', () => { expect(sanitizeInput(' test ')).toBe('test'); }); }); describe('validatePagination', () => { it('should enforce max limit', () => { const result = validatePagination(1000, 0); expect(result.limit).toBe(100); }); it('should use defaults', () => { const result = validatePagination(); expect(result.limit).toBe(25); expect(result.offset).toBe(0); }); }); }); ``` **Estimativa:** 2 dias (todos os utils) ##### 3.1.3 Testes de Tools **Ficheiro:** `tests/tools/documents.test.ts` ```typescript import { describe, it, expect, beforeAll, afterAll } from 'vitest'; import { GenericContainer, StartedTestContainer } from 'testcontainers'; import { Pool } from 'pg'; import { documentsTools } from '../../src/tools/documents'; describe('Documents Tools', () => { let container: StartedTestContainer; let pool: Pool; beforeAll(async () => { // Start PostgreSQL container container = await new GenericContainer('postgres:15') .withEnvironment({ POSTGRES_PASSWORD: 'test' }) .withExposedPorts(5432) .start(); pool = new Pool({ host: container.getHost(), port: container.getMappedPort(5432), user: 'postgres', password: 'test', database: 'postgres' }); // Setup schema await pool.query(/* schema SQL */); }); afterAll(async () => { await pool.end(); await container.stop(); }); it('should list documents', async () => { const result = await documentsTools[0].handler({}, { getPool: () => pool }); expect(result.content[0].text).toBeDefined(); }); }); ``` **Estimativa:** 3 dias (tools principais) --- #### 3.2 CI/CD (2 dias) ##### 3.2.1 GitHub Actions **Ficheiro:** `.github/workflows/ci.yml` ```yaml name: CI on: push: branches: [main, develop] pull_request: branches: [main] jobs: test: runs-on: ubuntu-latest services: postgres: image: postgres:15 env: POSTGRES_PASSWORD: test options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' - run: npm ci - run: npm run build - run: npm run test:coverage - name: Upload coverage uses: codecov/codecov-action@v3 lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '20' - run: npm ci - run: npm run lint ``` **Estimativa:** 1 dia --- #### 3.3 Refactoring (3 dias) ##### 3.3.1 Eliminar Duplicação **Criar:** `src/utils/tool-factory.ts` ```typescript /** * Factory para criar tools com padrão consistente */ export function createTool(config: { name: string; description: string; inputSchema: object; handler: (args: T, pool: Pool) => Promise; requiresTransaction?: boolean; auditLog?: boolean; }): BaseTool { return { name: config.name, description: config.description, inputSchema: config.inputSchema, handler: async (args, pgClient) => { try { // Validação automática const validatedArgs = validateToolInput(args, config.inputSchema); const pool = pgClient.getPool(); // Executar com ou sem transacção const result = config.requiresTransaction ? await withTransaction(pool, client => config.handler(validatedArgs, client)) : await config.handler(validatedArgs, pool); // Audit log automático if (config.auditLog) { await logAudit(pool, { action: config.name, resourceType: extractResourceType(config.name), resourceId: extractResourceId(result) }); } return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { logger.error(`Tool ${config.name} failed`, { error }); throw error; } } }; } ``` **Estimativa:** 2 dias ##### 3.3.2 Aplicar Factory **Refactoring de tools para usar factory:** ```typescript // Antes const listDocuments: BaseTool = { name: 'list_documents', description: '...', inputSchema: { /* ... */ }, handler: async (args, pgClient) => { try { const pool = pgClient.getPool(); // ... lógica return { content: [{ type: 'text', text: JSON.stringify(result) }] }; } catch (error) { // ... } } }; // Depois const listDocuments = createTool({ name: 'list_documents', description: '...', inputSchema: { /* ... */ }, handler: async (args, pool) => { // ... apenas lógica de negócio return result; } }); ``` **Estimativa:** 1 dia --- ### Checklist Fase 3 - [ ] **Testes** - [ ] Setup Vitest + Testcontainers - [ ] Testes de utils (100% coverage) - [ ] Testes de tools principais (>80% coverage) - [ ] Testes de integração - [ ] **CI/CD** - [ ] GitHub Actions - [ ] Codecov integration - [ ] Automated releases - [ ] **Refactoring** - [ ] Tool factory - [ ] Eliminar duplicação - [ ] Melhorar type safety ### Métricas de Sucesso Fase 3 - ✅ Code coverage > 80% - ✅ 0 linting errors - ✅ CI passing - ✅ Duplicação < 5% --- ## 🟢 FASE 4: Funcionalidades (P3) **Duração:** Ongoing **Prioridade:** BAIXA **Bloqueante:** NÃO - Melhorias incrementais ### Tarefas #### 4.1 Rate Limiting Distribuído - [ ] Integrar Redis - [ ] Implementar rate limiting distribuído - [ ] Adicionar CAPTCHA para operações sensíveis #### 4.2 Autorização - [ ] Implementar RBAC - [ ] Verificar permissões antes de operações - [ ] Adicionar testes de autorização #### 4.3 Monitoring - [ ] Integrar Prometheus - [ ] Adicionar métricas de performance - [ ] Dashboard Grafana #### 4.4 Documentação - [ ] API documentation (OpenAPI) - [ ] Guia de deployment - [ ] Troubleshooting guide --- ## 📊 Métricas Globais de Sucesso ### Segurança - ✅ 0 vulnerabilidades críticas - ✅ 0 vulnerabilidades altas - ✅ 100% queries parametrizadas - ✅ 100% operações críticas com transacções ### Performance - ✅ Queries < 100ms (p95) - ✅ Throughput > 1000 req/s - ✅ Pool utilization < 80% - ✅ 0 N+1 queries ### Qualidade - ✅ Code coverage > 80% - ✅ 0 linting errors - ✅ CI passing - ✅ Duplicação < 5% --- ## 📅 Timeline ``` Semana 1: SQL Injection Semana 2: Transacções + Validação + Audit Semana 3: Performance (N+1, Índices, Pool) Semana 4: Testes Unitários Semana 5: CI/CD + Refactoring Semana 6+: Funcionalidades (ongoing) ``` --- ## 🎯 Próximos Passos Imediatos 1. **Aprovar este plano** ✅ 2. **Criar branch `security-fixes`** 3. **Iniciar Fase 1.1.1: Auditar queries vulneráveis** 4. **Daily progress tracking em `task.md`** --- *Plano criado em 2026-01-31 | MCP Outline PostgreSQL v1.2.1 → v2.0.0*