Files
sar-android/_bmad-output/implementation-artifacts/3-3-incluir-vl-acrescimo-no-md5-de-change-detection.md
Julio Schlickmann dc61705c91 add project files
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 22:33:42 -03:00

23 KiB
Raw Blame History

Story 3.3: Incluir vl_acrescimo no MD5 de Change-Detection

Status: done

Story

Como sistema, quero que mudanças isoladas de vl_acrescimo num pedido disparem re-sync do registro local, para que ajustes de acréscimo no ERP (reabertura, correção manual server-side) sejam propagados ao dispositivo.

Acceptance Criteria

  1. Dado que um pedido já sincronizado em pedido_consulta tem vl_acrescimo = 87.50 e md5 = H1 Quando o ERP alterar apenas o acrev (Gerente) ou tx_acrescimo (SIG) do registro e o próximo selectAllPedConsulta rodar Então o md5 retornado pelo PG difere de H1, PedidoConsultaDB.saveAll detecta a divergência e dispara updateErp, gravando o novo vl_acrescimo e o novo md5 em pedido_consulta

  2. Dado que o MD5 server-side inclui o campo de acréscimo no hash (acrev para Gerente; tx_acrescimo para SIG quando coluna existe) Quando o mesmo pedido for lido em duas execuções consecutivas de selectAllPedConsulta sem alteração no ERP Então o md5 é idêntico nas duas execuções — não há loop de "diferente/igual" entre pedidos já em dia

  3. Dado que pedidos pré-Epic 3 no ERP têm acrev = 0 (Gerente) ou tx_acrescimo = 0 (SIG) após aplicação do DEFAULT 0 Quando o primeiro selectAllPedConsulta após o deploy de Story 3.3 rodar Então esses pedidos aparecem como "atualizados" (contador tt[2] incrementa pois o novo termo no MD5 altera o hash), vl_acrescimo gravado permanece 0.0, sem crash, sem travamento — comportamento aceitável e idempotente

  4. Dado que Global.sistema == SISTEMA_SIG e a coluna tx_acrescimo ainda não existe em sig.pedidos (hasColunaAcrescimoSig() == false) Quando o selectAllPedConsulta SIG construir o MD5 Então o MD5 é calculado sem o termo COALESCE(a.tx_acrescimo, 0.0), a query executa normalmente, e o comportamento de re-sync permanece idêntico ao de pré-Story 3.3 até o DDL ser aplicado

Escopo desta história

SIM:

  • Adicionar || COALESCE(A.acrev, 0.0) ao final do hash MD5 no SELECT Gerente de selectAllPedConsulta()
  • Adicionar condicionalmente || COALESCE(a.tx_acrescimo, 0.0) ao hash MD5 no SELECT SIG, reutilizando temAcrescSigConsulta (já calculado em Story 3.2)

NÃO cobre:

  • Cálculo de MD5 no lado do app (o app nunca calcula MD5 local — apenas armazena o hash retornado pelo PG; ver "Como o change-detection funciona de fato" abaixo)
  • PedidoConsultaDB.saveAll — já compara md5 stored vs ped.getMd5() e dispara updateErp desde versões anteriores (nenhuma mudança necessária)
  • DDL server-side — acrev em Gerente e tx_acrescimo em SIG são gerenciados pelo DBA; app continua tolerante (Stories 3.1/3.2)
  • pedido_consulta.md5 no SQLite — coluna já existe e é populada por insert/updateErp em PedidoConsultaDB

Pré-condições verificadas

  • MD5 é calculado apenas no PG: grep confirma que MD5(...) aparece só em PedidoPGSQL.java:628 (Gerente) e PedidoPGSQL.java:658 (SIG). Não existe cálculo de MD5 no app — o hash armazenado é o que o PG retornou no sync anterior.
  • temAcrescSigConsulta já é declarada em PedidoPGSQL.java:619 e populada via hasColunaAcrescimoSig() em PedidoPGSQL.java:653 (Story 3.2). Reutilizar sem duplicar.
  • acrev em Gerente já é lido pelo SELECT após Story 3.2 (linha 630: COALESCE(a.acrev, 0.0) as acrev). A coluna certamente existe em gerente.pedidos (confirmado em Story 3.1). Safe para incluir no MD5 sem tolerância.
  • tx_acrescimo em SIG é lido condicionalmente (linhas 661663). A condição if (temAcrescSigConsulta) vale tanto para a projeção quanto para o MD5.
  • PedidoConsultaDB.saveAll compara MD5 e dispara updateErp em PedidoConsultaDB.java:60-67updateErp já grava vl_acrescimo (linha 183) e md5 (linha 177). Fluxo completo funciona sem alterações nessa classe.
  • dbVersao permanece 43 — esta story não altera schema SQLite.
  • COALESCE(x, 0.0) produz string estável no md5() do PostgreSQL (concatenação via || com cast implícito para text); padrão idêntico ao já usado para id_formapag, id_pauta, obs.

Tasks / Subtasks

  • Task 1: Incluir acrev no MD5 do SELECT Gerente (AC: 1, 2, 3)

    • 1.1 — Em PedidoPGSQL.java:629, alterar a linha que termina com A.total),:
      // ANTES:
      sql.append("       COALESCE(B.id_formapag,0) || COALESCE(A.obs,'') || COALESCE(C.id_pauta,0) || A.total),");// 12
      
      // DEPOIS:
      sql.append("       COALESCE(B.id_formapag,0) || COALESCE(A.obs,'') || COALESCE(C.id_pauta,0) || A.total || COALESCE(A.acrev, 0.0)),");// 12
      

      A coluna acrev já existe em gerente.pedidos (confirmado em Story 3.1). COALESCE(..., 0.0) protege contra NULL histórico e garante hash estável para pedidos sem acréscimo. Não alterar a linha 630 (projeção COALESCE(a.acrev, 0.0) as acrev) — ela é o SELECT do valor para vlAcrescimo, não do termo do MD5, e já foi feita em Story 3.2.

  • Task 2: Incluir tx_acrescimo condicionalmente no MD5 do SELECT SIG (AC: 1, 2, 3, 4)

    • 2.1 — Em PedidoPGSQL.java:658-659, quebrar o append da linha do MD5 em três partes para permitir inserção condicional:
      // ANTES:
      sql.append("       MD5(num_ped_sar         || id_pedido          || numero || A.tipo || situa || data || clien || COALESCE((SELECT data_emissao FROM sig.pedidos WHERE id_empresa=A.id_empresa AND tipo='E' AND numer_ped_vinc=A.numero ORDER BY data_emissao DESC LIMIT 1),A.data) ||");
      sql.append("       COALESCE(id_formapag,0) || COALESCE(A.obs,'') || COALESCE(C.id_pauta,0)) as md5,");// 12
      
      // DEPOIS:
      sql.append("       MD5(num_ped_sar         || id_pedido          || numero || A.tipo || situa || data || clien || COALESCE((SELECT data_emissao FROM sig.pedidos WHERE id_empresa=A.id_empresa AND tipo='E' AND numer_ped_vinc=A.numero ORDER BY data_emissao DESC LIMIT 1),A.data) ||");
      sql.append("       COALESCE(id_formapag,0) || COALESCE(A.obs,'') || COALESCE(C.id_pauta,0)");
      if (temAcrescSigConsulta) {
          sql.append(" || COALESCE(a.tx_acrescimo, 0.0)");
      }
      sql.append(") as md5,");// 12
      

      Quando temAcrescSigConsulta = false, o SQL resultante é idêntico ao pré-Story 3.3 — garantindo AC 4 (tolerância total à ausência de DDL). Quando true, o hash passa a depender de tx_acrescimo, disparando re-sync em alterações server-side isoladas (AC 1).

Dev Notes

Como o change-detection funciona de fato

Apesar do epic mencionar "app calcular o MD5 local", a inspeção do código confirma que não há cálculo de MD5 no lado do app. O fluxo real é:

  1. Sync anterior: selectAllPedConsulta retorna md5 calculado pelo PG; PedidoConsultaDB.insert/updateErp armazena esse hash em pedido_consulta.md5.
  2. Sync atual: selectAllPedConsulta retorna novo md5 do PG; saveAll compara com o hash stored (PedidoConsultaDB.java:60).
  3. Decisão:
    • md5 == null → pedido novo → insert
    • md5.equals(ped.getMd5()) → inalterado → continue (skip)
    • !equals → alterado → updateErp (grava novos dados e novo hash)

Portanto, a consistência requerida pelo AC 2 é a consistência da fórmula MD5 do PG entre syncs — não entre app e servidor. Isso significa que, enquanto o código do SELECT permanecer o mesmo, o hash será determinístico para o mesmo conteúdo do pedido.

Por que acrev (não acrep) no MD5 Gerente

acrep é a taxa percentual (ex: 2.5). acrev é o valor monetário do acréscimo (ex: 87.50). Como vl_acrescimo no SQLite armazena o valor monetário (Story 2.3), e como acrev é o que Story 3.2 já lê para popular Pedido.vlAcrescimo em Gerente, faz sentido incluir apenas acrev no hash — mudar acrep sem mudar acrev implicaria inconsistência no próprio PG que o app não precisa detectar. Além disso, para todo pedido gravado via Story 3.1, acrep e acrev mudam juntos (são ambos derivados de tx_acrescimo + subtotal na hora do INSERT) — redundância desnecessária no hash.

Por que tx_acrescimo (não valor calculado) no MD5 SIG

Em SIG, o valor do acréscimo é derivado server-side (total × tx_acrescimo / 100). O único campo armazenado é tx_acrescimo. Se o valor calculado mudar por alteração de total (já no MD5) ou de tx_acrescimo (novo termo), ambos os casos ficam cobertos. Não é necessário incluir o cálculo completo — incluir só tx_acrescimo basta.

Snapshot do SQL resultante (para validação rápida no logcat)

Gerente (sempre):

MD5(A.num_ped_sar || A.id_pedido || A.numero || A.tipo || A.situa || A.data || A.clien || COALESCE(A.dtemi,NOW()) ||
    COALESCE(B.id_formapag,0) || COALESCE(A.obs,'') || COALESCE(C.id_pauta,0) || A.total || COALESCE(A.acrev, 0.0)),

SIG com DDL aplicado (temAcrescSigConsulta = true):

MD5(num_ped_sar || id_pedido || numero || A.tipo || situa || data || clien || COALESCE((SELECT ...),A.data) ||
    COALESCE(id_formapag,0) || COALESCE(A.obs,'') || COALESCE(C.id_pauta,0) || COALESCE(a.tx_acrescimo, 0.0)) as md5,

SIG sem DDL (temAcrescSigConsulta = false):

MD5(num_ped_sar || id_pedido || numero || A.tipo || situa || data || clien || COALESCE((SELECT ...),A.data) ||
    COALESCE(id_formapag,0) || COALESCE(A.obs,'') || COALESCE(C.id_pauta,0)) as md5,

Impacto operacional no primeiro sync após deploy (AC 3)

Como o MD5 passa a incluir acrev/tx_acrescimo, todos os pedidos em pedido_consulta terão hash diferente no primeiro selectAllPedConsulta após o deploy:

  • Gerente: todos os pedidos existentes serão marcados como "alterados" (tt[2]++) — updateErp rodará para cada um; vl_acrescimo será gravado com o valor de acrev (pode ser 0 para pedidos pré-Epic 3, ou valor real para pedidos enviados via Story 3.1).
  • SIG com DDL já aplicado: idem — vl_acrescimo recalculado via total × tx_acrescimo / 100.
  • SIG sem DDL: nenhum impacto — MD5 não muda; re-sync não dispara; comportamento pré-Story 3.3 preservado. Quando DDL for aplicado, o próximo sync disparará re-sync de todos os pedidos SIG de uma só vez.

Isso é aceitável e idempotente: após o re-sync, o hash estabiliza e os syncs seguintes operam normalmente.

Arquivo a modificar

Arquivo Caminho O que muda
PedidoPGSQL.java src/br/com/jcsinformatica/sarandroid/postgres/ selectAllPedConsulta(): append `

NÃO modificar:

  • PedidoConsultaDB.javasaveAll já compara e insert/updateErp já gravam (Stories 2.3/2.4)
  • Pedido.java — VO completo (Story 2.3)
  • DatabaseHelper.java — schema SQLite não muda; dbVersao permanece 43
  • hasColunaAcrescimoSig() — já existe (Story 3.1); reutilizar

Regras críticas do projeto (aplicáveis)

  • Sem Kotlin — somente Java puro
  • Sem Gradle — projeto Eclipse ADT
  • Sem JARs novos — nenhuma dependência adicional
  • Sem testes automatizados — validação manual via dispositivo/emulador
  • SQL por StringBuilder — padrão projeto-wide mantido
  • Thread background para I/OselectAllPedConsulta já roda em AsyncTask da ComunicaActivity
  • ConnectionManager.closeAll(st, rs) — já chamado em PedidoPGSQL.java:761; não alterar

Inteligência de histórias anteriores

  • Story 3.2: declarou temAcrescSigConsulta em PedidoPGSQL.java:619 e populou-o via hasColunaAcrescimoSig() em linha 653 — Story 3.3 apenas essa variável dentro do mesmo método; nenhum novo estado.
  • Story 3.1: criou hasColunaAcrescimoSig() (linhas 11961212), com log de warning em caso de exceção. Disponível para uso; não alterar.
  • Story 2.3: Pedido.vlAcrescimo com getter/setter e pedido.vl_acrescimo em SQLite (DatabaseHelper.onUpgrade v43 linha 713).
  • Story 2.4: PedidoConsultaDB.selectFullvl_acrescimo (linha 296) — tela de consulta já exibe corretamente após re-sync.
  • Padrão de MD5 existente: o hash Gerente já incluía A.total enquanto o hash SIG não incluía A.total — mantenha essa assimetria. Apenas adicionar o novo termo ao final, sem reorganizar a ordem dos demais termos (alterar a ordem invalidaria o hash de pedidos pré-deploy, gerando re-sync em massa desnecessário já capturado pelo novo termo).

Verificação manual após implementação

  1. Gerente — mudança isolada de acrev dispara re-sync:

    • Selecionar um pedido em gerente.pedidos já sincronizado no app (registro existe em pedido_consulta)
    • UPDATE gerente.pedidos SET acrev = acrev + 10.00 WHERE id_pedido = <X> direto no PG
    • Rodar ComunicaActivity
    • Verificar logcat: INSERT PEDIDO CONSULTA ou log de updateErp para o pedido; pedido_consulta.vl_acrescimo atualizado; pedido_consulta.md5 atualizado
  2. Gerente — sync repetido sem alteração não dispara re-sync:

    • Rodar ComunicaActivity duas vezes seguidas, sem mudar o PG entre rodadas
    • Contador tt[1] (skip) deve cobrir todos os pedidos na segunda execução
  3. SIG com DDL aplicado — mudança isolada de tx_acrescimo:

    • Pedido em sig.pedidos com tx_acrescimo = 2.5, total = 1000.00 → app tem vl_acrescimo = 25.00
    • UPDATE sig.pedidos SET tx_acrescimo = 3.0 WHERE id_pedido = <X> direto no PG
    • Rodar ComunicaActivity → pedido_consulta.vl_acrescimo = 30.00; hash atualizado
  4. SIG sem DDL — tolerância total:

    • Com tx_acrescimo ausente em sig.pedidos (ambiente sem DDL)
    • Rodar ComunicaActivity → nenhum crash; log SQL PEDIDO CONSULTA mostra MD5 sem o termo tx_acrescimo; syncs consecutivos têm contadores tt[0]/tt[1]/tt[2] idênticos ao pré-Story 3.3
  5. Primeiro sync após deploy — re-sync em massa aceitável (AC 3):

    • Dispositivo com pedido_consulta populado pré-Story 3.3 (md5 sem termo de acréscimo)
    • Fazer upgrade do app → rodar ComunicaActivity
    • Esperado: tt[2] (alterados) cobre todos os pedidos Gerente e SIG-com-DDL já sincronizados; execução conclui sem erro; syncs subsequentes normalizam para tt[1] (skip)
  6. Log para debug:

    • Log.d("SQL PEDIDO CONSULTA = ", sql.toString()) em PedidoPGSQL.java:679 — conferir presença/ausência do termo no MD5 conforme sistema e DDL

Git intelligence (commits recentes)

  • f2cf45d Story 2.4: pedido_consulta.vl_acrescimo + guard status >= STATUS_ENVIADO em consulta — campo já gravado e lido no SQLite
  • 3ff26a7 Story 2.3: Pedido.vlAcrescimo + PedidoDB/PedidoConsultaDB preparados
  • Story 3.1 (uncommitted): PedidoPGSQL.insert/insertSig/save/hasColunaAcrescimoSig — padrões a seguir
  • Story 3.2 (uncommitted): selectAllPedConsulta leitura inbound + temAcrescSigConsulta — variável a reutilizar nesta story

References

  • Epics: _bmad-output/planning-artifacts/epics.md#story-33-incluir-vl_acrescimo-no-md5-de-change-detection
  • FR17: MD5 de pedido_consulta inclui vl_acrescimo no cálculo de hash
  • FR16: Sync tolera ausência das colunas de acréscimo no PostgreSQL
  • PedidoPGSQL.java:619 — declaração temAcrescSigConsulta
  • PedidoPGSQL.java:628-629 — MD5 Gerente (alvo da Task 1)
  • PedidoPGSQL.java:653 — inicialização temAcrescSigConsulta = hasColunaAcrescimoSig()
  • PedidoPGSQL.java:658-659 — MD5 SIG (alvo da Task 2)
  • PedidoPGSQL.java:731 — leitura ped.setMd5(rs.getString("md5"))
  • PedidoPGSQL.java:1196-1212hasColunaAcrescimoSig() (não alterar)
  • PedidoConsultaDB.java:54-67 — comparação stored vs new MD5 em saveAll (não alterar)
  • PedidoConsultaDB.java:141 — gravação de md5 em insert (não alterar)
  • PedidoConsultaDB.java:177 — gravação de md5 em updateErp (não alterar)
  • Memory: project_pg_sync_acrescimo_schema.md — assimetria Gerente vs SIG confirmada

Dev Agent Record

Agent Model Used

claude-opus-4-7 (create-story workflow)

Debug Log References

N/A — projeto sem infraestrutura de testes automatizados; validação manual via dispositivo/emulador conforme roteiro em "Verificação manual após implementação".

Completion Notes List

  • Story 3.3 implementada em 2026-04-16.
  • Task 1 — Gerente MD5: PedidoPGSQL.java:629 — linha do MD5 Gerente (após || A.total) agora apenda || COALESCE(A.acrev, 0.0). Única linha alterada no branch Gerente; sem impacto na projeção de acrev (linha 630) já introduzida em Story 3.2. AC 1, 2 e 3 satisfeitos para Gerente: alterações em acrev no PG mudam o hash, disparando updateErp em PedidoConsultaDB.saveAll.
  • Task 2 — SIG MD5 condicional: PedidoPGSQL.java:658-663 — append da linha de MD5 SIG refatorado em três partes. Corpo do hash agora fecha com COALESCE(C.id_pauta,0) sem parêntese; if (temAcrescSigConsulta) apenda || COALESCE(a.tx_acrescimo, 0.0); ") as md5," fecha o MD5(...). Reutiliza temAcrescSigConsulta populado em linha 653 (Story 3.2) — nenhum novo estado.
  • AC 4 (tolerância SIG sem DDL) preservado por construção: quando temAcrescSigConsulta = false, o bloco if não executa e o SQL resultante é byte-for-byte idêntico ao pré-Story 3.3. Verificado via leitura do arquivo após edição.
  • PedidoConsultaDB não modificadosaveAll (linha 54-67) já compara md5 stored vs retornado e dispara updateErp; insert/updateErp já gravam md5 e vl_acrescimo desde Stories 2.3/2.4.
  • dbVersao permanece 43 — sem alteração de schema SQLite.
  • Sem testes automatizados: projeto não possui infraestrutura (conforme CLAUDE.md). Validação via logic trace + inspeção do SQL gerado pelo Log.d("SQL PEDIDO CONSULTA = ", sql.toString()) na linha 679 durante validação manual (roteiro em "Verificação manual após implementação" no corpo da story).
  • Nenhum import adicionado — todas as classes e métodos necessários já estão no arquivo.
  • AC 1 satisfeito: hash Gerente inclui acrev; hash SIG inclui tx_acrescimo quando coluna existe → mudanças isoladas disparam updateErp.
  • AC 2 satisfeito: fórmula MD5 determinística; hash estável entre syncs quando conteúdo não muda.
  • AC 3 satisfeito: primeiro sync pós-deploy marcará todos pedidos Gerente/SIG-com-DDL como alterados (hash novo ≠ hash stored); updateErp grava vl_acrescimo = 0.0 para pedidos pré-Epic 3 (acrev/tx_acrescimo default 0). Idempotente.
  • AC 4 satisfeito: temAcrescSigConsulta = false → SQL SIG idêntico ao pré-Story 3.3; nenhum impacto em ambientes sem DDL.

File List

  • src/br/com/jcsinformatica/sarandroid/postgres/PedidoPGSQL.java

Review Findings

  • [Review][Defer] Ramo Gerente adiciona A.acrev ao MD5 sem guard equivalente a hasColunaAcrescimoSig() [PedidoPGSQL.java:629] — deferred, premissa verificada: Story 3.1 confirmou via inspeção de código que gerente.pedidos.acrev existe desde antes do Epic 3 (estava na lista de colunas do INSERT sendo gravada como 0); não é cenário hipotético. Adicionar hasColunaAcrescimoGerente() seria over-engineering contra condição que não pode ocorrer no ambiente real.
  • [Review][Defer] Colisão teórica por concatenação MD5 sem delimitador entre A.total e COALESCE(A.acrev, 0.0) [PedidoPGSQL.java:629] — deferred, risco pré-existente e teórico: padrão de concatenação sem separador já existia em todo o MD5 Gerente/SIG original (num_ped_sar || id_pedido, etc.). Adicionar delimitador só ao novo termo seria inconsistente; refatorar o MD5 inteiro para usar separador é mudança arquitetural fora do escopo de Story 3.3 e forçaria segundo re-sync em massa.
  • [Review][Defer] Hash pode variar entre versões do PostgreSQL por mudanças em extra_float_digits / float4out (formatação textual de REAL) [PedidoPGSQL.java:629,661] — deferred, risco pré-existente: A.total (REAL) já estava no MD5 Gerente com a mesma exposição; mitigação real seria to_char(..., 'FM999999990.00') em TODOS os termos numéricos do hash — refatoração fora do escopo. Upgrade/mudança de config do PG já impactaria sync atual.
  • [Review][Defer] pedido_consulta.md5 TEXT sem NOT NULL → setMd5(null) geraria literal "'null'" e loop de update infinito — deferred, pré-existente: caminho requer falha do MD5() server-side (extremamente improvável); schema definido em Story 2.3 não por esta.
  • [Review][Dismiss] Pedido.vlAcrescimo potencialmente Double boxed (NPE em SIG sem DDL) — falso positivo: Story 2.3 Dev Notes e Story 3.1 linha 50 confirmam primitivo double com default 0.0.
  • [Review][Dismiss] Alias case a.tx_acrescimo vs A. maiúsculo — segue exatamente o padrão já estabelecido em Story 3.2 [PedidoPGSQL.java:662] na projeção do mesmo campo; PG é case-insensitive em identificadores não-quoted.
  • [Review][Dismiss] Cast de 0.0 (double) vs acrev (REAL) produzindo texto diferente — verificado: gerente.pedidos.acrev é REAL (confirmado por setDouble em Story 3.1 linha 183); mesmo tipo em ambos os lados do COALESCE.
  • [Review][Dismiss] Comentário // 13-16 em SIG não atualizado — correto por design: tx_acrescimo é coluna condicional (aparece apenas quando temAcrescSigConsulta=true), mesmo padrão já usado em Story 3.2 linha 666 (//17 dentro do if).
  • [Review][Dismiss] Filtro AND A.total>0 mascara pedidos com total=0 e acrev>0 — pré-existente, fora de escopo; cenário semanticamente inconsistente (acréscimo sobre zero).

Change Log

  • 2026-04-16: Story 3.3 criada pelo create-story workflow.
  • 2026-04-16: Story 3.3 implementada. PedidoPGSQL.selectAllPedConsulta(): MD5 Gerente agora inclui || COALESCE(A.acrev, 0.0) (sempre); MD5 SIG inclui condicionalmente || COALESCE(a.tx_acrescimo, 0.0) via if (temAcrescSigConsulta) — reutilizando a variável já populada em Story 3.2 sem duplicar a query information_schema. Completa FR17 do Epic 3: mudanças isoladas de acréscimo no ERP agora disparam re-sync do pedido_consulta local. Status → review.
  • 2026-04-16: Code review executado (Blind Hunter + Edge Case Hunter + Acceptance Auditor). Acceptance Auditor aprovou. 4 achados classificados como defer (todos pré-existentes ou premissas documentadas); 5 dismisses. Nenhum patch necessário. Status → done.