"Instalar o browser" levou de 27 s a 425 s por parte no run 36757657107
(main e8e291217). Os downloads do Playwright custaram 8-9 s em todas as
seis partes; a variação inteira era o apt-get baixando os mesmos 32-35 MB
de .deb do azure.archive.ubuntu.com a velocidades de 87 kB/s a 14 MB/s.
O apt passa a guardar os .deb numa pasta do usuário, restaurada e salva
por actions/cache (chave: imagem do runner + versão do Playwright). O
--with-deps roda igual; o apt confere o hash de cada arquivo restaurado e
só baixa o que mudou. Só em máquina do GitHub. Sem mudança em partição,
teto ou orçamento.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VcGP2iHbB4Jaw9T1PAbA4c
O ajuste de estado vai para o proprio render (padrao "you might not need
an effect"): o mes sem vaga nem chega a ser pintado antes de o painel
passar ao seguinte, e sai o setState-em-efeito que o lint apontava.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
No ultimo dia util do mes, depois do ultimo horario, "Novo agendamento"
abria no mes de hoje com todo dia apagado e "Nenhum horario livre", e o
proximo horario (amanha) ficava atras da seta. Foi o que reprovou o e2e de
todos os PRs em 30/09 a partir de ~16h BRT (16 casos da agenda).
- Na abertura, quando os horarios do mes de hoje chegam e nenhum dia tem
horario publicado, o painel passa ao mes seguinte. Uma vez so: quem volta
a mao (encaixe hoje) fica onde voltou; sem jornada publicada, nao mexe.
- Os dias so acendem com os horarios DO mes em tela (`mesCarregado`): a
janela de um mes vai ate endOfMonth + 1 dia, e o dia 1o do mes novo
acendia com a sobra do velho no quadro entre trocar e carregar.
Teste com relogio congelado em 30/09 17h, 31/10, 31/12 e 26/02/2027.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
O teste do worker de clima (Jev, #1747) nasceu na main sem o embed
organizations(status) na conversa simulada. Com o portão de elegibilidade do
PR 1, que lê esse status e falha fechado, os 21 casos passaram a ver a empresa
como parada e o Jev não perguntava nada. A simulação ganha o status "active"
que toda linha real tem, e um caso novo prova o outro lado: empresa suspensa,
turno vetado, nenhum pedido perguntado.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A tabela da §4 separava Bearer e MCP numa linha só com 403; pela decisão do
dono de 30/09, a consulta de pedidos de LGPD pelo MCP responde com a empresa
suspensa e as demais ferramentas recusam. E o risco residual das "15 rotas com
307 HTML" deixou de existir: as 20 rotas respondem 403 JSON, com cerca — a
frase vira o comando que conta.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A main recebeu a 0500 (avisos do Jev na Central), que acrescenta
jev_pedido_de_humano e jev_parar_de_receber ao CHECK de agent_inbox_items.kind.
A 0501 reconstrói esse CHECK com a lista inteira e roda DEPOIS da 0500 na
cadeia do CLI: sem os dois tipos, ela apagaria o vocabulário da 0500 (ou
falharia com avisos do Jev já gravados). A lista da 0501 e o bloco único do
baseline levam os três tipos. Nos testes, os dois lados ficam.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
fn_org_operante, fn_suspender_organizacao, fn_reativar_organizacao e
fn_followup_turno_descartado estavam coladas nos blocos escritos à mão, fora
da ordem alfabética e com os argumentos na ordem da assinatura. Passam ao
trecho gerado, na posição alfabética e com os argumentos ordenados, como o
`supabase gen types` escreve: a próxima regeneração não as move de lugar.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
O cabeçalho do bloco no baseline afirma "cópia byte a byte das seções A, B,
C0a, C0, C, E, F e G"; havia três linhas em branco antes da seção B no
baseline (uma na migration) e duas antes da D na migration (uma no
baseline, onde a D não entra). Sem efeito no SQL; agora a frase é exata:
da seção A ao fim, tirando a D, as 441 linhas dos dois lados são iguais.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Três ramos do page.tsx que nenhum caso exercitava: sem e-mail de suporte a
tela manda falar com quem administra o sistema (sem link) e a LGPD segue;
o dono da plataforma com papel de atendente vê o painel de admin, e em modo
suporte não; e sem empresa ativa a página manda para /app.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
O fragmento prometia o contato do suporte a quem administra, sem condição,
e fechava com "nenhuma configuração é necessária"; SUPPORT_EMAIL vem vazio
por padrão, e sem ele a tela manda falar com quem administra o sistema. A
nota também passa a dizer o que os lotes seguintes mudaram: a API responde
403 org_suspended em vez de devolver a página, e o link do e-mail de prazo
de LGPD abre o pedido no hub.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Três coisas que a prova afirmava sem fazer: a volta da admin para a empresa
que opera agora é clicada e chega ao inbox dela (antes só se via o botão); a
recusa a quem só tem leitura confere a resposta 403 forbidden_scope, já que
o toast é o mesmo para qualquer falha; e a foto do hub espera o pedido de
LGPD carregar, em vez de sair com o esqueleto da tabela. No mapa de
jornadas, a atomicidade da suspensão passa a apontar o test:db, que é quem a
prova.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
O cabeçalho de operante.ts tinha perdido a rota global de entrada do canal,
sem token, que continua fora do bloqueio de propósito. O do teste de
campanhas repetia "a mesma decisão da fila do agente", frase que o próprio
rodada.ts já desmente: a fila nunca olhou status; a régua é operante.ts.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
`{ "permiteOrgSuspensa": true }` é a mesma chave e não era contada: numa
rota fora de LGPD/cobrança passaria sem acusação, e numa rota de LGPD seria
lida como porta fechada. As duas direções agora usam o mesmo reconhecedor.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
`export const POST = comX(handle)` e `export { h as POST }` deixavam um
handler que chama requirePlatformAdmin sem a de escrita passar verde: o
primeiro porque a função passada como argumento não era seguida, o segundo
porque ExportDeclaration nunca virava handler exportado. Hoje nenhuma rota
do app é escrita assim; o instrumento agora acusaria a que for.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A rodada descartava o `error` da busca das campanhas em andamento e
respondia "nada_a_fazer" — uma falha do banco era indistinguível de uma
rodada sem trabalho, e nada ficava registrado. Agora o erro vai para o
logger (como promoverAgendadas já fazia) e a rodada devolve
`detalhe: "busca_falhou"`. Trocar a lista de ids de orgs paradas por um
filtro no banco fica para uma issue própria.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A suspensão descarta o turno de envio `pending` e grava `turn_discarded`,
que faz o motor enfileirar um turno novo na reativação. O turno que JÁ
rodava naquele segundo seguia, tinha o envio barrado (OrgNaoOperanteError)
e era cancelado sem o evento: na reativação os rechecks esgotavam o
dead-man e a inscrição morria com `action_turn_never_completed` — um
follow-up morto na Central com motivo falso, e o lead sem a mensagem.
Uma regra só para "o turno saiu sem enviar": `fn_followup_turno_descartado
(org, job)` (seção C0a da 0501, invoker, só service_role). A C0 passa a
chamá-la em vez de repetir o INSERT, e os dois donos de turno em voo a
chamam antes de devolver o job: o agent-worker (createFollowupTurnHandler,
só purpose send_message) e o envio inline (enviarTextoFixoPendente). Erro
que não é suspensão não grava nada — o dead-man segue valendo.
Prova: tests/invariants/followup-org-suspensa.test.ts reproduz a sequência
(turno running → suspende → cancela → reativa) com controle que mostra a
morte sem o evento; tests/unit/turno-rodando-na-suspensao.test.ts e
lib/followup/enviar-texto-fixo.test.ts cobrem as duas chamadas.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
O gatilho que impede a sessão (tela/PostgREST) de mexer no estado da
organização cobria status, suspensão e autoria, mas não `redacted_at`: um
platform admin só-leitura ainda gravava uma data de anonimização falsa. A
coluna entra na comparação da migration 0501 e, byte a byte, no apêndice do
baseline; o único escritor legítimo (lgpd-redact-worker, service_role)
segue passando. Caso novo em org-suspensa.test.ts para os dois scopes de
platform admin, e o controle do service_role passa a gravar a data como o
worker grava.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
O caso de reativação conferia apenas que não sobrou job 'pending' — o que
também passaria se o job tivesse sido apagado ou marcado 'done' por engano.
O job agora nasce com id fixo (JOB_REMANESCENTE, no padrão de JOB_A/JOB_B)
e o teste afirma 'failed|org_nao_operante' por esse id, como o caso irmão
da suspensão já fazia.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
O e-mail enviado com a empresa ativa e clicado depois da suspensão
ainda caía na lista do hub, sem o pedido: o link escolhia a porta
(/app ou o hub) no momento do envio, e a empresa pode mudar de estado
antes do clique.
Agora o link é sempre /lgpd/pedido/<id>, uma porta neutra fora de
/app que entrega o pedido ao hub; o hub já decide com as duas réguas
do layout de /app: empresa parada vê o pedido ali, empresa que opera
é devolvida a /app/lgpd/requests/<id>. A porta não é pública, então
quem abre o e-mail sem sessão passa pelo login com next= e volta ao
pedido (o hub, público, mandava ao login sem next).
Sai o que o conserto anterior precisava para escolher no envio: o
status da org no cron e no alarme, lib/lgpd/caminho-do-pedido.ts e a
entrada dele em NAO_E_FILTRO da cerca de crons, que volta ao que era.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Decisao do dono (30/09): a ferramenta de privacidade do MCP fica liberada
para empresa suspensa, porque LGPD nunca e bloqueada. Antes o token de
empresa parada morria no /api/mcp inteiro com 403.
O /api/mcp agora valida o token com permiteOrgSuspensa e o recebe marcado
(orgSuspensa). O servidor recusa com org_suspended toda ferramenta que nao
declara permiteOrgSuspensa - hoje so crm_list_privacy_requests, que e
leitura. A recusa fica depois do teto (a integracao em laco nao escreve
auditoria sem freio) e dentro do try que audita. O token de API nas rotas
REST (auth-dual, /api/v1/contacts) segue 403: so quem passa a opcao abre
a porta.
A cerca org-suspensa-so-nas-rotas-permitidas passa a admitir a chave nos
dois arquivos do MCP e exige que o /api/mcp e a ferramenta de privacidade
a passem; o fragmento diz o que continua respondendo.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A spec gravava em .superpowers/evidence/, que o .gitignore ignora; a cerca
evidencia-no-caminho-versionado reprovou o verify do #1772. Mesma pasta das
irmãs jev-pedidos (evidence/jev/).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
O link do alarme de SLA apontava para /app/lgpd/requests/<id>; com a
empresa suspensa o layout de /app desviava para o hub sem dizer qual
pedido era, e o DPO caia na lista com o prazo legal correndo.
Agora quem monta o link escolhe a porta (lib/lgpd/caminho-do-pedido.ts):
empresa parada recebe /account-suspended?pedido=<id>. E o hub, quando a
empresa voltou a operar antes do clique, devolve ao pedido em vez de a
/app. As duas pontas usam a mesma funcao, entao o link vale nos dois
estados do clique; status ilegivel cai no hub, que redireciona certo.
A cerca de crons ganha caminho-do-pedido.ts em NAO_E_FILTRO: escolher a
porta de um link nao e filtro, e o alarme segue saindo para a empresa
parada, de proposito.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Quem participa de mais de uma empresa via só "Conta suspensa" e ficava sem
saber qual — a própria tela oferece trocar para as outras. O nome da
empresa ativa aparece logo abaixo do título (é dado, então não passa pelo
dicionário).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
As server actions do painel (marca, e-mail, Google, Meta, destinos internos,
módulos, comportamento, cadastro, pedidos de cadastro e configuração da
instalação) chamavam requirePlatformAdminEscrita sem try/catch: a recusa de
support_readonly ou de MFA pendente subia ao error boundary, e a pessoa não
lia "seu acesso é somente leitura" nem "confirme a verificação em duas
etapas". Nada era gravado — a perda era a explicação.
Agora todas passam por escritaDeAdminOuRecusa(), que devolve
{ok:false, error:'forbidden_scope'|'mfa_required'} e deixa o redirect de
quem não é platform admin subir como antes. As telas leem a frase da mesma
tabela que o servidor usa (lib/auth/recusa-de-escrita-de-admin.ts, sem
next/*), com espanhol. A cerca admin-escrita-exige-scope-full ganha a regra
D: arquivo "use server" não chama o helper que lança.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
As rotas respondem 200 {changed:false, motivo} quando a empresa já estava
no estado pedido — outro admin agiu antes e a tela estava velha. Os hooks
ignoravam a resposta e mostravam "Tenant suspenso/reativado com sucesso";
na suspensão por cobrança o admin leria "reativado" com a empresa parada.
Agora changed:false vira toast.info "Nada mudou" com o motivo traduzido
(pt/es), e motivo desconhecido não inventa causa.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
O pré-check do 409 suspensao_de_cobranca perguntava "a empresa está
parada?" em vez de "está suspensa?". Uma empresa redigida pela LGPD é
parada e guarda o tipo residual 'cobranca' (o redact não limpa a coluna),
e o admin receberia a instrução errada de negociar pagamento. Agora a
régua é status = 'suspended'; a função responde nao_suspensa como sempre.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Quem estava com o CRM aberto na hora da suspensão ficava com listas vazias
e erros até recarregar: 20 rotas de app/api resolviam a org com
resolveActiveOrg, que faz redirect("/account-suspended"); o fetch seguia o
307 e entregava o HTML da página à tela como se fosse o dado.
- orgAtivaDaApi (lib/auth/require-role.ts): a org ativa para Route Handler
que não passa por requireRole; org não operante vira o MESMO 403
org_suspended em JSON de requireRole (mensagem única, traduzida).
- As 20 rotas passam a usá-la; páginas e server actions seguem com o
redirect de resolveActiveOrg.
- lib/api/client.ts: 403 org_suspended leva a janela a /account-suspended
(sem navegar de novo quando já está lá).
- Cerca por AST (tests/unit/api-nao-redireciona-org-suspensa.test.ts):
resolveActiveOrg não volta a app/api, e orgAtivaSemPortao só onde ver a
org parada é o objetivo (allowlist que só encolhe: impersonate).
- Teste de comportamento por 4 rotas reais e casos de orgAtivaDaApi.
Acabamentos do PR 1, itens 6 e 23.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A main recebeu 0497–0499 (carimbos até 20260930160000) e dois PRs abertos
usam 0500@20260930170000. A 0496@20260930130000 ficaria abaixo de migrations
já aplicadas pela cadeia do Supabase CLI. Passa a 20260930180000_0501; o
apêndice do baseline, o COMMENT da coluna, o MANIFEST e a prosa acompanham.
Os UUIDs de fixture c0de0496 são identificadores e ficam.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
O test:db reaplica sobre banco vazio, onde openai/gpt-4o-mini só existe
sob requesty, e por isso o defeito do backfill 0068 passou verde da
v1.63 à v1.67. Agora o seed planta a mesma linha sob openrouter, uma
guarda de vacuidade confere 2 modelos e 0 preços antes do update, e
depois da reaplicação com ON_ERROR_STOP=1 exige UMA linha de preço com
o menor preço de entrada (1/14).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
O order by do distinct on desempatava só pelo preço de entrada: dois
provedores com a mesma entrada e saída diferente escolhiam uma linha
arbitrária. Agora desempata por saída e depois por provedor.
Acrescenta a quebra de linha final do fragmento.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>