- 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>
33 lines
5.4 KiB
Markdown
33 lines
5.4 KiB
Markdown
# 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)
|
||
1. `FIREBIRD_WRITES` efetivo no Agent (`FISCAL_MODE=AUTO_APPLY` + caminho/esquema autorizados — as travas já existentes);
|
||
2. `RTC_AUTO_APPLY_ENABLED=1` no `agent.env` (chave própria; `RTC_AUTO_APPLY_MAX_PER_JOB`, padrão 500, limita o raio de ação);
|
||
3. autorização do **Hub** por instalação (`installations.rtc_auto_apply_authorized`, padrão `false`), entregue com cada job (`authorizations.rtc_auto_apply`) e alterada só pelo painel: botão "Autorizar" exige CSRF e digitar `PREENCHER`; toda mudança gera `audit_events` (`RTC_AUTO_APPLY_AUTHORIZE/REVOKE`).
|
||
Sem as três: modo `REPORT_ONLY` (conta candidatos, abre nenhuma conexão de escrita). O Agent reporta a capability `rtc_auto_apply` no 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ópia `scratch_rtc_e2e/TESTESOFICIAIS_RTC_COPY.FDB` via 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 coluna `CCLASSTRIB` mudou (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 em `docs/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.
|