git-subtree-dir: fiscal git-subtree-mainline:d95165fc8fgit-subtree-split:102ba84500
6.3 KiB
cClassTrib — preenchimento controlado centralmente (RTC_CCLASSTRIB_WRITE_V1)
O Agent nunca decide sozinho escrever no ERP. Não há chave local (agent.env, FIREBIRD_WRITES, PowerShell, RDP): a autorização vem somente dentro do job autenticado enviado pelo Hub. O Agent expõe apenas a capability restrita RTC_CCLASSTRIB_WRITE_V1 (heartbeat).
Modos (por instalação, painel: Agent → "Controle e candidatos cClassTrib")
| Modo | Efeito |
|---|---|
DISABLED (padrão) |
só informa; nada de preview, nada de escrita |
DRY_RUN |
calcula e envia o preview dos candidatos; zero escrita |
APPROVAL_REQUIRED |
escreve só os produtos aprovados por um humano no painel (lote de até 500 por job) |
ENABLED |
escreve os candidatos determinísticos (até RTC_AUTO_APPLY_MAX_PER_JOB, padrão 500, por job); exige digitar PREENCHER |
Texto do painel: "Somente cClassTrib com regra fiscal determinística e catálogo validado pode ser preenchido automaticamente."
Mudança de modo = evento de auditoria administrativa (RTC_WRITE_MODE). Migration 0028_rtc_write_modes (padrão DISABLED; instalações que já tinham a autorização 0027 viram ENABLED).
O que pode ser escrito
Somente C000025.CCLASSTRIB (allowlist ALLOWED_RTC_WRITE_FIELDS), somente quando todos valem: RTC ligada no ERP; regra AUTO_APPLY_SAFE (hoje só RTC-R3: NCM em exatamente um anexo I/VII/VIII/XV da LC 214, item limpo, CRT 3, uma única classe); classe MATCH_SAFE no catálogo local vs oficial; CST válido; empresa relida (C000004.CRT = único valor esperado e = 3); RTC/parâmetro/catálogo relidos; produto inalterado (NCM + descrição + valor, fingerprint); valor atual vazio. R4 (exclusão residual → 000001) é só sugestão e nunca vira candidato. Nunca sobrescreve: valor igual → NO_WRITE_REQUIRED; diferente → CCLASSTRIB_CONFLICT (bloqueio). Qualquer gate falho → zero escrita para aquele produto.
Sequência por produto (uma transação curta): reler tudo → recalcular a decisão → conferir fingerprint → UPDATE C000025 SET CCLASSTRIB = ? WHERE CODIGO = ? AND (CCLASSTRIB IS NULL OR TRIM(CCLASSTRIB)='') → rowcount = 1 → reler → commit → auditoria.
Reautorização imediata antes da escrita (1.2.1)
O modo entregue no job pode envelhecer (operador desativa durante um lote). Antes de cada UPDATE o Agent relê a autorização atual no Hub (GET /v1/agents/me/authorizations, autenticado pelo token do próprio Agent; devolve só modo, aprovados e reversões da sua instalação; cache de 3 s). Se o modo mudou, se o produto perdeu a aprovação ou se o Hub não puder ser consultado, o produto é interrompido sem escrever (AUTHORIZATION_CHANGED / AUTHORIZATION_UNVERIFIABLE). Autorização não confirmada não é autorização.
Quem pode mudar o que (RBAC, 1.2.1)
A identidade vem do SSO do GTI Gerenciador (perfil). ADMIN (e contas LOCAL de emergência) pode: APPROVAL_REQUIRED/ENABLED, aprovar/rejeitar, aplicar aprovados e reverter. Um colaborador PADRAO só vê a página e alterna DISABLED/DRY_RUN (somente leitura); tentativas restritas devolvem 403 e ficam auditadas (RTC_DENIED). Sair de um modo de escrita também é restrito a ADMIN. Toda mudança de modo, aprovação e reversão grava o operador e o perfil no evento de auditoria.
Backpressure de jobs pesados (1.2.1)
HEAVY_JOB_CONCURRENCY_LIMIT (padrão 3; <=0 desliga): no máximo N jobs INITIAL_SYNC/REPROCESS_ALL rodam ao mesmo tempo em todos os Agents; os demais ficam PENDING (fila) e o Agent segue com heartbeat e polling. Um job leve nunca fica preso atrás de um pesado. Job RUNNING há mais de 1 h não ocupa vaga. Medição (8 Agents × 1.042 produtos, Hub de desenvolvimento): sem limite 51,9 produtos/s, CPU pico 149%, cada Agent até 160 s; com limite 3: 66,7 produtos/s, CPU pico 116%, cada Agent até 45 s (espera máx. 88 s), 0 falhas.
Preview e aprovação
O Agent envia os candidatos (até 5.000 por job; preview_truncated sinaliza excesso) → tabela rtc_candidates (PENDING/APPROVED/REJECTED/APPLIED/STALE). O painel pagina no servidor (50/página), filtra por status, aprova/rejeita selecionados ou todos os pendentes e dispara "Aplicar aprovados agora". Uma proposta que muda invalida a aprovação anterior; produto que deixa de ser elegível numa avaliação completa vira STALE.
Auditoria (append-only, rtc_write_audits)
cliente, agent, installation_uuid, produto, snapshot da descrição, campo, valor antigo/novo, regra + versão (rtc-rules-v2), evidência, hashes/versões das fontes oficiais, hash e estado (MATCH_SAFE) do catálogo local, status RTC, CRT da empresa, timestamp, job, origem da autorização (HUB_MODE:<modo>), resultado, ação (APPLY/ROLLBACK). Identidade vem do contexto autenticado, nunca do payload. Sem segredos.
Rollback por registro
Painel → "Reverter" (digitar REVERTER) marca o registro REQUESTED e cria um job; o Hub entrega {audit_id, product_code, written_value} no job. O Agent só restaura (SET CCLASSTRIB = NULL) se o valor atual ainda for igual ao gravado; senão ROLLBACK_CONFLICT_VALUE_CHANGED (nada muda). Um único statement fixo, sem SQL livre. O registro original vira DONE/BLOCKED; o produto volta a ser candidato.
Validação
- Testes:
agent/tests/test_rtc_auto_apply.py,central/tests/test_zero_touch_hub.py,test_control_panel_pages.py,test_rtc_security.py,test_cclasstrib_resolver.py. - E2E em CÓPIA (
scripts/rtc_copy_write_e2e.py,docs/runs/rtc_copy_write_e2e.json): restaura a cópia do.fbk; DISABLED/DRY_RUN/decisão obsoleta = 0 escrita; aprovação de 5 → exatamente 5; ENABLED → 77 (total); reler; FK/CST órfãos 0; segunda execução 0; rollback de 3 + 1 conflito recusado; reaplicação de 3; idempotência 0; hash de 324 tabelas:NON_TARGET_COLUMNS_CHANGED=0,CATALOG_CHANGED=0,PARAMETERS_CHANGED=0,DOCUMENT_TABLES_CHANGED=0, nenhuma outra tabela alterada. - Escala (
scripts/scale_hub_test.py, 21.000 produtos, payload 7,96 MB): persistência 43 s → 5,5 s (upsert em lote; regime estável 64 statements); painel < 0,6 s. - Bancos reais: 0 escritas, 0 tentativas.
Limitações
Simples continua bloqueado (sem orientação oficial: NT 2025.002 v1.51; Ato Conjunto RFB/CGIBS 4/2026 fixa a obrigação de Simples em 2027-01-01). Regras além de R3 exigem evidência positiva nova. O Agent 2 permanece em DISABLED.