Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,27 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased] — 2026-04-17 — E2E Auth Stabilization & Observability [PID-SENTINEL]

### Added

- **Structured Logger**: `src/lib/logger.ts` — thin wrapper around `console.*` with Sentry-aware error forwarding; eliminates raw `console.*` calls across the codebase.
- **`data-testid="dashboard-welcome"`**: stable E2E anchor on the student dashboard `h1` (`dashboard-client.tsx`).

### Changed

- **Playwright env isolation**: port 3333 dedicated to E2E (`playwright.config.ts`); `reuseExistingServer: false` prevents reuse of a dev server carrying `.env.local` production credentials; explicit `env` block forwards Supabase vars to the webServer child process.
- **Seed determinism**: `purgeAuthUsers()` in `prisma/seed-e2e.ts` deletes `auth.users` by email before re-seeding with fixed UUIDs, eliminating UUID collision failures on re-run.
- **`sentry.client.config.ts` → `instrumentation-client.ts`**: migrated Sentry client init to Next.js 15 / v10 standard location; old file removed.
- **Observability**: replaced all `console.error` / `console.log` calls in client components and server actions with `Logger.*`; added `Sentry.captureException` in `alunos`, `financeiro`, `treinos` server actions.
- **Defensive XP serialization**: `/aluno/dashboard` page now null-guards `aluno.exp` and `aluno.nivel` and protects against division-by-zero in progress calculation.

### Fixed

- **ALUNO E2E login flake**: `loginAs()` helper adds a hard `page.goto(expectedPath)` after `waitForURL` to force a GET request where the browser includes the session cookie. Next.js App Router inline-renders the redirect target during the server action POST, where `Set-Cookie` is not yet in the `Cookie` request header, causing `getUser()` to fail and redirect to `/aluno/login`.
- **`isRedirectError` swallowed**: wrapped `redirect()` in `src/app/actions/auth.ts` with `try/catch` that re-throws via `isRedirectError` (from `next/dist/client/components/redirect-error`) so Next.js can inline-render the redirect target.
- **Strict-mode violation in heading assertion**: `getByRole('heading')` matched 2 elements on the admin dashboard; changed to `.first()` (and later to `data-testid` in student portal spec) to avoid Playwright strict-mode errors.

## [Unreleased] — 2026-04-17 — Sentinel Core Hardening

### Added
Expand Down
43 changes: 29 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,27 +75,34 @@ O projeto inclui o **Sentinel**, um sistema operacional de desenvolvimento alime

### Pré-requisitos

- Node.js 18.x ou superior
- Instância do PostgreSQL (ou projeto Supabase)
- Chave de API do Google AI (Gemini)
- Node.js 20+
- Docker (necessário para o stack E2E local via Supabase CLI)
- npm

### 1. Configurar Variáveis de Ambiente

Crie um arquivo `.env` na raiz do projeto com as seguintes chaves:
Crie um arquivo `.env.local` na raiz do projeto com as seguintes chaves:

```env
# Banco de Dados (Prisma)
DATABASE_URL="postgresql://user:password@host:port/dbname"
DIRECT_URL="postgresql://user:password@host:port/dbname"

# Supabase (Auth & API)
NEXT_PUBLIC_SUPABASE_URL="sua-url-do-supabase"
NEXT_PUBLIC_SUPABASE_ANON_KEY="sua-chave-anon"
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_DEFAULT_KEY="sua-chave-publishable"
SUPABASE_SERVICE_ROLE_KEY="sua-chave-service-role"

# Inteligência Artificial
GOOGLE_GENAI_API_KEY="sua-google-ai-key"

# Observabilidade
NEXT_PUBLIC_SENTRY_DSN="sua-dsn-do-sentry"
SENTRY_AUTH_TOKEN="seu-token-sentry"
```

Consulte `.env.example` para a lista completa de variáveis.

### 2. Instalação e Setup

