* 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>
* fix(seed): impedir ContentSeeder de sobrescrever conteúdo em produção
O migrate do Dokploy roda `db:seed --class=ContentSeeder --force` em todo
deploy, tanto em staging quanto em produção (mesmo compose, só o .env
muda). Como ContentSeeder usa updateOrCreate/delete incondicionais, cada
deploy em produção revertia edições feitas pela dona no Filament
(SiteSetting, Services, PortfolioCases), republicava os 3 casos e
serviços fictícios, apagava fotos reais da galeria e reenviava as
fixtures de imagem para o bucket R2 de produção — além de publicar os
depoimentos reais via TestimonialsSeeder, sem autorização explícita.
Adiciona uma guarda de ambiente no início de ContentSeeder::run(),
seguindo o mesmo padrão já usado em AppServiceProvider::
freezeClockWhenConfigured(): em APP_ENV=production o método retorna
sem efeito colateral (exit 0), preservando o passo migrate&&seed&&...
do docker-compose.deploy.yml. TestimonialsSeeder não é tocado — ele
continua sendo o único caminho de publicação de depoimentos em
produção, documentado como passo manual em docs/deployment/dokploy.md.
Staging (APP_ENV=staging) continua recebendo conteúdo de demonstração
normalmente, preservando a revisão visual.
Atualiza docs/deployment/dokploy.md, que afirmava (incorretamente,
desde o commit 10a1f0d) que os workflows de deploy eram "migrate-only"
e que ContentSeeder nunca rodava em staging/produção.
Adiciona teste de regressão cobrindo: banco vazio em produção (nenhum
registro criado, nenhuma fixture enviada ao disco), banco com conteúdo
editado pela dona em produção (seeding duas vezes não altera nada) e
comportamento inalterado fora de produção (semeia o conteúdo de demo
normalmente).
Co-Authored-By: Claude noreply@anthropic.com
AI-Assisted: yes
AI-Tool: claude-code
* fix(seed): trocar guarda de produção do ContentSeeder para allow-list
O guard anterior (`App::environment('production')`) é uma comparação
exata e sensível a maiúsculas contra o valor literal de APP_ENV, que é
digitado manualmente no editor de texto livre do Dokploy sem validação,
enum ou valor padrão garantido por este repositório. Se esse valor
algum dia ficar vazio, vier com case diferente ('Production',
'PRODUCTION'), espaço em branco ou for simplesmente digitado errado, o
guard falha aberto: o ContentSeeder roda seu caminho destrutivo em
produção sem nenhuma outra checagem no caminho de deploy que pegasse o
erro — exatamente o cenário que esta guarda deveria evitar.
Inverte a lógica para uma allow-list dos ambientes conhecidos e
seguros ('local', 'staging', 'testing'), então qualquer valor não
reconhecido de APP_ENV — incluindo 'production', vazio, mal digitado
ou com case diferente — falha fechado (no-op) em vez de falhar aberto
(sobrescrever dados reais). 'testing' entra na lista porque é o valor
de APP_ENV usado pelo job `feature` do CI (ver .github/workflows/ci.yml),
que roda testes que chamam ContentSeeder diretamente — sem esse valor
a suíte quebraria em CI mesmo passando localmente (APP_ENV=local via
.env).
Adiciona cobertura de regressão via data provider cobrindo tanto os
três ambientes permitidos quanto uma lista de valores não permitidos
('production', string vazia, 'Production', 'PRODUCTION', espaço à
direita, valor arbitrário desconhecido), para que a guarda seja testada
como allow-list e não apenas contra o literal 'production'.
Co-Authored-By: Claude noreply@anthropic.com
AI-Assisted: yes
AI-Tool: claude-code
* docs(deploy): corrigir descrição da guarda do ContentSeeder no runbook
O texto descrevia a guarda antiga (deny-list de 'production') e ainda
afirmava, de forma incorreta, que a publicação dos cinco depoimentos
reais era "um passo manual e deliberado em todo ambiente (incluindo
staging)". Isso nunca foi verdade: o ContentSeeder chama o
TestimonialsSeeder internamente e só pula essa chamada quando a guarda
não casa com o ambiente atual — em staging a guarda casa, então os
depoimentos são publicados/republicados automaticamente a cada deploy,
sem nenhum passo manual envolvido (o próprio teste
test_content_seeder_still_seeds_demo_content_outside_production já
provava isso).
Atualiza as duas passagens para descrever a allow-list
('local'/'staging'/'testing') introduzida no commit anterior e para
deixar explícito que staging publica depoimentos automaticamente,
reservando o comando manual apenas para produção. Também documenta o
trade-off do fail-closed: um APP_ENV de staging digitado errado agora
faz o conteúdo de demonstração parar de ser resemeado silenciosamente.
Co-Authored-By: Claude noreply@anthropic.com
AI-Assisted: yes
AI-Tool: claude-code
---------
Co-authored-by: manoel.neto <manoel.neto@creditas.com>
Replace tiny JPEG stubs with per-slot Unsplash editorial placeholders,
align public type scale to DESIGN.md display/headline tokens, add
final-cta on all subpages, about_image_path on site settings, and harden
visual regression with solid-color VisualContentSeeder fixtures.
Canonical upsert for five authorized couples so staging/prod
can load depoimentos without DatabaseSeeder/ContentSeeder.
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat: recreate public frontend with Heritage Editorial identity
Replace placeholder visual system with EB Garamond/olive tokens, brand assets, editorial home narrative, São Paulo settings, real testimonials, and regenerated visual baselines.
Co-authored-by: Cursor <cursoragent@cursor.com>
* docs: archive recreate-public-frontend and sync Heritage Editorial specs
Merge delta requirements into main OpenSpec capabilities and move the completed change into the dated archive.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat: ship public site with SEO and visuals
Publish CMS content on public routes with responsive media,
accessibility checks, and deterministic visual baselines.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: make browser visual CI deterministic for media
Mount host public storage into FrankenPHP so seeded fixtures are served,
and replace PNG-as-JPG fixtures with real JPEGs so Chromium can render them.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: serve public media on same-origin storage paths
Pest Browser hosts on 127.0.0.1:port while Storage::url used
http://localhost, so screenshots captured broken images. Use relative
/storage URLs for media and absolutize only OG tags via url().
Co-authored-by: Cursor <cursoragent@cursor.com>
* test: refresh visual baselines from CI Ubuntu screenshots
Media now loads on same-origin /storage paths, so baselines must
capture the rendered fixtures. Use full-page snapshots from the CI
runner to keep Pest's exact snapshot match stable across environments.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Implementa gestão Filament de configurações, serviços, portfólio e depoimentos com policies admin-only, upload validado e seed determinístico.
Co-authored-by: Cursor <cursoragent@cursor.com>