Guardiao: operacao e seguranca¶
Objetivo¶
Permitir que uma IA investigue dados fiscais no Qlik sem receber, no mesmo contexto, a identidade real do contribuinte.
O desenho reduz vazamento acidental e associacao indevida. Ele nao tenta proteger o computador contra um administrador local malicioso.
Fluxo de dados¶
Celular -> ChatGPT Remote -> Codex no PC -> MCP Guardiao(ticket)
|
Celular -> Google Sheet -> ticket + CNPJ -----+
|
VPN da prefeitura -> Qlik
- A planilha conhece
ticket + CNPJ, mas nunca recebe dados fiscais. - A IA conhece
ticket + dados fiscais, mas nunca recebe CNPJ. - O Guardiao e o unico componente que associa as duas partes.
- O CNPJ permanece na planilha durante a investigacao (o ticket sobrevive a reinicios do processo MCP); a planilha nunca e exposta a LLM.
encerrar_ticketapaga a associacao em memoria E o CNPJ da planilha. Para tickets esquecidos, o proprio guardiao apaga CNPJs com mais deGUARDIAO_TTL_HORAS(168h) na primeira utilizacao da fila em cada processo.
Nao escreva o CNPJ no chat que recebera o resultado da investigacao.
Planilha¶
Crie uma aba chamada Tickets com estes cabecalhos:
| ticket_id | cnpj | criado_em | status | consumido_em | consumidor | comentario |
|---|---|---|---|---|---|---|
| caso-azul-17 | valor real | data | pendente | caso para conferir periodo curto |
Somente ticket_id e cnpj sao obrigatorios. O ticket deve ter de 3 a 64
caracteres, conter letras e usar apenas letras, numeros, ., _ ou -.
Use um apelido aleatorio; nunca use CNPJ, CPF, nome ou razao social como ticket.
O campo cnpj aceita o CNPJ completo (com ou sem mascara) ou somente a raiz de
8 digitos. Quando recebe o CNPJ completo, o Guardiao preserva os 14 digitos
somente na memoria interna para rastreamentos NFSE por estabelecimento; as
ferramentas de Simples Nacional continuam usando apenas a raiz. Nenhum dos dois
valores e devolvido ao modelo. comentario e opcional, fica apenas na planilha
e nao e exposto ao modelo. Use-o somente para notas operacionais, sem
identificadores pessoais ou dados fiscais.
O arquivo docs/templates/tickets.csv pode ser importado para criar o
cabecalho sem digitacao manual.
No celular, um Google Form pode alimentar essa aba. O formulario deve pedir um apelido aleatorio, o CNPJ e, opcionalmente, um comentario operacional; nao inclua identificadores pessoais adicionais, valores ou informacao fiscal.
A expiracao de tickets esquecidos e feita pelo proprio guardiao: na primeira
utilizacao da fila em cada processo, CNPJs mais velhos que
GUARDIAO_TTL_HORAS (padrao 168h / 7 dias, seja qual for o status) sao
apagados e marcados como expirado. Linhas com CNPJ e sem data recebem
criado_em na primeira varredura e passam a envelhecer a partir dali.
integrations/google_apps_script/Code.gs e opcional — so vale instalar se
quiser preencher criado_em/status no envio do formulario ou ter uma
limpeza que roda mesmo com o guardiao desligado por muitos dias.
Conta de servico¶
- Crie um projeto Google Cloud e habilite a Google Sheets API.
- Crie uma conta de servico e baixe sua chave JSON.
- Compartilhe apenas a planilha de tickets com o e-mail da conta de servico, como editor.
- Salve o JSON fora do projeto, por exemplo em
%LOCALAPPDATA%\QlikGuardiao\google-service-account.json. - Nao conecte essa planilha como app ou ferramenta visivel para a LLM.
O Guardiao usa somente o escopo spreadsheets. Consumir um ticket marca
status=consumido mas preserva o CNPJ na celula: o mesmo ticket pode ser
reconsumido apos um reinicio do processo. A celula so e limpa por
encerrar_ticket, pela limpeza TTL do guardiao ou manualmente.
Configuracao local¶
Toda a configuracao mora em um unico arquivo fora do projeto:
%LOCALAPPDATA%\QlikGuardiao\guardiao.env. Copie
raw/mcp_qlik_guardiao/guardiao.env.example para la e preencha. O server.py
carrega esse arquivo sozinho na inicializacao (os.environ.setdefault), entao
nenhum cliente MCP precisa configurar env, nao ha setx e nao e preciso
reiniciar aplicativos ao mudar valores — basta a proxima sessao MCP.
Variaveis de ambiente reais tem precedencia sobre o arquivo, com uma excecao:
valores herdados contendo quebra de linha (artefato de setx mal quebrado, que
ja causou HTTP 400 no Sheets) sao considerados corrompidos e o arquivo vence.
Para validar tudo de ponta a ponta (env, chave, credencial Google, fila de tickets, autenticacao Qlik) sem expor nenhum dado:
.venv\Scripts\python.exe raw\mcp_qlik_guardiao\doctor.py
Timeouts, checkpoint e orcamento¶
As tools NFSE consultam volumes grandes (3M+ notas/mes) e podem estourar o
timeout de 600s do tools/call do MCP. As variaveis abaixo sao opcionais (o
server.py tem o default embutido) e ficam comentadas em
raw/mcp_qlik_guardiao/guardiao.env.example:
| Variavel | Default | Uso |
|---|---|---|
GUARDIAO_WS_TIMEOUT_S |
30 | timeout de connect do WebSocket do Engine |
GUARDIAO_WS_RECV_TIMEOUT_S |
60 | timeout de recv apos conectar; cada estouro durante um reload longo vira um GetProgress (keepalive) |
GUARDIAO_HTTP_TIMEOUT_S |
30 | timeout das chamadas REST (QRS/QPS) |
GUARDIAO_RELOAD_STALL_S |
300 | progresso do reload parado por mais que isso (segundos) vira ErroEngineTravado em vez de hang indefinido |
GUARDIAO_CHECKPOINT_DIR |
%LOCALAPPDATA%\QlikGuardiao\checkpoints |
diretorio dos checkpoints locais das tools NFSE |
GUARDIAO_CHECKPOINT_TTL_HORAS |
24 | checkpoint mais velho que isso (horas) e descartado |
GUARDIAO_BUDGET_S |
480 | orcamento de tempo (segundos) por chamada de tool antes de parar e devolver status: incompleto |
GUARDIAO_DIAG_LOG |
%LOCALAPPDATA%\QlikGuardiao\diagnostico.jsonl |
caminho do log de diagnostico sanitizado (ver secao propria abaixo) |
GUARDIAO_EXPORT_DIR |
%LOCALAPPDATA%\QlikGuardiao\exports |
diretorio dos .qvs gerados pelo fallback manual exportar_script_qvs |
TLS¶
TLS e validado por padrao. O certificado do pmpa-qs2 e emitido pela CA
interna do Qlik e so vale para o FQDN pmpa-qs2.pmpa.ad (por isso o server.py
usa o FQDN). Converta a CA para PEM (ex.:
openssl x509 -inform DER -in certs/<host>/root.cer -out %LOCALAPPDATA%\QlikGuardiao\qlik-ca.pem)
e aponte GUARDIAO_CA_BUNDLE para ela. GUARDIAO_TLS_VERIFY=false existe
apenas para diagnostico temporario e nao deve ser a configuracao permanente.
Clientes MCP¶
O servidor usa MCP via STDIO:
<python-do-venv> <raiz-do-projeto>\raw\mcp_qlik_guardiao\server.py
Codex¶
O arquivo .codex/config.toml registra o Guardiao somente para este projeto.
Isso exige Codex >= 0.145 (versoes anteriores ignoravam config de projeto — a
causa historica de o MCP "sumir" a cada sessao) e o repositorio marcado como
confiavel. Toda a env vem do guardiao.env; nao adicione blocos env/
env_vars ao config (ha teste que garante isso). Confirme a configuracao a
partir da raiz do projeto:
codex mcp list
Nao use codex mcp add para o Guardiao: esse comando grava a configuracao do
usuario e deixa o servidor disponivel em outros projetos. O servidor STDIO nao
deve ser iniciado com o Windows; o Codex o inicia sob demanda para a sessao e o
encerra junto com ela.
As definicoes e descricoes das ferramentas ficam disponiveis para a LLM quando o MCP esta habilitado. Dados Qlik e tickets so entram no contexto quando uma ferramenta e chamada.
Para acesso pelo celular:
codex remote-control start
codex remote-control pair
O PC precisa permanecer ligado, acordado, conectado a internet e com a VPN da prefeitura ativa para consultas Qlik. Nao publique o app-server em uma porta da internet.
Claude Code¶
O arquivo .mcp.json na raiz registra o mesmo servidor por auto-discovery:
basta abrir o Claude Code dentro do projeto e aprovar o server na primeira vez.
(A instabilidade antiga do auto-discovery era o server morrendo no startup por
falta de env herdada — resolvida pelo guardiao.env.)
Outra LLM¶
Qualquer cliente MCP com transporte STDIO pode iniciar o mesmo comando. O
contrato publico esta nas anotacoes das funcoes @mcp.tool() de server.py.
Clientes sem MCP podem analisar scripts e lineage, mas nao devem acessar dado
real.
Uso diario¶
- No celular, crie
ticket_id + CNPJna planilha ou formulario. - No chat remoto, diga:
investigue o ticket caso-azul-17. - Use
rastrear_cnpj_simples_nacional(ticket_id=...). - Continue usando o mesmo ticket para outras ferramentas — inclusive apos reiniciar a sessao ou o processo MCP (o CNPJ segue na fila ate encerrar).
- Ao terminar a investigacao, chame
encerrar_ticket: alem de limpar a memoria, ele apaga o CNPJ da planilha (status=encerrado). Se a limpeza da planilha falhar, a resposta trazavisopedindo remocao manual.
verificar_presenca_ecossistema(ticket_id=...) devolve somente presenca e
contagem agregada nas fontes fixas usadas por Cadastro Consolidado, NFSE e
dados abertos. Ela nao retorna identidade, notas ou campos cadastrais.
nfse_rastrear_ticket_periodos(ticket_id, periodo_inicial, periodo_final)
conta notas por mes nas camadas de extracao, transformacao, T1, fato agregada e
aplicacao final. Os periodos usam AAAAMM e ficam restritos a 202101-202412.
nfse_estatisticas_exclusao_chave(periodo_inicial, periodo_final) mede apenas
agregados do universo NFSE: tipo do documento na nota, tipo da chave gerada,
presenca/Encontrou no T2_Empresas_NFSE.qvd, elegibilidade da dimensao final,
quantidade de contribuintes e notas. Ela nao recebe ticket nem retorna
documentos ou chaves.
estatisticas_cancelamento_periodos_txt() varre os arquivos PERSIMEI e
PERSIMPLES mais recentes e devolve somente contagens e percentuais agregados
de cancelamento, datas inicial/final iguais, data final zerada e periodos nao
cancelados com duracao de ate 30 dias (a mesma regra inversa do filtro >30
do ETL), separados entre data inicial igual ou diferente da data final.
amostrar_periodos_curtos_simples_nacional() seleciona dez contribuintes com
periodos encerrados de 1 a 29 dias e datas diferentes, grava a associacao
somente na fila privada e devolve tickets opacos com origem, datas e duracao.
impacto_filtro_periodos_curtos_simples_nacional() compara o
T_PeriodoSN_Novo.qvd atual com uma simulacao em memoria do mesmo transformador
sem o filtro >30. A resposta contem apenas totais finais de linhas e
contribuintes, por regime e no conjunto; nenhum QVD e gravado.
listar_tickets_ativos mostra apenas apelidos presentes na memoria.
Consultas longas na NFSE (checkpoint e continuacao)¶
nfse_rastrear_ticket_periodos e nfse_estatisticas_exclusao_chave quebram o
periodo pedido em chunks de 1 mes. Cada chunk concluido e salvo em um
checkpoint local (GUARDIAO_CHECKPOINT_DIR) antes de seguir para o proximo -
sobrevive a queda de rede, reinicio do processo MCP e ao timeout de 600s do
tools/call. O checkpoint nunca guarda documento: a chave do arquivo e o
conteudo so podem ter ticket_id (ja normalizado) e parametros neutros (ex.
periodos AAAAMM); a funcao que monta a chave rejeita qualquer valor com 8+
digitos consecutivos.
A resposta de cada chamada traz um destes status:
completo: todos os chunks terminaram sem erro; o checkpoint e apagado.completo_com_erros: o periodo inteiro foi percorrido, mas alguns chunks falharam de forma permanente (script/travado/auth) - vemchunks_com_errocom categoria/code/tentativas de cada um.incompleto: o orcamento de tempo (GUARDIAO_BUDGET_S, default 480s) estourou antes de terminar todos os chunks. A resposta trazprogresso(concluidos/total/com_erro) eproxima_acao: "chame novamente com os mesmos parametros para continuar do checkpoint". Chame de novo com os MESMOS argumentos: chunks ja concluidos sao pulados e os que falharam por motivo transitorio (rede/ocupado) sao retentados.
encerrar_ticket apaga os checkpoints do ticket junto com a memoria e o CNPJ
da planilha. Checkpoints tambem expiram sozinhos por
GUARDIAO_CHECKPOINT_TTL_HORAS (default 24h), limpos na primeira utilizacao
da fila em cada processo.
Taxonomia de erros e retry¶
Toda falha de comunicacao com o Engine e classificada em uma destas
categorias antes de subir para qualquer camada superior - nunca a mensagem
bruta do Engine (message/qMessage), que pode ecoar trecho de script com
CNPJ:
| Categoria | Situacao | Retry automatico? |
|---|---|---|
rede |
falha de socket/websocket | sim, ate 3 tentativas, espera curta (~1.5s) |
engine_ocupado |
code 11000, outro reload em andamento | sim, espera maior (15s) |
engine_script |
Engine recusou o script | nao |
engine_travado |
reload sem progresso por mais de GUARDIAO_RELOAD_STALL_S |
nao |
auth |
autenticacao Qlik recusada | nao |
O retry acontece por chunk (1 mes), nunca pela consulta inteira: uma falha no mes 40 de 48 nao refaz os 39 anteriores.
diagnostico.jsonl - excecao explicita a leitura de logs¶
%LOCALAPPDATA%\QlikGuardiao\diagnostico.jsonl (override GUARDIAO_DIAG_LOG)
registra, por whitelist estrita, uma linha JSON por falha de chunk com apenas
estas chaves: ts, ferramenta, chunk, categoria, code, method, tentativas,
duracao_s. Nunca tem ticket_id, documento ou mensagem do Engine.
Este arquivo especifico pode ser lido pela LLM. E o unico log de erro do
Guardiao com essa permissao, porque e 100% sanitizado por construcao. A
proibicao de leitura continua valendo, sem excecao, para
guardiao.<pid>.log (log operacional de texto livre, para o humano) e para
.mapa_pseudonimos.jsonl (desenho legado) - nunca leia esses dois.
Fallback manual: exportar_script_qvs¶
Quando as duas tools NFSE nao completam mesmo com checkpoint/retry (por
exemplo, rede muito instavel), exportar_script_qvs(consulta,
confirmado_pelo_usuario, ticket_id, periodo_inicial, periodo_final) gera um
.qvs standalone para colar direto no Data Load Editor, sem depender do
WebSocket nem do timeout de 600s do MCP.
- So roda com
confirmado_pelo_usuario=true. O modelo deve perguntar antes e so confirmar depois que o usuario disser explicitamente, na conversa, que esta no computador para abrir o arquivo. Sem confirmacao, a tool devolve um erro pedindo a confirmacao e nao grava nada. - Quando a consulta e
nfse_rastrear_ticket_periodos, o CNPJ do ticket vai do processo direto para o arquivo emGUARDIAO_EXPORT_DIR(%LOCALAPPDATA%\QlikGuardiao\exports) - nunca passa pela resposta da tool nem pela conversa. - A resposta traz somente
arquivo(caminho),instrucoesecontem_documento(bool). Nunca leia nem cole o conteudo do.qvsde volta na conversa. - Quando
contem_documento=true, o proprio.qvstraz um aviso de sigilo no cabecalho ("NAO compartilhe este arquivo").
Diagnostico agregado da cadeia NFSE (nfse_diagnostico)¶
nfse_diagnostico(consulta, periodo_inicial, periodo_final) roda verificacoes
agregadas da cadeia NFSE direto pela LLM, sem o operador colar script manual no
Data Load Editor a cada investigacao de qualidade de dado (composicao por
origem, comparativo DW vs QVD, dupla contagem na malha).
Contrato de seguranca:
- so aceita as consultas fixas listadas em
_DIAGNOSTICOS; qualquer outro valor devolve{"erro": "consulta invalida", "opcoes": [...]}sem tocar o Engine. - saida sempre agregada por periodo (e origem/status quando a consulta usa
essas dimensoes) - nunca por contribuinte, nunca cod_cnpj/raiz. A agregacao
(
GROUP BY) roda no banco, nao no Qlik nem na resposta da tool. - nao existe, e nao deve existir, um runner generico que aceite SQL ou script
arbitrario vindo da LLM: cada consulta e um script fixo definido em
server.py(_script_diag_*), preso a allowlist. E essa ausencia de runner generico que impede a ferramenta virar um canal para extrair dado por contribuinte disfarcado de "diagnostico".
As 4 consultas da allowlist:
ping: consulta de fumaca do framework, sem dado sensivel; roda uma vez so (nao e mensal, ignora o periodo recebido).composicao_origem: notas distintas por mes x flag_nacional x status, direto devw_nfsemista_v1no DW_Novo.comparativo_dw_qvd: notas distintas do DW por mes vsQvdNoOfRecordsdeNfse_<mes>.qvdeNfseNacional_<mes>.qvd, com a diferenca (faltando_apos_nacional).dupla_contagem_detalhado: razao entre registros deNFSE_Detalhado_<mes>.qvde notas distintas do DW no mes mesmo (indicador de duplicacao na malha).
Fonte dos dados: as 3 consultas reais (todas exceto ping) fazem
LIB CONNECT TO 'DW_Novo' na mesma sessao do Engine que as demais tools NFSE
usam, e leem QVDs via QvdNoOfRecords quando aplicavel - nunca abrem o QVD
linha a linha.
Periodo: periodo_inicial/periodo_final usam AAAAMM e passam pela mesma
validacao de nfse_rastrear_ticket_periodos/nfse_estatisticas_exclusao_chave.
O teto agora e dinamico (ano corrente em UTC), nao mais fixo em 202412.
Continuacao: as consultas mensais (todas menos ping) rodam em chunks de 1
mes com o mesmo checkpoint e orcamento de tempo (GUARDIAO_BUDGET_S) da secao
"Consultas longas na NFSE" acima, e devolvem os mesmos status:
completo: todos os chunks terminaram sem erro.completo_com_erros: percorreu o periodo inteiro, mas alguns chunks falharam de forma permanente (chunks_com_erro).incompleto: orcamento de tempo estourou antes do fim; chame de novo com os MESMOS parametros (consulta,periodo_inicial,periodo_final) para continuar do checkpoint - chunks concluidos sao pulados, os com erro transitorio sao retentados.
Como estender: uma consulta nova precisa de 3 pecas em server.py, sempre
nessa ordem:
- entrada em
_DIAGNOSTICOS(chave = nome da consulta, valor = descricao curta); - um
_script_diag_<nome>(mes)que devolve o script Qlik fixo, interpolando somente omesdo chunk - nada vindo do usuario; - entrada em
_DIAGNOSTICOS_IMPL(campos do resultado, o builder acima,qtypee se a consulta e mensal).
Builders novos passam por revisao de codigo antes de entrar em producao - e essa revisao (nao um filtro em runtime) que mantem o contrato de saida agregada e sem campo de contribuinte. Nao existe, e nao deve existir, um caminho que aceite script arbitrario fora dessas 3 pecas.
Falhas de acesso¶
Google Sheets recusou apagar o CNPJ (HTTP 403)(ao encerrar): a conta de servico consegue ler a fila, mas nao editar. Compartilhe a planilha com ela como Editor.autenticacao Qlik recusada: verifique a chave JWT e o certificado do virtual proxy; esse erro ocorre depois do consumo do ticket.falha TLS: configureGUARDIAO_CA_BUNDLE.Qlik indisponivel: confira VPN, rede e servidor.Engine ocupado (code 11000): outro reload em andamento; a tool ja retenta automaticamente apos 15s - so investigue se persistir.reload sem progresso (Engine travado): sem avanco porGUARDIAO_RELOAD_STALL_S(default 5 min); verifique pressao de memoria no QMC (verdocs/GUARDIAO_QMC.md).Engine recusou o script: erro de script no proprio Qlik, nao adianta retentar; consultecode/methodemdiagnostico.jsonl.
O Guardiao encerra explicitamente sua sessao no QPS ao terminar, evitando acumular logins entre sessoes do Codex. Depois de atualizar o codigo, reinicie o Codex uma vez para carregar o processo MCP novo.
Volume e periodos longos¶
Nao existe limite fixo de periodo. A ferramenta atual percorre toda a cadeia do Simples Nacional.
O volume retornado por etapa usa:
GUARDIAO_DEFAULT_ROW_LIMIT=500GUARDIAO_MAX_ROW_LIMIT=50000GUARDIAO_DEFAULT_SAMPLE_LIMIT=20GUARDIAO_MAX_SAMPLE_LIMIT=500
Esses numeros sao configuracao operacional. Para uma investigacao maior:
- passe
limite_linhas_por_etapana ferramenta; - confira
truncadoem cada etapa; - se necessario, aumente o teto por variavel de ambiente e reinicie o MCP.
Para centenas de milhares de registros, uma nova ferramenta deve agregar e filtrar localmente antes de retornar o resultado. Isso reduz tokens sem impedir que o Qlik examine todo o periodo ou todas as linhas.
Uso por colegas¶
O recomendado e cada desenvolvedor autorizado ter:
- clone proprio do repositorio privado;
- ambiente virtual proprio;
- identidade Qlik propria, para auditoria individual;
- chave Qlik e credencial Google fora do clone;
GUARDIAO_OPERATOR_IDproprio;- aba ou planilha de tickets propria.
Nao compartilhe a sessao Remote do seu PC como forma normal de colaboracao. Isso mistura credenciais, historico, aprovacoes e autoria. Compartilhe codigo e documentacao pelo repositorio, e cada operador conecta sua propria instalacao.
Se ambos usarem a mesma planilha, use tickets diferentes e, preferencialmente,
abas separadas em GUARDIAO_SHEETS_RANGE.
Migracao do desenho antigo¶
raw/trace_cnpj.pyagora encerra imediatamente e nao consulta o Qlik..mapa_pseudonimos.jsonlnao e mais usado.- Chaves JWT novas nao devem ficar em
certs/ouraw/mcp_qlik_guardiao/. - Logs antigos e mapas legados nao sao apagados automaticamente. Revise e remova esses arquivos manualmente quando nao forem mais necessarios.
- Rotacione a chave de API encontrada nos scripts e as chaves Qlik que permaneceram acessiveis dentro do projeto.
Testes¶
Os testes nao acessam Google ou Qlik:
.\.venv\Scripts\python.exe -m unittest discover -s tests -v
Eles verificam consumo e limpeza da celula, cache em memoria, ausencia de parametro CNPJ nas ferramentas publicas, limites configuraveis e truncamento.