```bash
Expand All @@ -105,10 +112,10 @@ npm install
# Gerar o cliente Prisma
npx prisma generate

# Sincronizar o banco de dados
npx prisma db push
# Aplicar migrations
npx prisma migrate deploy

Comment on lines +115 to 117

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verificar se há migrations reais e qual fluxo Prisma está ativo no repositório

set -euo pipefail

echo "== Prisma migrations directory =="
fd -HI '^migrations$' prisma || true
echo

echo "== Arquivos dentro de prisma/migrations =="
if [ -d "prisma/migrations" ]; then
  find prisma/migrations -maxdepth 2 -type f | sed 's#^# - #'
else
  echo " - (diretório prisma/migrations não encontrado)"
fi
echo

echo "== Referências de workflow Prisma no repo =="
rg -n "prisma (migrate deploy|db push|migrate dev)" README.md docs package.json .github || true
echo

echo "== Scripts npm relevantes =="
cat package.json | jq '.scripts | with_entries(select(.key|test("prisma|db|migrate|seed")))'

Repository: EmiyaKiritsugu3/PWeb_Project

Length of output: 1231


Align local migration setup with CI workflow.

Line 116 uses npx prisma migrate deploy, but the prisma/migrations directory doesn't exist, making this a no-op during local setup. This issue is documented in docs/dev-errors.md:168-174 (ERR-014). The CI pipeline correctly uses npx prisma db push --accept-data-loss instead (.github/workflows/ci.yml:124).

Change Line 116 to match the working CI approach:

npx prisma db push --accept-data-loss

Alternatively, if migrations are planned, use npx prisma migrate dev --name <description> and ensure prisma/migrations is committed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` around lines 115 - 117, Replace the no-op local migration
instruction that currently runs "npx prisma migrate deploy" in README.md with
the CI-safe command used by the workflow ("npx prisma db push
--accept-data-loss") so local setup matches CI; alternatively, if you intend to
use migrations, update the README to instruct running "npx prisma migrate dev
--name <description>" and ensure the prisma/migrations directory is generated
and committed.

# (Opcional) Popular o banco com dados iniciais
# (Opcional) Popular o banco com dados de desenvolvimento
npm run prisma:seed
```

Expand All @@ -118,18 +125,26 @@ npm run prisma:seed
npm run dev
```

Acesse `http://localhost:3000` para ver a aplicação em execução.
Acesse `http://localhost:3001` para ver a aplicação em execução.

---
### 4. Quality Gates

## 📘 Projeto 02 - Iteração 1
```bash
npm run typecheck # TypeScript strict — 0 erros
npm run lint # ESLint — 0 erros
npm run test # Vitest — 18/18 testes
npm run e2e # Playwright — 15/15 cenários (requer Docker + supabase start)
```

---

Documentação técnica e requisitos da primeira iteração do Projeto 02:
## 📘 Documentação do Projeto

- [Arquitetura do Projeto](./docs/arquitetura.md)
- [Documento de Visão](./docs/doc-visao.md)
- [Documento de User Stories](./docs/doc-userstories.md)
- [AcademicDevFlow (Repositório Principal)](#)
- [Modelo de Dados](./docs/doc-modelos.md)
- [Estado Atual](./docs/CURRENT-STATE.md)
- [Milestones](./docs/pdr/MILESTONES.md)

---

Expand Down
72 changes: 37 additions & 35 deletions docs/CURRENT-STATE.md
Original file line number Diff line number Diff line change
@@ -1,49 +1,51 @@
# Current State — Five Star Academy

**Last Updated**: 2026-04-11
**Branch**: `fix/telemetry-and-e2e-stability`
**Version**: 0.4.1 (pre-release)
**Last Updated**: 2026-04-17
**Branch**: `fix/e2e-auth-stabilization` → PR #69 open against `main`
**Version**: 0.4.2 (pre-release)

## What Works Today

| Feature | Status | Notes |
| ------------------------------------------- | ------------- | ------------------------------ |
| Admin login | ✅ Working | Supabase Auth SSR |
| Student login (Portal do Aluno) | ✅ Working | Separate session |
| Admin dashboard | ✅ Working | GERENTE + RECEPCIONISTA |
| Financial routes (`/financeiro`, `/planos`) | ✅ Working | GERENTE-only gate |
| Student workout view | ✅ Working | `meus-treinos` |
| AI workout generator | ✅ Working | Genkit + Gemini |
| Student enrollment | ✅ Working | Admin creates aluno |
| Gamification (XP, streaks) | ✅ Working | Hook `use-workout-tracker` |
| Prisma migrations | ✅ Tracked | `prisma/migrations/` |
| ESLint quality gate | ✅ Done | 0 errors — `any` + unused vars |
| TypeScript typecheck | ✅ Clean | 0 errors (strict mode) |
| Unit tests | ✅ Passing | 18/18 (Vitest) |
| Ops documentation | ✅ Done | Runbook, SLOs, threat model |
| Process documentation | ✅ Done | RFC + Postmortem templates |
| Local E2E stack | ✅ Done | `supabase start` (Docker) |
| E2E seed script | ✅ Done | `prisma/seed-e2e.ts` (4 users) |
| Playwright E2E suite | ✅ Done | 15/15 passing |
| CI E2E job | ✅ Done | `.github/workflows/ci.yml` |
| Sentry error tracking | ✅ Modernized | Next.js 15, v10, User Context |
| Feature | Status | Notes |
| ------------------------------------------- | ------------- | -------------------------------------------------- |
| Admin login | ✅ Working | Supabase Auth SSR |
| Student login (Portal do Aluno) | ✅ Working | Separate session |
| Admin dashboard | ✅ Working | GERENTE + RECEPCIONISTA |
| Financial routes (`/financeiro`, `/planos`) | ✅ Working | GERENTE-only gate |
| Student workout view | ✅ Working | `meus-treinos` |
| AI workout generator | ✅ Working | Genkit + Gemini |
| Student enrollment | ✅ Working | Admin creates aluno |
| Gamification (XP, streaks) | ✅ Working | Hook `use-workout-tracker` |
| Prisma migrations | ✅ Tracked | `prisma/migrations/` |
| ESLint quality gate | ✅ Done | 0 errors — `any` + unused vars |
| TypeScript typecheck | ✅ Clean | 0 errors (strict mode) |
| Unit tests | ✅ Passing | 18/18 (Vitest) |
| Ops documentation | ✅ Done | Runbook, SLOs, threat model |
| Process documentation | ✅ Done | RFC + Postmortem templates |
| Local E2E stack | ✅ Done | `supabase start` (Docker) |
| E2E seed script | ✅ Done | `prisma/seed-e2e.ts` (4 users, purge-on-seed) |
| Playwright E2E suite | ✅ Done | 15/15 passing |
| CI E2E job | ✅ Done | `.github/workflows/ci.yml` |
| Sentry error tracking | ✅ Modernized | Next.js 15, v10, `instrumentation-client.ts` |
| Structured logging | ✅ Done | `src/lib/logger.ts` (Logger wrapper, Sentry-aware) |

## What Is Incomplete

| Area | Gap | Priority |
| ------------- | ------------------------------------------------------------------ | -------- |
| CI security | 3 moderate vulns in `@prisma/dev` (transitive, awaiting upstream) | P3 |
| Lint warnings | 31 `no-console` warnings remain (accepted for server debug) | P3 |
| GitHub secret | `SUPABASE_LOCAL_SERVICE_ROLE_KEY` must be set for CI seed | P1 |
| Env Sync | Ensure `NEXT_PUBLIC_SENTRY_DSN` is set in production for scrubbing | P1 |
| Area | Gap | Priority |
| ------------- | ----------------------------------------------------------------------- | -------- |
| CI security | 3 moderate vulns in `@prisma/dev` (transitive, awaiting upstream) | P3 |
| Lint warnings | `no-console` warnings reduced — remaining are accepted Logger internals | P3 |
| GitHub secret | `SUPABASE_LOCAL_SERVICE_ROLE_KEY` must be set for CI seed | P1 |
| Env Sync | Ensure `NEXT_PUBLIC_SENTRY_DSN` is set in production for scrubbing | P1 |
| PR merge | PR #69 (`fix/e2e-auth-stabilization`) open, awaiting CI green + review | P1 |

## Quality Gates (current status)

```
npm run typecheck → ✅ 0 errors
npm run lint → ✅ 0 errors (30 warnings: no-console)
npm run lint → ✅ 0 errors
npm run test → ✅ 18/18 passing
npm run e2e → ✅ 15/15 passing
npm run e2e → ✅ 15/15 passing (local, 2026-04-17)
```

## Tech Stack
Expand Down Expand Up @@ -71,7 +73,7 @@ npm run e2e → ✅ 15/15 passing
| `src/ai/flows/` | Genkit AI flows |
| `prisma/schema.prisma` | DB schema |
| `prisma/seed.ts` | Dev seed data |
| `prisma/seed-e2e.ts` | E2E seed (4 deterministic users, fixed UUIDs) |
| `prisma/seed-e2e.ts` | E2E seed (4 users, fixed UUIDs, purge-on-reseed) |
| `docs/security/THREAT-MODEL.md` | STRIDE analysis (17 threats) |
| `docs/operations/RUNBOOK.md` | Local setup, deploy, migrations, secrets |
| `docs/operations/INCIDENT-RESPONSE.md` | P1/P2/P3 response procedure |
Expand All @@ -96,14 +98,14 @@ npm run e2e → ✅ 15/15 passing
The following items are recognized as "Managed Debt" — intentional compromises for compatibility or performance:

1. **Sentry Boundary Casts**: Use of `any` in `beforeSend` (suppressed with `eslint-disable`) is required due to Sentry SDK interface rigidness.
2. **Server console.log**: 31 warnings in Server Actions are preserved to ensure minimal observability in the terminal during development.
2. **Logger internals**: `Logger` uses `console.*` internally — these are the only remaining `no-console` warnings and are acceptable as the structured logging layer.
3. **Prisma Type Overrides**: We use `@pg/types` pinning to resolve Prisma 7/Next 15 conflicts until `@prisma/adapter-pg` upstream fixes land.

## Known Issues

- 31 `no-console` warnings in server actions — accepted for server-side debug logging.
- GitHub Secrets for CI (Playwright) require one-time manual setup.
- Transitive dependency vulnerabilities in `@prisma/dev` (not reachable in production).
- `isRedirectError` must be imported from `next/dist/client/components/redirect-error` (not `next/navigation`) — no public API yet in Next.js 15.

## Update Protocol

Expand Down
71 changes: 70 additions & 1 deletion docs/dev-errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,4 +250,73 @@ Format: discovered → root cause → fix → effectiveness.
O flow só é necessário quando o usuário clica "Finalizar Treino" — nunca no render inicial.
- **Lição:** Imports de módulos pesados com dependências de runtime (OpenTelemetry, telemetria, etc.) devem ser dinâmicos quando usados apenas em handlers de evento. Import estático ≠ "só executa quando chamado" — o módulo é resolvido e bundled na inicialização.

- **Efetividade:** Pendente — CI em andamento (run `24266743760`)
- **Efetividade:** ✅ Resolvido — import dinâmico eliminou o erro de bundle; student-portal suite passou em CI

---

## ERR-019 — E2E Auth: `redirect()` em server action nunca executa — swallowed por `try/catch`

- **Data:** 2026-04-17
- **Contexto:** Branch `fix/e2e-auth-stabilization` — `src/app/actions/auth.ts` — login de todos os roles
- **Sintoma:** Login retornava sem erro, mas o browser ficava na página `/login` sem navegar. `waitForURL('**/dashboard**')` timeout em 15s.
- **Causa raiz:** Next.js App Router implementa `redirect()` lançando internamente uma exceção especial (`NEXT_REDIRECT`). Se o `redirect()` é chamado dentro de um bloco `try/catch` sem re-throw, a exceção é absorvida — o redirect nunca acontece e a server action simplesmente retorna `undefined`.
- **Fix:** Adicionar guard `isRedirectError` antes de qualquer catch genérico:

```ts
import { isRedirectError } from 'next/dist/client/components/redirect-error';
// ⚠️ NÃO importar de 'next/navigation' — não exporta isRedirectError (TS2305)

try {
redirect('/dashboard');
} catch (err: unknown) {
if (isRedirectError(err)) throw err; // ← re-throw obrigatório
return { error: 'Erro inesperado' };
}
```

- **Arquivo afetado:** `src/app/actions/auth.ts`
- **Lição:** `redirect()` do Next.js é implementado via throw. Nunca envolver em `try/catch` sem o guard `isRedirectError`. O path de import correto é `next/dist/client/components/redirect-error` — `next/navigation` não exporta essa função (erro TypeScript TS2305 se tentar).
- **Efetividade:** ✅ Resolvido — todos os roles redirecionaram corretamente após o fix

---

## ERR-020 — E2E Auth: ALUNO sempre aterrissa em `/aluno/login` após login bem-sucedido

- **Data:** 2026-04-17
- **Contexto:** Branch `fix/e2e-auth-stabilization` — `tests/e2e/specs/auth.spec.ts` — teste ALUNO
- **Sintoma:** Login do ALUNO: `waitForURL` passava (URL mudava para `/aluno/dashboard`), mas a página renderizava `/aluno/login`. `getByRole('heading')` — element not found.
- **Causa raiz:** Next.js App Router inline-renderiza o target do redirect durante a mesma resposta POST da server action. Nesse momento, `cookies()` no server component lê os **request headers** — mas o cookie de sessão (`sb-*-auth-token`) setado por `signInWithPassword()` só existe no **response** `Set-Cookie`. O browser ainda não armazenou o cookie, então `getUser()` falha e a página redireciona para `/aluno/login`.
- Ponto crítico: `/dashboard` (admin) não chama `getUser()` no server component e não foi afetado. `/aluno/dashboard` chama e foi afetado.
- **Fix:** No helper `loginAs()`, forçar uma navegação GET após o `waitForURL`:
```ts
await page.waitForURL('**/dashboard**', { timeout: 15_000 });
await page.goto(expectedPath); // GET real — browser já tem o cookie armazenado
```
- **Arquivo afetado:** `tests/e2e/helpers/auth.ts`
- **Lição:** Em Next.js App Router, o inline RSC render do redirect target não recebe o cookie de sessão que acabou de ser setado. Qualquer server component que chame `getUser()` ou `cookies()` e seja redirect target de uma server action está sujeito a esse timing. Em produção funciona (browser faz GET separado); em E2E é necessário forçar o GET explicitamente.
- **Efetividade:** ✅ Resolvido — ALUNO aterrou em `/aluno/dashboard` em todas as execuções subsequentes

---

## ERR-021 — E2E Auth: Todos os logins falhando com `TimeoutError: page.waitForURL` — servidor usando credenciais de produção

- **Data:** 2026-04-17
- **Contexto:** Branch `fix/e2e-auth-stabilization` — primeira execução após configurar o `.env.test`
- **Sintoma:** `TimeoutError: page.waitForURL: Timeout 15000ms exceeded` em todos os 3 testes de login. Apenas o teste de credenciais inválidas passava.
- **Causa raiz:** `playwright.config.ts` tinha `reuseExistingServer: true` (padrão) e a porta padrão (3001). Um servidor dev já estava rodando na porta 3001 carregado com `.env.local` (credenciais Supabase de produção). O Playwright reusou esse servidor — os usuários de teste não existem no projeto Supabase de produção, então todos os logins falhavam silenciosamente no `signInWithPassword()`.
- **Diagnóstico confirmado:** Trocar temporariamente para `reuseExistingServer: false` e port 3333 mostrou que o servidor novo (com `.env.test`) funcionava.
- **Fix:** Em `playwright.config.ts`:
```ts
webServer: {
command: 'npm run dev -- --port 3333', // porta dedicada E2E
url: 'http://localhost:3333',
reuseExistingServer: false, // ← nunca reusar
env: {
NEXT_PUBLIC_SUPABASE_URL: process.env.NEXT_PUBLIC_SUPABASE_URL ?? '',
// ... forward explícito de todas as vars do .env.test
},
Comment on lines +314 to +317

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Torne explícita a variável de chave Supabase no snippet do ERR-021.

No trecho da Line 314-317, a documentação fala em forward explícito, mas não mostra NEXT_PUBLIC_SUPABASE_PUBLISHABLE_DEFAULT_KEY. Isso pode levar alguém a reintroduzir o nome antigo por engano.

✍️ Sugestão de ajuste no snippet
 env: {
   NEXT_PUBLIC_SUPABASE_URL: process.env.NEXT_PUBLIC_SUPABASE_URL ?? '',
-  // ... forward explícito de todas as vars do .env.test
+  NEXT_PUBLIC_SUPABASE_PUBLISHABLE_DEFAULT_KEY:
+    process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_DEFAULT_KEY ?? '',
+  DATABASE_URL: process.env.DATABASE_URL ?? '',
+  DIRECT_URL: process.env.DIRECT_URL ?? '',
 },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
env: {
NEXT_PUBLIC_SUPABASE_URL: process.env.NEXT_PUBLIC_SUPABASE_URL ?? '',
// ... forward explícito de todas as vars do .env.test
},
env: {
NEXT_PUBLIC_SUPABASE_URL: process.env.NEXT_PUBLIC_SUPABASE_URL ?? '',
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_DEFAULT_KEY:
process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_DEFAULT_KEY ?? '',
DATABASE_URL: process.env.DATABASE_URL ?? '',
DIRECT_URL: process.env.DIRECT_URL ?? '',
},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/dev-errors.md` around lines 314 - 317, The env snippet is missing the
explicit Supabase publishable key variable; update the env object that currently
contains NEXT_PUBLIC_SUPABASE_URL to also include
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_DEFAULT_KEY (set to
process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_DEFAULT_KEY ?? '') so the
documentation shows the correct, current key name and avoids accidental
reintroduction of the old key name.

}
```
- **Arquivo afetado:** `playwright.config.ts`
- **Lição:** `reuseExistingServer: true` é conveniente para velocidade mas perigoso quando o projeto tem múltiplos ambientes (`.env.local` + `.env.test`). O servidor reutilizado pode ter sido iniciado com qualquer conjunto de variáveis de ambiente. Em projetos com staging/test isolation, sempre usar `reuseExistingServer: false` + porta dedicada para E2E.
- **Efetividade:** ✅ Resolvido — porta 3333 dedicada, servidor sempre iniciado com `.env.test`
Loading
Loading