Divergências conhecidas (código × contrato)

Esta página lista, sem esconder, onde o código atual do Dracma (dracma_api, dracma_webhooks, dracma_executor_rs, conferidos em 2026-09-27) faz algo diferente do Contrato v1 1.1.0, do openapi.yaml ou dos documentos irmãos (REGRAS, SEGURANCA). As outras páginas deste guia descrevem o que o código faz hoje e apontam para cá.

Como usar:

  • "Seu código" diz o que o conector deve fazer para funcionar nos dois mundos (hoje e depois da correção). Quase sempre é seguir o comportamento mais restritivo.
  • Cada item tem um id estável (D-A1, D-D2…). Use-o ao abrir chamado ou ao citar em código.
  • Quando uma divergência for resolvida, ela sai desta lista no mesmo PR que muda o código ou o contrato.

Autenticação, transporte e erros

Id Contrato diz Código faz hoje Seu código
D-A1 §3.6: chave de uma operação com certificado de outra → 401 a verificação do certificado mTLS fica na borda (proxy TLS) de produção; o cruzamento chave × certificado não está no código da API nunca misture chave e certificado de operações diferentes; trate o 401 como possível
D-A2 5 req/s para /events e /reconciliation (por operação) balde de listagem de 5 req/s por chave para GET /deposits, /withdrawals, /events (sem id); /reconciliation usa o balde geral de leitura pagine /events e /reconciliation devagar (≤ 5 req/s); fetch-back sempre por id
D-A3 catálogo §7 não tem 413 corpo > 64 KiB → 413 com code: validation_failed, reason: too_large trate 413 como 4xx de validação
D-A4 nonce = "32 hex" exige minúsculo (^[0-9a-f]{32}$); maiúsculo → 401 invalid_signature gere hex minúsculo
D-A5 §4.3: Dracma-Event-Id ≠ id do corpo → 400 SEGURANCA I8 diz 401 (o documento irmão está errado) responda 400 (como o operador de referência)
D-A6 não previsto 20 falhas de assinatura/min do mesmo IP → as seguintes viram 429 rate_limited corrija a assinatura; não repita em laço um 401
D-A8 503 psp_unavailable / outside_operating_hours não emitidos; indisponibilidade sai como 503 service_unavailable trate todo 503 igual: repita com a mesma chave depois do Retry-After
D-A9 limites por jogador não constam no contrato POST /deposits: 10 depósitos criados por jogador por hora; POST /withdrawals: 3 saques pedidos por jogador por hora (SEGURANCA §9.1) → 429 rate_limited com Retry-After mostre "tente mais tarde" ao jogador; no saque, mantenha a reserva e repita com a mesma chave depois do Retry-After

Idempotência e corpo

Id Contrato diz Código faz hoje Seu código
D-I1 openapi: operation_id no corpo "é ignorado se existir" ignorado só na raiz do corpo; aninhado → 400 unknown_field nunca envie operation_id
D-I2 chave "em processamento" a reserva da chave dura 30 s; depois disso outra execução pode assumir não faça duas chamadas concorrentes com a mesma chave

Depósitos

Id Contrato diz Código faz hoje Seu código
D-D2 POST /deposits/{id}/cancel cancela o depósito muda só o Dracma; não existe comando para cancelar a cobrança no PSP — o PIX continua pagável até expirar depósito cancelled com paid_amount preenchido: não credite; needs_decision + alerta
D-D3 paid → refunded / paid → chargeback gatilho só no sandbox: POST /v1/sandbox/deposits/{id}/refund e POST /v1/sandbox/deposits/{id}/chargeback (rotas aditivas ao openapi do contrato; só de paid, outro estado → 409; emitem deposit.refunded/deposit.chargeback). Em produção ainda sem gatilho (nem PSP, nem evento do executor) implemente o tratamento (débito por política, needs_decision) e teste-o no sandbox com essas rotas
D-D4 openapi CriarDeposito tem method_options e fx_quote_id os dois → 400 unknown_field não envie em PIX
D-D5 catálogo de eventos não existe deposit.created; o primeiro evento é deposit.pending com object_version 2 não espere evento de criação
D-D6 §6.1 não tem transição para pagamento depois de terminal pagamento em expired/cancelled/failed: estado não muda; paid_amount, paid_at e pagador são gravados e o Dracma alerta nunca credite fora de paid; paid_amount em terminal = needs_decision
D-D7 REGRAS §12 (catálogo de motivos) emite também RULES_UNAVAILABLE e RULES_INVALID_RESPONSE (em review_reasons e decision.reasons) leitor tolerante: motivo desconhecido não muda nada
D-D8 Documento.type aceita PASSPORT, NATIONAL_ID, TAX_ID em PIX só CPF/CNPJ: os outros → 422 document_invalid (depósito) ou bloqueio H2 (saque) em PIX mande só CPF/CNPJ

Exigências por operação (api-req)

