From b0a72fe61cafc7b6327f40ab9e3b380c4e4dd75a Mon Sep 17 00:00:00 2001 From: nogueiraguh Date: Sat, 21 Mar 2026 13:39:42 -0300 Subject: [PATCH] docs: update LESSONS_LEARNED.md with v2.5-v2.8 learnings (ADB, React, Security) --- LESSONS_LEARNED.md | 378 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 378 insertions(+) create mode 100644 LESSONS_LEARNED.md diff --git a/LESSONS_LEARNED.md b/LESSONS_LEARNED.md new file mode 100644 index 0000000..1424895 --- /dev/null +++ b/LESSONS_LEARNED.md @@ -0,0 +1,378 @@ +# OCI CIS Agent — Lições Aprendidas (Erros & Acertos) + +> Documento de referência para uso como prompt em projetos futuros. +> Projeto: FastAPI + React 19 SPA + OCI GenAI + MCP + Terraform Agent +> Evolução: v1.0 → v2.8 (~4 meses de desenvolvimento iterativo) + +--- + +## 1. Arquitetura & Decisões Fundamentais + +### ACERTOS + +#### 1.1 Chat Assíncrono desde o início (v2.0) +- **Padrão**: POST `/api/chat` retorna imediatamente com `message_id`; frontend faz polling em `/api/chat/{mid}/status` +- **Resultado**: Eliminou 504 timeouts em respostas lentas do GenAI +- **Regra**: Para qualquer endpoint que dependa de LLM, adote async-first desde o dia 1. Polling a cada 2s é aceitável + +#### 1.2 SQLite como banco principal para monolito +- **Decisão**: SQLite com WAL mode + busy_timeout=30s para estado compartilhado +- **Resultado**: Rápido (<5ms leitura), ACID, zero containers extras, funciona até ~12 chats simultâneos +- **Regra**: Use SQLite para monolitos. Migre para banco dedicado quando atingir ~50 requests concorrentes + +#### 1.3 Multi-worker com estado no banco (não em memória) +- **Problema**: 8 workers uvicorn = 8 processos separados. Dicts em memória NÃO são compartilhados +- **Solução**: Todo estado compartilhado vai para SQLite (sessões, cache MCP, workspaces Terraform) +- **Regra**: Ao escalar workers, assuma que memória é local. Banco de dados é a fonte de verdade + +#### 1.4 Cache agressivo para APIs externas +- **Padrão**: MCP sessions cacheadas por 2h; Terraform resource docs cacheados no SQLite +- **Resultado**: Scans OCI caíram de ~3min para <10s no cache hit +- **Regra**: Cache tudo que vem de API externa. TTL de 2h é prático para OCI; ajuste por serviço + +#### 1.5 System prompts compactos para geração de código +- **Evolução**: Prompt do Terraform foi de ~200 linhas → ~30 linhas (~2.3K chars) +- **Descoberta**: GPT-5.2/o3 via OCI funcionam melhor com regras concisas que com exemplos verbosos +- **Regra**: Para agentes de código, priorize **regras > exemplos**. Alvo: <3K chars no system prompt. Use exemplos apenas para casos ambíguos + +#### 1.6 Validação de tipos com fuzzy matching +- **Implementação**: `terraform providers schema -json` → SQLite (~937 tipos OCI) + `difflib.get_close_matches` +- **Resultado**: Modelo gera `oci_core_vcn_x` → sistema sugere `oci_core_vcn` +- **Regra**: Para geração de código, valide contra catálogo estático. Gere na startup do container, cache no banco + +#### 1.7 Geração de referência no startup (não no build) +- **Problema**: Arquivo gerado no build → perdido quando volume Docker sobrescreve o diretório +- **Solução**: Gerar no entrypoint do container, salvar em `/data/` +- **Regra**: Docker volume mounts sobrescrevem artefatos de build. Dados dinâmicos devem ser gerados em runtime + +#### 1.8 RAG separado por contexto de agente +- **Problema**: Terraform Agent tentava buscar RAG no ADB (banco vetorial externo) que ficava parado, travando o chat +- **Solução**: Terraform usa apenas SQLite local (resource reference + docs oficiais). ADB só para Chat Agent +- **Regra**: Cada agente deve ter suas fontes de dados bem definidas. Não compartilhe pipelines de RAG entre agentes com necessidades diferentes + +--- + +### ERROS + +#### 1.9 Provider region hardcoded no Terraform +- **Bug**: `provider.tf` era gerado com `region = "us-ashburn-1"` (string fixa da OCI config) em vez de `var.region` +- **Impacto**: Recursos provisionados em Ashburn ao invés de Madrid → erro de quota → recursos órfãos para limpar manualmente +- **Correção**: Provider sempre usa `var.region`. Se modelo não declara `variable "region"`, plan é **bloqueado** com erro explicativo +- **Regra**: NUNCA hardcode valores de infraestrutura. Sempre use variáveis. Implemente guardrails que bloqueiem antes de provisionar + +#### 1.10 Estado travado após restart do container +- **Bug**: Workspace com status `planning`/`applying` fica preso se container reinicia — nenhum botão aparece no frontend +- **Correção**: Frontend mostra botão Plan para todos os estados (incluindo os "travados") +- **Regra**: Sempre considere o cenário de restart. Status intermediários devem ter recovery path + +#### 1.11 HCL single-line blocks inválidos +- **Bug**: Modelos geram `variable "x" { type = string, default = "y" }` (inválido em HCL) +- **Correção**: Regex auto-fix em frontend E backend para expandir para multi-line +- **Regra**: Para geração de código, implemente auto-correção de sintaxe em múltiplas camadas + +#### 1.12 Modelo descarta arquivos na correção +- **Bug**: Ao corrigir erros, modelo retorna APENAS arquivos corrigidos, descartando os não alterados +- **Correção**: Merge-based updates: arquivos existentes com mesmo nome → substituídos; sem match → preservados +- **Regra**: Workflows de correção AI precisam de semântica de merge, não de substituição + +#### 1.13 sqlite3.Row não suporta .get() +- **Bug**: `row.get("column")` crasha porque sqlite3.Row não tem método `.get()` +- **Correção**: Usar `try/except` com acesso direto `row["column"]` ou converter para `dict(row)` +- **Regra**: Conheça as limitações do seu ORM/driver. sqlite3.Row é dict-like mas não é dict + +#### 1.14 reasoning_effort case-sensitive no OCI SDK +- **Bug**: OCI SDK requer `"HIGH"` (uppercase), não `"high"` +- **Regra**: Sempre consulte a documentação do SDK para formatos exatos. Normalize inputs com `.upper()` antes de enviar + +#### 1.15 Index.html monolítico +- **Status**: ~2820 linhas de vanilla JS — manutenção cada vez mais difícil +- **Limite prático**: ~2000 linhas para SPA vanilla. Acima disso, framework de componentes se paga +- **Plano**: Migração para React (Vite + TypeScript), mantendo FastAPI backend + +--- + +## 2. Pipeline de Geração de Código (Terraform) + +### ACERTO: Pipeline 3 camadas + +1. **Frontend** (`_splitTfMonolith`): Detecta arquivos monolíticos (≤2 blocos + >2000 chars) e divide +2. **Backend** (`_split_tf_monolith`): Validação Python, split por categoria (networking.tf, compute.tf) +3. **Deduplicação** (`_write_tf_files`): Remove declarações duplicadas de recursos + +**Regra**: Para geração de código, implemente validação + auto-correção em camadas (client + server). Belt-and-suspenders funciona. + +### ACERTO: Checklist de validação no prompt + +14 pontos obrigatórios no system prompt: +- Cross-references entre recursos +- Validação de CIDRs +- Security lists + route tables +- DRG attachments + RPC peering +- Sintaxe HCL (blocos multi-line) +- Tipos de recursos válidos +- Declaração de variáveis + +**Regra**: Checklists no prompt funcionam melhor que exemplos para evitar erros comuns + +### ACERTO: Auto-inject de terraform.tfvars + +- Scan de variáveis declaradas nos `.tf` files +- Map automático: `tenancy_ocid`, `user_ocid`, `fingerprint`, `compartment_id`, `ssh_public_key` +- Valores vêm da OCI config ativa (criptografada no banco) + +**Regra**: Automatize tudo que pode ser derivado de configuração existente. Menos inputs manuais = menos erros + +### ERRO: Timeout de polling insuficiente + +- **Bug**: Frontend tinha 1h de timeout para polling, mas operações grandes (multi-region) podiam levar mais +- **Regra**: Timeout de polling deve ser generoso. 1h para plan, 2h para apply. Melhor sobrar que faltar + +--- + +## 3. Integração com OCI + +### ACERTOS + +- **OCID-based model resolution**: Catálogo mapeia model IDs para OCIDs por região +- **16 regiões OCI**: Endpoints auto-gerados (`https://inference.generativeai.{region}.oci.oraclecloud.com`) +- **Wallet auto-parse**: Upload de .zip → extrai password + DSN automaticamente +- **GenAI config auto-resolve**: Detecta config a partir de credenciais OCI (menos setup manual) + +### ERROS + +- **VCN deletion em cascata**: VCNs não podem ser deletadas com dependências (subnets, route tables, security lists, DRG attachments, RPCs). Ordem: RPCs → DRG Attachments → Subnets → SGWs → non-default RTs/SLs → VCNs → DRGs +- **Default resources não deletáveis**: Route tables e security lists default são tied ao lifecycle da VCN — não tente deletar independentemente +- **SSH key ≠ API key**: SSH public key (para compute) é diferente da private key OCI API. Armazene separadamente na config + +--- + +## 4. Frontend / UX + +### ACERTOS + +- **SVG icons (Lucide)**: Consistência cross-OS, themeable via CSS, zero CDN +- **CSS variables para tema**: Dark/light mode com toggle simples (`:root { --bg: ... }`) +- **Oracle Dark Premium**: Palette documentada (`#0D0F12` background, `#C74634` accent) +- **Chart.js para dashboards**: Leve, sem dependência de React, gauge + donut + bar +- **Confirmation modals**: Digitar "DESTROY"/"ROLLBACK" para ações destrutivas + +### ERROS + +- **Emojis inconsistentes**: ✅ ❌ ⚠️ renderizam diferente em cada OS → SVG resolveu +- **Layout complexo sem framework**: Resize handles, split panels, tabs — tudo manual em vanilla JS. Funciona mas é frágil +- **Scroll references quebradas**: Renomear CSS classes quebra scroll automático silenciosamente + +--- + +## 5. Operacional & Deploy + +### Padrão de Deploy +```bash +docker compose up -d --build backend && docker compose restart frontend +``` +- Backend: rebuild necessário (código Python) +- Frontend: restart necessário (nginx serve index.html via bind mount, mas precisa reload) + +### RCP (Shortcut do usuário) +- **R**EADME: atualizar changelog +- **C**ommit: mensagem descritiva +- **P**ush: enviar para remote + +### Versionamento +- Bump de versão no badge do README + entrada no changelog +- Versão semântica simplificada (v2.1, v2.2, etc.) + +--- + +## 6. Regras para AI Coding Assistant + +### Fazer +- Ler arquivo antes de editar (entender contexto) +- Preferir editar existente vs criar novo (evitar file bloat) +- Deploy combo sempre (backend build + frontend restart) +- Testar cenários de restart/recovery +- Validar inputs em múltiplas camadas + +### Não fazer +- Incluir atribuição AI em commits/código +- Hardcode valores de infraestrutura +- Compartilhar pipeline de RAG entre agentes diferentes +- Injetar variáveis automaticamente quando o modelo esquece (forçar correção) +- Usar `.get()` em sqlite3.Row +- Confiar que status intermediários vão se resolver sozinhos + +--- + +## 7. Stack & Trade-offs + +| Componente | Escolha | Por quê | Limite | +|------------|---------|---------|--------| +| Backend | FastAPI | Python-first (OCI SDK), async nativo | Mais lento que Go/Rust | +| Frontend | Vanilla JS → React | Zero build step inicialmente | ~2000 linhas é o limite prático | +| Banco | SQLite → Oracle ATP | Simples, ACID, sem container extra | Single-machine, ~50 req/s | +| Cache | In-memory → Redis | Implícito; migrar quando distribuir | Não compartilhado entre workers | +| IaC | Terraform CLI (subprocess) | Testado, familiar | Overhead de processo; file locking | +| Charts | Chart.js | Leve, funciona sem React | Customização limitada vs D3 | +| Icons | SVG inline (Lucide) | Themeable, consistente | Cherry-picking manual | + +--- + +## 8. Métricas de Referência + +| Arquivo | Linhas | Propósito | +|---------|--------|-----------| +| backend/app.py | ~7200 | FastAPI server, todos endpoints, state | +| frontend-react/src/ | ~15500 | React 19 SPA (42 source files) | +| backend/mcp_cis_server.py | ~700 | MCP server, 12 tools CIS | +| backend/gen_tf_reference.py | ~70 | Gerador de catálogo Terraform | + +--- + +## 9. ADB Oracle — Embedding & Vector Search (v2.8+) + +### ACERTOS + +#### 9.1 Auto-mapeamento CSV → tabela ADB +- Cada CSV do CIS report mapeia automaticamente para sua tabela: `cis_Identity_*.csv` → `identityandaccess`, `cis_Networking_*.csv` → `networking`, etc. +- **Regra**: Quando possível, derive o destino do nome do arquivo. Menos configuração manual = menos erro + +#### 9.2 Purge antes de re-embed +- Deletar dados antigos do mesmo tenancy + extract_date antes de inserir novos +- Usa `JSON_VALUE(METADATA, '$.tenancy')` para filtrar +- **Regra**: Sempre limpe antes de re-inserir. Duplicatas em bases vetoriais poluem resultados + +#### 9.3 Auto-detect dimensão de embedding por tabela +- `_get_table_embedding_dim()` → `_DIM_TO_MODEL = {1536: "text-embedding-3-small", 3072: "text-embedding-3-large"}` +- Cache de embeddings por modelo: se 5 tabelas usam o mesmo modelo, gera 1 embedding só +- **Regra**: Nunca assuma que todas as tabelas têm a mesma dimensão. Detecte dinamicamente + +#### 9.4 Chunking com header de contexto +- Textos >8000 chars divididos em partes, cada parte repete header (Tenancy, Resource, ID) +- Cada chunk é auto-suficiente para busca vetorial +- **Regra**: Chunks sem contexto são inúteis. Sempre repita metadados essenciais + +#### 9.5 Tabelas globais vs tenant-specific +- `cisrecom` e `engineerknowledgebase`: busca SEM filtro de tenancy (dados genéricos) +- Tabelas de findings: busca COM filtro de tenancy (dados específicos) +- **Regra**: Separe dados genéricos de dados específicos. Filtrar dados genéricos por tenancy retorna zero resultados + +#### 9.6 Hierarquia de fontes no RAG +- Findings (dados reais) > cisrecom (remediação oficial) > engineerknowledgebase (complementar) +- RAG vs MCP Tools: system prompt diferencia dados armazenados vs scan em tempo real +- **Regra**: O modelo precisa saber a confiabilidade relativa de cada fonte + +### ERROS + +#### 9.7 ID RAW(16) no ADB +- Tabelas usam `ID RAW(16)`, não VARCHAR. Inserts devem usar `HEXTORAW(:1)` com `uuid.uuid4().hex.upper()` +- Erro: `ORA-01465: invalid hex number` +- **Regra**: Verifique o schema da tabela ANTES de implementar inserts + +#### 9.8 METADATA tipo JSON, não CLOB +- `LIKE '%..%'` não funciona com colunas JSON. Usar `JSON_VALUE(METADATA, '$.field') = :1` +- Erro: `ORA-01722: unable to convert string containing '%' to a number` +- **Regra**: Cada tipo Oracle tem suas funções. JSON → JSON_VALUE/JSON_EXISTS. CLOB → LIKE + +#### 9.9 FETCH FIRST bind variable +- Oracle não aceita bind variable no `FETCH FIRST :1 ROWS ONLY` +- Usar f-string: `FETCH FIRST {int(n)} ROWS ONLY` (safe pois n é int do Python) +- **Regra**: Nem toda cláusula SQL aceita bind variables. Teste cada uso + +#### 9.10 Token limit do modelo de embedding +- `text-embedding-3-large` aceita max 8192 tokens. `truncate="END"` do OCI não funciona com modelos OpenAI +- Solução: truncar no código Python (8000 chars max) + chunking (8000 chars por parte) +- **Regra**: Não confie em flags do SDK para limites. Implemente truncation no seu código + +#### 9.11 Dados legados sem metadata estruturado +- Embeddings antigos tinham metadata em formato diferente (`source: blob, blobType, doc_id`), sem campo `tenancy` +- Filtro `JSON_VALUE(METADATA, '$.tenancy') IS NULL` incluía esses dados indevidamente +- Solução: remover fallback IS NULL, aceitar que dados legados precisam ser re-embedados +- **Regra**: Ao mudar schema de metadata, considere migração dos dados existentes + +--- + +## 10. React Migration & State Management (v2.7→v2.8) + +### ACERTOS + +#### 10.1 Zustand para persistência entre abas +- Todas as configurações (model, params, tools, sessions, messages) no Zustand store +- Sobrevive navegação entre páginas sem re-fetch +- **Regra**: Estado que o usuário espera manter ao voltar → Zustand. Estado transiente (loading, dropdown open) → useState local + +#### 10.2 Code splitting com React.lazy +- Bundle de 1.3MB → ~550KB initial load (15 chunks por página) +- Suspense fallback com spinner +- **Regra**: Implementar lazy loading desde o início. Custo zero de implementação, ganho significativo + +#### 10.3 Background tasks com polling persistente +- task_id salvo no Zustand store → polling retoma ao voltar para a página +- Status em arquivo no disco (não dict em memória) → visível entre workers +- **Regra**: Todo task longo precisa: 1) backend salvar status em disco/DB, 2) frontend salvar task_id no store, 3) retomar polling ao montar + +### ERROS + +#### 10.4 useCallback com closure stale +- `sessionId` capturado na closure do useCallback ficava desatualizado +- Solução: usar `useAppStore.getState()` para ler valor atual no momento da execução +- **Regra**: Em callbacks assíncronos, sempre leia state do store diretamente, nunca da closure + +#### 10.5 setInterval inline no onClick +- Não limpa no unmount, pode duplicar. Race condition com estado +- Solução: useEffect com cleanup, ou usePolling hook customizado +- **Regra**: Nunca crie timers/intervals dentro de event handlers. Sempre use useEffect com cleanup + +#### 10.6 X-Frame-Options DENY bloqueando iframes internos +- Security header bloqueava compliance report e HTML report renderizados em iframes +- Solução: SAMEORIGIN no bloco /api/ do nginx +- **Regra**: Ao adicionar security headers, teste TODOS os fluxos que usam iframes + +--- + +## 11. Security & Docker Hardening (v2.8) + +### ACERTOS +- **Non-root container**: gosu entrypoint para chown do volume + exec como user `agent` +- **Security headers nginx**: X-Frame-Options, X-Content-Type-Options, X-XSS-Protection, Referrer-Policy, Permissions-Policy +- **Rate limiting**: 10 tentativas/5min por IP com threading.Lock +- **CORS restritivo**: métodos/headers explícitos, origins configurável via env +- **.gitignore + .dockerignore**: prevenir leak de secrets e reduzir build context +- **Graceful shutdown**: terminar subprocesses + executor no shutdown event +- **File upload validation**: 50MB max, extensão whitelist por endpoint +- **16 SQLite indexes**: em foreign keys e colunas frequentemente consultadas + +### ERROS +- **CORS allow_origins=["*"]**: acidentalmente permitia CSRF. Sempre restringir +- **Container rodando como root**: padrão do Docker se não especificar USER +- **Bare except clauses**: 13 instâncias engolindo erros silenciosamente. Sempre logar +- **Sem timeout em chamadas externas**: GenAI e ADB podiam travar indefinidamente. Sempre definir timeout + +--- + +## 12. Gaps Conhecidos (para endereçar em projetos futuros) + +1. **Testes automatizados**: Sem cobertura. Pré-requisito para migração PostgreSQL +2. **PostgreSQL + Redis**: Planejado para escalar (multi-container, concorrência real) +3. **Dashboard real**: Painel com compliance score, atividade recente, status ADB +4. **Notificações**: Toast/badge quando tasks longos completam +5. **Busca cross-report**: Comparar compliance entre datas +6. **Drift detection**: Terraform não detecta mudanças manuais + +--- + +## 13. Recomendações para Próximo Projeto + +1. **Async-first** para qualquer coisa que dependa de LLM +2. **SQLite** para começar, migrar quando necessário +3. **System prompts compactos** (<3K chars, regras > exemplos) +4. **Validação em 3 camadas** para geração de código +5. **Cache agressivo** para APIs externas (TTL 2h) +6. **CSS variables** para tema desde o dia 1 +7. **SVG icons** em vez de emojis +8. **Framework de componentes** quando ultrapassar ~2K linhas de frontend +9. **Auto-update** de ferramentas terceiras no startup do container +10. **Guardrails de segurança** (bloquear antes de provisionar, não corrigir silenciosamente) +11. **Recovery paths** para todos os estados intermediários +12. **Separar fontes de RAG** por agente/contexto +13. **Nunca hardcode** valores de infraestrutura — sempre variáveis +14. **Documentar deploy patterns** desde o início