O operador de protocolo decidia incorporar ou devolver sem conseguir ver os
anexos: o template confirmar_proposicao.html so expunha o texto original e o
editor OnlyOffice.
O timing era o pior possivel. Os anexos ja existem na hora da incorporacao — o
upload e bloqueado depois do envio — mas so viram DocumentoAcessorio durante o
ConfirmarProposicaoForm.save(), que converte cada AnexoProposicao e em seguida
os apaga. Ou seja, o operador so enxergava o que precisava para decidir depois
de ja ter decidido.
Adiciona um dropdown "Ver Anexos (N)" na barra de acoes, um item por anexo com
nome e tipo. Abre em nova aba de proposito: a tela e um formulario de
incorporacao/devolucao preenchido pelo operador, e navegar para fora perderia o
que ele digitou.
O link aponta para a URL de midia, igual ao botao "Texto Original da
Proposicao" ao lado. Nao reaproveita a view proposicao_texto porque a regra
dela negaria justamente o operador de protocolo: exige ser operador do autor
enquanto data_recebimento for nulo.
Fica fora, conscientemente: conversao de anexo nao-PDF para PDF, e o acesso
anonimo a /media/ servido pelo nginx — este ultimo merece tarefa e ADR
proprios, por afetar todas as telas e todas as camaras.
Inclui o bump do submodule knowledge/ para origin/main, exigido pelo
pre-commit da ADR-0012.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ADR-0015, fecha o incidente da IND 826/2026: em producao (fora de docker)
o laco dependia de alguem lembrar de subi-lo. Agora o laco grava batimento
no banco a cada tick (inclusive durante varredura longa) e um middleware
no processo web — o unico processo garantido, ja que o hub polla a API o
tempo todo — o ressuscita quando o batimento envelhece (checagem 1x/min
por worker, processo desgarrado, nunca custa um request).
Guarda de instancia unica: laco que encontra batimento fresco de outro
pid sai na hora, entao N workers ressuscitando juntos e inofensivo.
Deploy: mtime do comando mudou -> laco se encerra e renasce atualizado.
Painel mostra o batimento (vivo/pid/quando); supervisor vira redundancia
opcional (MATERIALIZACAO_AUTOSSUPERVISAO=False para quem gerir por fora).
Ponteiro do knowledge/ atualizado (ADR-0012).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
O SAPL de producao roda fora de docker; o start.sh do container nunca vale
la e o laco dependia de alguem lembrar de subir — o gap que deixou a IND
826/2026 sem materializar. O conf do supervisor (mesmo mecanismo do
gunicorn) da boot + autorestart, e o atualizar_producao.sh passa a
reiniciar o programa a cada deploy, avisando alto se ele nao existir.
Ponteiro do knowledge/ atualizado junto (ADR-0012).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fecha o gap entre o protocolo e a pendencia no app (ADR 0014): o post_save
de MateriaLegislativa grava uma marca leve (MateriaParaMaterializar) e o
laco, que agora acorda a cada --tick (15s), converte so as marcadas entre
uma varredura completa e outra (--intervalo, 300s, rede de seguranca).
O request nunca converte (dono unico da conversao, refinamento 5.1), o
lock unico de passada segue valendo para laco, tick e botao, e o painel
mostra a fila aguardando o proximo tick.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A falha desta rotina e MUDA: materia protocolada nao vira pendencia no app e nao
ha erro em lugar nenhum. Em 25/08 descobrir isso custou meia hora, o shell da VPS
de outra pessoa e tres idas e voltas com o operador para ler UMA variavel de
ambiente. Os numeros que respondiam a pergunta ja eram todos calculados pela
rotina — e morriam no stdout de um processo que ninguem lia.
PassadaMaterializacao / MateriaComFalhaMaterializacao
Uma linha por passada, com os contadores que ja existiam. Tabela vazia tambem
e diagnostico: em 25/08 a resposta certa era "nunca rodou neste servidor", e
nem isso era possivel saber. A tabela de falhas e por MATERIA e nao por
tentativa — com um acervo inteiro falhando a cada ciclo, historico afogaria o
caso interessante, que e a materia que falha sozinha.
em_andamento como lock de banco, nao checagem de aplicacao
NullBooleanField(unique=True) com dois valores: True rodando, NULL terminada,
nunca False. Em Postgres NULL nao colide em indice unico, entao o banco garante
no maximo uma passada em curso. Checagem na aplicacao nao daria isso: laco e
botao sao processos distintos e entre o SELECT e o INSERT cabe a corrida.
Consequencia tratada: processo morto deixaria o lock presos para sempre — a
MESMA falha muda que este painel existe para acabar. Passada aberta ha mais de
6h e fechada como abandonada pela seguinte, com os contadores marcados.
O botao dispara PROCESSO, nao thread nem flag
O refinamento propunha o botao gravar uma solicitacao lida pelo laco. Isso nao
funciona em Franco: la nao ha laco, porque o start.sh so roda em Docker e
aquele servidor nao usa Docker. Popen desacoplado (start_new_session) funciona
com ou sem laco. Thread esta descartada por dois motivos: a passada leva perto
de uma hora e morreria com o worker do gunicorn, e N workers dariam N passadas
convertendo a mesma materia.
Sempre em --somente-novos, e isso nao e configuravel de proposito: sem a flag o
comando entra em retificacao e ZERA assinatura ja feita. Decidido para o ato
isolado de retificar um texto, nunca para varredura por um clique.
Diagnostico de -4 no _relatar_motivos
So existia ramo para -8 (JWT). O -4 e o oposto: nao e token, e DOWNLOAD — o
servidor do OnlyOffice nao alcanca a URL que o SAPL entrega. A leitura natural
("a URL deve estar errada") manda conferir do jeito errado, porque a URL
funciona de dentro; _origem_servida_confere baixa do proprio SAPL e por isso
passa limpo. Em 25/08 eram 861 falhas com SAPL_INTERNAL_URL num endereco que so
existia dentro da VPS.
Menu por caminho literal, com teste
O resolvedor de menus prefixa nome de rota sem ':' com o app da pagina
(sapl.base aqui) e levantaria excecao derrubando a tela inteira de Tabelas
Auxiliares. O ramo de caminho literal passa direto, e
test_menu_aponta_para_a_rota_do_painel compara o literal com a rota para os
dois nao divergirem em silencio. O YAML aceita check_permission, entao o item
so aparece para quem tem pode_integrar.
.gitignore: .env.bak*
O padrao cobria .env mas nao os backups que o script de deploy gera, e eles
carregam SECRET_KEY, DATABASE_URL e ASSINATURA_API_KEY em claro.
14 testes verdes (pytest sapl/integracao_hub/tests/test_painel.py).
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
`serializar_materia_assinada` unificava a CHAVE (`data` ou `data_assinatura`),
nao o FORMATO. `data_assinatura` e gravado em %d/%m/%Y em seis pontos de
views_assinatura e no receiver da propria integracao — 1198 de 1207 registros
do acervo medido em 22/08/2026. O consumidor parseia como ISO, estourava, e o
ciclo inteiro do poll morria com o cursor parado: a fonte
sapl:assinaturas-concluidas ficou congelada desde as 18:01.
O laco era fechado: cada assinatura feita PELO APP envenenava a fonte que
contaria ao app que ela aconteceu.
Normaliza na fronteira de serializacao, e nao nos pontos de gravacao, porque
`data_assinatura` alimenta a tela de verificacao publica (views_assinatura:864,
2096, 2136): mudar o que se grava mudaria o que o cidadao ve e obrigaria a
migrar 964 materias. Um ponto so conserta o acervo inteiro sem tocar em dado.
Data ilegivel sai como null em vez de estourar — o consumidor ja recai em
`assinado_em`.
Refinamento: engineering/architecture/Assinatura_Volta_Concluidas_Refinamento.md
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Assinar pelo app uma materia ja assinada no SAPL apagava a assinatura anterior.
Sem erro nenhum: o `assinatura_info` seguia dizendo duas, e o PDF tinha uma.
Por que acontece. O encadeamento do amu-backend (ADR-0013) procura a pendencia
irma ASSINADA no banco DELE. Assinatura feita no SAPL nao cria irma la — ela
chega pelo evento `DocumentoAssinado`, e ate ele chegar o app recai no alvo em
branco. O micro entao ve zero assinaturas, compoe uma pagina nova, e o receiver
daqui SOBRESCREVE o `pdf_assinado` com esse PDF de uma assinatura so.
Com dado real (IND 820/2026): Kinho assinou as 16:01, o cursor de
assinaturas-concluidas do hub estava em 15:01 e o processo, parado. As duas
pendencias no AMU seguiam PENDENTE com `assinadoDocumentoKey` nulo. A janela
nao e teorica.
O que muda aqui:
1. O receiver conta as assinaturas no BINARIO que chega e compara com as
registradas. Um PDF que substitui N assinaturas precisa trazer pelo menos
N+1; menos que isso e fork, nao encadeamento → 409 e nada e gravado. A
pendencia continua de pe e o app reassina sobre o documento certo, que e o
desfecho correto. Perder assinatura em silencio nao e.
A conta que decide e a do artefato, nao a do banco: `assinatura_info` e o
que o SAPL ACHA que o documento tem, o PDF e o que ele TEM. Contagem
impossivel (PDF ilegivel) nao vira recusa — so barra o que PROVA o fork.
2. A pendencia passa a se descrever inteira: `assinaturas_existentes`,
`codigo_autenticacao` e `documento_encadeado` (URL + hash do pdf_assinado
corrente). Assim o consumidor acerta sem depender de ter processado o evento
anterior. `documento` continua sendo o ALVO BASE de proposito — e contra ele
que o receiver confere `hash_alvo_esperado` na retificacao (§5.1). Campos
aditivos: consumidor antigo ignora e segue como antes.
O par desta correcao esta no amu-backend (`assinaturaEncadeada` passa a olhar a
propria pendencia). Emenda registrada na ADR-0013.
De quebra, a assercao obsoleta em `test_grava_assinatura_no_formato_da_sprint`:
ela cobrava `info['nome']`, chave que o receiver deixou de gravar em 93af2c45
quando passou a usar as chaves da rotina nativa (ADR-0013 §4). O teste estava
vermelho desde entao.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* Fix(Assinatura): 2a assinatura reenvia o codigo ja emitido, e para de tomar 400 AB#1473
O microservico recusava toda segunda assinatura:
HTTP 400: O PDF ja tem assinatura: informe 'codigo_autenticacao' (o codigo
emitido na primeira assinatura, devolvido no header X-Codigo-Autenticacao).
Ele nao pode ser recalculado a partir do PDF assinado.
Ele estava certo. A exigencia esta escrita na C2 desde o refinamento
(`Convergencia_Assinadores_C1_Parametros.md`, "o PDF muda ao ser assinado; o
hash de agora nao reproduz o codigo impresso na pagina") e o helper daqui ja
tinha o parametro para atender: `hash_doc`.
O que faltava era o fio. Nenhuma das quatro chamadas de
`_assinar_pdf_com_pagina_auth` passava `hash_doc`, entao a linha
codigo_autenticacao=(hash_doc or '') if ja_tem_pdf_assinado else None
mandava string vazia justamente quando o codigo era obrigatorio. O valor
sempre esteve no banco (`materia.codigo_autenticacao`, gravado na 1a
assinatura) — ninguem o lia de volta.
Regressao da C3 (917bdba9). Antes dela a 2a assinatura ia por
`assinar_pdf_via_api` com coordenada explicita e sem `auth_page`, caminho em
que o micro nao pede o codigo; a C3 mandou o fluxo normal para `auth_page=auto`
sem ligar essa ponta. Nao e bug do microservico nem da delegacao — e a
implementacao do contrato pela metade.
Junto vai o `hash_doc = ''` que o backend local fazia por cima do valor
recebido do chamador: ele apagava o "Hash:" do carimbo exatamente na assinatura
em que o codigo ja existe para ser impresso.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Fix(Assinatura): pendencia e por autor — coautor que nao assinou nao some da lista AB#1473
IND 820/2026, dois autores: Kinho assinou, e a materia sumiu das pendencias do
Eric — que ainda precisa assinar. No app do AMU ela continuava la. As duas
telas discordavam, e a que escondia era a do sistema onde a assinatura
acontece.
A regra certa ja existia no repo, no `integracao_hub` (`_autores_pendentes`),
com teste e tudo:
pendente(autor, materia) = autor em autoria
e titular(autor) nao em assinatura_info.signed_by
O SAPL web perguntava outra coisa, em quatro lugares: "o documento tem
`pdf_assinado`?". Pendencia por DOCUMENTO. Na primeira assinatura o campo
preenche e a materia sai da lista de todo mundo. O `Assinatura_Pelo_App_
Refinamento.md` §2 ja tinha apontado isso como "provavel bug latente da
sprint" e deixado a validacao em aberto — esta e a resposta.
A regra vira fonte unica em `sapl/materia/pendencias.py`, e o hub reexporta de
la em vez de manter a segunda copia. Duas copias da mesma regra foi como
chegamos aqui: a do hub evoluiu, a do web nao, e ninguem viu ate um coautor
reclamar. Consumidores migrados: badge do menu, tela de pendentes, filtro da
pesquisa, modal do lote e o e-mail diario.
Sem autor no contexto (pesquisa livre com "Status de Assinatura: pendente")
nao ha a quem atribuir a pendencia. Ai vale a forma agregada `assinaturas <
max(autores, 1)`, em SQL com guarda de tipo no jsonb. O `max(..., 1)` preserva
o comportamento antigo para materia sem autoria cadastrada, que sem nenhuma
assinatura continua aparecendo em vez de sumir da pesquisa.
Tres coisas que apareceram no caminho e entram junto:
- `OperadorAutor.objects.get(user=...)` estourava MultipleObjectsReturned para
o assessor de mais de um vereador. Como a chamada estava dentro de um
`except Exception`, o badge zerava em silencio em vez de somar os dois.
- A invalidacao de cache so limpava a chave de quem assinou. Mas esta
assinatura muda a contagem dos OUTROS coautores, que ficavam com o numero
velho ate o TTL.
- `DocumentoAcessorio` pendente filtrava so `pdf_assinado=''` e deixava passar
os NULL (todos os anteriores ao campo existir). Doc acessorio nao tem
autoria, entao ali a pendencia por documento esta certa — o que faltava era
cobrir os dois estados de "vazio".
Verificado no banco de Franco: IND 820/2026 volta como pendente para o Eric e
nao para o Kinho, com badge, tela, pesquisa, lote e e-mail contando os mesmos
9 itens. 10 testes novos, incluindo o titular indeterminavel e o
`assinatura_info` no formato legado (dict em vez de lista).
O fluxo A3 (token) segue recusando materia com `pdf_assinado` — nunca suportou
multiassinatura. Limitacao anterior a esta correcao, nao tratada aqui.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
A pagina de verificacao le nome_assinante/cargo/data_assinatura/
tipo_certificado_display; o receiver do hub gravava nome/data e omitia cargo,
entao a assinatura pelo app aparecia sem nome/cargo/data na verificacao
enquanto a nativa aparecia. Alinha as chaves a rotina nativa do SAPL,
derivando o cargo do autor. Decisao em ADR-0013.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
`assinaturas-pendentes` era keyset puro por `materia_id`. A materializacao e
justamente o passo que pode acontecer MUITO depois do protocolo: alvo gerado
hoje para materia antiga nasce com id abaixo do cursor e nunca mais e lido —
sem erro, sem WARN, e a reconciliacao cobre proposicao/tramitacao, nao
assinatura.
Em Franco isso nao era caso de borda, era o acervo: em 22/08/2026, 861 materias
DOCX esperando a conversao voltar a funcionar, TODAS com id < 1076, contra um
cursor em 1078. No dia em que o OnlyOffice convertesse, as 861 materializariam
de uma vez e sumiriam todas — caladas.
Agora o keyset e `(gerado_em, materia_id)`, o mesmo de `assinaturas-concluidas`:
quem materializa tarde entra pela DATA, nao pelo id. O desempate por id impede
que uma passada de recuperacao inteira, com timestamps empatados, releia a
mesma primeira pagina para sempre (§1.1).
`gerado_em` e `auto_now`, entao a retificacao reapresenta a materia sozinha —
que e o comportamento desejado de qualquer forma: o app precisa saber que o
alvo mudou. O dedupe do hub (marcador = hash do documento) mata a releitura do
mesmo estado.
`desde` ausente mantem o keyset antigo por id, para o hub da versao anterior
seguir funcionando durante a subida.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* Fix(Assinatura): falha em massa vira uma linha legivel e URL de outra instancia nao converte AB#1473
Duas lacunas que o acervo real de Franco expos em 22/08/2026, as duas sobre a
materializacao falhar sem que ninguem consiga agir.
1. FALHA EM MASSA AGREGADA POR MOTIVO
`2 gerados, 873 falhas` passava por linha de rotina, e as 873 eram todas o MESMO
erro — 873 logger.error dispersos que ninguem le. O aviso anterior so disparava
com ZERO gerados, entao duas materias PDF passando escondiam o lote inteiro de
DOCX parado. Agora o motivo dominante sai numa linha com a contagem. Uma falha
isolada continua sem gritar: materia podre avulsa e ruido esperado (§5.1).
Junto vai a dica que evita consertar a variavel errada: `codigo -8` do OnlyOffice
e erro de TOKEN, nao de URL. Eu mesmo li errado primeiro e mandei configurar
SAPL_INTERNAL_URL. JWT desligado AQUI e exatamente o que produz -8 quando o
SERVIDOR do OnlyOffice exige assinatura — provado com POST direto ao
ConvertService, sem token, com URL publica respondendo 200: devolve -8. Se fosse
download quebrado seria -4. A dica so aparece com ONLYOFFICE_JWT_ENABLED=False;
com JWT ligado o -8 e outra coisa e a dica viraria pista falsa.
2. CONFERENCIA DA ORIGEM SERVIDA
`SAPL_INTERNAL_URL` e configuracao de operador e nao ha de onde deduzi-la: nao ha
contrib.sites, ALLOWED_HOSTS e ['*'] e no cron nao existe request. E, mais a
fundo, so quem opera sabe qual URL o servidor do OnlyOffice alcanca. O risco nao
e ela ser fixa — e ela apontar para OUTRA instancia em silencio: o hash_origem
sai do arquivo local e o PDF-alvo do arquivo do outro SAPL. Como e esse hash que
dispara a retificacao (§5.1), o alvo defasado nunca mais e regenerado e assina-se
um PDF que nao corresponde ao texto da materia.
Baixar a propria URL e comparar o sha256 fecha isso sem adivinhacao: seja qual
for o valor configurado, so passa se servir ESTE documento. Rodado contra o
ambiente local com SAPL_INTERNAL_URL apontando para demo.legisinc.com.br, pegou
na hora — materias 1066 e 1072 sao documentos diferentes nas duas instancias.
So o caminho DOCX confere; PDF copia bytes e nao toca o OnlyOffice.
Testes: 70 passed em sapl/integracao_hub/tests/ (eram 64).
* Fix(Assinatura): falha em massa diz o motivo, e a varredura nao apaga assinatura AB#1473
O #17 fez a materializacao recusar rodar sem base URL. Faltava o resto: com base
URL configurada e o OnlyOffice recusando a conversao, as 861 falhas de Franco
voltavam a ser 861 logger.error dispersos, e o aviso de lote so disparava quando
NENHUM PDF-alvo era gerado — bastava um dos 21 PDF passar na mesma passada para
o alerta sumir.
Tres mudancas:
1. As falhas sao agrupadas por MOTIVO e o motivo dominante grita. `873 falhas`
nao diz nada; `873 de 873 falhas pelo MESMO motivo: OnlyOffice codigo -8` diz
que o ambiente esta parado. Nao depende mais de zero gerados.
2. O -8 ganha o diagnostico certo. A leitura natural (URL ruim) manda consertar
a variavel errada: -8 e erro de TOKEN. Com `ONLYOFFICE_JWT_ENABLED=False` e o
servidor do OnlyOffice exigindo JWT, e exatamente esse o codigo. Provado em
22/08/2026 com POST direto ao ConvertService.ashx: URL publica respondendo
200, sem token, devolve -8 (download quebrado seria -4).
3. `--somente-novos`: gera o alvo AUSENTE e nunca retifica. Zerar assinatura em
retificacao (§5.1) foi decidido para o ato isolado de retificar um texto —
quem retifica sabe o que esta desfazendo. Numa passada sobre o acervo inteiro
ninguem pediu isso, e apagar assinatura e irreversivel. A decisao acontece
ANTES da conversao: adiar depois seria pagar o OnlyOffice para jogar fora.
Junto vai `_origem_servida_confere`: antes de gastar a conversao de um DOCX,
baixa a propria URL entregue ao OnlyOffice e compara o sha256 com o texto lido.
SAPL_INTERNAL_URL apontando para OUTRA instancia gera um PDF-alvo que nao
corresponde ao texto da materia — e como e o `hash_origem` que dispara a
retificacao, o alvo defasado nunca mais seria regenerado.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Duas lacunas que o acervo real de Franco expos em 22/08/2026, as duas sobre a
materializacao falhar sem que ninguem consiga agir.
1. FALHA EM MASSA AGREGADA POR MOTIVO
`2 gerados, 873 falhas` passava por linha de rotina, e as 873 eram todas o MESMO
erro — 873 logger.error dispersos que ninguem le. O aviso anterior so disparava
com ZERO gerados, entao duas materias PDF passando escondiam o lote inteiro de
DOCX parado. Agora o motivo dominante sai numa linha com a contagem. Uma falha
isolada continua sem gritar: materia podre avulsa e ruido esperado (§5.1).
Junto vai a dica que evita consertar a variavel errada: `codigo -8` do OnlyOffice
e erro de TOKEN, nao de URL. Eu mesmo li errado primeiro e mandei configurar
SAPL_INTERNAL_URL. JWT desligado AQUI e exatamente o que produz -8 quando o
SERVIDOR do OnlyOffice exige assinatura — provado com POST direto ao
ConvertService, sem token, com URL publica respondendo 200: devolve -8. Se fosse
download quebrado seria -4. A dica so aparece com ONLYOFFICE_JWT_ENABLED=False;
com JWT ligado o -8 e outra coisa e a dica viraria pista falsa.
2. CONFERENCIA DA ORIGEM SERVIDA
`SAPL_INTERNAL_URL` e configuracao de operador e nao ha de onde deduzi-la: nao ha
contrib.sites, ALLOWED_HOSTS e ['*'] e no cron nao existe request. E, mais a
fundo, so quem opera sabe qual URL o servidor do OnlyOffice alcanca. O risco nao
e ela ser fixa — e ela apontar para OUTRA instancia em silencio: o hash_origem
sai do arquivo local e o PDF-alvo do arquivo do outro SAPL. Como e esse hash que
dispara a retificacao (§5.1), o alvo defasado nunca mais e regenerado e assina-se
um PDF que nao corresponde ao texto da materia.
Baixar a propria URL e comparar o sha256 fecha isso sem adivinhacao: seja qual
for o valor configurado, so passa se servir ESTE documento. Rodado contra o
ambiente local com SAPL_INTERNAL_URL apontando para demo.legisinc.com.br, pegou
na hora — materias 1066 e 1072 sao documentos diferentes nas duas instancias.
So o caminho DOCX confere; PDF copia bytes e nao toca o OnlyOffice.
Testes: 70 passed em sapl/integracao_hub/tests/ (eram 64).
* Fix(Assinatura): materializacao recusa rodar sem base URL em vez de falhar 861 vezes AB#1473
Achado num acervo real em 22/08/2026: 0 de 861 materias DOCX tinham PDF-alvo, e
o motivo nao aparecia em lugar nenhum. So os 7 PDF passavam — PDF nao converte,
copia bytes — e davam a impressao de que a rotina estava viva.
A conversao DOCX->PDF e o OnlyOffice quem faz, e para isso ele BAIXA o documento
de origem por URL absoluta. `build_onlyoffice_url` prefere SAPL_INTERNAL_URL e so
cai no request sem ela; no cron o "request" e o `_RequisicaoDeSistema`, que monta
a partir de SITE_URL. Com as duas vazias a URL sai SEM HOST, o OnlyOffice
responde erro e cada materia vira um logger.error individual. O resumo da passada
(`0 gerados, 873 falhas`) nao se distingue de um dia normal no meio do log.
Duas mudancas, as duas sobre visibilidade:
1. Erro de CONFIGURACAO morre no comeco, alto: sem SAPL_INTERNAL_URL nem
SITE_URL o comando levanta CommandError antes de varrer o acervo, dizendo
qual variavel falta e por que. Vale para a passada unica e para o laco.
2. Passada que falha sem NENHUM gerado grita no stderr: falha em todas nao e
documento podre avulso, e ambiente parado (OnlyOffice fora, URL que ele nao
alcanca, MEDIA sem os binarios).
A fixture `base_url_configurada` entra em autouse no test_materializacao: base
URL e pressuposto de toda materializacao, nao caso de borda — a ausencia dela tem
os seus proprios testes.
Testes: 64 passed em sapl/integracao_hub/tests/ (eram 61).
* feat(assinatura): pendencia emite URL de verificacao e casa legislativa AB#1473
Sem estes dois campos o AMU assina SEM a pagina de autenticacao, e o documento
sai divergente do assinado no SAPL — exatamente o que a convergencia dos
assinadores elimina. Achado testando a assinatura pelo app: o microservico
respondeu 400 "verification_url_base vazio.".
So o SAPL sabe a URL publica desta casa e o nome dela, entao os campos precisam
viajar COM a pendencia. O amu-backend ja os consome (grava em
`casa_legislativa`/`verification_url_base` e repassa ao /sign); hoje chegavam
vazios porque a fonte nao os emitia, e o fallback por tenant_settings tambem
nao estava configurado.
Reusa `_construir_url_verificacao_base` e `_obter_nome_casa_legislativa` de
views_assinatura em vez de remontar a URL aqui: sao a forma canonica que o
proprio SAPL usa ao chamar o microservico. Duplicar a montagem e o caminho
conhecido para as duas divergirem na primeira mudanca. Sem ciclo de import
(views_assinatura nao importa integracao_hub).
Verificado no container sapl-localhost:
- 23 testes de integracao_hub/tests/test_assinatura.py passando
- payload real da materia 811/2026:
verification_url_base: http://<host>/materia/1065/verificar/
casa_legislativa: Camara Municipal de Franco da Rocha
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Achado num acervo real em 22/08/2026: 0 de 861 materias DOCX tinham PDF-alvo, e
o motivo nao aparecia em lugar nenhum. So os 7 PDF passavam — PDF nao converte,
copia bytes — e davam a impressao de que a rotina estava viva.
A conversao DOCX->PDF e o OnlyOffice quem faz, e para isso ele BAIXA o documento
de origem por URL absoluta. `build_onlyoffice_url` prefere SAPL_INTERNAL_URL e so
cai no request sem ela; no cron o "request" e o `_RequisicaoDeSistema`, que monta
a partir de SITE_URL. Com as duas vazias a URL sai SEM HOST, o OnlyOffice
responde erro e cada materia vira um logger.error individual. O resumo da passada
(`0 gerados, 873 falhas`) nao se distingue de um dia normal no meio do log.
Duas mudancas, as duas sobre visibilidade:
1. Erro de CONFIGURACAO morre no comeco, alto: sem SAPL_INTERNAL_URL nem
SITE_URL o comando levanta CommandError antes de varrer o acervo, dizendo
qual variavel falta e por que. Vale para a passada unica e para o laco.
2. Passada que falha sem NENHUM gerado grita no stderr: falha em todas nao e
documento podre avulso, e ambiente parado (OnlyOffice fora, URL que ele nao
alcanca, MEDIA sem os binarios).
A fixture `base_url_configurada` entra em autouse no test_materializacao: base
URL e pressuposto de toda materializacao, nao caso de borda — a ausencia dela tem
os seus proprios testes.
Testes: 64 passed em sapl/integracao_hub/tests/ (eram 61).
* fix(assinatura): materializacao do PDF-alvo sobe com o servico
O comando `materializar_pdfs_para_assinatura` era idempotente e "feito para
cron" — mas nada o agendava: nem crontab no repo, nem celery, nem timer, nem
entrada em /etc/cron.d na imagem. Na pratica virava passo manual de
implantacao, e a falha e MUDA: materia protocolada nunca vira pendencia no
app, sem erro nenhum, sem log. So aparece quando um vereador reclama que a
materia nao chegou para assinar.
- `--intervalo SEGUNDOS` no proprio comando. 0 (padrao) preserva a passada
unica da invocacao manual; maior que zero fica em laco. A logica ficou em
Python, e nao num `while true` de shell, para ser testavel, logar pelo
Django e sobreviver a passada que estoura — um laco que morre em silencio
seria o mesmo problema, so que mais dificil de achar.
- `start.sh` sobe o laco junto do gunicorn, ao lado de migrate_db e
create_admin. Onde a imagem subir, a rotina sobe. Intervalo por
MATERIALIZACAO_INTERVALO_SEGUNDOS, padrao 300s.
- compose de dev faz o mesmo, para o ambiente local nao divergir.
Testes: passada unica preservada, laco dorme entre passadas e nao retorna, e
laco sobrevive a passada que levanta excecao.
Refs: refinamento da assinatura §5/§5.1 (o doc ja previa "em cron na VPS" —
isto tira a dependencia de alguem lembrar de instalar).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Fix(Assinatura): pendencia carrega o id do keyset e PDF sumido nao derruba o lote AB#1473
Dois defeitos mudos no mesmo caminho, os dois cortando a pendencia antes de
ela chegar ao app:
1. `serializar_pendencia` nao emitia `id` no topo. O hub le exatamente esse
campo para avancar o cursor (PollerSapl.pollAssinaturasPendentes) e devolve
como `id_gt`, que a view filtra em `materia_id__gt`. Sem ele o cursor virava
string vazia e a fonte relia a primeira pagina para sempre. Vai o id da
MATERIA, nao o do registro de pendencia: o alvo e OneToOne e a view pagina
por materia_id. Mesmo tratamento na fonte de concluidas, onde o id e o
desempate do cursor composto (assinado_em, id).
2. Referencia no banco sem binario no MEDIA (969 materias em Franco, dump
restaurado sem a media) levantava OSError em `.size`/`.read()` e derrubava
a resposta INTEIRA do poll com 500 — uma materia podre travava a fonte para
todas as outras. Agora o item continua na lista com `documento` nulo, o
cursor anda e o buraco grita no log.
Testes: 61 passed em sapl/integracao_hub/tests/.
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Ponteiro do knowledge/ em 2ee89cb (origin/main do docs-ia-projects) e stubs
versionados em .githooks/ delegando para knowledge/scripts/hooks/.
Instalacao por clone: knowledge/scripts/instalar-hooks.sh
Passo 2 de 2 da conciliacao. O passo 1 (chore/conciliacao-3.1.2-na-dev) trouxe a
3.1.2 inteira para a dev, inclusive as telas "Assinar Despachos em Lote". Este
aplica a delegacao da C3 por cima **sem derrubar essas telas**, que e a decisao
tomada: o individual delega a composicao ao microservico, o lote continua
compondo localmente.
Por que as duas coisas convivem sem contradicao: o /sign/batch nao compoe a
pagina — seus parametros valem para o lote inteiro, e a composicao e por
documento (a URL de verificacao carrega o pk, o codigo sai do hash daquele PDF).
Mas ele **carimba** um PDF ja composto e posicionado. Entao o lote compoe cada
documento aqui e manda coordenadas explicitas. O artefato sai igual: a grade e o
algoritmo do codigo (sha256[:16].upper(), congelado) sao os mesmos dos dois lados.
Como foi feito, e por que assim: em vez de resolver o merge das duas linhagens no
braco (21 blocos de conflito, 708 linhas), o delta C2→C3 do views_assinatura foi
aplicado sobre a base da 3.1.2 — que e literalmente "portar a C3 para cima do que
existe". Sobraram 3 conflitos, todos de uma linha ou duas:
- assinatura de _assinar_pdf_com_pagina_auth: mantidos os parametros de A3
(assinatura_a3_bytes, cert_chain_bytes) que so a 3.1.2 tinha, e acrescentado o
hash_doc que a C3 usa no corpo — sem ele o codigo ja emitido nao voltaria ao
microservico da 2a assinatura em diante;
- imports do PyPDF4: mantida a uniao (PdfFileWriter e base64 seguem em uso no
caminho A3);
- bloco de nome/cargo: fica o da C3 (_nome_e_cargo_do_assinante, fonte unica),
preservando o tipo_cert_display que o resto da funcao usa.
_carregar_certificado nao voltou de proposito: a C3 ja o havia removido em
178865f5 e ele nao tem chamador na 3.1.2 — conferido no repositorio inteiro.
assinar_pdf_lote_via_api volta ao cliente, agora com chamador de verdade e com a
condicao de validade no docstring: so aceita PDF ja composto e posicionado. O
docstring do modulo passa a explicar as duas composicoes que convivem e quando
usar cada uma, em vez de dizer que lote nao serve.
Uma melhoria junto: as duas telas de lote duplicavam a composicao inline (ler
dimensoes, gerar codigo, montar URL, anexar pagina). Passam a chamar
_compor_pagina_auth_localmente, o mesmo helper do caminho individual local.
Eram tres copias da mesma composicao; agora e uma.
Verificado:
- nenhum marcador de conflito; sapl/materia compila;
- todas as funcoes da 3.1.2 seguem no arquivo (exceto a morta acima) e todas as
oito novas da C3 tambem;
- todo nome importado de views_assinatura e do cliente em urls.py e views.py
existe;
- analise estatica de nome livre nas duas telas de lote e no modulo inteiro: nada
fora de escopo depois do port.
Nao testado com o microservico ao lado: o teste ponta a ponta da C3 (primeira
assinatura anexa a pagina, segunda cai no bloco 1, mesmo retangulo nos dois
backends) precisa ser refeito aqui, agora incluindo uma rodada pelas telas de
lote. E o que falta antes do merge.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Passo 1 de 2 da conciliacao das linhas do SAPL. A dev vinha da integracao
(hub, inventario, anexos, polls) e a 3.1.2 vinha do produto (assinatura, GED,
proposicao, Franco da Rocha). Nenhuma das duas continha a outra: a dev estava
33 commits a frente e 20 atras.
Traz os 20 commits que faltavam. Como a sp_importante_virou_urgente esta
inteiramente contida na 3.1.2 (zero commits exclusivos), ela vem junto e nao
precisa de merge proprio. As branches feature/add-agents-tracking e
feat/integracao-hub-app foram conferidas arquivo a arquivo e estao superadas:
o que adicionam (OnlyOffice, templates de documento, AGENTS.md, autor_nome no
poll) ja esta na dev por outro caminho.
O unico conflito real foi o docs/LOCALHOST_SETUP.md — dois roteiros escritos em
paralelo para a mesma tarefa. Resolvido a favor do roteiro da dev (mais novo,
validado em 13/08, com a arvore de decisao sobre migration), com tres ajustes:
- a nota sobre DEBUG foi corrigida. Ela dizia "DJANGO_DEBUG, nao DEBUG"; depois
deste merge o settings.py aceita as duas (linha 38, vindo da 3.1.2). Manter a
nota antiga seria documentar o codigo errado a partir de agora.
- "Problemas comuns" da 3.1.2 foi incorporado (alcance do banco remoto,
collectstatic com Gunicorn).
- as instrucoes que dependiam de docker/docker-compose-local.yml ficaram de
fora, com o motivo registrado no proprio documento: esse arquivo nao existe em
nenhuma das duas linhas.
Verificado no resultado do merge, e nao por suposicao:
- as quatro funcionalidades que so existiam na 3.1.2 chegaram inteiras — telas
de "Assinar Despachos em Lote" (docacessorio_assinar_lote, materia_assinar_lote),
preview PNG da pagina de assinatura (as tres funcoes), remocao de assinatura
(_pode_remover_assinatura) e o helper _carregar_certificado;
- nenhum marcador de conflito sobrou em .py, .md ou .html;
- sapl/materia e sapl/base compilam;
- todo nome importado de views_assinatura em urls.py e views.py existe — que era
o modo de falha mais perigoso aqui (urls.py importa no topo do modulo, entao
um nome faltando derruba o boot);
- a integracao da dev (sapl/integracao_hub) segue no lugar.
O passo 2 e a unificacao da assinatura: aplicar a delegacao da C3 por cima
mantendo as telas de lote com composicao local, decisao registrada em
docs-ia-projects#63.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Correcao da regra juridica (decisao do arquiteto, 20/08/2026): assinatura e
ato pessoal e indelegavel — o certificado ICP e do vereador; o assessor pode
DISPARAR o ato pelo app, mas NUNCA aparece como autor da assinatura. A versao
anterior resolvia o signatario por 'primeiro operador por id', o que gravaria
a assessora no lugar do vereador (no dado real de Franco, o autor CESINHA tem
operadores {cesinha, Juciana} e so cesinha e o titular).
Vinculo estrutural do titular = o Votante do Parlamentar que o Autor
representa (parlamentares_votante.user). Confirmado no banco: CESINHA
(parlamentar 2) tem votante {cesinha}, isolando o titular da assessora — o
Votante e o vinculo confiavel, casar username com o nome do autor seria
coincidencia fragil.
Duas camadas distintas:
- signed_by / assinado_por / nome = SEMPRE o vereador titular (autoria
juridica); resolvido por resolver_titular() e sua inversa no poll de
concluidas (signed_by -> autor via Votante, nao mais 'primeiro por id');
- operado_por = quem disparou o ato (rastro operacional, auditoria interna) —
novo campo em AssinaturaRecebida + chave no assinatura_info.
Titular indeterminavel (multi-operador sem Votante ou com Votante ambiguo) =
recusa 422 com mensagem clara, em vez de adivinhar — falha visivel e melhor
que atribuir a autoria errada.
NOTA PARA O APP: o evento DocumentoAssinado (origem amu) ainda nao carrega a
identidade do assessor logado; ate o app enviar 'operado_por', o rastro recai
sobre o proprio titular. Campo ja preparado no SAPL para quando o app enviar.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Gerada por makemigrations no container padrao de teste — duas tabelas novas
no app isolado, nenhuma alteracao em tabela do core.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Cobre as regras que sustentam o contrato: retificacao zera o processo de
assinatura; pendencia por autor nao some para o coautor apos a primeira
assinatura; poll so devolve materia com PDF-alvo; POST idempotente por
chave e 409 em hash divergente. Usernames explicitos e curtos nos fixtures
(o auditing do SAPL grava em varchar(100) dentro do atomic).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A pendencia viaja como as outras fontes — poll, nao push (refinamento §3):
- assinaturas-pendentes: keyset por id da materia, SO materia com PDF-alvo
materializado (§5.1 — DOCX nao convertido nao sai do SAPL). Pendencia e
POR AUTOR (§2): autor da autoria cujo usuario (OperadorAutor) nao esta em
assinatura_info.signed_by — a primeira assinatura nao some com a
pendencia dos coautores.
- assinaturas-concluidas: cursor composto assinado_em|id (mesmo keyset das
fontes por data — empate de timestamp nao trava cursor), com hash do
assinado calculado do arquivo e autor_id resolvido de signed_by via
OperadorAutor (e por ele que o consumidor fecha a pendencia).
- GET documentos-assinatura/<id>/alvo|assinado: serve os bytes ao hub
(token + pode_integrar) — o Authorization nunca vaza ao consumidor final.
- POST assinaturas: idempotente por chave (padrao EventoRecebido), confere
o hash do alvo ANTES de gravar (divergencia = 409, retificacao no meio do
caminho) e grava pdf_assinado + assinatura_info no MESMO formato da
sprint — para a tela do SAPL, indistinguivel de assinatura local.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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>
Evita depender de alguém lembrar de marcar a caixa em Tabelas Auxiliares
depois do deploy: a migration de dados marca dispensa_protocolo no tipo de
proposição "Ofício" que já existir no banco.
A comparação normaliza acentos, espaços e caixa, porque a descrição é digitada
em cada Casa e aparece como "Ofício", "OFICIO" ou "oficio". Casas que não têm
esse tipo cadastrado não são afetadas.
A migration é reversível: o reverse desmarca os mesmos tipos.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ofícios são documentos de uso próprio do gabinete do vereador e não precisam
de validação da Casa, mas hoje caem na fila de Proposições Pendentes como
qualquer outro tipo — a fila é definida apenas por data_envio/data_recebimento/
data_devolucao, sem considerar o tipo. O protocolo vinha devolvendo esses
ofícios na mão.
Adiciona TipoProposicao.dispensa_protocolo, configurável em Tabelas Auxiliares
-> Tipo de Proposição, em vez de fixar a regra no tipo "Ofício". Para os tipos
marcados:
- As ações 'send' e 'send_setor' são recusadas, então data_envio nunca é
gravada e a proposição não entra na fila do Protocolo nem do Setor
Legislativo.
- O botão "Enviar Proposição" dá lugar a um selo explicativo no detalhe.
- A listagem do vereador ganha o status "Documento de Gabinete" e o filtro
correspondente; esses documentos saem da contagem de "em elaboração".
A visibilidade já é resolvida pelo container_field='autor__operadores' do
ProposicaoCrud: a proposição só é acessível ao gabinete do autor.
Proposições já existentes não são tocadas — as que estão enviadas, devolvidas
ou já incorporadas seguem com o status atual.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Customização para o Procurador Jurídico da Câmara de Franco da Rocha,
identificado pelo grupo "Operador de Norma Jurídica" (SGVP_GROUP_NORMA):
- Tela inicial exibe somente o card "Matérias Legislativas".
- Documento Acessório já abre com Tipo="Parecer Jurídico", Nome="Aprovado"
e Data=data atual. Os campos seguem editáveis.
Adiciona o helper sapl.rules.is_procurador_juridico, usado pelas duas
funcionalidades para manter o critério de grupo em um único lugar.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>