ligbox-ops-platform/specs/019-ops-console-active-operations/plan.md
Ligbox Spec Hub 3a2c64834b Initial import: ligbox-ops-platform + specs + LAPTOP + obsidian merge (CT130)
Source: VM122 /opt + obsidian-infra + LAPTOP
Hub: CT130 spec-hub 10.10.10.130
2026-06-19 17:26:41 +00:00

465 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Implementation Plan: Ligbox Ops Console — Operação Activa (019)
**Branch:** `019-ops-console-active-operations`
**Date:** 2026-06-16
**Spec:** [spec.md](./spec.md)
---
## Summary
Implementar a **Ligbox Ops Console** — navegação investigativa tipo Wazuh com **`CH-*` como hub central** (chamado único, assumir, runbooks, aprovações humano + agentic, Assist Opção A).
**IA:** [design/navigation-ia.md](./design/navigation-ia.md)
| Camada | Host | Deploy |
|--------|------|--------|
| **Console UI** | **VM123** | **Docker Compose** (obrigatório — VM já tem serviços) |
| **API motor** | VM122 | Docker Compose existente (`ligbox-ops-platform`) |
| **Detecção** | VM104 | Wazuh manager (inalterado) |
| **Eventos onboard** | VM112 | systemd wizard |
**URL pública:** `https://console.ligbox.com.br` → Traefik CT114 → VM123:8100
**API:** `https://api.ops.ligbox.com.br` → VM122:8080
---
## Technical Context
**Language/Version**
| Componente | Stack |
|------------|-------|
| Console UI | React 18 + Vite 6 + TypeScript (recomendado) ou React JS |
| API extensões | Python 3.11+ FastAPI (VM122 `api/app/`) |
| VM112 Assist | Python FastAPI — novo endpoint `ops-status` |
| Deploy VM123 | Docker Compose v2, `nginx:alpine` |
**Primary Dependencies**
- Console: React Router (`/chamados/:publicId` = hub), TanStack Query, Zustand (auth)
- API: FastAPI, sqlite3, httpx, websockets (Fase 6)
- Agentic: gateway LLM existente (A6/A7 — Fase 6)
**Storage (VM122)**
Novas tabelas SQLite (ver Phase 1). MVP mantém SQLite; migração Postgres fora de escopo.
**Testing**
- `scripts/verify-console-health.sh` — VM123 Docker + Traefik
- `scripts/verify-chamado-unico.sh` — agregação eventos → `CH-*`
- `scripts/verify-runbook-r1.sh` — fila aprovação + execução mock
**Target Platform**
- VM123 LAN `10.10.10.123` (confirmar IP no Proxmox)
- Bind UI: `127.0.0.1:8100` ou `10.10.10.123:8100`
- Comunicação VM123 → VM122: LAN HTTPS via Traefik ou direct `http://10.10.10.122:8080` (dev)
**Performance Goals**
- Console first paint < 2s (gzip brotli via nginx)
- Discover feed < 800ms (paginação 50)
- Chamado detail + timeline < 500ms
- Poll Assist Opção A: 30s (MVP)
**Constraints**
- **VM123:** zero alteração em serviços/containers pré-existentes
- **Sem fork** `wazuh-dashboard` / OpenSearch
- **Sem replay** browser (Opção A apenas)
- Runbooks R2/R3: audit log obrigatório
- JWT Spec 003 em todas as rotas Console
---
## Constitution Check
| Princípio | Status |
|-----------|--------|
| IV. Mail vs Ops | PASS UI na VM123; motor VM122; VM112 webhooks + ops-status read-only |
| V. Security baseline | PASS fail2ban host VM123; container non-root; secrets em `.env` |
| VII. Spec-Driven | PASS |
| IX. YAGNI | PASS SQLite; poll antes de WebSocket |
---
## Project Structure
```text
specs/019-ops-console-active-operations/
├── spec.md
├── plan.md
├── tasks.md
├── deploy/ # Copiar para /opt/ligbox-ops-console/ na VM123
│ ├── docker-compose.yml
│ ├── .env.example
│ ├── nginx/
│ │ └── default.conf
│ └── scripts/
│ ├── preflight-vm123.sh # Inventário portas antes do deploy
│ └── deploy-console.sh
├── contracts/
│ └── chamados-api.md
└── design/
└── tokens.css # Referência paleta Wazuh-like
# Repositório produção (a criar)
/opt/ligbox-ops-console/ # VM123
├── docker-compose.yml # ← cópia de deploy/
├── .env
├── frontend/ # SPA React
│ ├── Dockerfile
│ ├── src/
│ │ ├── views/ # Overview, Discover, ChamadosList, ChamadoHub
│ │ ├── components/ # Timeline, Observables, AssistPanel, DrillDownLink
│ │ ├── api/ # client → api.ops.ligbox.com.br
│ │ └── theme/ # tokens Wazuh-like
│ └── dist/ # build → nginx volume
/opt/ligbox-ops-platform/ # VM122 (existente)
├── api/app/
│ ├── main.py # + rotas /chamados, /discover, /aprovacoes
│ ├── chamados.py # agregação CH-*, estados
│ ├── runbooks/ # executor + políticas R0-R3
│ └── agentic/ # propostas (Fase 6)
└── worker/
└── chamado_aggregator.py # merge eventos → chamado_id
```
---
## Phase 0: Pré-deploy VM123 (obrigatório)
**Objectivo:** garantir que Docker Console não conflita com serviços existentes.
```bash
# Na VM123 — antes de qualquer deploy
./scripts/preflight-vm123.sh
```
Checklist:
| # | Verificação | Comando | Critério |
|---|-------------|---------|----------|
| 1 | Porta 8100 livre | `ss -tlnp \| grep :8100` | Vazio |
| 2 | Docker activo | `docker info` | OK |
| 3 | Containers existentes | `docker ps` | Listar **não parar** |
| 4 | Disco | `df -h /opt` | 5 GB livres |
| 5 | IP LAN | `ip -4 addr show` | Confirmar `10.10.10.123` |
| 6 | Acesso VM122 | `curl -s -o /dev/null -w '%{http_code}' http://10.10.10.122:8080/api/health` | 200 |
Documentar resultado em ticket interno ou `docs/network/VM123_INVENTARIO.md` (criar após preflight).
---
## Phase 1: Data Model — Chamado único (VM122)
```sql
-- Migração 019_001_chamados.sql
CREATE TABLE chamados (
id INTEGER PRIMARY KEY AUTOINCREMENT,
public_id TEXT NOT NULL UNIQUE, -- CH-2026-00042
status TEXT NOT NULL DEFAULT 'novo',
assignee TEXT,
domain TEXT,
session_id TEXT,
wizard_step TEXT,
wizard_step_at TEXT,
sources TEXT NOT NULL DEFAULT '[]', -- JSON array
max_severity INTEGER,
title TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE INDEX idx_chamados_domain ON chamados(domain);
CREATE INDEX idx_chamados_session ON chamados(session_id);
CREATE INDEX idx_chamados_status ON chamados(status);
CREATE TABLE chamado_eventos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
chamado_id INTEGER NOT NULL REFERENCES chamados(id),
webhook_event_id INTEGER REFERENCES webhook_events(id),
event_type TEXT NOT NULL,
source TEXT NOT NULL,
payload TEXT,
created_at TEXT NOT NULL
);
CREATE TABLE runbook_execucoes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
chamado_id INTEGER NOT NULL REFERENCES chamados(id),
runbook_code TEXT NOT NULL,
nivel TEXT NOT NULL, -- R0, R1, R2, R3
status TEXT NOT NULL, -- pendente, aprovado, executando, ok, falhou, rejeitado
proposta TEXT, -- JSON agentic
actor TEXT, -- user | agent | system
resultado TEXT,
created_at TEXT NOT NULL,
executed_at TEXT
);
CREATE TABLE aprovacoes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
runbook_exec_id INTEGER NOT NULL REFERENCES runbook_execucoes(id),
aprovador TEXT,
decisao TEXT, -- aprovado | rejeitado
nota TEXT,
created_at TEXT NOT NULL
);
CREATE TABLE chamado_observables (
id INTEGER PRIMARY KEY AUTOINCREMENT,
chamado_id INTEGER NOT NULL REFERENCES chamados(id),
tipo TEXT NOT NULL, -- domain, session_id, agent, rule_id, ip, email
valor TEXT NOT NULL,
fonte TEXT NOT NULL, -- wazuh, onboard, manual, extractor
created_at TEXT NOT NULL,
UNIQUE(chamado_id, tipo, valor)
);
CREATE TABLE chamado_notas (
id INTEGER PRIMARY KEY AUTOINCREMENT,
chamado_id INTEGER NOT NULL REFERENCES chamados(id),
autor TEXT NOT NULL,
texto TEXT NOT NULL,
created_at TEXT NOT NULL
);
-- Denormalizar chamado_id em webhook_events (opcional fase 1b)
ALTER TABLE webhook_events ADD COLUMN chamado_id INTEGER REFERENCES chamados(id);
```
### Lógica de agregação (`chamado_aggregator.py`)
1. Novo `webhook_event` inserido worker busca chamado aberto:
- `session_id` + `domain` match
- senão `domain` + status (`fechado`, `resolvido`) + created < 72h
- senão `wazuh.agent.id` + domain/IP
2. Se não encontrar `INSERT chamados` + `public_id` sequencial
3. `INSERT chamado_eventos` + update `chamados.sources`, `max_severity`, `updated_at`
4. Eventos `account.created` / `wazuh.alert` L10 podem forçar criação imediata
5. **Extractor** popula `chamado_observables` a partir de cada payload (domain, agent, rule_id, …)
---
## Phase 2b: Hub API (VM122)
| Endpoint | Hub section |
|----------|-------------|
| `GET .../chamados/{id}` | `timeline`, `observables`, `assist`, `infra`, `links` |
| `POST .../notas` | append timeline |
| `POST .../eventos/anexar` | Discover hub |
| `GET .../links/wazuh` | deep link VM104 |
Helper `build_wazuh_deep_link(agent, from, to, rule_id)` URL dashboard VM104.
---
## Phase 2: API VM122
Ver [contracts/chamados-api.md](./contracts/chamados-api.md).
| Prioridade | Endpoint | Fase |
|------------|----------|------|
| P1 | `GET /api/v1/chamados` | F2 |
| P1 | `GET /api/v1/chamados/{public_id}` | F2 |
| P1 | `POST /api/v1/chamados/{public_id}/assumir` | F2 |
| P1 | `PATCH /api/v1/chamados/{public_id}` | F2 |
| P1 | `GET /api/v1/discover` | F3 |
| P1 | `POST /api/v1/chamados/{id}/runbooks/{code}/executar` | F4 |
| P1 | `GET /api/v1/aprovacoes` | F4 |
| P1 | `POST /api/v1/aprovacoes/{id}/aprovar` | F4 |
| P2 | `WS /api/v1/chamados/{public_id}/live` | F6 |
**Compatibilidade:** `GET /api/v1/desk/tickets` mantém-se 90 dias; resposta inclui `chamado_public_id` quando mapeado.
---
## Phase 3: Console UI — VM123 Docker
### Stack deploy (referência)
Ficheiros em [deploy/](./deploy/):
```bash
# VM123
rsync -av specs/019-ops-console-active-operations/deploy/ root@10.10.10.123:/opt/ligbox-ops-console/
ssh root@10.10.10.123 'cd /opt/ligbox-ops-console && cp .env.example .env && nano .env'
ssh root@10.10.10.123 'cd /opt/ligbox-ops-console && ./scripts/deploy-console.sh'
```
**Importante:** `docker compose up -d` afecta serviços definidos neste compose **não** `docker compose down` global na VM.
### Traefik CT114 (label router)
```yaml
# Adicionar em dynamic.yml ou labels do service discovery
http:
routers:
ligbox-ops-console:
rule: Host(`console.ligbox.com.br`)
entryPoints: [websecure]
service: ligbox-ops-console
tls:
certResolver: letsencrypt
services:
ligbox-ops-console:
loadBalancer:
servers:
- url: http://10.10.10.123:8100
```
### Views MVP (F3)
| View | Rota | Conteúdo |
|------|------|----------|
| **Overview** | `/` | Stats, funil, alertas link hub |
| **Discover** | `/discover` | Feed + filtros **abrir hub** |
| **Chamados** | `/chamados` | Lista fila trabalho |
| **Hub** | `/chamados/:publicId` | **Investigação central** timeline + tabs |
| **Tenants** | `/tenants` | Agentes/VMs; drill-down hub |
| **Aprovações** | `/aprovacoes` | Fila; cada item hub origem |
### Componentes hub (prioridade implementação)
1. `ChamadoHubLayout` shell 2 colunas
2. `InvestigationTimeline` eventos + notas + runbooks
3. `ObservablesPanel` entidades clicáveis (`DrillDownLink`)
4. `AssistPanel` Opção A
5. `InfraScorecardEmbed` proxy 009
6. `WazuhDeepLinkButton` novo tab VM104
7. `DiscoverAttachBar` seleccionar eventos anexar ao hub activo
### Tema Wazuh-like
- Ficheiro referência: [design/tokens.css](./design/tokens.css)
- Navegação: [design/navigation-ia.md](./design/navigation-ia.md)
---
## Phase 4: Runbooks + aprovações (VM122)
| Código | Nível | Executor |
|--------|-------|----------|
| `infra_recheck` | R0 | HTTP VM112 `/api/onboarding/infrastructure/status/{domain}` |
| `traefik_cert_sync` | R1 | Script/API CT114 (Spec 011) |
| `admin_nginx_reload` | R1 | SSH VM112 ou API futura |
| `zmproxy_admin_provision` | R2 | SSH VM112 humano obrigatório |
| `domain_purge` | R3 | API VM122 VM112 Spec 017 dupla aprovação |
| `wazuh_acknowledge` | R1 | Nota + tag chamado (VM104 ack futuro) |
| `escalate_kimi_human` | R0 | Link OB-* ticket chamado |
Fluxo R1:
```text
Técnico clica Executar → runbook_execucoes status=pendente
→ Agentic (opcional) gera proposta → aprovacoes fila
→ Humano Aprovar → worker executa → timeline + status ok|falhou
```
---
## Phase 5: Assist Opção A (VM112 + Console)
**VM112 — novo endpoint:**
```
GET /api/onboarding/session/{session_id}/ops-status
Header: X-Ops-Secret: <OPS_STATUS_SECRET>
LAN only: 10.10.10.0/24
```
Resposta:
```json
{
"session_id": "abc…",
"domain": "myvexx.com",
"wizard_step": "dns_cloudflare",
"wizard_step_label": "DNS Cloudflare",
"time_on_step_sec": 142,
"last_error": null,
"planned_email": "admin@myvexx.com"
}
```
**VM122:** poll 30s no detalhe chamado ou cache Redis `ops:session:{id}`.
**Console:** painel lateral `AssistPanel` sem iframe wizard, sem senhas.
---
## Phase 6: Agentic + WebSocket (pós-MVP)
- Agente A6 analisa chamado propõe runbook R0/R1
- Políticas em `agentic/policies.yaml` confiança mínima, allowlist runbooks
- WebSocket `live` push: novo evento + passo wizard (substitui poll)
---
## Implementation Phases (time estimate)
| Fase | Entrega | VM | ~Semanas |
|------|---------|-----|----------|
| **F0** | Preflight VM123 + Traefik DNS | 123, 114 | 0.5 |
| **F1** | Schema + aggregator | 122 | 11.5 |
| **F2** | API chamados | 122 | 1.52 |
| **F3** | Console Docker MVP (3 views) | **123** | 34 |
| **F4** | Runbooks R0R2 + aprovações | 122, 123 | 34 |
| **F5** | Assist ops-status | 112, 122, 123 | 23 |
| **F6** | Agentic + WS | 122, 123 | 34 |
**MVP (F0F4):** ~1012 semanas
**Completo (F0F6):** ~1620 semanas
---
## Sequencing with other specs
| Spec | Relação |
|------|---------|
| **001/002** | Eventos ingress aggregator alimenta chamados |
| **004** | Funil widget no Overview; timeline enriquece chamado |
| **009** | Tenants view + scorecard drill-down |
| **010** | Runbooks admin + `admin.validation.failed` chamado |
| **017/018** | Purge R3 + tile Serviços linka chamado |
**Ordem recomendada:** F1+F2 (API) em paralelo com F0+F3 scaffold UI Docker; F4 após API estável; F5 quando chamado único validado.
---
## Risk & Mitigation
| Risco | Mitigação |
|-------|-----------|
| Porta 8100 ocupada na VM123 | `preflight-vm123.sh`; variável `CONSOLE_HOST_PORT` no `.env` |
| Docker compose conflita rede | Rede bridge dedicada `ligbox-console`; não usar `network_mode: host` |
| CORS Console API | Allowlist `console.ligbox.com.br` no FastAPI VM122 |
| Agregação errada (chamados duplicados) | Regra session_id first; ferramenta merge manual ops |
| Runbook destrutivo sem R3 | Middleware bloqueia `domain_purge` sem dupla aprovação |
| VM123 serviços desconhecidos | Inventário obrigatório F0 antes deploy |
---
## Deploy checklist (cutover)
- [ ] `preflight-vm123.sh` OK
- [ ] `docker compose ps` `ligbox-ops-console-ui` healthy
- [ ] `curl -I https://console.ligbox.com.br/health` 200
- [ ] Login JWT Spec 003 funcional
- [ ] Chamado teste `CH-*` com evento Wazuh + onboard
- [ ] `desk.ligbox.com.br` 302 `console.ligbox.com.br` (após 90d aviso)
- [ ] Rollback: `docker compose down` no dir `/opt/ligbox-ops-console` apenas
---
## Referências
- [spec.md](./spec.md)
- [tasks.md](./tasks.md)
- [deploy/docker-compose.yml](./deploy/docker-compose.yml)
- [contracts/chamados-api.md](./contracts/chamados-api.md)