Repository navigation
release(financial): o pré-voo do Pix ganha governança, exclusividade e a conta que fecha (#948) - #1005
Merged
Conversation
…ra de recusas do emissor (#948 CA3) Havia duas redes por campo — `pix-key-single-source` (#948) e `inscription-single-source` (#863) — e cada uma nasceu DEPOIS do defeito que impede. Cobrem o que já se descobriu. A CA3 pede a outra coisa: varrer `CnabTranslateError` inteira e cobrar, de cada variante, resposta à pergunta "o pré-voo barra isto antes do `allocateNsa`?". O mecanismo é o `Record<CnabTranslateError, Coverage>`, não os `assert`: recusa nova na união não COMPILA até ser classificada. Três classificações, e a distinção entre elas é a ação de quem recebe: · `preflight` — o pré-voo bloqueia, e a sonda EXECUTA o pré-voo para provar. Classificar como coberto e não estar é justamente o erro que um teste de tipos deixaria passar. · `file-scope` — a recusa é do ARQUIVO ou do cedente (convênio, nome do arquivo, veredito do inspetor). Uma régua por título não tem onde vê-la. · `gap` — o emissor recusa e o pré-voo aprova. Dívida declarada, com issue obrigatória: há caso cobrando URL rastreável, para `gap` não virar o balde onde a próxima variante cai só para o teste continuar verde. Das 10 variantes: 4 com sonda, 5 `file-scope`, 1 `gap`.⚠️ A rede achou uma lacuna VIVA na primeira execução, e não na frente Pix — no boleto. Favorecido com inscrição válida e NOME em branco sai `ready` no pré-voo e `billet-party-unidentified` no emissor, com o NSA já consumido; `'€€€'` cai junto, porque `alpha()` o transforma em brancos (#862) e a guarda mede o que VAI PARA O ARQUIVO. A causa é estrutural: `PayeePaymentTarget` não tem campo de nome, então o pré-voo não vê o dado que o emissor guarda. Registrada como #985 e classificada `gap` — não corrigida aqui, porque o formato do boleto está validado em produção e o conserto atravessa tipo compartilhado. Nada de produção muda: o arquivo é só teste. Gate: typecheck, lint e format:check verdes; 11698 testes, 0 falhas. Assisted-by: Claude-Code:claude-opus-5 Claude-Session: https://claude.ai/code/session_01YZHvfYtCefkh7AUNwxnYaP
…lidou por escrito (#948) O bloco do arquivo de Pix ancorava no golden de 01/09, fornecido pela P.O. A régua agora é o `TESTE_PIX_BLOQUEADO_CNAB240_REV2.REM`, submetido em 02/09 e VALIDADO POR ESCRITO em 05/09 pela equipe Multipag Pix/VAN — "o arquivo foi validado e não necessita de ajustes em sua estrutura, estando apto para transmissão". É a diferença entre um exemplo e um veredito, e é o mais perto de homologação que esta rota chega: não há ambiente de teste para remessa de pagamento (ADR-0061).⚠️ O COMENTÁRIO CONTRADIZIA A ASSERÇÃO 60 LINHAS ABAIXO. Duas linhas afirmavam que o bloco bancário do favorecido sai PREENCHIDO no Segmento A, apoiadas no golden de 01/09; o caso `o Segmento A [...] ZERA o bloco bancário` mede o oposto, e é ele que está certo. A prosa sobreviveu à mudança de tipo da #945 que a tornou falsa — a mesma classe de divergência que o #983 corrigiu no emissor, e que o CLAUDE.md chama de defeito a registrar, não de texto a escolher. Quem arbitrou foi o banco, no sexto ponto do laudo: "é possível enviar os campos referentes ao banco, agência e conta do favorecido preenchidos com zeros". Conferido campo a campo contra o REV2: 14 campos, ZERO divergências. Cobre os seis pontos que o laudo confirmou (modalidade 45, câmara 009, G100 = 04, ISPB zerado, movimento 0 + instrução 09, bloco bancário zerado) e três que só o arquivo revela e que nenhum teste de unidade pegaria, porque medem o que este código programou: · `G100` sai `04` + BRANCO, alfa à esquerda num campo de 3 — não `004` numérico; · a literal `PIX` ocupa 172-174 do header de arquivo, contra o que o Validador Universal pede; · `043` (DV agência/conta) sai em BRANCO (#754). Só comentário muda; nenhum caso foi acrescentado ou alterado. Gate: typecheck, lint e format:check verdes; a suíte do arquivo passa 57/57. Assisted-by: Claude-Code:claude-opus-5 Claude-Session: https://claude.ai/code/session_01YZHvfYtCefkh7AUNwxnYaP
…recusada antes do NSA (#948 CA4) Decisão da P.O. de 03/09/2026. A barreira de tela já existe no web-app; faltava a régua do servidor, e uma regra que só vive no cliente não é regra — a rota é alcançável sem passar por tela nenhuma. A recusa nasce em `planRemittanceFiles`, que roda ANTES do `allocateNsa`. É a posição, e não a mensagem, que torna a recusa barata: recusar depois deixaria um número da série queimado sem arquivo do outro lado, e o NSA não volta por desenho.⚠️ A REGRA É ESPECÍFICA DO PIX, e isso é o ponto. TED, transferência entre contas Bradesco e boleto já foram testados, habilitados e validados EM PRODUÇÃO pelo cliente — arquivo transmitido, aceito e pago. Para eles repartir continua sendo o comportamento certo, e nada aqui os alcança: a régua exige DOIS grupos de arquivo, e só o Pix cria o segundo. Há caso fixando isso (`não toca a seleção sem Pix`), porque "não alterou" se demonstra, não se presume. O erro sobe NOMEADO pelas quatro camadas em vez de virar `translation-failed`, e o contraste com `remittance-mixed-file-modalities` é a razão: os dois falam de mistura e param em lugares opostos. Aquele é a defesa do MONTADOR contra quem não repartiu — chegar lá é defeito de código, e o operador não tem o que fazer. Este é régua de SELEÇÃO — o operador refaz e segue. Um nome só para os dois mandaria metade das pessoas procurar um defeito que não existe. A mensagem ao operador nomeia a regra em vez de descrever a falha, e diz que nada foi consumido: "O Pix sai em remessa exclusiva: gere os títulos de Pix numa remessa e os demais em outra. Nada foi gerado e nenhum número de arquivo foi consumido." Dois efeitos que valem registro: · `planRemittanceFiles` NÃO TINHA teste direto — era exercida só através do use case. Passa a ter quatro, incluindo a guarda da faixa madura. · O `switch` da partição perdeu o `default`. Com ele, a variante seguinte da união entraria calada no desfecho genérico; enumerado, o `switch-exhaustiveness-check` obriga quem acrescentar uma recusa a escolher o nome que o operador vai ler. Foi o ESLint que cobrou, e está certo. E a rede de governança do CA3 fez o trabalho dela: acrescentar a variante à união quebrou a COMPILAÇÃO do `Record<CnabTranslateError, Coverage>` antes de qualquer caso rodar, exigindo a classificação. Ela entrou como `file-scope`, com a razão que a distingue das outras desse balde — não é recusa do arquivo nem do cedente, é da SELEÇÃO, e uma régua por título não enxerga os vizinhos. Gate: typecheck, lint e format:check verdes; 11702 testes, 0 falhas. Assisted-by: Claude-Code:claude-opus-5 Claude-Session: https://claude.ai/code/session_01YZHvfYtCefkh7AUNwxnYaP
…onta da tela fecha (#948 CA5) `planBatches` já produzia `unplannedCount` e `unplannedTotalCents`; o pré-voo os DESCARTAVA, propagando só `batches`. A tela recebia os lotes e a seleção, via que não batiam, e não tinha como dizer quanto ficou de fora — levantava a dúvida sem oferecer a resposta.⚠️ E PROPAGAR O CAMPO DO PLANEJADOR DIRETO ESTAVA ERRADO, que é o achado desta fatia. O nome é o mesmo e o comentário dele prometia `lotes + não planejado = seleção`, mas a seleção que ELE vê já passou por `plannableOf`, que retém só título `ready` com rota. O número dele conta o resíduo — pronto que ainda assim não agrupou —, não o que o operador marcou e não vai sair. O primeiro teste da aritmética falhou com `2 !== 4` numa seleção de quatro títulos com dois impedidos, e foi ele que mostrou a diferença. "Não planejado" tem DUAS origens, e a conta só fecha somando as duas: o que não chegou ao planejador (a diferença entre a seleção e `plannable`) mais o que ele recusou. As parcelas vêm de fontes independentes de propósito — se as duas etapas divergirem, a soma para de bater e o teste acusa, que é o único motivo de este número existir. O comentário do port foi corrigido junto: ele dizia "da seleção" onde só podia dizer "dos recebidos aqui", e é a leitura que me levou ao erro. O planejador não tem como fechar a conta da seleção sozinho — ele não sabe o que não lhe foi dado.⚠️ Os dois campos NÃO substituem `blockedCount` e companhia, e confundi-los produz tela errada: os contadores classificam o TÍTULO pela ação do operador (completar cadastro, retirar da seleção, aprovar); estes medem o LOTE. O mesmo título conta nos dois, e isso não é dupla contagem — são respostas a "o que preciso fazer?" e "a soma dos lotes explica minha seleção?". Publicados no schema, então entram no contrato OpenAPI. `0` é resposta e vai sempre: campo que só aparece às vezes obriga a tela a tratar dois formatos, e é aí que ela decide errado — há caso fixando isso. Gate: typecheck, lint e format:check verdes; 11704 testes, 0 falhas. Assisted-by: Claude-Code:claude-opus-5 Claude-Session: https://claude.ai/code/session_01YZHvfYtCefkh7AUNwxnYaP
… teste só (#948 CA6) Estava preso a #923/#945 — "aguardando o banco". O laudo da equipe Multipag Pix/VAN de 05/09/2026 destravou: "o arquivo foi validado e não necessita de ajustes em sua estrutura, estando apto para transmissão". As duas metades já eram cobertas separadamente, e é por isso que este arquivo existe: um pré-voo que aprova e uma geração que produz arquivo não provam que o operador consegue ir de um ao outro. A #837 nasceu dessa lacuna — as duas pontas concordavam em teste e divergiam em uso. O caminho medido, em três etapas no mesmo caso: 1. `previewRemittance` aprova o título (`readyCount = 1`, rota `pix`), a conta do CA5 fecha (`unplannedCount = 0`), e o lote que a TELA mostra é a forma `45` — a mesma que vai sair. 2. `generateRemittance` emite UM arquivo, que é o CA4 visto do outro lado: seleção só de Pix produz exatamente um. 3. O objeto chega a `saida/<fileName>` e é lido de volta do armazenamento. É aqui que o teste deixa de medir valor de retorno e passa a medir o que a VAN vai buscar — arquivo correto que não chega ao armazenamento não paga ninguém. A estrutura é conferida contra o `TESTE_PIX_BLOQUEADO_CNAB240_REV2.REM`: 6 registros, SEM Segmento J, literal `PIX` em 172-174, forma `45`, layout `045`, movimento `0` + instrução `09`, câmara `009`, bloco bancário do favorecido zerado em 021-042, `043` em branco, `G100` = `04 ` (alfa à ESQUERDA, não `004`) e ISPB zerado. O inspetor de forma aprova o objeto depositado.⚠️ O QUE ELE NÃO PROVA, escrito no próprio arquivo para ninguém confiar de mais: os dois use cases leem por PORTS DIFERENTES, então as duas fixtures descrevem o mesmo título sem serem o mesmo dado. Que o cadastro real alimente as duas coerentemente é dos adapters Drizzle, e é medido na integração. O que se prova aqui é o CAMINHO. O planejador e o tradutor são os adapters REAIS, não fakes: um fake reimplementaria a régua de agrupamento e de layout dentro do teste, e duas réguas divergem — a mesma razão pela qual o front não pode replicar a partição. Verificado que o teste MORDE: trocando a asserção da forma para `46`, ele falha com `'45' !== '46'`. Caso novo que passa de primeira merece a prova. Gate: typecheck, lint e format:check verdes; 11705 testes, 0 falhas. Assisted-by: Claude-Code:claude-opus-5 Claude-Session: https://claude.ai/code/session_01YZHvfYtCefkh7AUNwxnYaP
…ade, não dívida (#985) A divergência continua REAL e medida — boleto com inscrição válida e nome em branco sai `ready` no pré-voo e `billet-party-unidentified` no emissor. O que estava errado era a classificação: eu não perguntei se o estado é ALCANÇÁVEL antes de abrir a issue, que é o passo 0 da skill. Não é. `name` é `NOT NULL` em todas as tabelas de parceiro, `PayeeContractor.name` é `string` não-anulável, e a criação valida `.min(1)` na borda. Sobra o nome composto só de caracteres sem transliteração legível — emoji, ideograma —, que não sai de cadastro brasileiro. É a mesma categoria das condições do reader sobre `dueDate` e `valueCents`.⚠️ E NÃO FOI A VALIDAÇÃO EM PRODUÇÃO QUE FECHOU ISSO, embora a rota de boleto esteja validada no Validador Universal e em produção pelo cliente. Aquele laudo prova que o FORMATO está certo para os dados que passaram; ele é cego para pré-voo aprovando o que o emissor recusa, porque isso não é defeito de forma e nunca chega a virar arquivo. Quem fecha é o `NOT NULL`. Ficou escrito no teste porque o argumento errado, se ficar de pé, dispensa o próximo achado desta classe — e o próximo pode ser alcançável. A rede ganhou a classificação `schema` e a cobrança que a torna honesta: quem alega inalcançável CITA a coluna ou a validação que sustenta a alegação, e a próxima pessoa consegue conferir se ainda vale. Sem isso, `schema` viraria o balde que o `gap` deixou de ser. O caso de `gap` fica de pé passando a VAZIO, e é o estado desejado, não teste esquecido — está dito no comentário para ninguém o remover por parecer inútil. Gate: typecheck, lint e format:check verdes; 11708 testes, 0 falhas. Assisted-by: Claude-Code:claude-opus-5 Claude-Session: https://claude.ai/code/session_01YZHvfYtCefkh7AUNwxnYaP
…#948) O lote de Pix saía no pré-voo como `45` cru, sem nome legível — apareceu na primeira validação em tela da rota. O mapa de rótulos da borda cobria `01`, `30`, `31` e `41`, e ninguém contou a quinta.⚠️ O ARQUIVO SEMPRE ESTEVE CERTO, e a distinção é a lição desta fatia. A forma `45` sai correta nas posições 012-013 do header de lote — é o que passou no Validador Universal e ganhou o laudo escrito do Bradesco em 05/09/2026, conferido campo a campo contra o `TESTE_PIX_BLOQUEADO_CNAB240_REV2.REM`. O que faltava era a tradução na BORDA, que validação de arquivo nenhuma alcança: o banco confere o que recebe, não o que o operador lê. E a lacuna ficou invisível por uma segunda razão, que vale registrar junto: o rótulo só apareceria numa tela de pré-voo com título Pix APROVADO, e não há nenhum na base local — só `Transmitted` e `Paid`. A ausência de DADO escondeu a ausência de RÓTULO, e as duas foram descobertas no mesmo minuto, pela mesma tentativa de validar em tela. O nome é o do manual (`batch-profile.ts:84`, G029 p. 100): "Pix Transferência". Não é invenção da borda — é o que o operador encontra se procurar a modalidade na documentação do banco. Para a próxima forma não repetir isto, `batch-profile.ts` passa a exportar `LAUNCH_FORMS` com as cinco que o emissor pode escrever, e o teste novo percorre a lista exigindo rótulo de cada uma. Acrescentar forma nova ali passa a ser parte de emiti-la, não um passo separado que se esquece. O fallback NÃO foi substituído, e há caso fixando isso: forma desconhecida continua caindo no próprio código, porque um traço esconderia que o emissor ganhou uma rota que a borda não acompanhou. Foi justamente o fallback que tornou este defeito visível em vez de silencioso — o que se cobra agora é que nenhuma forma CONHECIDA dependa dele. Gate: typecheck, lint e format:check verdes; 11711 testes, 0 falhas. Assisted-by: Claude-Code:claude-opus-5 Claude-Session: https://claude.ai/code/session_01YZHvfYtCefkh7AUNwxnYaP
…sta leva (#948) Esta branch é a #948 separada da #983, e a #863 fica de fora — decisão da P.O. A rede de governança enumera `CnabTranslateError` inteira, então tirar a variante da #863 da união obriga a tirar a classificação dela daqui: o `Record` não compila com chave que o tipo não tem, que é exatamente o mecanismo funcionando. O que saiu, e por que nada se perde: · a entrada `cnab-inscription-alphanumeric-unsupported` e sua sonda — a variante não existe mais na união desta branch; · a citação a `inscription-single-source` no cabeçalho, que é arquivo da #863 e não existe aqui. Citar teste inexistente é a classe de divergência que o CLAUDE.md manda registrar, não herdar; · o piso de sondas cai de 4 para 3, que é quantas restaram.⚠️ O PISO NÃO É COSMÉTICO, e vale dizer por que ele desce em vez de sumir. Ele existe para impedir o esvaziamento silencioso — alguém trocar `preflight` por `file-scope` em massa e o teste seguir verde sem sonda nenhuma. Deixá-lo em 4 com três sondas o transformaria em vermelho permanente; removê-lo tiraria a guarda. Desce para o número real. Quando a #863 voltar, a variante volta à união e o `Record` para de compilar até alguém reclassificá-la — a rede cobra sozinha, sem precisar que ninguém se lembre disto aqui. Gate: typecheck + format:check + lint verdes; 11799 testes, 0 falhas (baseline 11785). Refs: #948, #863 Assisted-by: claude-code:claude-opus-5 Claude-Session: https://claude.ai/code/session_01XNaURHGErqVMsDMAMDThyg
…sustenta (#948) Achado do code review desta branch. Ao inserir `sumOf`, eu o coloquei ENTRE o docblock de `plannableOf` e a própria `plannableOf` — então o parágrafo que descreve "`null` para tudo que não está `ready`" passou a documentar um somador que não faz nada disso, e a função cujo contrato ele descreve ficou sem comentário algum. Não é preciosismo num repositório onde o comentário É o contrato: o que torna a aritmética do CA5 correta é exatamente `plannableOf` reter só "pronto com rota". Quem afrouxar esse predicado muda a conta da tela sem tocar numa linha de `sumOf` — e, com o docblock no lugar errado, sem ler nada que o avisasse. O comentário volta para junto da função, e ganha a frase que faltava: a ligação explícita entre o predicado e o `unplannedCount`. Gate: typecheck + format:check + lint verdes; 11799 testes, 0 falhas. Refs: #948 Assisted-by: claude-code:claude-opus-5 Claude-Session: https://claude.ai/code/session_01XNaURHGErqVMsDMAMDThyg
…nca-exclusividade-948 fix(financial): o pré-voo do Pix ganha governança, exclusividade de arquivo e a conta que fecha (#948 CA3–CA6)
This was referenced Sep 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Segunda leva do dia. Leva o pré-voo do Pix — CA3, CA4, CA5 e CA6 da #948 — pela PR #1004. Tudo em
fin_*.O que entra
allocateNsa, sem queimar sequênciaunplannedCounteunplannedTotalCentssobem na resposta do pré-voo — a conta da tela fechaMais o rótulo da forma
45na tela de confirmação — o arquivo sempre esteve certo, faltava a tradução na borda.O que importa para o deploy
unplannedCounteunplannedTotalCents. Campo aditivo — não quebra o front que não os lê.remittance-pix-requires-exclusive-file, com mensagem PT-BR que nomeia a regra e diz explicitamente que nada foi consumido.O que ficou de fora, e por quê
A #863 (CNPJ alfanumérico) não vem nesta leva, por decisão da P.O. Ela estava misturada com a #948 na PR #983, sob um título que só citava a #863 — sete dos dez commits eram da #948. As duas foram separadas; os commits da #863 seguem na #983, e a régua de inscrição continua fora da
main.A separação exigiu um commit de adaptação: a rede da CA3 enumera
CnabTranslateErrorinteira, então a classificação da variante da #863 saiu junto com a variante. Quando a #863 voltar, oRecordpara de compilar até alguém reclassificá-la.Estado
dev— os 22 checks da fix(financial): o pré-voo do Pix ganha governança, exclusividade de arquivo e a conta que fecha (#948 CA3–CA6) #1004, incluindo a suíte de integração inteira contra MySQL real.typecheck+format:check+lintverdes; 11799 testes, 0 falhas (baseline 11785)./security-review: sem achado High ou Medium./code-review: dois achados nesta fatia. O do docblock do CA5 foi corrigido na própria PR; o do pré-voo não antecipar a exclusividade do Pix está registrado como [financial] antecipar no pré-voo a recusa de seleção mista de Pix, que hoje só a geração faz #1003 — nenhum NSA é queimado, e a CA4 cobra a recusa na geração, que está entregue.Fora desta PR
A dependabot #973 (
mysql23.22.3 → 3.23.1) segue apontando para amainem vez dadev.https://claude.ai/code/session_01XNaURHGErqVMsDMAMDThyg