Files
Investidor-V2/tools/portal-stability-package.py
nala 287e991486
tests / ci (push) Waiting to run
oportunidades 7
2026-10-13 11:40:29 +02:00

222 lines
32 KiB
Python

"""Seal the demonstrated QA revision; never deploy, mutate active data or export secrets."""
from pathlib import Path
from datetime import datetime, timezone
import hashlib, json, shutil, zipfile, difflib, re
ROOT=Path(__file__).resolve().parent.parent
D=ROOT/'storage/app/private/investmoz-v2/portal-audit'
OUT=ROOT/'outputs/investmoz-portal-stability-20261011'
OUT.mkdir(exist_ok=True)
read=lambda p:json.loads(Path(p).read_text(encoding='utf-8-sig'))
sha=lambda p:hashlib.sha256(Path(p).read_bytes()).hexdigest()
canonical=lambda v:json.dumps(v,sort_keys=True,separators=(',',':')).encode()
write=lambda name,v:(OUT/name).write_text(json.dumps(v,ensure_ascii=False,indent=2),encoding='utf-8')
plan=read(D/'portal-stability-latest.json'); source=Path(plan['source']); build=read(D/'portal-stability-production.json')
assert build['result']=='passed' and build['active_assets_preserved']
for name,digest in build['inputs'].items(): assert sha(source/name)==digest, 'Production input changed: '+name
for name,digest in build['assets'].items(): assert sha(source/'public/build'/name)==digest
assert hashlib.sha256(canonical(build['inputs'])).hexdigest()==build['input_hash']
country=read(D/'portal-stability-country-chromium-20261011.json')
persistence=read(D/'portal-stability-country-persistence.json')
final=read(D/'portal-stability-final-consumers.json'); cold=read(D/'portal-stability-cold-chromium.json')
runtime=read(D/'portal-stability-runtime-probe.json'); physical=read(D/'portal-stability-disaster-restore.json')
post=read(D/'portal-stability-post-use-backup.json'); active=read(D/'portal-stability-active-preservation.json')
consumers=read(D/'portal-stability-consumer-persistence.json'); initial=read(D/'portal-stability-consumers-chromium.json')
for p in [country,persistence,final,cold,runtime,post,active,consumers]: assert p['result']=='passed'
assert physical['result']=='restored_exact'
assert country['build_sha256']==build['build_manifest_sha256']
assert all(country['checks'].values()) and not country['javascript_errors'] and not country['mixed_requests']
assert active['country_enabled'] is False and active['installed_050000'] is False
old=ROOT/'outputs/investmoz-country02-https-20261011'
baseline=Path(plan['baseline_candidate'])
changed={n:{'active_before':sha(ROOT/n) if (ROOT/n).is_file() else None,'approved_before':sha(baseline/n) if (baseline/n).is_file() else None,'after':h} for n,h in build['inputs'].items() if not (ROOT/n).is_file() or sha(ROOT/n)!=h}
delta={n:v for n,v in changed.items() if v['approved_before']!=v['after']}
generated=[n for n in delta if n.startswith(('resources/js/actions/','resources/js/routes/'))]
assert len(generated)==58 and len(delta)-len(generated)==18
assert not any('/_qa/runtime' in (source/n).read_text(encoding='utf-8') for n in generated)
for name in ['portal-investidor.test-https.proposta.conf','ambiente-https.proposta.txt']:
shutil.copyfile(old/name,OUT/name)
dll=Path(plan['compatible_apache_executable']).parent/'nghttp2.dll'
assert sha(dll)=='4bee6c3aa3270c49395ff528db1ebd1e3d78c211503a7b9ea6c23aa9ad2dec85'
runtime_dir=OUT/'runtime';runtime_dir.mkdir(exist_ok=True);shutil.copyfile(dll,runtime_dir/'nghttp2.dll')
deployment={n:sha(OUT/n) for n in ['portal-investidor.test-https.proposta.conf','ambiente-https.proposta.txt','runtime/nghttp2.dll']}
inputs={'production_inputs_hash':build['input_hash'],'production_manifest':build['build_manifest_sha256'],'assets':build['assets'],'application_files':changed,'deployment_files':deployment}
revision=hashlib.sha256(canonical(inputs)).hexdigest()
write('manifesto-candidata.json',{'candidate_id':'CAND-PORTAL-STABILITY-20261011','at':datetime.now(timezone.utc).isoformat(),'delivery_revision':revision,'application_input_hash':build['input_hash'],'country_installation_fingerprint':country['code_hash'],'production_manifest_sha256':build['build_manifest_sha256'],'revision_input':inputs,'delta_from_approved_13644ed74a48':delta,'generated_wayfinder_files':generated,'hand_edited_application_delta_count':18,'production_inputs':build['inputs'],'technical_QA_rehearsal_passed':True,'activation_ready':False,'activation_suspended_by_user':True,'approved_delivery_preserved':'13644ed74a48','approved_code_preserved':'162daff6ce08','active_country_enabled':False,'active_sources_configuration_database_modified':False,'QA_guard_credentials_private_keys_excluded':True,'authorized_future_DDL_scope':'Only 050000: two nullable organization columns; zero tables/accounts/organizations/memberships','new_runtime_dependency_requires_explicit_review':'nghttp2.dll 1.70 from existing PHP8.5.11 distribution; active Apache retains 1.47 until separate reviewed availability action'})
write('country02-interface.json',{k:country[k] for k in ['at','origin','target','code_hash','build_sha256','result','checks','javascript_errors','console_errors','mixed_requests','cookies','tls','session_transitions','logout_back','active_writes']})
for filename,record in [('country02-persistencia.json',persistence),('runtime-qa.json',runtime),('restauro-original.json',physical),('backup-pos-utilizacao-qa.json',post),('ativo-preservado.json',active),('consumidores-persistencia.json',consumers),('gerador-operacao.json',read(D/'portal-stability-operation-key-test.json')),('geo-leitura-128M.json',read(D/'portal-stability-geo-reader-128M.json')),('geo-comparacao-exata.json',read(D/'portal-stability-geo-reader-comparison.json')),('clamd-e-exemplares.json',read(D/'portal-stability-services-qa.json')),('geo-scan-especializado.json',read(D/'portal-stability-gis-scan.json'))]:
# Private file directories/paths are kept in the private evidence originals.
public={k:v for k,v in record.items() if k not in ['file_directory','source_backup','dump_path']}
write(filename,public)
coverage={'initial_receipt_result':initial['result'],'initial_failure':'Consecutive room message cleared while prior response completed; repaired and demonstrated in final receipt','initial_demonstrated_criteria':initial['checks'],'final_demonstrated_criteria':final['checks'],'cold040_demonstrated_criteria':cold['checks'],'cold040_operations':cold['operations'],'geometry_recovered_without_new_test_writes':consumers,'scope':'14 affected component paths; criteria listed individually, no assertion that their entire functional backlog is closed','active_writes':0}
write('consumidores-interface.json',coverage)
if (D/'portal-stability-finalize-20261011/preservation-proof.json').exists():
counts=read(D/'portal-stability-finalize-20261011/preservation-proof.json')
counts['existing_files_sha256']={name:sha(ROOT/'docs/investmoz/v2/auditoria-portal'/name) for name in ['matriz-requisitos.csv','backlog-priorizado.csv']}
write('matriz-preservacao.json',counts)
cert=read(old/'certificado-ativo-proposto.json');cert.update({'prepared_not_trusted':False,'CurrentUser_root_trust_confirmed_by_user':True,'browser_trust_demonstrated_in_QA':True,'active_TLS_configuration_applied':False,'CA_SHA1':'7D30D7FB226481B6243051309818CF426AF6B6B7'})
write('certificado.json',cert)
differences='''As portas QA são 18444/18106 e18445/18107, em vez de443/80; o Apache ativo não foi reiniciado. PostgreSQL55441 contém clones com guardas antes dos providers, em vez de investemoz5432. Cookies QA têm nomes próprios, Secure/HttpOnly/Lax/host-only/path/, e contextos Chromium separados: cookies do mesmo hostname podem ser partilhados entre portas. Mantém-se a chave original de cada origem (ativo e QA funcional usam chaves diferentes), sem recriar chaves. Fixtures sintéticas e representação declarada existem somenteQA. OPcache QA usa namespace e fallback para coexistir com Apache ativo; nenhuma alteração do php.ini ativo é proposta como consequência automática. ClamD QA13310 tem definições oficiais atualizadas e fila própria; notificações são locais, decisões/serviços externos não são simulados como reais. Guardas, probe e credenciais QA não fazem parte da entrega.'''
report=f'''# Estabilização integrada — revisão {revision[:12]}
**Ativação suspensa. COUNTRY02 continua desabilitado no ativo.** A revisão é fixa e o percurso COUNTRY02 completo foi demonstrado no Chromium, no hostname de destino com HTTPS reconhecido. A/B e C37 mantêm os fechos anteriores; a auditoria global permanece aberta.
Aplicação: `{build['input_hash']}`. Produção: `{build['build_manifest_sha256']}`. Fingerprint específico050000: `{country['code_hash']}`, inalterado. A entrega13644ed74a48/código162daff6ce08 permanece imutável e autorizada no âmbito registado, agora suspenso. Esta revisão acrescenta as mudanças identificadas no manifesto; essa autorização não é tratada como aprovação automática do acréscimo.
## Frente técnica comum e falhas concretas
|Caso|Causa / consumidores|Correção e prova|Estado|
|---|---|---|---|
|C110|UUID browser ausente no hostnameHTTP; instaladores e25 chamadas/14componentes|COUNTRY usa chave de servidor. Restantes usam utilitárioCSPRNG comum (UUID nativo ou getRandomValues); chamadas corrigidas e componentes ensaiados com randomUUID ausente; mesma chave em erro/repetição|DemonstradoQA; não ativo|
|C111|crypto.subtle/histórico cifrado indisponível em contextoHTTP não seguro|Apache/PHP real com certificado do hostname final; TLS1.3/contexto seguro, Back/Forward cifrados, sem conteúdo misto|DemonstradoQA; HTTPS ativo suspenso|
|C112|Logout invalidava sessão mas deixava chave do histórico Inertia|RespostaFortify após invalidação limpa histórico; API401, Back sem master, nova sessão/MFA recuperaMZ|DemonstradoQA; não ativo|
|C113|Apache resolve nghttp2.dll1.47, sem export exigido pelo curl do PHP8.5.11|CópiaQA com DLL1.70 já fornecida pelo PHP; curl8.22 carregado no mod_php; executávelApache e restantes módulos preservados|DemonstradoQA; alteração de runtime ativa exige revisão explícita|
|C114|OPcache de dois Apache Windows partilhava identidade SHM e falhava no arranqueQA|Namespace QA próprio e fallback gravável; OPcache permanece habilitado. [Configuração PHP oficial](https://www.php.net/manual/en/opcache.configuration.php)|Resolvido para coexistênciaQA; php.ini ativo intacto|
|C115|Prévia cartográfica decodificava coleção inteira; excedia128M e depois timeout no parser inicial|Leitura por feature/64KiB e varrimento otimizado;581features idênticas (11/159/411), pico38MiB; níveis1/2/3 apresentados; loteL1 aplica/replay/reverte preservando11geometrias|DemonstradoQA; não importa nem publica camadas ativas|
|C116|Bootstrap040 lia como segredo cru o envelope serializado pelo MFA normalFortify|Leitura compatível com formatos existentes, sem unserialize de objetos nem recifrar; bootstrap normal, rejeição de MAC inválido preservada; instalação/replay únicos e nova sessão do titular sintético|DemonstradoQA; titulares ativos intactos|
|C117|Resposta da mensagem anterior limpava texto já escrito na seguinte|Campo bloqueado enquanto envio está em processamento; duas mensagens consecutivas distintas visíveis e recuperadas comF5, em Parcerias; mesma proteção no Balcão|DemonstradoQA; não ativo|
## Resultado utilizável demonstrado
COUNTRY02: estado anterior288tabelas/050000 ausente/flagOFF → login/MFA → prévia consistente → confirmação/instalação somente050000 → replay do mesmo pedido sem segunda auditoria → perfil/guardarPT/F5 → atualizarMZ/versão3 → Back/Forward cifrados → logout/API401/Back sem dados privados → nova sessão/MFA/MZ e fonte recuperados → flagOFF/ON conserva valores/histórico. Foram conferidos47recursos efetivamente carregados com os hashes da produção final e sem conteúdo misto. SQL confirma duas colunas nullable, zero tabelas novas, uma auditoria de instalação e dois snapshots cifrados recuperáveis. Conta/organização/representação sintéticas foram preparadas expressamente sóQA, fora da instalação.
Nos consumidores, as provas individuais incluem oportunidade incompleta guardada/F5, localização incompleta/ediçãov2 recuperada, parceria rascunho, dossier investidor/pedido no Balcão, dossier direto/pedido, mensagens e comunicação interna, perfis/040000 a frio, pedido dePromotor após422 com mesma chave/estado pendente, scan real e download byte a byte do original autorizado, cinco acessos indevidos403 e autogravação pública recuperada. Consultar [cobertura por componente](cobertura-consumidores.md) e [critérios individuais](consumidores-interface.json); o recibo inicial que falhou no envio consecutivo é preservado como histórico e complementado pela prova final, não reclassificado globalmente.
## Ambiente equivalente e diferenças
Apache2.4.54Win64/PHP8.5.11TS/mod_php,128M, módulosPDOpgSQL/pgsql/openssl/mbstring/fileinfo/gd/intl/zip/curl, produçãoVite; certificado/SANportal-investidor.test reconhecido pelo Chromium sem ignorarTLS, CA SHA1 completa7D30D7FB226481B6243051309818CF426AF6B6B7. Sessões database, cifragemAES256CBC estável, regeneração apósMFA e namespace de cookies. A QA não usa servidorPHP de desenvolvimento nem proxyTLS como substituto do Apache final.
{differences}
Serviços: scan ClamD real1.5.4 com definições28150/data11-10-2026; [freshclam oficial](https://docs.clamav.net/manual/Usage/SignatureManagement.html). Os limites normais20/25MiB são preservados. ADM3(26,77MB) continua inconclusivo no ClamD normal por limite; o exemplar retido foi analisado separadamente pelo scannerGIS/clamscan40MiB, hash original e mesma base. Não confundir os dois resultados nem alargar upload comum. A filaQA tem worker guardado por identidade55441, prazo3600s e comando de manutenção documentado; anexos dependem desse worker/scan. COUNTRY02 não depende de fila, cartografia030000, perfis040000 ou percurso190000 para instalar.
## Backup, restauro e preservação
Backup real do destino obtido por leitura08:35:02UTC:288tabelas/31sequências/379ficheiros/zero ausentes/dois envelopesMFA legíveis. Dump`e3b12ad7942d9357d610471a323b6dfbabc9be2d1cd6759048d70f45640d05c4`, ZIPficheiros`3458400ac0abcba3f31b1f1b7bceb1128f69d1dc2a2009279118865433e00d46`. Restauro completo anterior ao ensaio e outro restauro de desastre confirmam linhas/hashes/sequências/ficheiros/MFA. Depois da utilização, backupQA restaurado noutro clone conserva país/histórico novos. Estes recibos estão ligados a esta revisão pelo manifesto; a prova antiga não recebeu flags artificiais de sucesso nem se tornou prova de ativação.
379vs282ficheiros:97assets imutáveis conservados após reversão anterior; sem eliminação. QAfuncional295vsclone288tabelas:3tabelas de recursos030000+4deperfis040000. Cartografia neste clonefuncional:408registos já existentes com hash integral inalterado;393é contagem de um lote histórico, não a base atual do ensaio. Lote provincial criou0/reutilizou11/reverteu0/preservou11, sem divulgação.
## Limitações e critérios ainda abertos
Ativo:13contas,0organizações,0representações,79migrations/126audits,050000 ausente eCOUNTRYOFF. Não há dados mestres legítimos para demonstrar gravação ativa do país; podem ser registados depois pelo fluxo autorizado e representação legítima, sem inferir país da residência. A ausência não bloqueiaDDL. Não há ensaio ativo/TLS ativo efetuado; o runtimeDLL novo e a revisão diferente exigem apreciação do acréscimo antes de levantar a suspensão. A execução humana futura resume-se ao controlo normalLaragon para substituirDLL carregada e à autenticação/MFA do gestor real, agrupados no procedimento; não se pede novo teste básico ao utilizador agora.
Cobertura destes14componentes é técnica e limitada às ações registadas; não fecha categorias/contratos/workflow, todas as operações documentais/derivadas, correspondências ambíguasMandie/Guro, acessibilidade integral ou dispositivos reais. Ofertas/competências modulares/SLA continuam decisões separadas. MercadoFinanceiro permanece privado sem corpo. Cinco fases/19etapas e dossiers privados/oportunidades públicas preservados. Mesma matrizAUD-COUNTRY-02 (implementado mas não ativado) eAUD-OPS-01 (parcial), sem fechos por contagem de testes.
[Procedimento único, condições de paragem e reversão](procedimento-unico.md). [Manifesto](manifesto-candidata.json).
'''
(OUT/'relatorio-estabilizacao.md').write_text(report,encoding='utf-8')
procedure=f'''# Procedimento único — revisão {revision[:12]}
**Preparado e ensaiadoQA; execução ativa suspensa pelo responsável.** Nenhum passo deste documento autoriza aplicar a nova revisão. A autorização anterior13644ed74a48/162daff6ce08 é preservada com o seu âmbito original. Acréscimos no manifesto:25chamadas/utilitário, leituraGeo, compatibilidadeMFA, proteção de mensagens e DLLnghttp2; devem ser apreciados explicitamente. Não aplicar030000/040000/190000 nem importar/publicar cartografia, perfis, ofertas ouSLA.
1. Fixar esta revisão e confirmar hashes dos inputs`{build['input_hash']}`, produção`{build['build_manifest_sha256']}` e todos os membrosZIP. Antes de disponibilizar no ativo, renovar backup/restauro real correspondente ao destino investemoz5432 e a esta revisão (<=24h), incluindo ficheiros/chave/cipher/config/código/bundle/linhas/sequências; comparar com estado anterior e registrar mudança legítima posterior. Os restaurosQA nesta entrega são provas técnicas, não autorização de escrita ativa. Preservar assets anteriores.
2. Preparar conjuntamente dependências/configuração: originalnghttp2 SHA27dbfd55cbff5dc0f580b4e109f18e651c804322382c04dd9588c733517a34f6 → DLLPHP existente1.70 SHA4bee6c3aa3270c49395ff528db1ebd1e3d78c211503a7b9ea6c23aa9ad2dec85; snapshotpara reversão. Validar Apache/PHPextensões/permissões storage/bootstrapcache/OPcache/cipher, certificado/chave existente correspondente/SAN/validade/CA no utilizador que usará o portal; não exportar a chave. CA já aceite neste utilizador; outra máquina/utilizador necessita confiança própria. OPcache namespace/fallbackQA é diferença de coexistência, não mudança automática do php.ini ativo. Não substituirDLL de processo carregado.
3. Se/quando a suspensão for levantada e acréscimos apreciados, agrupar a intervenção operacional: pararApache pelo controlo normalLaragon, disponibilizar DLL, fontes enumeradas e produção imutável comCOUNTRYfalse, configuraçãoHTTPS aprovada/cookie novo; validar sintaxe e iniciarApache pelo mesmo controlo. Sem terminarprocessos porcomando. PreservarAPP_KEY/AES256CBC/DB/flags restantes. Listeners443/80únicos, TLS1.2+1.3; GET/HEADHTTP308 conserva caminho/query, restantes métodos405. Manter assets anteriores para páginas/sessões em curso. Invalidar apenas cache de configuração pelo mecanismo previsto e confirmar configuração efetiva; não alterar chaves nem fazer migrate global.
4. Confirmar certificado real/contextoseguro/curl/módulos/recursossem mixedcontent, cookieportal_investidor_https host-only/Secure/HttpOnly/Lax/path/ e guard/middleware normais. Antes de POST, gestor EXISTENTE legítimo autentica/MFA localmente em **https://portal-investidor.test/management/opportunity-resources-install/organization-country**. Não usar contasQA ou inventar organizações/representações. Conferir prévia: investemoz5432, fingerprint050000`{country['code_hash']}`, consistente/pronta/nãoinstalada, backup válido, somente050000 duascolunas nullable/zero tabelas/zero contas/zero organizações/zero representações. Hash e chave de operação emitidos pelo servidor, persistentes na tentativa. Qualquer divergência para antes de aplicar; não contornar ready=false.
5. Confirmar e aplicar somente050000 pela ação autenticadaCSRF/MFA/confirm/reason/chave; verificar2colunas/288tabelas e1migration+1auditoria, restantes dados intactos. Replay mesmo conteúdo/chave retorna mesmo recibo sem novaDDL/auditoria; pedidoalterado é recusado. Habilitar exclusivamenteCOUNTRY02 após essa comprovação. Prévia prontatrue era preparação, não instalação.
6. Verificarperfil vazio controlado (estado real atual:0org/0representações), gravação/recuperação apenas quando houver organização/vínculo real legitimamenteautorizados. Antesdeles, deixar critério explícitopendente; não fabricar provaativa. Confirmar nova sessão/logout/API401/Back semdadosprivados e produção/hash. Não repetirA/B/C37 integrais: só comportamentos afetados peloHTTPS/logout. Registrarresultadoexato namatriz.
7. Reversão operacional preferida: COUNTRYfalse, manterTLS/correção delogout/colunas/valores/fontes/ledger/auditoria; isto foi ensaiadoQAcomOFF/ONconservandoversão/proveniência. Antesdequalquerrestaurodestrutivo, novo backup pósuso dos dados posteriores e decisão explícita. Backuporiginal/ficheiros e pósusoQA foram realmente restaurados em clones; não autoriza restaurar o ativo automaticamente. Não down/drop, key:generate, MFAoff ou troca de chave. Reporbundle/config/runtime exige compatibilidade revisada e controlo normalLaragon; voltarHTTP reabreC111e não é uma reversão funcional equivalente.
## QA conservada para revisão
COUNTRY: https://portal-investidor.test:18444; consumidores: https://portal-investidor.test:18445. Não são o ativo. Clones55441 com nomes únicos/guard antesproviders; MFA/segredos só em ficheirosprivadoslocais. ApacheQA eClamDQA13310 isolados. Worker: PHP8.5.11TS `tools/portal-stability-queue.php --continuous` (guardconsumers55441, max3600s); uso pontual semargumentos esvaziafilaQA. Revalidar serviços antesdeusarQA apósesseprazo. Estes auxiliares/probe/creds/.env não são ficheiros de deployment.
## Aceitação objetiva da candidata
Revisão/fontes/bundle correspondentes; destino efetivo demonstradoQAcomApache/PHP/HTTPS; percursoCOUNTRYcom resultadoPT→MZ visível/persistido/recuperado/históricocifrado; sessão/logoutprotegidos; replay umaaudit; guardasantesescrita; originals/MFApreservados; backupanteriorrestauradoe pósusoQArestaurado. Disponibilizaçãoativa exige ainda levantar suspensão e apreciar explicitamente o delta; certificado real e prévia devem corresponder antesdoPOST. Dependências deuploads(fila/ClamD) não são tratadas como instalaçãoCOUNTRYnemativadasporarrasto.
'''
(OUT/'procedimento-unico.md').write_text(procedure,encoding='utf-8')
procedure=(ROOT/'tools/portal-stability-procedure-template.md').read_text(encoding='utf-8')
for marker,value in [('REV',revision[:12]),('INPUT',build['input_hash']),('MANIFEST',build['build_manifest_sha256']),('CODE',country['code_hash'])]:procedure=procedure.replace('{{'+marker+'}}',value)
(OUT/'procedimento-unico.md').write_text(procedure,encoding='utf-8')
shutil.copyfile(ROOT/'tools/portal-stability-cases-template.md',OUT/'casos-reproducao.md')
rows=[
('simulation-form',1,'/simulador-fiscal; público','Autoguardar via header CSPRNG eF5 recupera Simples/ApenasSimular','Execução fiscal e estatísticas são critérios históricos separados; nenhuma regra fiscal ativada'),
('desk-internal-operations',2,'/balcao-virtual/pedidos/5dfb79db-52b4-4b97-954a-6ec7b630bd4b; equipaQA','Comunicação interna guardada/visível; chave seguinte separada','Nenhuma comunicação/decisão externa inventada'),
('desk/panel',1,'/balcao-virtual/painel; QA21','Dossierdireto criado, pedido/F5 recuperado','Teste técnico em dossier separado deA/B'),
('desk/case',1,'/balcao-virtual/dossiers/b0e591ab-c6b3-41ea-b3ed-cd9efa575bc5; QA21','Preparar pedido e recuperar; QA23recusado403','Idem'),
('desk/request',2,'/balcao-virtual/pedidos/8a42ee0d-e47e-4f27-9e73-b986d0345058; QA21','Pedido/F5, duas mensagensBalcão, scan/downloadoriginal, QA23recusado403','Textarea bloqueada apenasduranteenvio'),
('investor-path/index',1,'/investimentos; QA21','Criação com422 inicial e mesma chave na correção; dossier recuperadoF5','A/Bfechadospreservados; não novoensaiointegral'),
('investor-path/show',1,'/investimentos/d8ac9133-65b6-4ed4-964e-e230bc656a02; QA21','PrepararBalcãocomcontexto, pedido persistido; QA23recusado403','Não altera19etapas'),
('opportunity-draft',1,'/minhas-oportunidades/create; /minhas-oportunidades/53/edit; QA21','Rascunhoincompletoguardado eF5 recuperado','Workflow/contratos restantes não fechados'),
('opportunity-resources',3,'/minhas-oportunidades/53/resources; QA21','Seletor/rascunholocalização/erro-retry mesma chave; ediçãov2 recuperada','Anexação/derivadas completas: só provas históricas aplicáveis, não fecho global por estes3calls'),
('territory-geometry-import',6,'/management/territory-geometry-import; gestorgeoQA','Níveis1/2/3; aplicarL1/replay/reverter; trocar nível/atualizarprévia;408geometriasoriginaishashinalterado','Correspondências pendentes/ambíguas mantidas; publicação separada'),
('partnerships/editor',1,'/parcerias/2/editar; QA21','Rascunhoguardado eF5 recuperado','Não aprovação automática de ofertas'),
('partnerships/room',2,'/parcerias/salas/1; QA21','Duas mensagens consecutivas conteúdoexato eF5 apóscorreçãoC117','Falha inicialpreservada, prova finalespecífica'),
('profiles/installation',1,'/management/profile-install; gestorQA; novo clone a frio','Erro transporteantescommit→retry mesmachave→040único→replayrecibo→bootstrapMFA normal→novasessão titular','Duas chaves independentesagora(install/bootstrap);0titularrealdesignado'),
('profiles/request',2,'/perfil/promotor; QA20','422semdocumento→retry mesmachave/documentoscaneado→pedidopendenterecuperado','Nenhum perfil ativado pela solicitação')]
assert sum(r[1] for r in rows)==25
table='# Consumidores afetados — critérios da frente técnica\n\nEsta tabela referencia AUD-OPS-01 na matriz existente; não cria requisitos ou estados paralelos. Nos ensaios foi retirado apenas `Crypto.prototype.randomUUID`; `getRandomValues` permaneceu disponível no contextoHTTPS real. Chaves inválidas/ausência de gerador são tratadas de forma controlada.\n\n|Componente|Chamadas anteriores|Rota/perfil|Demonstrado|Limite da prova|\n|---|---:|---|---|---|\n'
table+=''.join('|'+ '|'.join(str(v) for v in r)+'|\n' for r in rows)
(OUT/'cobertura-consumidores.md').write_text(table,encoding='utf-8')
# Preserve literal hashes/routes/IDs while making the compact technical prose readable.
separations={
'específico050000':'específico 050000','entrega136':'entrega 136','código162':'código 162',
'hostnameHTTP':'hostname HTTP','contextoHTTP':'contexto HTTP','e25':'e 25','14componentes':'14 componentes',
'utilitárioCSPRNG':'utilitário CSPRNG','DemonstradoQA':'Demonstrado em QA','RespostaFortify':'Resposta Fortify',
'API401':'API 401','recuperaMZ':'recupera MZ','dll1.47':'dll 1.47','DLL1.70':'DLL 1.70',
'PHP8.5.11':'PHP 8.5.11','curl8.22':'curl 8.22','CópiaQA':'Cópia QA','executávelApache':'executável Apache',
'arranqueQA':'arranque QA','coexistênciaQA':'coexistência em QA','excedia128M':'excedia 128M',
';581features':'; 581 features','pico38MiB':'pico 38 MiB','níveis1/2/3':'níveis 1/2/3',
'loteL1':'lote L1','preservando11geometrias':'preservando 11 geometrias','Bootstrap040':'Bootstrap 040000',
'normalFortify':'normal Fortify','comF5':'com F5','anterior288tabelas':'anterior: 288 tabelas',
'→':' → ','/flagOFF':'/flag OFF','/050000 ausente':'/050000 ausente','guardarPT':'guardar PT',
'atualizarMZ/versão3':'atualizar MZ/versão 3','sessão/MFA/MZ':'sessão/MFA/MZ',
'flagOFF/ON':'flag OFF/ON','conferidos47recursos':'conferidos 47 recursos',
'040000 a frio':'040000 a frio','após422':'após 422','acessos indevidos403':'acessos indevidos 403',
'sóQA':'só em QA','PHP8.5.11TS':'PHP 8.5.11 TS','8.5.11TS':'8.5.11 TS',
'Apache2.4.54Win64':'Apache 2.4.54 Win64','módulosPDOpgSQL':'módulos PDO pgSQL',
'SANportal-investidor.test':'SAN portal-investidor.test','ignorarTLS':'ignorar TLS',
'completa7D':'completa 7D','database55441':'database 55441','PostgreSQL55441':'PostgreSQL 55441',
'QA13310':'QA 13310','real1.5.4':'real 1.5.4','definições28150/data11-10-2026':'definições 28150/data 11-10-2026',
'scanClamD':'scan ClamD','QAcom':'QA com','20/25MiB':'20/25 MiB','ADM3(26,77MB)':'ADM3 (26,77 MB)',
'scannerGIS/clamscan40MiB':'scanner GIS/clamscan 40 MiB','prazo3600s':'prazo 3600 s',
'55441, prazo':'55441, prazo','cartografia030000':'cartografia 030000','perfis040000':'perfis 040000',
'percurso190000':'percurso 190000','leitura08:35:02UTC':'leitura às 08:35:02 UTC',
'288tabelas/31sequências/379ficheiros':'288 tabelas/31 sequências/379 ficheiros',
'envelopesMFA':'envelopes MFA','ZIPficheiros':'ZIP de ficheiros ',
'379vs282ficheiros:97assets':'379 versus 282 ficheiros: 97 assets',
'QAfuncional295vsclone288tabelas:3tabelas':'QA funcional 295 versus clone 288 tabelas: 3 tabelas',
'recursos030000+4deperfis040000':'recursos 030000 + 4 de perfis 040000',
'clonefuncional:408registos':'clone funcional: 408 registos','hash integral inalterado;393é':'hash integral inalterado; 393 é',
'criou0/reutilizou11/reverteu0/preservou11':'criou 0/reutilizou 11/reverteu 0/preservou 11',
'Ativo:13contas,0organizações,0representações,79migrations/126audits':'Ativo: 13 contas, 0 organizações, 0 representações, 79 migrations/126 audits',
'eCOUNTRYOFF':'e COUNTRY OFF','runtimeDLL':'runtime DLL','normalLaragon':'normal do Laragon',
'certificado real e prévia':'certificado real e prévia','destes14componentes':'destes 14 componentes',
'ambíguasMandie/Guro':'ambíguas Mandie/Guro','MercadoFinanceiro':'Mercado Financeiro',
'fases/19etapas':'fases/19 etapas','matrizAUD':'matriz AUD','eAUD':'e AUD',
'Revisão/fontes/bundle correspondentes':'Revisão/fontes/bundle correspondentes',
'artigosautor':'artigos autor','sem mixedcontent':'sem conteúdo misto',
'duascolunas':'duas colunas','zero tabelas':'zero tabelas','prior proof':'prova anterior',
}
for file in [OUT/'relatorio-estabilizacao.md',OUT/'procedimento-unico.md',OUT/'cobertura-consumidores.md']:
text=file.read_text(encoding='utf-8')
for before,after in separations.items():text=text.replace(before,after)
file.write_text(text,encoding='utf-8')
with (OUT/'relatorio-estabilizacao.md').open('a',encoding='utf-8') as f:
f.write('\nAcréscimo exato em fontes: 18 ficheiros de aplicação editados (14 consumidores, utilitário comum e três serviços PHP), 58 artefactos gerados pelo Wayfinder na compilação, além dos três ficheiros já aprovados que permanecem com o mesmo conteúdo. O manifesto enumera cada hash. Os artefactos gerados não acrescentam rotas de servidor; o probe QA não está incluído. O executável Apache permanece igual; somente a DLL nghttp2 é proposta para o runtime ativo.\n')
report=(ROOT/'tools/portal-stability-report-template.md').read_text(encoding='utf-8')
for marker,value in [('REV',revision[:12]),('INPUT',build['input_hash']),('MANIFEST',build['build_manifest_sha256']),('CODE',country['code_hash'])]:report=report.replace('{{'+marker+'}}',value)
(OUT/'relatorio-estabilizacao.md').write_text(report,encoding='utf-8')
patch=[]
for name in changed:
before=(ROOT/name).read_text(encoding='utf-8-sig').splitlines(True) if (ROOT/name).is_file() else []
after=(source/name).read_text(encoding='utf-8-sig').splitlines(True)
patch.extend(difflib.unified_diff(before,after,fromfile='a/'+name,tofile='b/'+name))
(OUT/'codigo-corrigido.patch').write_text(''.join(patch),encoding='utf-8')
members={'application/'+n:source/n for n in changed}
members.update({'application/public/build/'+n:source/'public/build'/n for n in build['assets']})
members['verification/tests/Feature/PortalProfileInstallationTest.php']=source/'tests/Feature/PortalProfileInstallationTest.php'
for file in OUT.iterdir():
if file.is_file() and file.suffix in ['.json','.md','.conf','.txt','.patch'] and file.name!='manifesto-pacote.json':members[file.name]=file
members['runtime/nghttp2.dll']=runtime_dir/'nghttp2.dll'
assert not any('key.pem' in n or any(p in ['.env','vendor','node_modules'] for p in Path(n).parts) for n in members)
archive=OUT/f'portal-stability-{revision[:12]}-revisao.zip'
with zipfile.ZipFile(archive,'w',zipfile.ZIP_DEFLATED) as z:
for n,file in sorted(members.items()):
info=zipfile.ZipInfo(n,(2026,10,11,0,0,0));info.compress_type=zipfile.ZIP_DEFLATED;info.external_attr=0o644<<16;z.writestr(info,file.read_bytes())
with zipfile.ZipFile(archive) as z:
for n,file in members.items():assert hashlib.sha256(z.read(n)).hexdigest()==sha(file)
write('manifesto-pacote.json',{'archive':archive.name,'archive_sha256':sha(archive),'entries':len(members),'delivery_revision':revision,'verified_members_sha256':{n:sha(file) for n,file in sorted(members.items())},'secrets_private_keys_credentials_databases_excluded':True})
print(json.dumps({'result':'sealed_for_review_activation_suspended','revision':revision,'application_input_hash':build['input_hash'],'manifest':build['build_manifest_sha256'],'changed_application_files':len(changed),'delta_from_previous_approved':len(delta),'archive':archive.name,'archive_sha256':sha(archive),'members':len(members),'active_changes':0}))