Skip to content

feat!: migrar client e mock para o contrato oficial v1.1.0 (0.2.0) - #11

Merged
Mozurok merged 5 commits into
mainfrom
feat/migracao-contrato-v1-1-0
Sep 4, 2026
Merged

feat!: migrar client e mock para o contrato oficial v1.1.0 (0.2.0)#11
Mozurok merged 5 commits into
mainfrom
feat/migracao-contrato-v1-1-0

Conversation

@Mozurok

@Mozurok Mozurok commented Sep 4, 2026

Copy link
Copy Markdown
Owner

Migra os dois pacotes do OpenAPI v0.0.10 para o v1.1.0, publicado pelo CGIBS em 24/08/2026, e entrega o guia de migração. Quebra compatibilidade com a linha 0.1.x.

A decisão que moldou o resto: quem assina

O contrato exige X-JWS-Signature nas 43 operações: JWS Compact Detached sobre o corpo canonicalizado em JCS, RS256, sete atributos no protected header.

O b64: false (RFC 7797) decide o desenho. Ele faz a assinatura cobrir os bytes crus do payload, então quem assina precisa ver exatamente o que vai na rede. Se a assinatura viesse pronta de fora, o chamador serializaria por conta própria e as duas serializações divergiriam em ordem de chave, formato de número ou escaping — produzindo uma assinatura válida sobre bytes que ninguém enviou, e um 4xx sem pista.

Então o client faz a parte difícil e delega só o RS256:

createSplitClient({
  baseUrl,
  kid: "minha-chave-01",
  assinar: (bytes) => hsm.signRS256(bytes),   // a chave privada é sua
})

Existe uma serialização só, e é a que vai na requisição.

O que mudou no contrato

v0.0.10 v1.1.0
rotas 32 35
schemas 57 78
autenticação de mensagem 4 headers X-JWS-Signature
stream {idPsp}/tributos {cnpjRaizPspRecDir}/transacoes
Mecanismo de Ocorrências 3 rotas

Os quatro headers obrigatórios não existem mais. Sumiram de components.parameters e o manual v1.1.0 não os cita uma única vez.

Campos renomeados: cnpjCpfPagOrigcnpjPagOrig (que passou a aceitar só CNPJ, o pagador pessoa física saiu do contrato), valorTotalCbs/IbsvlTotalCbs/Ibs, fromNsu/toNsunsuInicial/nsuFinal, tributostransacoes. Tipos: numIdentcBaixa e nsuId viraram string. Formatos fixos: numCodBarras com 44 dígitos, idLote = idInfSegr + sequencial (40 posições). dtHrDisp entrou como obrigatório no Super Inteligente. PATCH /api/v1/pix-automatico deixou de existir.

Três escolhas que valem revisão

1. O pós-processamento do MOC no codegen. MocOcorrenciaSol e Not usam allOf com not, e o openapi-typescript emite tipos inconstruíveis: o discriminador vira o literal com o nome do schema (arrj: "MocOcorrenciaSol" em vez de BOL|PXE|...) e o not vira Record<string, never>, zerando o resto. Pus a reescrita no gerador, não num override manual, para que ela suma sozinha quando o upstream corrigir. O teste moc-tipos.test.ts existe porque sem ele isso quebra em silêncio: nada mais no pacote toca aqueles schemas, então tsc sairia 0 com as duas rotas POST inutilizáveis.

2. O mock confere a forma da assinatura, sem exigir. Verificar de verdade obrigaria quem roda npx splitbr-mock a gerar par de chaves antes da primeira resposta, e o valor do mock é não precisar de setup. Aceitar qualquer string não ensinaria nada. Conferir a forma pega o erro provável em produção: JWS bem-formado com b64 errado, ou payload anexado em vez de detached. buildServer({ exigirAssinatura: true }) liga o modo fiel.

3. O MOC não ganhou matriz M/O/N-E. O princípio do repo é que as matrizes são o produto, mas aqui o próprio spec já codifica a regra por arranjo com oneOf, required e additionalProperties: false, e o Ajv já a aplica. Criar matriz duplicaria a fonte da verdade. Contrariei a convenção de propósito; se preferir a coerência, dá para reverter só isso.

