Management command para cron na VPS (refinamento §5.1): a conversao
DOCX->PDF acontece UMA vez, no SAPL (dono do OnlyOffice), fora do caminho
quente das requisicoes. Reusa _gerar_pdf_da_materia da sprint em vez de
duplicar — PDF copia os bytes, DOCX converte.
- varre materias protocoladas com texto_original; sem alvo gera, com alvo
em dia pula (idempotente), hash_origem divergente e RETIFICACAO
- retificacao ZERA o processo de assinatura (decisao do arquiteto,
19/08/2026): regenera o alvo e limpa pdf_assinado, assinatura_info,
assinado_em, assinado_por e codigo_autenticacao — mesmo efeito da rotina
remover-assinatura. Assinar texto retificado e impossivel por construcao.
- falha de conversao de uma materia loga e segue: OnlyOffice fora do ar
nao trava o ciclo, a pendencia aparece no ciclo seguinte
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
O invariante do documento unico (refinamento §5): o PDF que o SAPL assina,
o que o app exibe e o que aparece assinado sao o MESMO binario. Hoje o fork
viola isso por construcao — o PDF nasce no momento da assinatura, e dois
momentos de geracao podem produzir dois binarios.
- DocumentoParaAssinatura: o alvo persistido, OneToOne com a materia, com
sha256 do PDF e sha256 do texto_original usado na geracao (hash_origem) —
e ele que detecta retificacao (§5.1). Vive no app isolado, nao em
MateriaLegislativa: custo zero de rebase do fork, mesmo racional do
AnexoProposicao.
- AssinaturaRecebida: dedupe do POST de assinaturas, padrao do
EventoRecebido; guarda o hash respondido para a reentrega devolver a
mesma resposta, e marca a origem da gravacao (anti-eco §5.1).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Correcao de semantica (decisao do arquiteto, 17/08/2026): o que o vereador anexa
no app e evidencia da demanda — foto da rua, video da indicacao — nao o
documento legislativo. O codigo gravava o anexo como texto_original, o que faria
um video virar "texto" da proposicao.
- AnexoProposicao (app isolado, custo zero de rebase): arquivo + nome + mime +
tamanho + sha256 DOS BYTES RECEBIDOS — o SAPL declara o que guardou, fechando
a cadeia app -> hub -> legislativo. So gravacao por ora; consulta vem depois.
- limite de 1 arquivo removido (premissa errada); foto e video convivem
- texto_original volta a ser exclusivo do fluxo proprio do SAPL (tipo_texto '')
De quebra, conserta flakiness pre-existente dos testes: o baker criava usuario
com username de 150 chars e o auditing do SAPL grava em varchar(100) — o INSERT
do AuditLog estourava DENTRO do atomic e envenenava a transacao inteira.
Username explicito nos fixtures.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
O hub nao guarda referencia por tramitacao — ele guarda a referencia da MATERIA.
Devolvendo so o id, o hub comparava id de tramitacao com id de materia e contava
o acervo inteiro como lacuna: em 14/08 foram 500 "ausentes" que eram historico
que a v1 decidiu nao integrar.
A materia e o que diz se aquela tramitacao pertence a algo que o hub deveria ter
integrado. Sem ela a conferencia nao tem como distinguir lacuna de fora-de-escopo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Implementa o lado SAPL do refinamento da reconciliacao.
INVENTARIO — GET /api/integracao/reconciliacao/?id_gt=&tramitacao_id_gt=
devolve apenas IDS de proposicoes e tramitacoes acima do corte, com a flag
`truncado` avisando quando a faixa encheu. E lista de conferencia, nao
segunda via dos dados: o hub compara com o que conhece e descobre o que
ficou de fora. Corte por ID porque Proposicao NAO tem campo de criacao — as
datas que existem sao de estado (envio, recebimento, devolucao) e nenhuma
responde "quando apareceu". Mesmo filtro dos polls (cancelado=False), senao
a reconciliacao apontaria canceladas como ausentes para sempre.
KEYSET (data, id) nos polls por data — corrige o travamento do refinamento
§1.1: com cursor so de data e >=, uma pagina inteira de registros com o
MESMO timestamp devolvia sempre o mesmo cursor, ele nao avancava e a fonte
relia a mesma pagina para sempre, sem erro e sem nunca progredir. Agora
id_gt desempata dentro do instante.
TESTES: 9 novos (inventario + o cenario exato do travamento). Corrigidos
tambem tres defeitos das fixtures existentes, que nunca haviam rodado:
Token.objects.create colidia com o token que sapl/api/signals.py:8 cria no
post_save; TipoProposicao exige content_type; e Tramitacao sem status
quebrava a asercao. Resultado: 25 de 29 passam.
Os 4 que faltam sao de recepcao e falham por problema interno do
sapl.rules em banco de teste novo (ContentType matching query does not
exist), NAO por este diff — na versao da dev, sem estas mudancas, o mesmo
arquivo da 10 erros. O caminho de recepcao esta provado em producao
(proposicao 1071, criada pela ida em 14/08).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Dois achados do teste manual de integracao (13/08, ambiente real):
1. select_related('materia_gerada') estourava FieldError — o campo NAO
existe no modelo: o bloco `materia_gerada = models.ForeignKey(...)` do
materia/models.py esta DENTRO de uma docstring (codigo morto). O vinculo
real e a generic FK conteudo_gerado_related (content_type + object_id),
que aponta para MateriaLegislativa OU DocumentoAcessorio — so a materia
interessa ao contrato. Sem isso, ProtocoloGerado nunca sairia.
2. O commit do autor_nome (ebf0fac) ficou fora do merge do PR #4 e o campo
sumiu do poll. Sem ele o acervo perde o nome do autor nao mapeado, que e
a regra do refinamento do historico §3.2. Reaplicado.
Verificado no ambiente real: poll devolve as proposicoes do acervo com
autor_nome e rascunho corretos.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
O ProposicaoCadastrada/ProtocoloGerado do contrato pedem descricao (e sigla,
na materia) do tipo; id sozinho obrigaria o hub a segunda consulta.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Componente A da spec (POST /api/integracao/proposicoes/): TokenAuthentication
com permissao propria, criacao pelo ProposicaoForm com autor fixado na
instancia antes do save (a numeracao usa a sequencia do autor), proposicao
nasce rascunho, dedupe por EventoRecebido (reentrega devolve 200 com a
proposicao existente), throttle auto-contido.
Componente B pelo fallback nomeado na spec §4: o get_queryset do
_ProposicaoViewSet nao expoe rascunho ao usuario de integracao, entao os
cinco polls (cadastradas/enviadas/recebidas/devolvidas por cursor proprio e
tramitacoes insert-only) vivem no app isolado, com ordenacao estavel
(data, id) para o cursor do hub.
Toque no core: duas linhas (INSTALLED_APPS e include no urls) — custo de
rebase contra o Interlegis proximo de zero, principio 1 da spec.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CLAUDE.md/AGENTS.md agora apontam para knowledge/AGENTS.md com regras
anti-alucinação explícitas (vale para Claude Code, Codex e Cursor):
não inventar regra/contrato, dizer quando não está documentado, citar o
doc governante, knowledge/ prevalece sobre doc local divergente.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Documentacao oficial em knowledge/ (submodule de docs-ia-projects, branch
main). Path knowledge/ e nao docs/ porque tres repositorios ja tem docs/
proprio ainda nao migrado.
Clone: git clone --recurse-submodules
Atualizar: git -C knowledge pull origin main && git add knowledge
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A borda era desenhada no TextBoxStyle (tamanho do texto), ficando menor
que o bloco. Movida para o TextStampStyle (tamanho da annotation box)
para coincidir com os blocos do ReportLab. Texto alinhado topo-esquerdo.
O parâmetro avg_width do SimpleFontEngineFactory estava com valor 600
(unidades glyph space) quando o pyhanko espera fração do font_size (0.5).
Isso causava largura natural do texto de ~105.000pt, e o SHRINK_TO_FIT
reduzia a escala para ~0.2%, tornando o texto invisível nos stamps
das assinaturas subsequentes.
- Adicionar validador combinado (documentos + imagens) para DocumentoAcessorio
- Criar modelo AnexoProposicao para anexos durante edição de proposição
- Adicionar upload drag-and-drop na lista de Documentos Acessórios da matéria
- Adicionar seção de anexos na tela do editor OnlyOffice de proposição
- Transferir anexos automaticamente para DocumentoAcessorio ao incorporar proposição
- Corrige fonte dos stamps subsequentes de Courier para Helvetica
- Elimina borda dupla (border_width=0 no outer, 1 no inner)
- Substitui y_base=500 fixo por grid dinâmico baseado nas dimensões da página
- Unifica col_width entre ReportLab e pyhanko (era 70mm vs calculado)
- Footer (código, QR, URL, aviso legal) em posição fixa no rodapé (68mm)
- Adiciona Hash do documento abaixo da Data em cada bloco de assinatura
- Suporta até 14 assinaturas por página de autenticação
Signed-off-by: rangelbruno <rangelbruno@users.noreply.github.com>
Assinaturas após a primeira usavam a aparência padrão do pyhanko
em vez do template customizado da página de autenticação. Agora
usa PdfSigner com TextStampStyle para manter o visual consistente.
Remove a página de documentação customizada (/api/doc/) e adiciona
card de atalho para Swagger UI e ReDoc na tela inicial do administrador.
Corrige ReDoc em branco fixando versão estável do CDN.
Adiciona /api/doc/ com documentação auto-gerada de todos os endpoints
REST, incluindo testador interativo com suporte a GET, POST, PUT, PATCH
e DELETE, autenticação por token e busca por endpoints.
Adiciona fluxo intermediário de revisão pelo Setor Legislativo antes do
envio ao Protocolo, protegido por feature flag (desabilitado por padrão).
- Novos campos: data_envio_setor, data_retorno_setor, usuario_envio_setor,
usuario_retorno_setor, observacao_setor em Proposicao
- Flag revisao_setor_legislativo em AppConfig (Sistema > Configurações)
- Novo grupo "Operador do Setor Legislativo" com permissões específicas
- Ações send_setor e return_from_setor no DetailView
- Views ProposicaoPendenteSetor (inbox) e RevisarProposicaoSetor
- OnlyOffice: setor pode editar documento quando proposição está com eles
- Status S (Enviada ao Setor) e V (Revisada pelo Setor) no histórico
- Templates: badges, alertas, botões condicionais e subnav
- Fonte Arial forçada em todos os slots XML (ascii, hAnsi, eastAsia, cs)
nos documentos criados via python-docx e na numeração de matéria
- Substituir alert() nativo por SweetAlert2 nos 3 templates do editor
- Corrigir beforeunload que disparava sem alterações no documento
- Substitui carimbo sobreposto na última página por página dedicada de
autenticação no final do PDF com blocos de assinatura, código de
autenticação (SHA-256 truncado) e QR Code
- Remove exposição de CPF do PDF e da interface web
- Adiciona campo codigo_autenticacao em MateriaLegislativa e
DocumentoAcessorio com migration
- Adiciona views públicas de verificação (/materia/<pk>/verificar/ e
/materia/documentoacessorio/<pk>/verificar/) sem exigir login
- Cria template verificar_assinatura.html com status verde/vermelho
- Adiciona botão "Verificar Autenticidade" nos templates de detail
- Corrige lookup de cargo do assinante via Autor.operadores
- Corrige URL de download para OnlyOffice usando build_onlyoffice_url
- Melhora layout dos botões de ação na página de detail da matéria
- Adiciona dependência qrcode[pil]==7.4.2
Permite que múltiplos co-autores assinem o mesmo documento sequencialmente.
O primeiro assinante usa o fluxo atual (ReportLab + pyhanko), e os subsequentes
adicionam assinatura incremental preservando as anteriores. Template mostra
botões "Ver PDF Assinado" e "Assinar" simultaneamente, e o card lista todos
os assinantes. Backward-compatible com assinatura_info legado (dict → lista).
Co-Authored-By: rangelbruno <rangelbruno@users.noreply.github.com>
Adiciona opção de baixar matéria + acessórios mesclados em um único PDF,
além do ZIP já existente. Reorganiza botões da página de detalhe com
flex-wrap responsivo e cores distintas para cada ação.
Permite baixar ZIP com o PDF da matéria (assinado ou original) junto
com todos os documentos acessórios. Botão aparece na página de detalhe
e na pesquisa de matérias quando há documentos acessórios cadastrados.
Adiciona verificação de signer None antes de acessar signing_cert,
evitando erro 'NoneType object has no attribute signing_cert' ao
assinar matéria e documento acessório com certificado inválido.
Signed-off-by: rangelbruno