Arquitetura do Backend CRM
Phase 22.0–48. Status: DEPLOYED (VPS ao vivo, 68+ endpoints, arquitetura modular). Última atualização: 2026-04-28 (Phase 48 — Decomposição de Arquitetura)
Visão Geral
Browser (:18888)
│
├── Static files ──► Nginx ──► Docker frontend (React + Nginx)
│
├── /api/crm/* ────► Nginx ──► Bun Master Bot (:19210)
│ ├── crmAuthMiddleware (HMAC-SHA256)
│ └── shared/routes/router.ts → 17 domain modules
│
├── /api/auth/* ───► Nginx ──► Bun (:19210) → master-bot/routes/auth.ts
├── /api/cli/* ───► Nginx ──► Bun (:19210) → master-bot/routes/cli.ts
├── /api/internal/* ► Bun (:19210, loopback only) → master-bot/routes/internal.ts
│
├── /api/sse/* ────► Nginx (unbuffered) ──► Bun (:19210)
│
└── /ws/terminal/* ► Nginx (upgrade) ──► Bun (:19210) → master-bot/routes/websocket.ts
Estrutura de Módulos (Phase 48)
API Server (master-bot/api-server.ts, 196 linhas) — dispatcher enxuto:
master-bot/routes/auth.ts(562) — OAuth, login, register, 2FA, device codemaster-bot/routes/internal.ts(282) — chat/save, bridge-event, relay, binary downloadsmaster-bot/routes/cli.ts(293) — ARC CLI + integração MCPmaster-bot/routes/websocket.ts(303) — terminal, event-stream, local-bridge
Rotas CRM (shared/routes/router.ts, 512 linhas) — tabela de dispatch para 17 módulos de domínio:
projects.ts(723),workers.ts(636),skills.ts(665),chat.ts(862)sage.ts(757),onboarding.ts(601),files.ts(381),wiki.ts(258)system.ts,pins.ts,learnings.ts,docs.ts,specs.ts,restart.ts,analytics.ts_utils.ts(343) — tipos compartilhados, segurança de path, helpers de auth
Fluxo de Autenticação
User ──► LoginOverlay (frontend)
│
├── Email/password → POST /api/auth/login
└── OAuth → /api/auth/{google,github} → callback
│
▼
Auth bem-sucedida → token HMAC-SHA256
│
├── payload = { sub: userId, iat, exp: +24h }
├── signature = HMAC-SHA256(base64url(payload), CRM_SECRET)
└── token = base64url(payload) + "." + signature
│
▼
localStorage('crm-token') → Todas as chamadas de API: Authorization: Bearer <token>
│
▼
Token expirado → 401 → login necessário
Arquivos-Chave
| Arquivo | Função |
|---|---|
shared/auth.ts |
Geração/verificação de token, email/OAuth, reset de senha, verificação de email, CORS, middleware |
shared/vault.ts |
Armazenamento seguro AES-256-GCM (CRM_SECRET criado automaticamente) |
frontend/src/crm/components/LoginOverlay.jsx |
UI de login (email, OAuth, reset de senha, verificação de email) |
Propriedades de Segurança
- CRM_SECRET: hex aleatório de 32 bytes, criptografado no vault (AES-256-GCM)
- TTL do token: 24 horas, não renovável
- Assinatura: HMAC-SHA256, codificação base64url (sem padding)
- CORS: echo de Origin, limitado aos headers Authorization + Content-Type
- Verificação de email obrigatória antes do login para novas contas
- Autenticação básica do nginx permanece como camada de defesa em profundidade
Rotas da API CRM
Todas as rotas sob /api/crm/ exigem Authorization: Bearer <token>.
GET /api/crm/projects
Lista todos os projetos registrados com status de health ao vivo. Inclui o master como pseudo-projeto (DEBT-6).
| Campo | Fonte |
|---|---|
name, type, bot_username |
config/bot_registry.json (master: entrada virtual, type: "system") |
healthy |
HTTP health check (children: porta do child, master: /api/master/health) |
tmux_alive |
tmux has-session -t <name> (master: citadel-master) |
status |
Arquivo de heartbeat ou derivado de health+tmux (master: healthy/degraded/down) |
O master é sempre adicionado como primeira entrada no array de resposta.
GET /api/crm/projects/:name
Card de detalhes completo do projeto.
| Campo | Fonte |
|---|---|
manifest |
Texto bruto de <cwd>/MANIFEST.md |
heartbeat |
JSON de state/heartbeat_<name>.json |
skills[] |
Nomes de arquivos <cwd>/skills/*.md (excluindo _*.md) |
healthy, tmux_alive |
Mesmo que o endpoint de listagem |
GET /api/crm/projects/:name/logs
Tail de logs estruturados JSONL.
| Parâm. | Padrão | Máx. |
|---|---|---|
lines |
100 | 500 |
category |
dialog |
system, dialog, error |
Fonte: /var/log/citadel/<name>/<category>-YYYY-MM-DD.log
Lê os arquivos de log mais recentes primeiro, faz parse do JSONL e retorna em ordem cronológica.
GET /api/crm/projects/:name/files
Listagem de diretório com proteção contra path traversal.
| Parâm. | Padrão | Descrição |
|---|---|---|
path |
/ |
Path relativo dentro do cwd do projeto |
Segurança: resolve(base, requested) deve resultar em startsWith(resolve(base)). Violação → 403.
Entradas da resposta: { name, type: "file"|"directory", size?, modified }, ordenadas com diretórios primeiro.
GET /api/crm/projects/:name/skills
Skills instaladas com atribuição de fonte.
| Fonte | Detecção |
|---|---|
file |
<cwd>/skills/*.md (excluindo _*.md) |
manifest |
MANIFEST.md → skills[] + library_skills[] (parse JSON) |
Deduplicado: a fonte file tem prioridade sobre o manifest.
POST /api/crm/projects/:name/restart (Phase 22.3)
Reinicia um child bot via watchdog.
| Aspecto | Detalhe |
|---|---|
| Método | Apenas POST (GET retorna 405) |
| Auth | Token HMAC-SHA256 (mesmo das outras rotas CRM) |
| Cooldown | 30 segundos por projeto (retorna 429 se muito frequente) |
| Mecanismo | Chama restartChild() de watchdog.ts |
| Resposta | { ok: true } ou { error: "..." } |
GET /api/crm/projects/:name/metrics (Phase 22.1)
Série temporal de métricas de qualidade para gráficos sparkline.
| Parâm. | Padrão | Descrição |
|---|---|---|
days |
7 | Número de dias para agregar |
Fonte: /var/log/citadel/<name>/quality-events.log → contagens de sucesso/falha por dia.
WS /ws/terminal/:name (Phase 22.3)
Terminal WebSocket transmitindo conteúdo do painel tmux.
| Aspecto | Detalhe |
|---|---|
| Auth | Parâmetro de query ?token= (verificado via verifyToken().valid) |
| Modo | Somente leitura por padrão; ?mode=interactive habilita send-keys |
| Poll | tmux capture-pane -p -e -t {session} -S -80 a cada 200ms |
| Delta | Envia apenas quando o conteúdo muda em relação ao anterior |
Segurança: Proteção contra Path Traversal (Phase 22.3, DEBT-7)
Todos os endpoints validam nomes de projeto antes de processar:
const SAFE_NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/;
export function isValidProjectName(name: string): boolean {
return name.length > 0 && name.length <= 64 && SAFE_NAME_RE.test(name);
}
Aplicado em 3 pontos de entrada: routeCrmRequest(), routeSseRequest(), handler de upgrade WebSocket em bot.ts.
Nomes inválidos → 403 Forbidden.
Integridade de Dados: Escritas Atômicas (Phase 22.3, DEBT-2)
Escritas no registry em onboarding.ts usam padrão atômico:
async function atomicWriteJson(filePath: string, data: unknown): Promise<void> {
const tmp = `${filePath}.tmp.${process.pid}`;
await Bun.write(tmp, JSON.stringify(data, null, 2));
Bun.spawn(["mv", tmp, filePath]); // POSIX atomic rename
}
Arquivos-Chave
| Arquivo | Função | Linhas |
|---|---|---|
shared/crm-routes.ts |
55+ handlers + roteador routeCrmRequest() + isValidProjectName() |
~4800 |
shared/sage.ts |
Sage Worker: runSageAnalysis(), runBenchmark(), LLM judge, A/B testing |
~550 |
shared/db.ts |
SQLite SSOT: userQueries, projectQueries, skillQueries, benchmarkQueries, chatQueries | ~600 |
shared/auth.ts |
Geração de token, verificação, CORS, middleware | ~100 |
master-bot/bot.ts |
Bun.serve: roteador HTTP + handler de terminal WebSocket | ~1160 |
master-bot/onboarding.ts |
atomicWriteJson() para escritas no registry |
~850 |
Proxy Nginx
Arquivo: infra/nginx/citadel-crm.conf — substitui a config legada citadel-os.
Três upstreams:
| Upstream | Porta | Serviço |
|---|---|---|
crm_backend |
19210 | Bun Master Bot (CRM API, health) |
phaser_frontend |
18889 | Docker Phaser office (legado) |
mcp_bridge |
19200 | Docker MCP state-bridge (legado) |
| Location | Backend | Auth | Config Especial |
|---|---|---|---|
/ |
phaser_frontend |
basic auth | Docker Phaser frontend |
/api/crm/ |
crm_backend |
token (sem basic) | timeout 30s, keepalive |
/api/master/health |
crm_backend |
nenhuma | Health check público |
/api/sse/ |
crm_backend |
token (sem basic) | proxy_buffering off, timeout 1h (Phase 22.1) |
/ws/ |
crm_backend |
token (sem basic) | Upgrade WebSocket, timeout 1h (Phase 22.1) |
/api/ |
mcp_bridge |
nenhuma (sem basic) | API de estado legada + SSE |
/health |
mcp_bridge |
nenhuma | Health do bridge legado |
Todos os locations encaminham X-Real-IP, X-Forwarded-For, X-Forwarded-Proto.
Paths bloqueados: /.* (dotfiles), /config/, /state/, /scripts/.
Nota: A prioridade de location do Nginx garante que /api/crm/ e /api/sse/ correspondam antes do catch-all /api/ (MCP legado).
Integração no Master Bot
master-bot/bot.ts Bun.serve (:19210):
async fetch(req) {
/api/master/health → public, no auth
/api/crm/* → CORS preflight → crmAuthMiddleware → routeCrmRequest()
* → 404
}
O roteador despacha para funções handler de shared/crm-routes.ts. Headers CORS são injetados em toda resposta CRM (incluindo erros 401).
Resumo de Endpoints (Todas as Phases)
| Phase | Funcionalidade | Endpoint | Status |
|---|---|---|---|
| 22.0 | Lista de projetos | GET /api/crm/projects |
DONE |
| 22.0 | Detalhes do projeto | GET /api/crm/projects/:name |
DONE |
| 22.0 | Tail de logs | GET /api/crm/projects/:name/logs |
DONE |
| 22.0 | Listagem de arquivos | GET /api/crm/projects/:name/files |
DONE |
| 22.0 | Listagem de skills | GET /api/crm/projects/:name/skills |
DONE |
| 22.1 | Streaming SSE de logs | GET /api/sse/logs/:name |
DONE |
| 22.1 | Histórico de métricas | GET /api/crm/projects/:name/metrics |
DONE |
| 22.3 | Reinício de projeto | POST /api/crm/projects/:name/restart |
DONE |
| 22.3 | Terminal WebSocket | WS /ws/terminal/:name |
DONE |
| 24.5 | CRUD de specs | GET/POST /api/crm/projects/:name/specs |
DONE |
| 24.5 | Papel ativo | GET/POST /api/crm/projects/:name/active-role |
DONE |
| 25 | Bundle de skills | GET /api/crm/projects/:name/skills-bundle |
DONE |
| 25 | Sync de learnings | GET/POST /api/crm/projects/:name/learnings |
DONE |
| 26 | CRUD de workers | GET/POST /api/crm/projects/:name/workers |
DONE |
| 32 | Salvar wiki | PUT /api/crm/projects/:name/wiki/save |
DONE |
| 32 | Salvar/excluir skills | PUT/DELETE /api/crm/projects/:name/skills/* |
DONE |
| 33 | Configurações da conta | GET/PUT /api/crm/account/settings |
DONE |
| 33 | Criar projeto | POST /api/crm/projects/create |
DONE |
| 34 | CRUD de issues | POST/GET/PUT /api/mcp/issues/:project |
DONE |
| 34 | Wiki MCP | PUT /api/mcp/wiki/:project |
DONE |
| 34 | Sync de roadmap | GET/PUT /api/mcp/roadmap/:project |
DONE |
| 34 | Init CLI | GET /api/cli/init/:project/:mode |
DONE |
| 35 | Ingestão de log de terminal | POST /api/crm/projects/:name/terminal/log |
DONE |
| 36 | Chat Cloud PM | POST /api/crm/projects/:name/chat |
DONE |
| 36.6 | Geração de skill | POST /api/crm/projects/:name/skills/generate |
DONE |
| 36.7 | Listagem de notebooks | GET /api/crm/projects/:name/notebooks |
DONE |
| 40.14 | Roadmap (conteúdo) | GET /api/mcp/roadmap/:project (+ campo content) | DONE |
Total: 46+ endpoints (REST + SSE + WebSocket + CLI/MCP)
NotebookLM Bridge (Phase 36.3)
Serviço Python FastAPI separado em :19213 (somente localhost). Não faz parte da API CRM Bun.
| Endpoint | Método | Propósito |
|---|---|---|
/query |
POST | Busca semântica via Google NotebookLM |
/sync |
POST | Enfileira fonte para sync assíncrono (fire-and-forget) |
/notebooks/init |
POST | Cria notebook para novo projeto |
/health |
GET | Status de auth, contagem de notebooks, stats da fila |
O CRM Bun dispara chamadas fire-and-forget para o bridge ao:
handleUpdateIssue(issue close/open) →/synchandleMcpWikiUpdate(salvar wiki) →/synchandleCreateProject/handleOnboardingSetup→/notebooks/initexecuteAskNotebooklm→/query(timeout 15s, fallback para local)
Dívida Técnica
Veja docs/backlog/technical-debt.md para o registry completo. Itens-chave:
| ID | Título | Status |
|---|---|---|
| DEBT-2 | Escritas JSON atômicas | RESOLVIDO (Phase 22.3) |
| DEBT-3 | Token Docker fixo → auth de sessão | RESOLVIDO (Phase 31) |
| DEBT-6 | Ponto cego do master bot no CRM | RESOLVIDO (Phase 22.3) |
| DEBT-7 | Defesa contra path traversal | RESOLVIDO (Phase 22.3) |
Mantido por Rick (Orchestrator). Atualizado após a conclusão de cada phase.