Versão: 0.2.0, não 1.0.0

Pré-1.0, a quebra se sinaliza no minor, e ^0.1.1 não atravessa minor em 0.x — quem depende assim já fica protegido. Subir para 1.0.0 afirmaria uma estabilidade que o cenário não sustenta: três manuais da família não foram publicados, dois são minuta, o Manual de Segurança (que descreve o JWKS por trás da assinatura) não existe, e a plataforma pulou de 0.0.10 para 1.1.0 em dois meses.

Verificação

build       OK
typecheck   0 erros
testes      227  (71 client + 115 mock + 41 detector)   antes: 162
drift       4/4 MATCH
site        build OK, guia na navegação
tutorial    primeiro curl rodado contra o mock: 201, 1 item válido

O client saiu de 27 para 71 testes: vetores da RFC 8785 (incluindo o caso do emoji antes do hebraico, que separa ordenação por code unit de code point), verificação RS256 real com par de chaves gerado, teste de adulteração de byte, e a prova de tipo do MOC.

Guia de migração

Novo em docs/site/migracao.md, na navegação. Além do campo a campo, avisa dois erros que custam tempo: o do b64: false acima, e que nsuId virou string, então "9" > "10" é verdadeiro e comparar NSU com > passou a ordenar lexicograficamente.

BREAKING CHANGE: os quatro headers obrigatorios sairam e entrou a assinatura
X-JWS-Signature. createSplitClient troca tenantId por kid e assinar.

O v1.1.0 nao declara messageId, correlationId, tenantId nem timestamp: eles
sumiram de components.parameters e o Manual de Integracao v1.1.0 nao os cita
uma unica vez. No lugar, X-JWS-Signature required nas 43 operacoes.

A parte que exigiu decisao foi quem assina. O manual pede JWS Compact Detached
sobre o corpo canonicalizado em JCS, com b64=false, e e esse b64=false que
resolve a questao: a assinatura cobre os bytes CRUS do payload, entao quem
assina precisa ver exatamente o que vai na rede. Se a assinatura viesse pronta
de fora, o chamador serializaria por conta propria e as duas serializacoes
divergiriam em ordem de chave, formato de numero ou escaping, produzindo uma
assinatura valida sobre bytes que ninguem enviou.

Entao o client canonicaliza, monta o protected header, calcula a entrada e
remonta o detached; a operacao RS256 sai por callback. A chave privada nunca
entra no pacote e pode ficar num HSM ou KMS, e existe uma serializacao so.

- jcs.ts: RFC 8785. Curto porque o JavaScript ja acerta a maior parte, e o
  comentario registra por que: o `<` de string compara unidades de codigo
  UTF-16, que e a ordenacao que a RFC pede, e JSON.stringify ja usa o
  Number::toString do ES6 para numeros.
- assinatura.ts: protected header, entrada de assinatura e conferencia de
  forma (que o mock reusa).
- codegen: pos-processamento para MocOcorrenciaSol/Not. O allOf com not do
  spec faz o openapi-typescript emitir tipos inconstruiveis, com o
  discriminador virando o nome do schema e o resto zerado por
  Record<string, never>. A reescrita mora no gerador de proposito: quando o
  upstream corrigir, o bloco sai e o gerado continua igual, o que um override
  manual nao daria.

Testes de 27 para 71, incluindo verificacao RS256 real contra par de chaves,
os vetores da RFC 8785 e uma prova de tipo do MOC (sem ela, um tipo
inconstruivel passa despercebido: nada mais no pacote toca aqueles schemas).
BREAKING CHANGE: rotas de stream renomeadas, os quatro headers antigos deixam
de ser exigidos e campos foram renomeados ou mudaram de tipo.

Spec embarcado e trava de hash agora sao do v1.1.0 (35 rotas).

