Files
amare/openspec/changes/complete-foundation-parity/tasks.md
Manoel Freitas 7e68c0e37f chore: fechar portões de qualidade da Fase 0 (MAN-120) (#39)
* feat: gates de qualidade da Fase 0 (auth, npm audit, cobertura, compose local)

Implementa os itens pendentes de SPEC.md §18 mapeados pela mudança OpenSpec
complete-foundation-parity (grupos 1, 2 e 3 de tasks.md):

- MustVerifyEmail em User, com canAccessPanel exigindo is_active E
  hasVerifiedEmail(). Seeds de admin/assistant local passam a marcar
  email_verified_at. Verificação de e-mail permanece administrada apenas
  pelo admin (seed/edição) — não há rota de auto-verificação registrada
  (->emailVerification() não foi chamado) — decisão deliberada, já que
  usuários são criados pelo admin, não se autocadastram.

- Reset de senha seguro: página customizada
  App\Filament\Pages\Auth\RequestPasswordReset substitui a página padrão do
  Filament, que vaza a existência de contas via notificação de erro
  distinguível em dois casos — Password::INVALID_USER (e-mail inexistente)
  e Password::RESET_THROTTLED (só ocorre para usuário existente, quando um
  token foi criado há pouco). Ambos os casos agora respondem com a mesma
  notificação de sucesso.

- declare(strict_types=1) adicionado aos 15 arquivos de app/ que ainda não
  tinham: AdminPanelProvider, Controller e 13 páginas de Filament Resources.

- npm audit adicionado ao script `quality` do composer e ao job `static` do
  CI, com --audit-level=high (documentado inline no workflow). O gate é hoje
  vazio na prática — package.json não tem `dependencies` de runtime — mas
  protege contra regressões futuras.

- Gate de cobertura Domain/Application >= 80% no job `unit` do CI. Escopo
  via phpunit.coverage.xml dedicado (o phpunit.xml global continua cobrindo
  todo app/ para os demais usos). O job `unit` ganhou serviço de postgres e
  passou a rodar Unit+Architecture+Feature juntas, porque as classes de
  App\Application\Queries\Marketing só são exercitadas por testes Feature
  hoje — um gate baseado só em Unit ficaria bem abaixo de 80%. Medido
  localmente com pcov: 98,1% (nenhum teste novo foi necessário para atingir
  o limiar).

- Serviço `app` (FrankenPHP) no docker-compose.yml para paridade local com
  staging/produção, dependente de postgres saudável, com DB_HOST/DB_PORT
  sobrescritos para resolver o serviço postgres pelo nome (o padrão
  127.0.0.1 do .env só funciona para processos no host). Validado com um
  smoke isolado (projeto/portas dedicados via overlay `!override`, sem
  tocar o container amare-postgres compartilhado por outra worktree):
  depends_on/healthcheck funcionou, `php artisan migrate` rodou dentro do
  container via DB_HOST=postgres, e /up respondeu 200. README atualizado
  (PHP 8.4 canônico, composer.json mantém ^8.3 por design).

Fora do escopo: deploy hello-world em staging (Dokploy com falha, item de
infra não relacionado a este change) — L2353 e o critério de saída da Fase
0 continuam sem marcar. Também ficam pendentes, por dependerem de uma
execução real de CI/PR (task 3.4, 6.1, 6.2 do OpenSpec): prova de que o CI
falha com quebra intencional de audit/cobertura, e evidência de deploy/
rollback em staging.

Co-Authored-By: Claude noreply@anthropic.com
AI-Assisted: yes
AI-Tool: claude-code

* fix: fecha lockout de admin-criado e documenta pcov/compose no auth de usuários

Correção de acompanhamento ao commit anterior (gates de qualidade da Fase 0),
achada por revisão adicional após o primeiro commit:

- CreateUser::handleRecordCreation agora define email_verified_at ao criar um
  usuário pelo painel. Sem isso, UserForm não expõe esse campo (nem está no
  #[Fillable] de User — de propósito, para nunca virar mass-assignable via
  formulário), então todo usuário criado pelo admin nascia com
  email_verified_at nulo e, com canAccessPanel() agora exigindo e-mail
  verificado, ficava trancado para sempre — sem rota de auto-verificação e
  sem recuperação via reset de senha (o callback do Filament pula o envio
  para quem falha canAccessPanel(), mas ainda reporta sucesso). Usa
  forceFill() (não atribuição direta, que o PHPStan rejeita pelo tipo
  Carbon/string do cast) para contornar o guard de mass assignment só nesse
  ponto. Regressão coberta por teste que cria um usuário via
  Livewire::test(CreateUser::class) — não pela factory, que não passa pelo
  mesmo caminho — e confirma login em seguida; teste falha sem a correção
  (verificado manualmente revertendo e rodando de novo).

- Confirmado que DatabaseSeeder::run() (commit anterior) já funciona sem
  ajuste: User::query()->updateOrCreate() pareceria sofrer o mesmo guard de
  #[Fillable], mas `php artisan db:seed` executa dentro de
  Model::unguarded() (Illuminate\Database\Console\Seeds\SeedCommand), o que
  levanta o guard durante o seed inteiro. Adicionado teste de regressão que
  roda via $this->seed(DatabaseSeeder::class) (o mesmo caminho de
  `composer setup`) para travar esse comportamento — chamar a mesma lógica
  fora do comando db:seed reproduz o guard bloqueando o campo, confirmando
  que a dependência do unguard() é real, não coincidência.

- README: nota de que `docker compose up -d postgres` agora exige o `.env`
  do passo 1, porque o serviço `app` referencia `.env` via `env_file` e o
  Compose valida o arquivo inteiro antes de subir qualquer serviço.

- AGENTS.md: documentado `composer test:coverage` (usado pelo job `unit` do
  CI) e que `docker compose up -d` sem argumento também sobe o serviço `app`
  agora, não só o Postgres.

Nota para quem for escrever o próximo teste de recurso Filament neste repo:
Livewire::test(...)->fillForm([...]) é um no-op silencioso aqui —
app()->runningUnitTests() retorna false no setup de testes deste projeto,
então o guard de Filament\Schemas\Concerns\InteractsWithSchemas::
fillFormDataForTesting() nunca aplica o estado, e o teste falha depois com
erros de validação "required" que parecem um bug no formulário, não no
teste. Use ->set('data.campo', valor) (como os testes de Login já fazem)
até isso ser investigado — fora do escopo deste change.

Co-Authored-By: Claude noreply@anthropic.com
AI-Assisted: yes
AI-Tool: claude-code

* fix(auth): verificar usuários existentes ao adotar MustVerifyEmail

O `canAccessPanel` passou a exigir `hasVerifiedEmail()`, mas nenhuma
migration preenchia `email_verified_at` para contas que já existem. Toda
conta criada antes desta mudança tem o campo nulo e ficaria trancada fora
do /admin no instante do deploy.

E não haveria volta pelo aplicativo: nenhuma rota de verificação
self-service é registrada, e o fluxo de reset de senha deliberadamente
não notifica quem falha no `canAccessPanel` — ainda reportando sucesso.
Recuperar exigiria shell no contêiner.

Verificar essas contas é o padrão correto, não um atalho. Não existe
cadastro público: toda conta existente foi criada por um admin, pelo
painel ou pelo snippet de tinker do runbook de deploy. Esse ato é a
verificação — exatamente o raciocínio que o CreateUser aplica às contas
criadas de agora em diante.

A verificação é datada pelo `created_at` da conta, não pelo momento em
que a migration roda, para não inventar um histórico. O `down()` é um
no-op documentado: anular as colunas recriaria justamente a falha que
esta migration existe para evitar, e a divisão original entre nulos e
não-nulos não está registrada em lugar nenhum.

Co-Authored-By: Claude noreply@anthropic.com
AI-Assisted: yes
AI-Tool: claude-code

---------

Co-authored-by: manoel.neto <manoel.neto@creditas.com>
2026-08-10 10:48:04 -03:00

5.4 KiB
Raw Permalink Blame History

1. Auth and strict types parity

  • 1.1 Add MustVerifyEmail to User and require verified + active in canAccessPanel; update seed so admin/assistant are verified; feature tests for unverified denial and verified access
  • 1.2 Confirm Filament/Laravel password reset is enabled; add feature tests for registered vs unknown email without account enumeration — required a custom App\Filament\Pages\Auth\RequestPasswordReset overriding Filament's stock page, which discloses account existence via a distinguishable danger notification on Password::INVALID_USER
  • 1.3 Add declare(strict_types=1); to project-owned PHP files missing it (e.g. AdminPanelProvider); architecture/unit regression as needed — 15 files total: AdminPanelProvider, Controller, and 13 Filament Resource Pages classes
  • 1.4 Run composer pint, composer phpstan, and composer test:feature for auth changes — all green

2. Local runtime and PHP 8.4 alignment

  • 2.1 Extend docker-compose.yml with FrankenPHP app service (build Dockerfile, depend on healthy postgres, publish 8000); document in README
  • 2.2 Align README/docs to PHP 8.4 canonical (keep Composer ^8.3); verify Dockerfile/CI already on 8.4
  • 2.3 Smoke local compose: docker compose up -dGET /up returns 200 — run as an isolated -p fase0smoke project (separate container names/ports via a !override compose overlay, kept outside the repo) so it didn't collide with the amare-postgres container already running for a concurrent sibling worktree session. depends_on: condition: service_healthy correctly gated app on Postgres's healthcheck, curl localhost:18000/up returned 200, and docker exec ... php artisan migrate --force succeeded — proving DB_HOST: postgres resolves the app container to the postgres service by Compose's service-name DNS, not just that the image boots. Torn down afterwards (down -v + image removal); the shared sibling amare-postgres container was untouched throughout.
  • 2.4 Run composer quality after compose/docs changes — pint/phpstan/test:feature all green locally (browser suite is CI-only, per AGENTS.md)

3. Quality gates: npm audit and coverage

  • 3.1 Add npm audit step to composer quality and CI static (policy: production deps; document any allowlist) — npm audit --omit=dev --audit-level=high, rationale documented inline in ci.yml; currently a vacuous forward guard since package.json has no runtime dependencies
  • 3.2 Enable Domain/Application coverage in CI unit with 80% fail threshold; exclude views/migrations/framework — scoped via a dedicated phpunit.coverage.xml (not the project-wide phpunit.xml), run as Unit,Architecture,Feature because the Application/Queries/Marketing classes are only exercised via Feature/HTTP tests; measured locally with pcov at 98.1%, well above the 80% gate
  • 3.3 Add/adjust unit tests if current Domain/Application coverage is below threshold — no-op: measured coverage (98.1%) already clears 80% with existing Feature-suite coverage of the Marketing queries plus existing PageMeta/HomeContent unit tests
  • 3.4 Verify CI static and unit fail appropriately on intentional audit/coverage breakage in a branch experiment or equivalent proof — blocked: no push/PR in this task's scope, so no real CI run exists to break intentionally; defer to a follow-up once a PR is open

4. Staging/production Compose and Dokploy prep

  • 4.1 Add versioned Compose template (docker-compose.deploy.yml: web, queue, scheduler, migrate one-shot) parameterized by APP_IMAGE/IMAGE_TAG for staging and production stacks
  • 4.2 Document Dokploy project setup: GHCR registry credentials, Postgres per environment, Compose import, required env vars (APP_KEY, DB, Resend, R2), trusted proxies/session cookies
  • 4.3 Document rollback procedure: move environment alias to previous SHA and redeploy without rebuild
  • 4.4 Document PostgreSQL daily backup (≥14d retention), restore procedure, and test restore on staging before first production promotion

5. Deploy workflow and smoke

  • 5.1 Create .github/workflows/deploy-staging.yml gated on successful CI on main: build image, push ghcr.io/...:<sha> + :staging, trigger Dokploy compose.deploy
  • 5.2 Create .github/workflows/promote-production.yml (workflow_dispatch + confirmation): retag same digest as :production, deploy production stack, smoke
  • 5.3 Wire migrate-before-serve (Compose migrate service) and healthcheck on /up
  • 5.4 Add post-deploy smoke script/job for /up, /, /admin/login returning 200
  • 5.5 Store orchestration secrets only in GitHub; Laravel/DB/R2/Resend only in Dokploy; ensure no secrets in image layers

6. Phase 0 exit evidence

  • 6.1 Perform first successful staging deploy of a main SHA and capture evidence (workflow URL, smoke output)
  • 6.2 Verify rollback to previous SHA works once on staging
  • 6.3 Update SPEC.md §18 Fase 0 checkboxes only for items with evidence; note remaining deferred items if any — flipped L2338 (FrankenPHP/Compose) and the auth/npm-audit/coverage bullets to [x]; left the staging hello-world bullet and the phase exit-criterion line unchecked (Dokploy deploy still failing, out of scope here)
  • 6.4 Run full composer quality and confirm all five CI jobs + staging deploy path green
  • 6.5 Report in SPEC §24 format; archive this change only after remaining parity tasks (13) also complete