Id Contrato diz Código faz hoje Seu código
D-R1 player.email/player.name opcionais; Moeda sem method_details; Problema.errors[].reason sem os motivos novos GET /currencies traz method_details[] (method, deposit_requirements, psp_notes) — campo aditivo: está no openapi.yaml servido (schema DetalheMetodo), não no openapi do contrato 1.1.0; POST /deposits recusa com 400 (required_by_operation, format, max_length, reserved_domain_required) antes de criar objeto. Mercado Pago exige player.name + player.email; MaximaxPay exige player.name. player.email é validado sempre que vier; player.name > 120 → 400 (limite que o openapi já tinha e não era aplicado). Commit 6d81813 do dracma_api leia as exigências de GET /currencies; mande os campos; trate o 400 como recusa
D-R2 §11 (lado da Nero): corpo do depósito do front é {amount, method, accept_bonus, cpf?} a operação com Mercado Pago exige player.name e player.email (MaximaxPay exige player.name) o conector preenche nome e e-mail a partir do cadastro do jogador (servidor), nunca do corpo do front
D-R3 exemplos do openapi usam jogador1@test.nerobet.lol fora de produção o Dracma só aceita e-mail de TLD reservado (.test, .invalid, .example); test.nerobet.lol recebe reserved_domain_required em sandbox use …@….test (ex.: jogador1@nero.test)

Saques

Id Contrato diz Código faz hoje Seu código
D-W1 approved → processing = "enviado ao PSP" vira processing no primeiro retorno do PSP; envio recusado antes de sair (caixa, guarda de ambiente) deixa em approved + alerta approved pode durar; mantenha a reserva
D-W2 approved → cancelled permitido (cancelar "só antes de processing") com o comando de envio já publicado, cancelar → 409 (evita pagar e devolver) trate 409 no cancel como "espere o desfecho"
D-W3 diagrama §6.2 não desenha requested → cancelled permitido (o texto do §6.2 e o openapi permitem) —
D-W4 Decisao.mode obrigatório em requested (DICT pendente): mode: "manual", reasons: [], decided_at: null não conclua nada de mode em requested
D-W5 catálogo de eventos não existe evento público withdrawal.requested —
D-W6 paid → returned quando o banco devolve só a ação de sandbox return aplica; o executor ainda não publica devolução real implemente a política de returned (D7) mesmo assim
D-W7 ações de sandbox mudam o estado pay/fail respondem 200 com o estado atual; o desfecho chega por evento espere o webhook / faça fetch-back
D-W8 player.ip opcional sem player.ip o saque nunca é automático: under_review com OWNERSHIP_UNVERIFIED (o PSP de saque exige o IP do titular; decisão D17 do Dracma) mande o IP do jogador em todo saque
D-W9 §2: POST /withdrawals responde 202 se o PSP demorar sempre 201 (a criação não espera o PSP) trate 201 e 202 igual
D-W10 motor fora → (implícito) erro na criação do saque: 201 under_review com RULES_UNAVAILABLE (na dúvida, revisão); no depósito: 503 mantenha a reserva em under_review

Reconciliação e leitura

Id Contrato diz Código faz hoje Seu código
D-C1 digest: itens ordenados por object_id empate (mesmo object_id em dois itens) desempatado por kind crescente ordene por (object_id, kind) — os exemplos já fazem
D-C2 — não há vetor normativo do digest em vetores-assinatura.json use os testes dos exemplos; sugestão ao Dracma: publicar um vetor
D-C3 GET /rules sem currency usa a primeira moeda habilitada da operação —
D-C4 pending_in do caixa calculado pela API (depósitos recebidos e ainda em revisão), não vem do livro use só para acompanhamento

Webhooks

Id Contrato diz Código faz hoje Seu código
D-H1 §5: backoff exponencial com jitter intervalos fixos, sem jitter não dependa do horário exato da reentrega
D-H2 omisso sobre redirect 3xx conta como falha (reentrega) responda 2xx direto na URL cadastrada
D-H3 lista de IPs de egress publicada e assinada ainda não há endpoint público da lista; ela chega no onboarding assinatura + fetch-back são a defesa principal; aplique a lista quando recebê-la

Sandbox e conformidade

Id Contrato diz Código faz hoje Seu código
D-S1 o openapi do contrato 1.1.0 não declara Idempotency-Key nas rotas /sandbox/* exigida em todo POST; o openapi.yaml publicado (o servido) declara mande chave nova em cada ação de sandbox
D-S2 §10: suíte em nero_v2/nero_dracma_sandbox/conformidade/ a suíte está em dracma_api/conformance/ e é entregue no onboarding —
D-S3 o openapi do contrato 1.1.0 (cópia fiel em contrato-1.1.0.openapi.yaml) traz o server 127.0.0.1:3016 ("Dracma FALSO da bet") e o e-mail jogador1@test.nerobet.lol o openapi.yaml publicado é o servido pela API: sem esse server; o exemplo de e-mail continua ignore esse server; ver D-R3 para o e-mail
D-S4 allowlist vazia = sem acesso vale em produção; em sandbox, lista vazia aceita qualquer IP cadastre a allowlist desde o sandbox

Divergências já resolvidas (histórico)

Id O que era Como ficou
D-D1 o executor não publicava charge.paid.v1 quando valor ou pagador divergiam (o depósito ficava pending) corrigido no dracma_executor_rs (a892360): o pagamento sempre chega com os valores reais e a API decide under_review
D-S0 a suíte de conformidade chamava /sandbox/* sem Idempotency-Key corrigido: a suíte gera idem_<uuidv7> em todo POST