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>
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 setup do agente de prospecção grava na criação o hash da versão montada, a
partir do objeto parseado, que não tem a chave `inbound_debounce_ms`. Depois ele
confere esse hash contra a linha lida do banco, onde a coluna nova da 0498 vem
`null`. Os dois hashes divergiam, e o setup recusava com "O rascunho foi alterado"
ou "O conteúdo do agente mudou" nas 5 specs de
tests/invariants/prospecting-agent-setup.test.ts (invariants vermelho no #1997).
`trigger_config` já tinha essa exceção. A lista passa a nomear as duas colunas.
Medido: test:db do arquivo, 8/8 com o conserto; sabotado (coluna fora da
lista), 5 failed | 3 passed, as mesmas 5 do CI; restaurado e conferido por grep.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Os três INSERTs de versão da server action (rascunho novo de agente
publicado, revert pelo Histórico e criação pela tela) não levavam
inbound_debounce_ms: só o PATCH de rascunho existente gravava o campo, e a
janela voltava calada para a env. A coluna entra nos três, e
agent-version-columns-drift ganha a cerca que lê cada .insert({...}) de
ai_agent_versions na action (sabotada: tirar a chave de um INSERT deixa o
caso vermelho).
Ajustes de acabamento:
- drain.ts: o comentário dizia "a MESMA resolução que o turno"; a
resolução é a de loadConversationAgentConfig (dono ou agente da sessão),
sem router/campanha. Falha da consulta degrada para a env com log.warn,
como a checagem de elegibilidade, em vez de mandar o evento para retry.
- AgentForm: o campo sai do meio do par "Mensagens anteriores que ele lê"
/ "Tamanho máximo desse histórico"; o valor em ms é arredondado.
- database.types.ts e o comparador do Histórico (VersionDiff) conhecem a
coluna. Chave i18n junto das irmãs; fragmento com o crédito do PR.
Refs: #1997, #1856
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A 0498 levava o carimbo 20260930121224, anterior ao da 0497 já na main
(20260930140000): a ordem número x carimbo quebrava. Passa a
20260930150000, entre a 0497 e a 0499 do #1996; MANIFEST acompanha.
No baseline, a coluna e a CHECK saem do CREATE TABLE do dump e o bloco
sai do meio do arquivo: o mesmo DDL da migration vira o apêndice rotulado
"(migration 0498)" no fim. Não cria função, então não conflita com a
varredura de anon.
Refs: #1997, #1856
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Por cima do trabalho de @webtecnica (#1996), quatro ajustes da triagem:
- baseline.sql e migration 0499: o `drop constraint if exists` fica logo
antes do `add constraint`. Mesmo efeito; entra na janela de 10 linhas que
tests/unit/baseline-reaplicavel.test.ts procura.
- dicionario.ts: "Minimo" e "Maximo" ganham espanhol (i18n-espanhol-cobre-a-tela),
e "aquecido demais parece robo" vira "rapido demais parece robo".
- PUT /api/v1/ai/pacing: minimo acima do maximo efetivo devolve 422 com
mensagem clara. Antes, com os dois gravados, o CHECK do banco recusava e a
tela lia um 500 generico; com so o minimo acima do teto padrao (7500), o
clamp o ignorava sem aviso.
- .changes/atraso-humano-por-conexao.md, com o credito.
Refs: #1996, #653
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Os quatro números que governam o atraso humano ANTES da primeira bolha
(atraso-humano.ts: NOTAR 900ms, POR_CARACTERE 22ms, MINIMO 1200ms, MAXIMO
7500ms) eram literais no módulo, sem superfície. Uma clínica e um e-commerce
querem ritmos diferentes e recebiam sempre o mesmo valor cravado (issue #653).
Os quatro saem do literal e passam a ser 4 colunas NULL em channel_knobs — a
MESMA tabela dos knobs de pacing por conexão (0010/0495); NULL cai no default
conservador em defaults.ts que espelha os valores de antes (regressão zero).
Exposição na ficha Anti-ban via /api/v1/ai/pacing (POST/GET), com CHECK de
sanidade (max >= min, teto intervalMaxMs).
O jitter entre bolhas (literal 1200 + rand*800 em inbound-turn.ts) NÃO ganha
coluna: ele já é throttle_ms + jitter_max_ms (defaults 1200/800) e o call site
passa a LER os knobs da conexão em vez do literal.
DoD: migration 0499 + apêndice idempotente no baseline + linha no MANIFEST;
leitor testado (default, null -> default, knobs da conexão), regressão zero em
calcularAtrasoHumano; teste novo vermelho sem a mudança (sabotagem: 4 falharam).
O debounce de rajada (mensagens do MESMO contato dentro da janela viram uma
resposta só) era só a env do worker INBOUND_DEBOUNCE_MS (default 8000, global,
invisível na tela, exige VPS). Agora vira campo opcional por agente em
'Freios de segurança': vazio = usa a env da instalação (regressão zero),
0 desliga a coalescência, teto de 60s clampa pelo leitor (debounceEfetivo).
O drain lê a config do agente da conversa (loadConversationAgentConfig, mesma
resolução do turno) e usa o debounce do agente no lugar do literal.
DoD: migration 0498 + apêndice idempotente no baseline + linha no MANIFEST;
leitor testado (default/env, campo, teto no limite); gate de consumidor de
knob atendido.
O caso da #1994 sempre montava o agendamento com description "Primeira
consulta", então o ramo "observação vazia -> usa o notes" nunca executava:
a linha literal da #1959 (`a.description?.trim() || a.notes?.trim()`)
passava verde, 36/36. Vira `it.each` com observação visível, null e em
branco; nas duas últimas a description tem de sair undefined.
Corrige também o comentário: acrescentar `notes` aos tipos não quebra a
compilação (medido, tsc exit 0). A cerca é de runtime, porque o
sync-executor passa o snapshot inteiro com `...a`.
Medido: 38/38 verde; com a linha da #1959 aplicada, 2 casos vermelhos.
Refs: #1994, #1959
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Efeito nada_mudou: o operador não faz nada; a atualização só corrige o
que a anonimização apaga.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Invariante novo prova pelo comportamento: a virada de is_anonymized
zera a transcrição da conversa do contato e da mensagem de grupo dele;
o pedido formal chega ao mesmo estado; o vizinho da mesma org fica; a
cura do apêndice (lida do baseline) alcança quem já era anonimizado e
poupa quem voltou.
Os invariantes de catálogo decidem por TABELA: um comando em messages
bastava para ela contar como coberta, e foi assim que a coluna passou
com os gates verdes. A régua por coluna, em todo UPDATE de messages dos
dois corpos instalados, nasce aqui porque aqueles arquivos estão sob o
congelamento de tests/invariants/**.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A 0497 fecha o gatilho e cura o passado, mas nenhum gatilho alcança a
corrida do media-derive-worker: ele lê a mídia antes da anonimização e
grava o texto depois dela. A cascata da aplicação, que a rota e o cron
de retenção compartilham, ganha o passo 9: mensagem do contato com body
'[mensagem anonimizada]' que ainda guarda media_derived_text perde a
transcrição. O marcador do body poupa quem voltou a escrever. O filtro
de não-nulo vai ao banco (.not is null) para a rodada saudável não
trazer todas as mensagens de todo contato anonimizado.
O adaptador pg-como-supabase ganha not(is), com caso em arquivo próprio
(o do adaptador está congelado): sem ele o invariante da triagem 194
estourava, como o adaptador manda.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
messages.media_derived_text guarda a transcrição do áudio e o OCR da
imagem que o media-derive-worker extrai. Nenhum caminho de anonimização
a zerava: o body virava '[mensagem anonimizada]' e o texto do áudio
seguia legível, inclusive para o agente.
Uma linha `media_derived_text = null` em cada UPDATE de messages de
fn_redigir_conversas_ao_anonimizar (corpo da 0494, conversas do contato
e mensagens de grupo dele) e de fn_lgpd_cascade_redact_contact (corpo
da 0483, passo 3). Os corpos foram gerados a partir do atual por script
e o diff contra a origem só tem essas três linhas a mais. Cura
idempotente pelo marcador do body: alcança quem já foi anonimizado e
poupa a mensagem nova de quem voltou a escrever.
--no-verify: o hook local check-migration-triple.sh não termina neste
clone (defeito conhecido, conserto no #1978). O triplo foi conferido
com o hook do #1978 (exit 0; controle positivo com NNNN=0495 recusado).
Achado na triagem do #1988 (@AlecLimaDev).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- baseline.sql: o cabeçalho do apêndice 0496 lista as seções copiadas de
fato (A, B, C0, C, E, F e G), não só A, B, C e E.
- MANIFEST.md: a linha da 0496 descreve C0 (descarte da fila que assenta
rascunho, Meet e turn_discarded), F (claim do follow-up sem org parada,
retomada na reativação) e G (turn_discarded só pelo servidor), e cita o
invariante followup-org-suspensa.
- dispatcher-org-parada.test.ts: o comentário do caso da org operante diz
o que a sabotagem mediu — `!ehOperante` já avermelhava; só "toda org
parada" escapava sem o caso novo.
- .changes/suspensao-que-suspende.md: os follow-ups ficam parados na
suspensão e retomam na reativação; o passo com envio descartado ganha
envio novo, no ritmo normal da fila.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
O comentário do par organizations.suspended_kind × TIPOS_DE_SUSPENSAO
dizia "migration 0495"; a migration é a 0496 (na main, 0495 é outra).
Troca num comentário do bloco criado por este PR; contra a origin/main o arquivo segue só com acréscimos.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
fn_meet_notice pode lançar appointment_notice_busy (40001) dentro de
fn_org_parada_descarta_fila, e as duas rotas respondiam 500 genérico.
Com error.code '40001' da RPC, respondem 409 `retry_later` com "Outra
operação está em andamento para esta empresa. Tente de novo em
instantes." A transação inteira voltou, então nada foi gravado nem
auditado.
A trava continua sem espera no SQL: esperar ali, com a linha da org em
`for update`, arrisca deadlock.
Código novo em lib/api/errors.ts; um caso por rota no teste unitário.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A policy followup_enrollment_events_insert (0490) deixa manager inserir
pela sessão, e a chave `…:descartado` não termina em `:<n>`, então a
guarda do passo interno não a pegava: um manager forjava turn_discarded
pelo PostgREST e o motor enfileirava um 2º turno de envio.
Seção G da 0496 (migration e apêndice idênticos): a definição vigente
da 0488 de fn_followup_generation_write, com o ramo da trilha recusando
também event_type = 'turn_discarded' quando há auth.uid() (42501
followup_step_internal). service_role e as funções de estado seguem
gravando.
Invariante novo em followup-org-suspensa.test.ts: manager pela sessão
recusado com 42501/followup_step_internal (não a recusa genérica da
RLS); service_role grava.
Invariante alterado sem afrouxar invariante da main — checar-invariantes-so-crescem.sh verde.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A e2e suspensao-administrativa rodou sobre a árvore corrigida (1 passed).
central-apos-reativar.png passa a mostrar o aviso só com o fato e a
orientação que manda procurar nas abas Fila e Automático.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Nenhum teste passava uma org ativa pelo drainEventLog: o describe cobria só
a suspensa, a ausente e a leitura que falha, e nenhum invariante chama o
dreno. Inverter a régua (`!ehOperante`) calaria a IA e as automações de
todas as empresas com a suíte verde. Caso novo: org `active` → o handler
'pula' roda, o evento fecha `done` com a chave em consumed_by e sem
org_nao_operante. Sabotagem da régua conferida depois do commit.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
O apêndice dizia "(migration 0495)" — na main a 0495 é outra
(janela_de_resposta_separada) — e todo self-hoster grava esse texto no
catálogo. Era a única divergência entre o apêndice e a migration.
O comentário em tests/invariants/vocabulario-banco-x-typescript.test.ts:429
também diz 0495, mas o arquivo existe na main e a troca remove uma linha:
fica para o dono (a autorização é só acréscimo).
Prova: caso novo em org-suspensa.test.ts lendo col_description.
Invariante alterado sem afrouxar invariante da main — checar-invariantes-so-crescem.sh verde.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Dizia que a suspensão só bloqueava o acesso e que "pode ser revertida".
Desde a 0496 suspender cala a IA, os envios e as automações, e descarta
sem volta as mensagens na fila e as tarefas agendadas — o dono decidia
sobre uma afirmação falsa. O texto novo diz isso, e diz também o que o
conserto do follow-up decidiu: os follow-ups em andamento retomam.
Chave e `es` do dicionário trocados (a velha sai); o catálogo zh-CN
acompanha. Prova: SuspendDialog.test.tsx.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
resolveActiveOrg redireciona para /account-suspended quando a org ativa
não opera. Dentro do route handler isso vira 307; o fetch do
ImpersonateButton segue o redirect, `res.ok` fica true e o admin cai em
"Conta suspensa" sem acompanhamento nenhum aberto. Regressão deste PR.
A rota só precisa saber para onde voltar ao fim do acompanhamento: passa a
usar orgAtivaSemPortao, e o comentário do helper ganha esse uso.
Prova: route.test.ts novo — com a org ativa do admin suspensa, o POST dá
200 e p_previous leva o id dela (controle com a org ativa).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Era um <Link href="/login">: a pessoa continuava logada, e o próximo /app
a devolvia ao hub — que agora é onde todo membro de empresa suspensa cai.
Vira <form action={signOut}> com botão de envio, como a irmã
/acesso-revogado.
Provas: page.test.tsx (o botão 'Sair' é type=submit dentro de um form, e
não há link 'Sair'); no e2e da suspensão a atendente clica em Sair, cai no
/login, e /app a manda de volta ao login.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Numa empresa com IA, a conversa sem dono e sem silêncio é classificada
como `automatico` (comando-da-conversa.ts) e só cai na Fila quando a
empresa não tem atendimento automático — o aviso org_reativada mandava
revisar só a Fila. E o texto se repetia: o corpo gravado pelo banco e a
orientação da tela diziam a mesma instrução.
- fn_reativar_organizacao (migration e apêndice do baseline, idênticos):
o corpo diz só o fato, e a contagem deixa de fora as conversas de grupo.
- Orientação em inbox-destino.ts nomeia as duas abas; chave e `es` do
dicionário trocados (a velha sai); o comentário órfão do agent_case sai.
- Invariante: as duas strings novas e o caso em que o grupo não conta.
Mapa de arquitetura e J37 acompanham.
Invariante alterado sem afrouxar invariante da main — checar-invariantes-so-crescem.sh verde.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>