Banda de modalidades passa a abrir wa.me com mensagem distinta (fallback /briefing). Glossário canônico vive em SPEC.md §8.0.
Co-authored-by: Cursor <cursoragent@cursor.com>
A cidade de atuação é São Paulo, garantida por teste em quatro lugares e
exigida por openspec/specs/site-settings/spec.md. O identificador de
timezone, porém, era America/Fortaleza. O identificador passa a
acompanhar o negócio.
A mudança não altera comportamento. America/Sao_Paulo e
America/Fortaleza são UTC-3 o ano inteiro desde que o horário de verão
brasileiro foi extinto — verificado para janeiro, março e dezembro de
2026, idênticos ao segundo. Nada renderizado muda, o relógio congelado
dos testes visuais usa offset absoluto (-03:00) e os baselines seguem
válidos.
O motivo de mexer é outro: a divergência entre o timezone e a cidade
custou tempo real. Uma sessão anterior a interpretou como drift e
"corrigiu" a SPEC no sentido errado, mudando o documento normativo para
Fortaleza em vez de olhar o que o negócio é. Com os dois valores dizendo
São Paulo, não há mais o que interpretar.
Escopo: config/app.php, .env.example, os dois pontos do ci.yml,
SPEC.md (§0, §13.5, §15.4), README.md, docs/deployment/dokploy.md,
CLAUDE.md, openspec/config.yaml, openspec/specs/visual-regression/spec.md
e o withTimezone do VisualRegressionTest.
Intocados de propósito: as asserções que garantem que Fortaleza não
aparece como cidade de operação, em PublicPagesTest, SiteSettingsTest,
ContentSeederProductionGatingTest e openspec/specs/site-settings. Essas
tratam de cidade, não de fuso, e continuam corretas.
Co-Authored-By: Claude noreply@anthropic.com
AI-Assisted: yes
AI-Tool: claude-code
Co-authored-by: manoel.neto <manoel.neto@creditas.com>
A SPEC §4.1 declarava CRM de leads, conversão de lead em evento, eventos,
tarefas, fornecedores, orçamento, pagamentos manuais, documentos,
dashboard e auditoria como parte do MVP. O projeto do lançamento aprovado
com a dona do produto é o site institucional — Fases 0 e 1 — e o
repositório reflete isso: não existe model, migration ou tela para
nenhuma dessas capacidades.
Nada disso estava registrado em lugar nenhum, então a §1 e a §25 seguiam
instruindo qualquer agente que lesse o documento a implementar as Fases 2
a 5. A divergência era silenciosa e a §0.2 proíbe justamente isso.
O que muda:
- §4 ganha nota de recorte apontando para a ADR-016, e a §4.1 separa o
que entra no lançamento do que fica adiado. A distinção entre adiado e
fora do MVP fica explícita: os itens adiados seguem sendo o produto
pretendido, enquanto a §4.2 lista o que não deve ser implementado nunca.
- §18 ganha nota equivalente e as Fases 2 a 5 passam a ser marcadas como
ADIADA no próprio título.
- §21 registra a ADR-016.
- §23 é dividida em dois recortes. Sem isso o documento passaria a dizer
que o MVP nunca pode ser concluído: das dezoito condições originais,
nove pertencem às fases adiadas. A §23.1 fecha por conta própria e a
§23.2 não bloqueia o lançamento.
Uma condição foi corrigida no caminho, não só movida: a original dizia
"briefing cria leads de forma segura". Criar Lead é Fase 2. O formulário
atual envia e-mail e não persiste nada, então a condição do lançamento
passa a ser enviar o pedido com proteção contra abuso e aceite de
privacidade registrado — o que o código realmente faz.
Marca também o hello-world em staging na §18, agora que existe evidência:
o deploy passou em 7e68c0e depois da correção do PR #38, com smoke verde.
Com isso o critério de saída da Fase 0 está cumprido.
O openspec/config.yaml recebe a linha de stack corrigida que ficou de
fora do PR #35. A afirmação sobre a cidade de atuação permanece: São
Paulo está correto e é garantido por teste — a suspeita de que também
estivesse desatualizada não se confirmou.
Co-Authored-By: Claude noreply@anthropic.com
AI-Assisted: yes
AI-Tool: claude-code
Co-authored-by: manoel.neto <manoel.neto@creditas.com>
* 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>
Conclusão da auditoria pedida na MAN-117.
A pergunta da issue era se "Vue + Livewire" faz sentido na arquitetura
atual. Vue não aparece em lugar nenhum: não está no package.json, não há
nenhum arquivo .vue, e a própria SPEC §11.3 já proibia framework SPA.
Metade da premissa da issue não tem base no repositório.
Livewire existe, mas não onde a SPEC dizia. O pacote entra apenas como
dependência transitiva de filament/support e opera somente dentro do
painel /admin. O site público é Blade renderizado no servidor mais JS
vanilla progressivo: o layouts.public carrega apenas
@vite(['resources/css/app.css', 'resources/js/app.js']), sem
@livewireScripts, e não há uma única diretiva wire: ou x-data em
resources/views. Alpine só existe empacotado dentro do runtime do
Livewire, duas indireções abaixo do Filament — nunca chega a uma página
pública.
Mesmo assim, cinco lugares afirmavam o contrário: §0 metadados, §5.3,
§9.1, §11 inteira e a ADR-002, que está marcada como Aceita descrevendo
uma arquitetura que a área que ela nomeia não implementa.
A correção adota o que existe em vez de construir o que estava
declarado. O motivo é o próprio documento: a §11.2 já registrava
Blade + Controller como resultado aceito, os requisitos que a §11.2 pede
do formulário (loading state, botão desabilitado, aria-busy) já são
atendidos por resources/js/app.js, e nada nas Fases 2 a 5 pede
reatividade no site público. Adotar Livewire agora colocaria um bundle
global de JS e CSS em páginas cujo resultado visual está fixado por
baselines de snapshot, a serviço de um requisito que ninguém escreveu.
Como a §0.2 proíbe alterar silenciosamente uma decisão do documento, a
correção não reescreve a ADR-002 fingindo que ela sempre disse outra
coisa: o texto dela é emendado apontando para uma ADR-015 nova, que
registra a decisão explicitamente. E a §22 ganha uma linha nomeando a
condição objetiva que justificaria reabrir o assunto — a mesma que a
§11.1 já previa em prosa, "se houver necessidade de consulta dinâmica".
Fica de fora deste commit, para evitar conflito com trabalho em curso no
mesmo arquivo: a linha equivalente em openspec/config.yaml, que também
carrega uma afirmação desatualizada sobre a cidade de atuação e será
corrigida de uma vez só.
Co-Authored-By: Claude noreply@anthropic.com
AI-Assisted: yes
AI-Tool: claude-code
Co-authored-by: manoel.neto <manoel.neto@creditas.com>
* docs: add CLAUDE.md with repository architecture guidance
Imports AGENTS.md as the primary contract and records the cross-file facts
that are not discoverable from any single file: the public-site request spine
(controller -> Application query -> DTO -> Blade), the layouts.public view
composer, what the architecture test actually enforces, the CI-parity
constraint on visual baselines, and the determinism trio behind them.
Co-Authored-By: Claude <noreply@anthropic.com>
AI-Assisted: yes
AI-Tool: claude-code
AI-Harness: creditas-agentic-harness
AI-Harness-Command: /init
* docs: corrigir timezone normativo para America/Fortaleza
SPEC.md e openspec/config.yaml declaravam APP_TIMEZONE=America/Sao_Paulo,
divergindo da implementação (config/app.php, .env.example), dos quatro jobs
de CI e de openspec/specs/visual-regression/spec.md, que fixa
America/Fortaleza e cita SPEC §13.5 como autoridade. As baselines visuais
foram geradas sob America/Fortaleza.
Cidade de atuação permanece São Paulo (capital) — é copy pública, não
timezone de runtime.
Co-Authored-By: Claude <noreply@anthropic.com>
AI-Assisted: yes
AI-Tool: claude-code
AI-Harness: creditas-agentic-harness
AI-Harness-Command: /init
* docs: corrigir referências mortas no AGENTS.md
- Design Context apontava para `.impeccable.md` na raiz, que não existe. O
sistema de design vive em DESIGN.md/PRODUCT.md, tokens em
resources/css/tokens.css, briefs por superfície em .impeccable/surfaces/ e
a skill vendorizada em .github/skills/impeccable/SKILL.md.
- Domain docs afirmava CONTEXT.md na raiz. Não existe: o domínio está em
SPEC.md §8 e PRODUCT.md, capacidades em openspec/specs/, e
docs/adr/README.md é apenas índice das ADRs decididas em SPEC.md §21.
- Issue tracker: registra que o repo não traz .mcp.json, portanto o MCP do
Linear precisa estar habilitado na sessão.
Co-Authored-By: Claude <noreply@anthropic.com>
AI-Assisted: yes
AI-Tool: claude-code
AI-Harness: creditas-agentic-harness
AI-Harness-Command: /init
* docs: registrar autenticação do remote no CLAUDE.md
O repo é do account `manoel-freitas`, mas a identidade SSH padrão da máquina é
outro account (`manoel-freitas-neto`) que não enxerga o repo — o push falha com
`Repository not found`, que parece repo inexistente e é problema de acesso. A
chave certa é `~/.ssh/id_github_pessoal`, já fixada em `core.sshCommand`.
Registra também que o `gh` autentica por token, não por chave SSH, então
`gh pr create` continua falhando mesmo com o push funcionando.
Co-Authored-By: Claude <noreply@anthropic.com>
AI-Assisted: yes
AI-Tool: claude-code
AI-Harness: creditas-agentic-harness
AI-Harness-Command: /init
---------
Co-authored-by: manoel.neto <manoel.neto@creditas.com>
Co-authored-by: Claude <noreply@anthropic.com>
Archive completed public-site and production-provider changes into main specs, remove duplicate active changes, and add complete-foundation-parity so Phase 0 staging and remaining foundation gaps block Phase 2 cleanly.
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>