Mecanismo de Ocorrencias (3.9), tres rotas novas. A resposta da RFB/CGIBS e
simulada de forma deterministica porque sem ela a consulta devolveria 204 para
sempre e a rota seria decoracao: o valor de simular esta em o integrador ver o
ciclo fechar. O MOC nao ganhou matriz M/O/N-E de proposito: o proprio spec ja
codifica a regra por arranjo com oneOf, required e additionalProperties false,
e criar matriz duplicaria a fonte da verdade que o Ajv ja aplica.

Assinatura: o mock confere a FORMA do protected header quando o header vem, e
nao exige que venha. Verificar de verdade obrigaria quem roda `npx
splitbr-mock` a gerar par de chaves antes da primeira resposta, e o valor do
mock e nao precisar de setup; aceitar qualquer string nao ensinaria nada,
porque o erro provavel em producao e mandar um JWS bem-formado com b64 errado
ou com o payload anexado em vez de detached, e a conferencia de forma pega
exatamente isso. `exigirAssinatura` liga o comportamento fiel.

Os quatro headers antigos continuam ACEITOS e o correlationId segue ecoado,
por utilidade de debug, mas nenhum e obrigatorio: exigi-los deixaria o mock
mais estrito que a plataforma real, que e o defeito oposto ao que um mock deve
ter.

Renomes aplicados no codigo e nas matrizes: {idPsp}/tributos para
{cnpjRaizPspRecDir}/transacoes nas 12 rotas de stream, fromNsu/toNsu para
nsuInicial/nsuFinal, cnpjCpfPagOrig para cnpjPagOrig (que passou a aceitar so
CNPJ), valorTotalCbs/Ibs para vlTotalCbs/Ibs. Mudancas de tipo: numIdentcBaixa
e nsuId viraram string, numCodBarras exige 44 digitos e idLote passou a ser
idInfSegr mais sequencial (40 posicoes), o que amarra o lote ao informe no
proprio identificador. dtHrDisp entrou como obrigatorio no Super Inteligente e
e emitido na fronteira HTTP, onde o instante de disponibilizacao existe.

Testes de 105 para 115.
Atualiza as linhas do MANIFEST e o comentario de cobertura do detector: o
openapi-v1_1_0.json alimenta o codegen desde a 0.2.0, e o v0.0.10 fica como
registro do que gerou ate a 0.1.1. Nenhuma linha sobrescrita, como manda a
convencao do arquivo.
Guia novo em docs/site/migracao.md, registrado na navegacao. Formato
append-only com um H2 por migracao, espelhando o que novidades.md ja faz: da
URL estavel e uma entrada so no menu.

O guia cobre campo a campo, mas o trecho que mais evita perda de tempo e o
aviso sobre o b64=false: montar a assinatura por fora e serializar o corpo por
conta propria produz uma assinatura que parece correta e a plataforma rejeita
sem dizer por que. Tambem avisa que nsuId virou string, entao comparar NSU com
`>` passou a ordenar lexicograficamente e "9" > "10" e verdadeiro.

Tutorial: os quatro headers sairam dos curl e os payloads foram corrigidos
para o contrato novo (numCodBarras com 44 digitos, cnpjPagOrig). Verifiquei
subindo o mock e rodando o primeiro comando: 201 com 1 item valido. O tutorial
de curl sobrevive porque o mock nao exige assinatura por padrao, que e
exatamente para isso que esse default existe.

READMEs dos dois pacotes (via site-sync), README raiz nas duas metades, quadro
de estado da regulacao e uma entrada em novidades.
Pre-1.0, entao a quebra se sinaliza no minor: `^0.1.1` nao atravessa minor em
0.x, e quem depende assim fica protegido sem precisar de um 1.0.0. Subir para
1.0.0 afirmaria uma estabilidade de API que o cenario nao sustenta, com tres
manuais da familia ainda nao publicados, dois em minuta, o Manual de Seguranca
(que descreve o JWKS por tras da assinatura) inexistente, e a propria
plataforma tendo pulado de 0.0.10 para 1.1.0 em dois meses.
@Mozurok
Mozurok merged commit 6e8411f into main Sep 4, 2026
2 checks passed
@Mozurok
Mozurok deleted the feat/migracao-contrato-v1-1-0 branch September 4, 2026 18:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant