- resolver (rules RTC-R0..R4/A1..A3/G1..G2) over the official cClassTrib table 2026-06-22 and LC 214/2025 annexes; local vs official catalog comparison - ONE root-cause RTC blocker per product (missing/unresolved/ambiguous/conflict/official mismatch/catalog outdated/evidence missing) - auto-apply: C000025.CCLASSTRIB only, three switches (FIREBIRD_WRITES + RTC_AUTO_APPLY_ENABLED + Hub authorization), per-product re-read and re-decision, never overwrites, rowcount 1, audit table rtc_write_audits (migration 0027) - copy-only write E2E (280/280 applied, only CCLASSTRIB changed, 2nd run 0 writes, stale decision 0 writes); real databases untouched - docs: cclasstrib_resolution, rtc_auto_apply, unified_fiscal_completeness Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
5.4 KiB
Preenchimento automático do cClassTrib (RTC auto-apply)
Código: agent/src/gti_fiscal_agent/rtc_auto_apply.py · integração agent/scripts/run_agent_service.py::_rtc_auto_apply · Hub central/app/rtc_audit.py, migração 0027_rtc_auto_apply, rota POST /agents/{id}/rtc-auto-apply.
Estado de entrega: mecanismo pronto, DESLIGADO em todo lugar. Nenhuma escrita em banco real foi feita ou é possível sem as três chaves abaixo.
Escopo de escrita
Uma coluna: C000025.CCLASSTRIB. ALLOWED_RTC_WRITE_FIELDS = ["CCLASSTRIB"]; o SQL é uma constante (UPDATE C000025 SET CCLASSTRIB = ? WHERE CODIGO = ? AND (CCLASSTRIB IS NULL OR TRIM(CCLASSTRIB) = '')). Não há write genérico de produto, nem escrita em NFCE/itens/catálogo RTC.
Só candidatos RESOLVED_* (ver docs/cclasstrib_resolution.md) com CCLASSTRIB vazio e nenhum outro problema RTC. Nunca sobrescreve: valor igual ⇒ NO_WRITE_REQUIRED; valor diferente ⇒ blocker CCLASSTRIB_CONFLICT (a política de correção precisa de aprovação futura).
Três chaves (todas necessárias; padrão = fechadas)
FIREBIRD_WRITESefetivo no Agent (FISCAL_MODE=AUTO_APPLY+ caminho/esquema autorizados — as travas já existentes);RTC_AUTO_APPLY_ENABLED=1noagent.env(chave própria;RTC_AUTO_APPLY_MAX_PER_JOB, padrão 500, limita o raio de ação);- autorização do Hub por instalação (
installations.rtc_auto_apply_authorized, padrãofalse), entregue com cada job (authorizations.rtc_auto_apply) e alterada só pelo painel: botão "Autorizar" exige CSRF e digitarPREENCHER; toda mudança geraaudit_events(RTC_AUTO_APPLY_AUTHORIZE/REVOKE). Sem as três: modoREPORT_ONLY(conta candidatos, abre nenhuma conexão de escrita). O Agent reporta a capabilityrtc_auto_applyno heartbeat.
Proteção contra decisão velha (por produto, uma transação curta)
Imediatamente antes do UPDATE o Agent relê tudo do banco: parâmetro USAR_REFORMA_TRIBUTARIA (todas as linhas; divergência ⇒ aborta), integridade do catálogo, hash do catálogo (deve ser o da decisão), a linha do produto (única; NCM igual ao da decisão; CCLASSTRIB ainda vazio) e re-deriva a resolução com dados novos (mesma classe e mesma regra). Qualquer diferença ⇒ ABORTED daquele produto (RTC_NO_LONGER_ENABLED, RTC_CATALOG_INVALID, LOCAL_CATALOG_CHANGED_SINCE_DECISION, PRODUCT_CHANGED_NCM, VALUE_ALREADY_SET_NEVER_OVERWRITTEN, NO_WRITE_REQUIRED, DECISION_CHANGED, PRODUCT_ROW_NOT_UNIQUE, UPDATE_ROWCOUNT_NOT_ONE). rowcount deve ser 1; releitura confirma o valor; a FK física C000025.CCLASSTRIB → RTC_CCLASSTRIB é a segunda barreira. Falha de um produto não interrompe os demais.
Idempotência: com o valor correto gravado o produto deixa de ser candidato (valid_existing); segunda execução = 0 escritas.
Auditoria e rollback lógico
Cada tentativa vira uma linha em rtc_write_audits (append-only): tenant/installation/agent (do contexto autenticado, nunca do payload), client, job_id, product_code, field, outcome (APPLIED|ABORTED|FAILED), reason, old_value (sempre nulo hoje: só se preenche campo vazio), new_value, rule_id, evidence (regra, entradas, item do anexo), official_source_version (data + sha256 da tabela cClassTrib e sha256 da LC 214), local_catalog_hash, written_at. Um resumo vai como evento RTC_AUTO_APPLY (severidade WARNING quando houve escrita). Reverter = UPDATE C000025 SET CCLASSTRIB = NULL para os product_code do job_id (não há botão de rollback nesta versão).
Painel
Mesmo painel, mesma fila. No detalhe do Agent, com RTC ativa: "Reforma Tributária ativa — cClassTrib pendente em N produtos", "Produtos RTC: total · válidos · prontos para preenchimento · ambíguos/bloqueados", estado do preenchimento automático ("Desativado / Autorizado no Hub, aguardando o Agent (…) / Ativo") e o formulário de autorização. RTC desligada não mostra nada disto.
Validação
- Testes:
agent/tests/test_rtc_auto_apply.py(gates, candidatos, escrita restrita, idempotência, RTC desligada entre decisão e escrita, catálogo alterado/inválido, NCM alterado, valor existente, duplicata, limite por job),central/tests/test_zero_touch_hub.py,central/tests/test_control_panel_pages.py,tests/test_rtc_copy_write_guard.py. - E2E de escrita em CÓPIA descartável (
scripts/rtc_copy_write_e2e.py; cópiascratch_rtc_e2e/TESTESOFICIAIS_RTC_COPY.FDBvia gbak; o script recusa qualquer outro caminho): empresa da cópia trocada para regime normal (CRT 3) como experimento; 280 candidatos → 280 escritas verificadas (000001×203,200034×75,200014×2), somente a colunaCCLASSTRIBmudou (diff da tabela inteira), 0 órfãos de FK/CST, catálogo RTC local intacto (145 linhas antes e depois), cobertura da cópia 0% → 53,55%; decisão velha (RTC desligada na cópia entre decisão e escrita) → 280 abortos, 0 escritas; segunda execução → 0 candidatos, 0 escritas. Resultado emdocs/runs/rtc_copy_write_e2e.json. - Bancos reais: 0 escritas, 0 tentativas.
Limitações
Simples continua bloqueado (sem orientação oficial); CCLASSTRIB_CONFLICT só reporta; não há rollback automático; o texto dos anexos é interpretado de forma conservadora (condição ⇒ ambíguo). Para o piloto real: escolher uma instalação em regime normal com RTC ligada, ligar as três chaves e revisar a amostra de auditoria antes.