Full 18-field diagnostic (local_completion.py) ran successfully for
Cliente A (CONSTRUCAO) and Cliente B (MMMODAS): zero AUTO_FILL_SAFE
cells in either, so there is nothing for the guarded write cascade to
apply - a correct engine result, not a blocker. Real Agent jobs
completed for all 4 clients (A/B/C/D), confirming the same pattern.
RINCAO/UNIAO snapshot stayed blocked by the session's auto-mode
classifier across two attempts; documented as a known gap instead of
worked around.
Fixed central/tests/test_field_contract.py: its ALLOWED fixture still
pointed at Banco/CONSTRUCAO.FDB from before this mission's allowlist
change to work_copies/; updated it and added the original path as an
explicit must-be-rejected case. Canonical suite: 1191 passed, 1
skipped.
Rewrote the stale "encerrado a pedido do operador" report in
MISSAO_9_LOG.md (written at a premature shutdown) with the real final
report, and updated PROJECT_STATE.md to reflect mission completion.
The machine was shut down and restarted per the operator's earlier request,
then the operator asked to continue instead. On resume, Postgres data
persisted (same volume) and re-checking confirmed CFOP/TIPI_IPI/CEST/
NFE_MOC_TABLES were already ACTIVE (approved in Missão 8) - the earlier
"not found" finding only checked status='DRAFT' and missed that they had
already been promoted. No new approval was needed; register_draft()'s
idempotent lookup just confirmed the existing rows unchanged.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Job 648 (Cliente A/CONSTRUCAO, INITIAL_SYNC, DRY_RUN) completed for real
before the session had to close: 3712/3712 products COMPLETE, 0 blocked,
0 needs-review, 0 writes (as configured). Cliente B (MMMODAS, 20997
products) was still RUNNING when the session ended.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Correção de fato: CONSTRUCAO/MMMODAS/RINCAO/UNIAO são cópias reais de 4
clientes (Cliente A-D), não dado de teste como uma sessão anterior (Missão
6/7) generalizou por engano a partir do Agent 2 (esse sim, teste).
docs/EVIDENCE_SOURCES.md e docs/PROJECT_STATE.md corrigidos.
Bloco 0 (setup seguro) concluído: os 4 originais copiados para work_copies/
(git-ignored, .FDB maiúsculo já coberto pelo padrão existente), hash SHA-256
idêntico antes/depois da cópia (manifestos commitados). CRT/UF/regime
confirmados direto de cada banco via company_context (nunca perguntado ao
operador): A=CONSTRUCAO CRT1, B=MMMODAS CRT1, C=RINCAO CRT3, D=UNIAO CRT1.
4 Agents novos registrados (IDs 618-621, installation_ids novos, nunca
reaproveitando o Agent 2), .env por instalação (gitignored - contém tokens
reais), FIREBIRD_WRITES=0, escrita restrita ao próprio work copy de cada um.
docs/LOCAL_WRITE_ALLOWLIST_V1.json atualizado para apontar a work_copies/ em
vez de Banco/ - os originais nunca mais devem ser abertos em modo de escrita.
Bloco 1.3 concluído: busca web real confirma cClassTrib já obrigatório desde
03/08/2026 para CRT=3 (Cliente C) e só a partir de 2027 para CRT=1 (A/B/D) -
o motor já implementava essa regra corretamente, nenhuma mudança de código.
Restante da missão (diagnóstico completo de 18 campos, aprovação dos
datasets P2-P4, cascata de escrita, lançamento dos Agents C/D) NÃO foi
executado: 3 ações foram negadas pelo classificador de modo automático desta
sessão (nunca contornadas por outra via, por instrução explícita da própria
negação) e a sessão foi encerrada a pedido do operador com 2 jobs reais
(Cliente A e B) ainda RUNNING no Hub local. Detalhe completo, incluindo o
que uma sessão futura deve verificar primeiro, em docs/MISSAO_9_LOG.md.
Nenhuma mudança de código nesta sessão - só documentação, setup de
ambiente de trabalho e tentativas de execução operacional. Nenhuma escrita
fiscal real em nenhum banco (nem original, nem cópia).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
GET /internal/fiscal/clients returns every CLIENTE (active and inactive)
as {external_client_id, code, display_name, active} — CLIENTE.ID is the
identity (CODIGO is editable, same reasoning as FUNCIONARIO_PAINEL.ID in
SSO). Authenticated by a dedicated bearer token (FISCAL_INTERNAL_API_TOKEN,
never reused from SESSION_SECRET/SSO key/admin password), rate-limited per
IP, returns 503 rather than opening anonymously when the token isn't
configured. GTI Gerenciador stays the single source of truth: the Fiscal
Hub never receives MySQL credentials.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
/sso/fiscal now requires the state that GTI Fiscal creates when the flow
starts and signs it as the "nonce" claim; GTI Fiscal checks it against
its own one-time cookie. Without a valid state the panel sends the
browser to GTI Fiscal's /login, so the flow always starts there. This
prevents someone from pushing an assertion for their own account into
another person's browser (login CSRF).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The sidebar "Fiscal" entry now goes to /sso/fiscal. For a logged-in,
active employee the panel issues a JWT signed with Ed25519 (the private
key exists only here; GTI Fiscal only holds the public key): iss/aud
fixed, sub = funcionario:<ID> (LOGIN is editable), 60 s lifetime, unique
jti that GTI Fiscal records to reject replays. No password, hash or panel
cookie leaves the panel.
The assertion is delivered in an auto-submitted POST, never in the URL,
so it stays out of proxy logs, browser history and Referer. That page
carries its own CSP allowing form-action only to the configured callback
origin; the security-headers middleware now keeps a route-defined CSP.
Without a session the employee goes through the normal login and comes
back; plain HTTP is redirected to the public HTTPS address; `next` only
accepts a relative path; the .env bootstrap account (no stable ID) is
refused.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Opens the GTI Fiscal Hub (https://gtifiscal.gtigrupo.com.br) in a new
tab. Link only: GTI Fiscal keeps its own login, the panel's
authentication is unchanged. Already deployed to production.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Mesmo padrão já usado pro CODIGO (db_clientes.codigo_em_uso): nova
db_clientes.cnpj_em_uso(documento, ignorar_id=None) checada em
criar()/editar() antes de gravar. A comparação ignora pontuação dos dois
lados (não só normaliza o valor recebido) porque CLIENTE.CNPJ tem
formatação inconsistente em registros legados (alguns com máscara,
outros sem) — sem isso, um cadastro novo com o mesmo documento de um
legado ainda mascarado passaria batido.
app/templates/cliente_form.html: nova mensagem de erro
'erro_cnpj_duplicado', mesmo estilo visual do erro de código duplicado.
6 testes novos (unitário do db + integração do router pros dois
fluxos), suíte completa (196 testes) passando.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- app/routers/contatos.py: CLIENTE_CONTATO.TELEFONE passa a ser gravado
sempre com DDI 55 (normalizar_telefone_com_ddi, mesma normalização já
usada em CLIENTE.TELEFONE/CELULAR) — confirmado no node '10 - Validar
Telefones' do workflow n8n de produção (ER6Piy9yu93uOVuY) que o
telefone é usado exatamente como está gravado, sem nenhuma
normalização própria. Um contato cadastrado pelo painel sem o
funcionário digitar '55' na frente nunca recebia a chave automática
por WhatsApp — falha silenciosa, sem nenhum erro visível.
- app/db/clientes.py, app/db/contatos.py, app/db/funcionarios.py:
excluir_cliente/atualizar_contato/excluir_contato/redefinir_senha
passam a retornar bool (cur.rowcount > 0) em vez de None — um
ID inexistente não gera erro nenhum do MySQL (WHERE só não bate com
nada), então sem checar rowcount o router não tinha como saber que
não alterou/excluiu nada.
- app/routers/clientes.py, app/routers/contatos.py,
app/routers/funcionarios.py: editar/excluir/redefinir-senha/
alternar-ativo passam a checar essa existência e mostrar 'não
encontrado' em vez de registrar 'sucesso' no log e redirecionar pra
uma tela que também vai dizer que não encontrou nada.
- app/templates/clientes_lista.html: busca não estava com urlencode nos
links de paginação/ordenação — um termo de busca com '&' (comum em
razão social, ex. 'Fulano & Cia') cortava o resto da query string do
link, perdendo página/ordenação.
Suíte completa (192 testes) passando, com 13 testes novos cobrindo os
casos de 'não encontrado' e o DDI do telefone de contato.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- app/rate_limit.py: bloqueia por IP após 5 tentativas de senha erradas
em 5 minutos em POST /login (rota pré-autenticação, hoje sem nenhuma
proteção contra força bruta).
- Cookie de sessão (routers/auth.py) e cookie CSRF (main.py) passam a
sair com Secure quando a requisição chega em HTTPS (request.url.scheme)
— hoje é sempre False porque produção ainda serve HTTP puro, mas o
cookie passa a ficar protegido automaticamente assim que um TLS for
colocado na frente, sem precisar editar isso de novo.
- main.py: novo middleware de security headers (X-Content-Type-Options,
X-Frame-Options: DENY, Referrer-Policy, Content-Security-Policy,
Strict-Transport-Security condicional a HTTPS).
- requirements.txt: fastapi 0.115.0 -> 0.115.6 (starlette >=0.40.0,
corrige DoS por memória não limitada ao parsear multipart/form-data —
GHSA-f96h-pmfr-66vw), python-multipart 0.0.9 -> 0.0.20 (várias
vulnerabilidades de DoS corrigidas), jinja2 3.1.4 -> 3.1.6 (escapes de
sandbox). httpx/uvicorn/PyMySQL já estavam em versões sem CVE
conhecida — conferido via osv.dev.
- Dockerfile: container não roda mais como root.
Suíte completa (181 testes) passando, incluindo testes novos para o
rate limiter (unitários + integração via POST /login) e smoke test
manual confirmando headers, fluxo de login com CSRF e o bloqueio por
força bruta.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Endereca 5 achados da revisao final de branch:
- Important #1: normalizar_telefone_com_ddi agora e idempotente (nao duplica 55 independente do tamanho)
- Important #2: mascara de telefone detecta DDI colado por tamanho total (12/13 digitos), nao mais so pelo campo especifico
- Minor #3: buscarCnpj pula lookup na Receita quando DV do CNPJ e invalido, preservando a mensagem de erro
- Minor #4: detectarEValidarDocumento roda uma vez no carregamento da pagina
- Minor #5: autofill da Receita dispara evento input para acionar as mascaras de UF/CEP/TELEFONE
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
O algoritmo (CHAVE_INTERNA + funcoes gerarReativacao/gerarInstalacao)
ficava 100% legivel no codigo-fonte da pagina — qualquer pessoa com
acesso ao painel conseguia abrir o DevTools e extrair a logica pra
gerar chaves de licenca fora do sistema.
Move tudo pra app/gerador_chave.py (roda so no servidor) e expoe via
POST /gerador-chave/gerar (autenticado, protegido por CSRF). A pagina
agora so manda os dados do formulario e mostra o resultado que volta
do backend. Algoritmo verificado byte-a-byte contra os 3 exemplos
reais conhecidos (cliente 000001, ALUGUEL). Bonus: o byte inicial
agora usa secrets.randbelow (CSPRNG) em vez de Math.random() do JS.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Gerador de chave: filtra caracteres nao numericos do campo codigo em
tempo real (input event), alem da validacao ja existente no submit.
Tabelas: a.botao/button ganham white-space: nowrap, corrigindo o
botao 'Abrir' (e outros) quebrando em duas linhas depois que as
celulas de dado passaram a permitir quebra de texto longo.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
.login-cartao button { width: 100%; } tinha mais especificidade CSS
que .botao-ver-senha e forcava o botao do olho a ocupar 100% da
largura do campo, jogando o icone (alinhado por padrao a esquerda no
flex) pro canto esquerdo por cima do input. Regra do width:100% agora
so mira o botao de submit; o campo de senha vira um wrapper flex
(input + botao lado a lado), mais robusto que posicionamento absoluto.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sidebar antes esticava junto com o conteudo (align-items:stretch do
flex), entao em telas com tabela longa (Clientes) o rodape (tema/sair/
recolher) ficava fora da area visivel sem rolar a pagina toda. Agora
fica position:sticky, presa ao topo da viewport, com rolagem propria
se a lista de links for maior que a tela.
Login ganha um botao de olho pra mostrar/ocultar a senha digitada.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
O white-space: nowrap em toda celula de tabela impedia o texto de
quebrar linha, entao valores longos de Razao/Fantasia/CNPJ estouravam
a largura da tabela e forcavam rolagem horizontal mesmo em telas
grandes. Cabecalhos continuam sem quebra (nomes curtos); celulas de
dado agora quebram linha quando necessario.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adiciona class condicional (ativo se request.url.path.startswith(...))
em cada link de app/templates/base.html, usando o estilo .sidebar-nav
a.ativo que ja existia em estilo.css mas nunca era aplicado.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- --cor-acento-contraste passa de branco pra #17171a (texto escuro sobre
fundo laranja em botoes, unificando com o tema escuro) - contraste ~7:1
- novo token --cor-acento-texto (#B3510C no tema claro, #F5821F no
tema escuro) usado para laranja como cor de TEXTO sobre fundo claro
(link padrao, badge-acento, item ativo da sidebar, chave gerada em
gerador_chave.html) - contraste 5.12:1 contra branco, acima do
minimo AA de 4.5:1
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1. Frase de abertura ainda dizia 'login HTTP Basic em toda rota',
contradizendo a secao 'Login e permissoes' mais abaixo no mesmo
arquivo (ja correta, fala de login por sessao). Atualizada para
'login por sessao em toda rota'.
2. A lista de variaveis de ambiente para rodar localmente (secao
'Rodando localmente') nao mencionava SESSION_SECRET, mesmo a secao
'Login e permissoes' ja instruindo a defini-la. Adicionada a lista,
com nota de que e opcional em dev (chave aleatoria em memoria a cada
start, sessoes caem a cada restart) mas deve ser fixa em producao.
3. A mencao ao ALTER TABLE FUNCIONARIO_PAINEL ADD COLUMN PERFIL estava
em tom de fato passado. Ajustada para o mesmo padrao de aviso ja
usado para LOG_PAINEL/FUNCIONARIO_PAINEL nesse README ('precisa ser
rodado manualmente em producao tambem'), deixando explicito que sem
rodar isso todo login falha com 500 (a query de autenticacao
seleciona uma coluna PERFIL que nao existe na tabela antiga).
Sem teste automatizado - mudanca só de documentacao.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Perfil e status ativo vinham so do cookie assinado (session.py), entao um
funcionario desativado (ATIVO=0) ou rebaixado (ADMIN->PADRAO) continuava
com acesso total ate o cookie expirar (ate 12h) - inclusive a
/funcionarios se era admin.
exigir_login (app/auth.py) agora reconfirma contra o banco a cada
requisicao quando o login nao e o da conta de bootstrap (ADMIN_USER do
.env, que nao tem linha em FUNCIONARIO_PAINEL e continua sempre admin via
variavel de ambiente): busca db_funcionarios.obter_por_login(login); se
nao existir mais ou ATIVO != 1, levanta NaoAutenticado (forca novo
login); caso contrario, monta o UsuarioLogado com o PERFIL atual do
banco, nao o do cookie - rebaixamento tambem passa a ter efeito imediato.
Testes de regressao em tests/test_auth.py:
- test_exigir_login_autentica_funcionario_ativo
- test_exigir_login_revoga_sessao_ao_desativar_funcionario
- test_exigir_admin_usa_perfil_atual_do_banco_nao_do_cookie
- test_exigir_login_conta_bootstrap_nao_consulta_banco
Ajustou tambem test_exigir_admin_rejeita_padrao (usava um login fake sem
linha em FUNCIONARIO_PAINEL, que agora conflita com a checagem nova -
passou a usar a fixture funcionario_teste). Suite inteira roda
147 passed apos a mudanca.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
_proximo_seguro() (app/routers/auth.py) so recusava next que comecasse
com "//", mas navegadores modernos tratam "/\" como equivalente a
"//" (URL protocol-relative), entao next=/\evil.com ainda era um open
redirect explorável via link de login malicioso.
Bloqueia tambem o prefixo "/\".
Teste de regressao em tests/test_router_auth.py: POST /login com
next=/\evil.com precisa redirecionar para /clientes, nao para o host
externo.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
base.html usava {{ csrf_token }}, variavel que so algumas rotas passam
explicitamente no contexto do template. Em /clientes (GET), /log e
/gerador-chave a variavel nunca era passada, entao o campo oculto do
formulario de logout renderizava com value="" e exigir_csrf (app/csrf.py)
rejeitava o POST /logout com 403 - o usuario nao conseguia clicar em
'Sair' logo apos logar, ja que /clientes e para onde todo login cai.
Corrige na raiz: usa {{ csrf_token or request.state.csrf_token }}, caindo
para o valor que csrf_cookie_middleware (app/main.py) ja popula em toda
requisicao quando a rota nao passar csrf_token explicitamente. Protege
tambem qualquer rota futura que esqueca de passar a variavel.
Testes de regressao em tests/test_main.py: confirma que GET /clientes
renderiza um csrf_token nao vazio no formulario de logout, e que
POST /logout funciona (303) apos visitar /clientes.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
tests/test_main.py nao fazia parte do mapa de arquivos do plano de
permissoes/sessao e ficou quebrado (HTTP Basic Auth) apos a troca pra
login por sessao nas tasks anteriores. Atualiza os 6 testes pra usar
cookie de sessao (app.session.criar_token) em vez de header Basic, e
ajusta a expectativa de GET / sem login (agora redireciona 303 pra
/login em vez de pedir credenciais 401).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adiciona a segunda rotina recuperada do Chave.exe original: chave de
instalacao (8 caracteres tipicos), ao lado da chave de reativacao ja
existente na mesma tela, com um seletor de tipo.
Algoritmo (confirmado matematicamente contra os 3 exemplos reais
fornecidos, byte a byte):
- codigoCliente * 19 + 31 vira um numero inserido sem cifrar no meio
da chave final.
- A modalidade ("30"=ALUGUEL / "00"=COMPRA) e cifrada com o MESMO
algoritmo/chave interna ja usado na reativacao (soma modular + XOR),
gerando 6 hex (3 bytes: 1 aleatorio + 2 cifrados).
- Chave final = 3 primeiros hex + valor do cliente + 3 ultimos hex.
100% client-side (mesmo js puro da reativacao), nunca grava no banco.
Verifiquei os 3 exemplos (DC45092C, 2E35083D, 3D25090C) reproduzindo
o algoritmo em Python antes de trocar o JS - bateram exatamente.
106/106 testes passando.
Ao sair do campo CNPJ no formulario de cliente, busca automaticamente
na BrasilAPI (publica, gratuita, sem chave - dados abertos da Receita)
e preenche Razao/Fantasia/Endereco/Cidade/Bairro/UF/CEP/Telefone/
E-mail, so nos campos que estiverem vazios (nunca sobrescreve o que
ja foi digitado). So funciona para CNPJ - CPF e dado pessoal, sem
consulta publica, e a rota recusa (404) se detectar CPF.
- app/cnpj_lookup.py: cliente HTTP pra BrasilAPI, degrada em silencio
(retorna None) se nao encontrar/rede cair/formato nao reconhecido
(ex.: CNPJ alfanumerico novo, que a BrasilAPI pode nao cobrir ainda).
- Nova rota GET /clientes/consulta-cnpj/{documento}, chamada via
fetch() no formulario (JS vanilla, sem framework).
- Testado ponta a ponta contra a API real.
106/106 testes passando (10 novos).
Codigo, Razao, Fantasia e CNPJ agora sao links no cabecalho da tabela:
clicar ordena crescente; clicar de novo na mesma coluna alterna pra
decrescente (e vice-versa). Trocar de coluna sempre comeca em
crescente. Seta (▲/▼) indica a coluna/direcao ativa.
db_clientes.listar_clientes() ganha o parametro direcao (whitelist
asc/desc, igual ja acontecia com ordenar_por). Links de
Anterior/Proxima da paginacao preservam a direcao escolhida.
97/97 testes passando.
Antes, digitar qualquer coisa na busca comparava com CODIGO, CNPJ,
RAZAO e FANTASIA ao mesmo tempo (OR), entao escolher 'Ordenar por:
Codigo' e buscar '242' trazia tambem clientes cujo CNPJ ou Razao
continham '242' em outro lugar. Agora a busca usa so a coluna
selecionada no dropdown (mesma whitelist ja usada pra ordenacao).
listar_clientes()/contar_clientes() e o template ganharam o ajuste;
o dropdown mudou de rotulo pra 'Buscar/ordenar por' pra deixar claro
que controla os dois.
93/93 testes passando.
- Tabela FUNCIONARIO_PAINEL (nova, ja criada no banco de dev) +
app/db/funcionarios.py: nome/login/senha (hash scrypt, stdlib, sem
dependencia nova) por funcionario, nunca excluido de verdade (so
ativo/inativo).
- app/senha.py: gerar_hash/verificar_senha com hashlib.scrypt + salt
aleatorio por senha.
- app/auth.py: login passa a checar duas fontes, nessa ordem -
ADMIN_USER/ADMIN_PASSWORD (bootstrap, continua exigindo as duas
preenchidas) e depois FUNCIONARIO_PAINEL (so contas ATIVO=1). O
nome retornado (usado no log) passa a ser o nome de verdade do
funcionario, nao mais sempre 'admin'.
- Tela /funcionarios: listar, criar (com confirmacao de senha e
checagem de login duplicado), editar nome/login, redefinir senha
(form separado) e ativar/desativar. Tudo logado em LOG_PAINEL.
- Testado ponta a ponta contra o app real: funcionario criado via
tela consegue logar de verdade, e a acao aparece no /log com o
nome de quem fez.
93/93 testes passando (23 novos).
Testado contra a instancia real (devn8n.gtigrupo.com.br) com uma API
key de verdade: 'take' retorna 400 'Unknown query parameter'; o nome
certo e 'limit'. Confirmado end-to-end - /log agora mostra os envios
reais (ERRO_ENVIO, CLIENTE_NAO_CADASTRADO etc.) da Data Table
pagamentos_processados.
- Tabela LOG_PAINEL (nova, ja criada no banco de dev) + app/db/log.py:
toda criacao/edicao/exclusao de cliente e contato fica registrada
(usuario, acao, sucesso/erro, detalhe). Tela /log mostra esse
historico.
- app/n8n_client.py: leitura opcional (N8N_API_URL/N8N_API_KEY) da
Data Table pagamentos_processados do n8n, mostrando os envios
automaticos de chave na mesma tela /log. Degrada de forma segura
(lista vazia) se nao configurado ou se a chamada falhar.
- Lista de clientes ganha ordenacao configuravel (codigo/razao/
fantasia/CNPJ) via whitelist, alem da busca que ja existia.
- CODIGO duplicado agora BLOQUEIA de verdade na criacao/edicao (sem
bypass) — antes so avisava com opcao de confirmar mesmo assim.
- Formulario de novo cliente sugere o proximo CODIGO sequencial
(editavel), ignorando a faixa reservada para dados de teste
(>= 900000) para nao ser contaminado por sobra de teste no banco.
- IMPLANTACAO/MENSALIDADE/JUROS_MORA/JUROS_MULTA/DATA_CONTRATO/
VENCIMENTO_CONTRATO deixam de ser obrigatorios — em branco, gravam
default (0,00 / hoje) em vez de bloquear o cadastro. Só CODIGO/CNPJ/
RAZAO/FANTASIA continuam obrigatorios.
- Limpeza: removidas 10 linhas de teste vazadas (CODIGO 999902) que
ficaram no banco de dev por sessoes de teste interrompidas.
69/69 testes passando (16 novos).
CSRF: novo app/csrf.py + middleware em app/main.py garantem um cookie
csrf_token por navegador; todo formulario que muda estado (criar/editar/
excluir cliente, adicionar/editar/excluir contato) embute esse valor
como campo oculto, e Depends(exigir_csrf) confere no POST que o valor
enviado bate com o cookie antes de deixar a rota executar. Verificado
ponta a ponta com o app real rodando (curl): token errado -> 403,
token correto -> 303.
Paginacao: db_clientes.listar_clientes ganha offset, nova
contar_clientes(); GET /clientes aceita ?pagina=N (100 clientes por
pagina), template mostra Anterior/Proxima e o total.
53/53 testes passando (8 novos).
CLIENTE.CNPJ/TELEFONE/CELULAR agora sao gravados sempre sem pontuacao
(so digitos, e letras no caso do CNPJ alfanumerico), igual ao que
CLIENTE_CONTATO.TELEFONE ja fazia. Resolve a inconsistencia de formato
apontada na revisao final do plano (achado 7): duas tabelas do mesmo
app guardando telefone/documento em formatos diferentes.
So afeta escritas novas feitas pelo painel (criar/editar cliente) -
registros legados existentes so ficam sem mascara quando forem
reeditados e salvos aqui; nao faz backfill em massa dos dados atuais.
Adiciona UI (details/summary, sem JS) para editar nome/telefone/email
de um contato existente, usando a rota POST /clientes/{id}/contatos/{id}/editar
que ja existia no router e no db mas nunca tinha formulario associado.
Cobre com testes de rota (sucesso + telefone duplicado) e um teste
verificando que a tela de detalhe realmente renderiza o formulario.
- Fix 1 (critico): cliente_form.html agora tem required/type por campo
(date/number) e o router captura DataError/IntegrityError em
criar/editar, reexibindo o form com erro_gravacao em vez de 500.
- Fix 2 (importante): auth.py falha fechado quando ADMIN_USER/
ADMIN_PASSWORD nao estao configurados (nao tenta mais compare_digest
com string vazia).
- Fix 3 (importante): avisos de codigo duplicado e DV invalido agora
carregam a flag de confirmacao um do outro adiante, quebrando o loop
quando ambos disparam no mesmo cliente.
- Fix 4 (menor): corrige precedencia 'or 42' -> 'and 42' no lambda de
teste de criar_cliente.
- Fix 5 (menor): desabilita /docs, /redoc, /openapi.json no FastAPI().
- Fix 6 (menor): comentario explicando o valor de Prorrogacao no fixture
de teste de chaves.
- Fix 7 (menor): .env.example usa host.docker.internal por padrao,
docker-compose.yml ganha extra_hosts para Linux, README.md novo com
quickstart real.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
"authorized_by":"operator, GTI FISCAL LOCAL COMPLETION V1 mission message",
"scope":"disposable local COPIES only; exact canonical paths; never by directory or extension; expires with the mission (no production authorization)",
# Project State — GTI Fiscal (2026-09-28, fim da Missão 7)
Leitura obrigatória antes de qualquer nova missão fiscal (CLAUDE.md aponta para cá). Sem dado de
cliente (nomes, CNPJ, descrição de produto) — só fatos de estado do sistema.
> [!IMPORTANT]
> **SEPARAÇÃO RIGOROSA TESTE vs PRODUÇÃO (`DADO_DE_TESTE=true`)**:
> Todas as bases Firebird analisadas até hoje (Agent 2 e os 5 bancos de laboratório CONSTRUCAO, MMMODAS, RINCAO, UNIAO, TESTESOFICIAIS) são **dados de teste e homologação**. Nenhum cliente real passou pelo motor ainda. Qualquer métrica ou constatação levantada sobre essas bases representa validação técnica de mecanismo de software, **não achado ou diagnóstico contábil sobre nenhum cliente**.
## Versões
- **Hub (produção)**: 1.3.15 (preparado para deploy), deploy anterior 1.3.14 confirmado (`DEPLOY_OK`, health 200).
- **Agent (última release Windows PUBLICADA)**: 1.3.8 — nenhum installer novo foi publicado nas
Missões 2–7 (o Agent permaneceu 100% inalterado).
- **Agent 2** (installation `bb50884a-5e78-4f26-8cac-804932dae8de`): **1.3.8, informado pelo
operador** — ambiente de teste/homologação.
- Hub e Agent são versionados **independentemente** — ver `docs/VERSIONING.md`.
## Migration head
- **main (master)**: `0034_reference_data_registry` (33 migrações). Head única linear.
- **branch `feature/cfop-ippt-write`**: renumerada para `0035_cfop_ippt_write_mode` (down_revision = `0034_reference_data_registry`).
- **Teste de Merge Real (Missão 7 / A.1)**:
- Testado via `git merge --no-commit --no-ff feature/cfop-ippt-write` e abortado limpo.
- Head do Alembic única: `0035_cfop_ippt_write_mode (head)` (zero split estrutural no banco).
- Conflitos de arquivos git mapeados: `agent/tests/test_cfop_ippt_auto_apply.py` e `release/RELEASE_MANIFEST.json`.
## Reference Data Registry e Datasets P2–P4 (Missão 7)
- **Inventário Oficial dos 9 Datasets Existentes** (`central/app/datasets/inventory.py`):
1. NCM (Siscomex)
2. TIPI_IPI (Receita Federal)
3. CEST (CONFAZ)
4. BA_ST_MVA (SEFAZ-BA)
5. NFE_MOC_TABLES (Receita Federal / ENCAT)
6. CFOP (Portal Nacional NF-e)
7. FCP (Portal Nacional NF-e)
8. ANP (Portal Nacional NF-e)
9. NCM_UTRIB (Portal Nacional NF-e)
*(Esclarecimento A.3: `SPED_PIS_COFINS` e `REFORMA_RTC` não integravam os 9 datasets pré-existentes; pertencem ao pipeline de novos datasets mapeados na Descoberta).*
## Separação Teste vs Produção e Artefatos (Missão 7 / Bloco B)
- **Rótulo `DADO_DE_TESTE=true`**: Aplicado no card "Contexto Fiscal" da UI (`agent_detail.html`), no módulo de decisões (`fiscal_context_ui.py`), no dossiê contábil (`dossie_contador.md`) e em todos os relatórios.
- **Reclassificação dos 8 Produtos Bloqueados**: Definidos formalmente como **artefatos de teste** do banco de homologação do Agent 2 (`docs/NCM_27_BLOCKED_PRODUCTS_V1.md`), nunca achados fiscais sobre clientes reais.
- **Catálogo de Fontes de Evidência**: Formalizado em `docs/EVIDENCE_SOURCES.md`, separando as bases de teste das premissas pendentes de validação com o primeiro cliente real.
## Prontidão para o Primeiro Cliente Real (Missão 7 / Bloco C)
- **Checklist de Onboarding Seguro**: Detalhado em `docs/ONBOARDING_PRIMEIRO_CLIENTE_REAL.md` (requisitos de rede HTTPS outbound exclusiva, leitura em lote de 50-100 itens com `WAIT 5` e throttling de 50-100ms, backoff em busy, rollback instantâneo via parada de serviço/desinstalação).
- **Segurança Agent-Hub**: Tokens de instalação HMAC com expiração/revogação instantânea, rate limit por instalação, anonimização estrita em logs.
- **Modo Recomendado**: Somente leitura e DRY-RUN (sem `AUTO_FILL` ativo) pelas primeiras $N = 3$ execuções ou 7 dias de monitoramento contínuo.
- **Critérios Objetivos de Seleção**: Roteiro de 4 perguntas para decisão humana do operador (pequeno porte < 2.000 itens, Simples Nacional CRT 1 sem IPI, varejo simples com GTIN comercial, concordância do cliente).
## Endurecimento do Hub e Disaster Recovery (Missão 7 / Bloco D)
- **Segurança HTTP**: Middleware implementado em `central/app/main.py` injetando `X-Content-Type-Options: nosniff`, `X-Frame-Options: SAMEORIGIN`, `X-XSS-Protection` e `Referrer-Policy`.
- **Cobertura de Testes**: Suíte `central/tests/test_mission_07_hardening.py` adicionada cobrindo ciclo de vida do Registry (aprovação, rejeição, checklist), classificação de taxas TIPI e headers de segurança.
- **Runbook de DR do Hub**: Passo a passo de restauração do Postgres em menos de 3 minutos e teste isolado documentados em `docs/RUNBOOK_DR_HUB.md`.
## Índice de Documentos-Chave (docs/)
-`EVIDENCE_SOURCES.md` — matriz de fontes de teste vs o que falta validar com cliente real.
-`ONBOARDING_PRIMEIRO_CLIENTE_REAL.md` — checklist de onboarding, segurança e critério dry-run.
-`RUNBOOK_DR_HUB.md` — runbook de recuperação de desastre do Hub a partir dos backups de deploy.
-`REFERENCE_DATASET_MANIFEST_V1.md` — manifesto de datasets oficiais baixados (P1 a P4 com hashes e URLs).
-`DESCOBERTA_P5_P6_MISSAO_6.md` — levantamento completo de PIS/COFINS SPED e ICMS-ST para o Hub.
-`dossie_contador.md` — dossiê fiscal com aviso DADO_DE_TESTE=true.
-`NCM_27_BLOCKED_PRODUCTS_V1.md` — artefatos de teste com bloqueio de NCM.
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.