From 265fabb3478ee635132d9a139608fe93dc4b8f1c Mon Sep 17 00:00:00 2001
From: johnlaff <46091881+johnlaff@users.noreply.github.com>
Date: Fri, 10 Jul 2026 18:02:05 -0300
Subject: [PATCH] docs(observability): documenta a integracao Sentry + health
check
Cobre a nova observabilidade (PR #64) em toda a documentacao viva:
- docs/adr/0006-observabilidade-sentry.md: ADR novo com contexto (o 500 do
clock-out invisivel), decisao (Sentry server+client, tunnel /monitoring,
source maps por SHA, sendDefaultPii false, hour-bank reportando, /api/health +
healthCheckPath), consequencias e alternativas (Datadog/App Insights/self-host).
- AGENTS.md: secao Observabilidade com as regras operacionais (init, tunnel na
allowlist do proxy, catches silenciosos reportando, connection() no health).
- README.md: feature de observabilidade + Sentry na linha de infra; alt do
diagrama enriquecido (a11y: via o aria-label interno nao e lido).
- docs/assets/architecture.svg: caixa Observabilidade (Sentry via tunnel +
/api/health) abaixo do app, seta de source maps CI->Sentry, legenda das setas,
contraste AA, geometria alinhada e fallback de presentation attributes.
- supabase-security-checklist.md: DSN publico vs SENTRY_AUTH_TOKEN segredo.
---
.gitignore | 3 +
AGENTS.md | 18 ++++
README.md | 5 +-
docs/adr/0006-observabilidade-sentry.md | 57 ++++++++++
docs/assets/architecture.svg | 134 ++++++++++++++----------
docs/supabase-security-checklist.md | 3 +
6 files changed, 165 insertions(+), 55 deletions(-)
create mode 100644 docs/adr/0006-observabilidade-sentry.md
diff --git a/.gitignore b/.gitignore
index b41d1c7..7e0a1c3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -58,3 +58,6 @@ public/sw.js
# Claude Code local settings / scratch
.claude/
+
+# Playwright MCP session artifacts
+.playwright-mcp/
diff --git a/AGENTS.md b/AGENTS.md
index 950cf48..ed9787d 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -104,6 +104,24 @@ Regras duras:
- Performance de navegação não pode degradar.
- Para bibliotecas pesadas, prefira lazy-load/code-split em vez de reimplementar tudo custom por reflexo.
+## Observabilidade
+
+- Error tracking via **Sentry** (`@sentry/nextjs`). Server inicializa no `src/instrumentation.ts`
+ (junto da validação do keyring, sem quebrá-la) e exporta `onRequestError`; client no
+ `src/instrumentation-client.ts`. Ligado **só em produção** (`NODE_ENV === 'production'`), com
+ `sendDefaultPii: false` — não envie dados da usuária.
+- Os eventos vão pelo tunnel same-origin **`/monitoring`** (`tunnelRoute`). Se mexer no matcher do
+ `src/proxy.ts`, mantenha `api/health` e `monitoring` na allowlist — senão o tunnel e o health
+ check quebram (redirect para login). A CSP (`connect-src 'self'`) já cobre o tunnel.
+- Catches que engolem erro de propósito (ex.: os `safeRecalculateHourBankFor*` do `hour-bank`)
+ devem reportar ao Sentry (`captureException`) sem deixar de engolir — falha silenciosa não pode
+ ficar invisível.
+- **Health check:** `GET /api/health` (público, `SELECT 1`) alimenta o probe do App Service
+ (`healthCheckPath = /api/health`) e o uptime monitor do Sentry. Use `await connection()` em rotas
+ dinâmicas que dependem de request time (com `cacheComponents`, `force-dynamic` é proibido).
+- Segredos: `NEXT_PUBLIC_SENTRY_DSN` é público (build-arg, inlinado no bundle); `SENTRY_AUTH_TOKEN`
+ é segredo, só docker build secret (upload de source maps). Ver ADR 0006.
+
## Design System
- Use OKLCH nos tokens de cor.
diff --git a/README.md b/README.md
index 7b438d1..dc7d189 100644
--- a/README.md
+++ b/README.md
@@ -42,12 +42,13 @@ sem rede** — o registro entra numa fila local e sincroniza sozinho quando a co
| 🗂️ | **Histórico auditável** | Edição de registros com trilha de auditoria; filtros por projeto e atividade sem deslocar a UI. |
| 🔐 | **Integridade dos registros** | Cada sessão fechada carrega um **HMAC-SHA256** (versionado por `keyId` quando o keyring está configurado, como em produção); `/api/integrity` distingue formato inválido, adulteração e uma chave histórica indisponível. |
| 📲 | **PWA instalável** | Service worker (Serwist), ícone/manifest e experiência de app nativo no celular. |
+| 🔭 | **Observabilidade** | Erros de produção (server e client) capturados no **Sentry** com alerta; `/api/health` alimenta o health check do App Service e o uptime monitor. Ver [`docs/adr/0006`](docs/adr/). |
## Arquitetura
-
+
@@ -56,7 +57,7 @@ sem rede** — o registro entra numa fila local e sincroniza sozinho quando a co
| **Cliente** | PWA · React 19 · Serwist (service worker) · IndexedDB (`idb`) · Tailwind 4 + shadcn/ui + Radix |
| **Servidor** | Next.js 16 App Router · `output: standalone` + PPR · API Routes · Prisma 7 (query compiler **WASM**) |
| **Dados** | Supabase PostgreSQL (`sa-east-1`) · Supabase Auth (Google OAuth) |
-| **Infra** | Azure App Service (container Linux, Brazil South) · `ghcr.io` · GitHub Actions |
+| **Infra** | Azure App Service (container Linux, Brazil South) · `ghcr.io` · GitHub Actions · Sentry (observabilidade) |
Princípio central: **toda escrita passa por uma API Route** — nenhum Client Component escreve
direto no banco. O modelo de confiança de origem (CSRF via header `Host`/`Origin`) está detalhado
diff --git a/docs/adr/0006-observabilidade-sentry.md b/docs/adr/0006-observabilidade-sentry.md
new file mode 100644
index 0000000..2dd9ab0
--- /dev/null
+++ b/docs/adr/0006-observabilidade-sentry.md
@@ -0,0 +1,57 @@
+# ADR 0006 — Observabilidade com Sentry + health check
+
+**Data:** 2026-07-10 · **Status:** Aceito
+
+## Contexto
+
+Um HTTP 500 no clock-out em produção ficou invisível até a usuária principal reclamar que não
+conseguia encerrar a sessão. O app não tinha **nenhuma** captura de erro (`grep` de
+`console.error`/`captureException` nas rotas de API retornava zero) nem health check
+(`healthCheckPath` do App Service estava vazio). A cegueira a falhas de produção era o risco
+operacional #1 da auditoria: o próximo erro também chegaria por reclamação, não por alerta.
+
+## Decisão
+
+Integrar **`@sentry/nextjs`** (plano Team patrocinado via GitHub Student Pack) para error tracking
+server e client, e expor um health check.
+
+- **Server:** `Sentry.init` no `src/instrumentation.ts` (`register()`), preservando a validação do
+ keyring de HMAC no boot. `onRequestError = captureRequestError` cobre route handlers, server
+ actions e RSC — inclui o 500 de clock-out que originou esta decisão.
+- **Client:** `src/instrumentation-client.ts` (convenção do Next 16) + `onRouterTransitionStart`.
+- **`enabled` só em produção** (`NODE_ENV === 'production'`) e **`sendDefaultPii: false`** — nenhum
+ dado da usuária (IP, headers, corpo) sai do app.
+- **Tunnel:** `tunnelRoute: '/monitoring'` roteia os eventos pelo próprio domínio (same-origin) —
+ evita ad-blockers e mantém a CSP intacta (`connect-src 'self'`). Exige que `api/health` e
+ `monitoring` fiquem na allowlist do matcher do `src/proxy.ts`, senão são redirecionados ao login.
+- **Falhas silenciosas por design viram visíveis:** os recálculos fail-safe do `hour-bank`
+ (`safeRecalculateHourBankFor*`) engolem o erro de propósito (o cache é derivado e roda após o
+ commit da mutação primária); agora reportam ao Sentry **sem** deixar de engolir. Era exatamente a
+ classe de falha que o `onRequestError` (que só captura erro que propaga) não pegaria.
+- **Source maps:** subidos no build da imagem quando `SENTRY_AUTH_TOKEN` (build secret) está
+ presente; pulados no CI e no dev sem o token. O `release` é o SHA do commit (`SENTRY_RELEASE`),
+ já que o `.git` não entra no contexto do build Docker.
+- **Segredos:** `NEXT_PUBLIC_SENTRY_DSN` é público (inlinado no bundle em build time, via build-arg);
+ `SENTRY_AUTH_TOKEN` é segredo, entra só como docker build secret (não persiste na imagem).
+- **Health check:** `GET /api/health` (público, `SELECT 1` no banco) para o probe do App Service e o
+ uptime monitor do Sentry. Usa `await connection()` (não `force-dynamic`, incompatível com
+ `cacheComponents`). O App Service passa a apontar `healthCheckPath = /api/health`.
+
+## Consequências
+
+- **+** Erros de produção passam a gerar alerta em vez de esperar reclamação.
+- **+** As falhas silenciosas do `hour-bank` ficam observáveis.
+- **+** O App Service recicla a instância se ela travar (antes o `healthCheckPath` era vazio).
+- **−** Com 1 instância B1, um banco fora do ar por tempo prolongado (`/api/health` = 503) pode levar
+ a Azure a reciclar a instância; trade-off aceitável para um app pessoal.
+- O plano patrocinado (50k erros/mês, 5 TB logs, 1 uptime monitor) vale enquanto o mantenedor for
+ estudante; em **2027-07-10** rebaixa para o plano Developer grátis (5k erros/mês), suficiente para
+ um app de uma usuária. Nenhuma ação necessária na virada.
+
+## Alternativas rejeitadas
+
+- **Datadog** (Pro grátis por 2 anos no Student Pack): observabilidade completa, mas pesado demais
+ para um app de uma usuária e caro quando os 2 anos acabam. Guardado para se o produto crescer.
+- **Azure Application Insights** (nativo do App Service, coberto por créditos): ótimo para APM/infra,
+ mas a DX de error tracking + alerta é mais fraca que a do Sentry para o problema em questão.
+- **Self-host** (OpenTelemetry + coletor): custo operacional injustificável nesta fase.
diff --git a/docs/assets/architecture.svg b/docs/assets/architecture.svg
index 9e5782a..3509344 100644
--- a/docs/assets/architecture.svg
+++ b/docs/assets/architecture.svg
@@ -1,13 +1,13 @@
-