Files
Investidor-V2/tools/portal-audit-update-i0.py
nala 6ddafc95ba
tests / ci (push) Canceled after 0s
oportunidades 4
2026-10-10 07:03:59 +02:00

107 lines
19 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Supplement the existing audit with concrete I0 evidence; never grants active authorization."""
import csv
import json
import xml.etree.ElementTree as ET
from pathlib import Path
root = Path(__file__).resolve().parent.parent
private = root / 'storage/app/private/investmoz-v2/portal-audit'
docs = root / 'docs/investmoz/v2/auditoria-portal'
geometry = json.loads((private / 'geometry-auth-http-qa.json').read_text(encoding='utf-8-sig'))
resources = json.loads((private / 'resources-real-http-qa.json').read_text(encoding='utf-8-sig'))
persistence = json.loads((private / 'document-real-persistence-qa.json').read_text(encoding='utf-8-sig'))
assert geometry['api_persistence_passed'] and resources['api_persistence_passed'] and persistence['passed']
assert geometry['target'] == resources['target'] == persistence['target']
assert all(not p['interface_observed'] and p['active_writes'] == 0 for p in [geometry, resources, persistence])
tests = list(ET.parse(private / 'import-document-real-qa.xml').iter('testcase'))
assert len(tests) == 15 and all(not list(t) for t in tests)
consumer_tests = list(ET.parse(private / 'document-consumers-real-qa.xml').iter('testcase'))
assert consumer_tests and all(not list(t) for t in consumer_tests)
summary = json.loads((private / 'contagens-requisitos.json').read_text(encoding='utf-8-sig'))
assert summary['requisitos'] == summary['linhas_matriz'] == 3354 and summary['linhas_origem'] == 5830
assert summary['conservation_sha256'] == '35f4af4af5447faad115f4761954557d4458df658ccedbc0be781cf65a5536c6'
proof = json.loads((private / 'investor-path-evidence.json').read_text(encoding='utf-8-sig'))
proof.update({'geometry_authenticated_http': 'geometry-auth-http-qa.json', 'document_operations_real_http': 'resources-real-http-qa.json',
'document_operations_real_persistence': 'document-real-persistence-qa.json', 'qa_url': 'http://127.0.0.1:18097', 'candidate_revision_fixed': False,
'i0_selected_suite': {'junit': 'import-document-real-qa.xml', 'tests': len(tests), 'assertions': sum(int(t.get('assertions', 0)) for t in tests),
'cases': [{'class': t.get('class'), 'method': t.get('name'), 'assertions': t.get('assertions'), 'scanner_scope': 'mock fixture' if t.get('class') == 'Tests\\Feature\\OpportunityResourcesTest' else 'real ClamD or explicit negative control'} for t in tests]},
'integrated_ui_ab_passed': False, 'activation_ready': False})
proof['document_consumer_suite'] = {'junit': 'document-consumers-real-qa.xml', 'tests': len(consumer_tests), 'assertions': sum(int(t.get('assertions', 0)) for t in consumer_tests),
'cases': [{'class': t.get('class'), 'method': t.get('name'), 'scanner_scope': 'real ClamD consumer/worker' if t.get('class') in ['Tests\\Feature\\PortalRealDocumentConsumersTest', 'Tests\\Feature\\ClamDQueueWorkerIntegrationTest'] else 'mock/manual fixture or schema compatibility; not real ClamD proof'} for t in consumer_tests]}
(private / 'investor-path-evidence.json').write_text(json.dumps(proof, ensure_ascii=False, indent=2), encoding='utf-8')
def insert(filename, heading, body):
p = docs / filename
text = p.read_text(encoding='utf-8-sig')
block = heading + '\n\n' + body.strip() + '\n\n'
start = text.find(heading + '\n')
if start >= 0:
end = text.find('\n## ', start + len(heading))
text = text[:start] + block + (text[end + 1:] if end >= 0 else '')
else:
first = text.find('\n')
text = text[:first + 1] + '\n' + block + text[first + 1:].lstrip('\n')
p.write_text(text, encoding='utf-8')
states = '; '.join(f"{k}: {v['requisitos']} requisitos/{v['linhas_origem']} ocorrências" for k, v in summary['por_estado'].items())
body = f'''**QA atual: http://127.0.0.1:18097/investimentos.** Porta18096 anterior não usar; erro PHP/Symfony no arranque com GD, antes do cabeçalho QA. Processo isolado recuperado com GD e OPcache desligado, sem mudança do PHP ativo. Base `desk_restore_20261009_174726_test:55441`, sessões próprias; SuiteCRM/AI externos desativados. CUA permanece sem apps/browsers. Nenhuma observação visual nem escrita ativa.
**Mesma matriz:**3354 requisitos/linhas canónicas,5830 ocorrências preservadas; {states}. Nenhum requisito amplo fechado por este lote. `candidate_revision_fixed=false`, `integrated_ui_ab_passed=false`, `activation_ready=false`; não fixar candidata antes dos resultados manuais e correções.
| Requisito | Correção/prova concreta deste lote | Estado |
| --- | --- | --- |
| AUD-GEO-02/03, OPS-01 | C30: GET/POST `/management/territory-geometry-import`, POST `/{{batch}}/reverse`. Fontes retidas/edição/hash/scan; nomes exatos+pai+país, conflitos, preview vinculado ao ator/estado, MFA, `geo_write_receipts`, `audit_events` append-only; nada publicado. HTTP real com login+TOTP, três níveis,361 reutilizadas/0 novas; replay e reversão de lote de reutilização mantêm361. QA de testes demonstra criação e reversão de lote novo, preservação de histórico e recusas de alteração/publicação/FK/scan expirado/MFA/alvo não QA. | Código preparado + API/persistência QA; interface e ativo pendentes |
| AUD-SCAN-01 | C31: nome da rota assinada corrigido para `management.opportunity-image-preview`, mantendo URL. Servidor web QA gera3WebP320/640/1280; PDF+PNG+3derivadas têm cinco scans reais ClamAV1.5.4, clean/hash próprios, cifragem Laravel e classificação restricted; downloads reais comparados, assinaturas ausentes403 e outra conta403. QA de testes prova quarentena423, scanner inconclusivo e retry real, substituição sem apagar original, versão409, exclusão/restauro e adulteração409. | Operações de oportunidades demonstradas por API/persistência; apresentação visual pendente |
| AUD-LIB-01/SCAN-01/DESK-02 | C35 corrigiu erro500 ao omitir data/emissor opcionais. C36 corrigiu recuperação404 de originais cifrados: os consumidores comparavam o hash do ciphertext com o original. VerifiedDocumentBinary reutiliza decifragem/integridade sem cópia pública nem alteração de permissões. PortalRealDocumentConsumersTest usa ClamD real: biblioteca privada→scan→classificação→publicação separada→download; versão409/dedup/404hash. Balcão testa upload/confidential→scan→download, dedup/403estranho. Original canónico cifrado é reutilizado sem duplicar documents; downloads na gestão/Balcão/biblioteca e view PDF devolvem os bytes originais; antes de publicação/adulteração404. | API/persistência QA; bytes de preview PDF verificados, renderização visual pendente; sem publicação ativa nem fecho global |
| AUD-UI-01/DASH-01/SIM-01/DESK-02/GUIDE-01/CRM-01 | A/B repetidos por HTTP no servidor recuperado; nova sessão real, IDs/drafts próprios, recuperação, aplicabilidade, resultado20/0 e20/70000, Balcão e tarefa concluída enquanto terra aguarda; outra conta/contexto recusados. | API/persistência QA; cenários completos na interface pendentes |
**Correspondências:** províncias11→9/2; distritos159→120/39; postos411→232/179. Esperado refere o exemplar2019, não um cadastro atual certificado. Maputo e Maputo City são as duas pendências provinciais. Distritos:9 por pai não resolvido/30 por nome; postos:86 por pai/93 por nome. [Todas220 lacunas por nome/código/pai/causa](correspondencias-cartograficas.md). Sem polígonos de localidades no exemplar; nenhum limite equivale a implantação/DUAT. Aceitação da fonte oficial preservada.
**Evidências:** `geometry-auth-http-qa.json`, `resources-real-http-qa.json`, `document-real-persistence-qa.json`, `import-document-real-qa.xml`, `investor-path-http-qa.json` e `investor-path-recovered-calculations.json`, no acervo privado. As15 verificações dirigidas são inventário:4 importação,2 operações documentais reais,5 scanner/negativas e4 recursos com fixture. Não substituem prova por requisito ou interface.
**Ainda não verificado:** interface de A/B, cada formulário/estatística, cada seletor/mapa e operações documentais visuais. Biblioteca/Balcão têm agora os ensaios reais específicos acima; PDF view tem prova de bytes/MIME/CSP, mas renderização, importação em lote, todos os tipos/documentos/perfis e partilhas/imutabilidade por consumidor continuam pendentes. Os ensaios de compatibilidade/schema e fixtures não constituem prova ClamD real. ClamD real demonstrado nesta sessão é distinto de arranque após reboot/manutenção de assinaturas e GD no PHP ativo, ainda pendentes. Workflow e contratos especializados permanecem abertos; sem jornadas educativas novas.
'''
for filename in ['relatorio-executivo.md', 'execucao-percurso-i1-i3.md']:
insert(filename, '## Lote I0 atual — importação autenticada e operações documentais reais', body)
insert('backlog.md', '## Próximo lote I0–I3 — após importação e scans reais', '''
1. **P0/P1 interface:** executar A primeiro em http://127.0.0.1:18097/investimentos; depois B e perfis. Registar URL/ação/esperado/observado/IDs/versão e prova visual; incorporar os resultados manuais na mesma matriz e corrigir falhas antes de fixar candidata. API/HTTP já passaram, não antecipar UI.
2. **P1 países/origem/estatísticas:** percorrer cada formulário de cobertura-percursos-i0-i3.md, incluindo negócios/fundiário, serviços Balcão, filtros e agregados separados. Não confundir residência/constituição/origem de fundos com origem declarada de solicitações.
3. **P1 territorial:** observar cada seletor/mapa/consumidor; testar nova página `/management/territory-geometry-import?level=1`, depois2/3, confirmando correspondências, resultados e histórico com gestor QA/MFA. A importação não publica. Resolver somente correspondências explícitas fundamentadas, listadas por nome em correspondencias-cartograficas.md; conservar casos2/39/179 e ausência de localidades.
4. **P1 documental:** testar visualmente upload/scan/download/substituição/exclusão/restauro/previews; C31/C35/C36 corrigidos em QA. Biblioteca e Balcão têm provas reais específicas, incluindo reutilização do original cifrado sem duplicação e resposta PDF inline verificada. Renderização PDF, lote, tipos/perfis/partilhas/imutabilidade continuam abertos por consumidor. Configuração permanente ClamD/assinaturas/GD ativo é pendência operacional específica.
5. **OPS-01:** após demonstração e correções, fixar hashes e renovar backup/restauro/compatibilidade. Preparar provas vinculadas à revisão/alvo, incluindo a prova específica cartográfica. Recursos030000, percurso190000, flag e importação/publicação separados. Nenhuma ativação autorizada agora; fonte oficial aceite não se reabre.
''')
insert('plano-ativacao.md', '## Importação C30 — ações exatas e dependências', '''
Esta sequência é preparação, fora da autorização ativa atual. Antes de cada ação reconfirmar alvo/ledger/contagens; números abaixo são previsão do último retrato, não valores a forçar.
1. Incorporar A/B e consumidores manuais, corrigir falhas, repetir provas pertinentes e fixar revisão/manifest/fingerprints. Hoje continuam pendentes.
2. Renovar backup de DB/ficheiros privados/chaves e originais cartográficos/metadados; restaurar em nova QA com guarda, comparar schema/contagens/hashes/IDs/relações/classificações/RLS. Preservar prova anterior. Validar reversão vazia e recusa após uso. Preparar prova específica cartográfica a partir dessas evidências e resultados manuais; não fabricar `ui_passed=true`.
3. Disponibilizar código/assets, mantendo `INVESTOR_PATH_ENABLED=false` e `TERRITORY_GEOMETRY_IMPORT_ENABLED=false`.
4. Gestor autenticado: GET/POST `/management/opportunity-resources-install`, somente se030000 ausente. Previsto3tabelas de associação+1coluna de documents+função/3triggers;0recursos novos automáticos. Não repetir gestão/Balcão/Bolsa já instalados.
5. GET/POST `/management/balcao-install`, somente migrations realmente pendentes, prevista190000.2colunas+1definição imutável;0conversões/dossiers/steps automáticos. Prova visual da revisão é pré-condição; instalar não disponibiliza percurso.
6. Habilitar separadamente `INVESTOR_PATH_ENABLED` após schema/provas; novos dossiers instanciam19etapas nas5fases sem renumerar roteiros. Rollback imediato: flagfalse preservando dados e ferramentas diretas.
7. Preparar `geometry-import-activation-proof.json`, vinculada ao fingerprint cartográfico, alvo real, QA/UI e backup restaurado/hash/data<24h; configuração específica habilita rota, não equivale a aplicar. Este artefacto final não foi emitido. Gestor com MFA: GET `/management/territory-geometry-import?level=1`, rever linhas/hash; POST `/management/territory-geometry-import` com level/hash/idempotency_key/reason/confirm. Repetir2 só após pais1, depois3. Se prévia conservar todas as correspondências e ativo0:9+120+232=361 geometrias privadas novas;0territórios/locais/pontos novos. Hoje QA reutilizou361 e inseriu0. Fonte/scan expirado exige atualização da prova por bytes, não revalidação de credibilidade oficial.
8. Publicação de camadas por consumidor/âmbito é ato separado. O importador não tem comando de publicação. Não alterar para public nem usar publicação de zonas de investimento para publicar limites administrativos; mecanismo de divulgação correspondente permanece no backlog quando necessário.
Reversão do lote: POST `/management/territory-geometry-import/{batch}/reverse`, com MFA, idempotency_key/reason/confirm. Só faz exclusão lógica das geometrias criadas por esse lote, intactas e sem FK consumidor; preserva361 reutilizadas, originais, receipts e auditoria. Se houver uso/alteração/publicação, recusa e exige reconciliação, sem apagar relações. Rollback de migrations030000/190000 apenas vazio; após utilização desligar disponibilização e preservar dados. Restauro integral nunca sobrescreve escritas posteriores sem reconciliação/autorização.
''')
insert('cobertura-percursos-i0-i3.md', '## Complemento atual C30/C31 — critérios distintos', body)
cases = docs / 'casos-reproducao.md'
text = cases.read_text(encoding='utf-8')
additions = {
'C30': '## C30 — importação oficial dependia de CLI QA sem ato autenticado — P1\n\nAUD-GEO-02/03, OPS-01. Reprodução anterior: existiam361 limites privados aplicados por tools/portal-audit-geometry-qa.php, sem rota de prévia/aplicação/reversão por gestor. Esperado: gestor/MFA, prévia/hash/correspondências, receipts idempotentes, auditoria e reversão preservando adotadas. Corrigido: serviço/controlador/página nas entidades existentes, flag desativada fora de QA e guarda de backup/provas no alvo ativo. HTTP real nos3níveis e ensaio de criação/reversão/FK na QA; apresentação visual pendente.\n',
'C31': '## C31 — previews de derivadas lançavam RouteNotFoundException — P0\n\nAUD-SCAN-01. Reprodução: gerar e limpar uma derivada; abrir recursos da oportunidade. Esperado: URL assinada com preview WebP. Observado: Route [opportunity-image-preview] not defined; GET da página falha após existir derivada limpa. Causa: grupo management prefixa nome da rota, mas controlador usava nome sem prefixo; teste antigo saía no ramo GD ausente. Corrigido: management.opportunity-image-preview, sem mudar URL. PHP QA com GD e ClamD1.5.4:3derivadas320/640/1280, scans separados, página/bytes recuperados, assinatura/ator indevidos403 e adulteração409. Visual pendente.\n',
'C32': '## C32 — arranque PHP QA com GD falhava antes do cabeçalho de isolamento — P1\n\nReprodução: processo18096 reiniciado com extensão GD. Observado: Fatal error Cannot instantiate interface RecursiveIterator no arranque Symfony/Finder, resposta200 sem cabeçalho QA; ensaio abortou antes de escrever. Causa exata do PHP/OPcache não comprovada; funcionamento restabelecido num processo QA18097 com GD e OPcache desligado apenas nele, sem editar vendor/PHP ativo. Tentativa agrupada de reinício foi rejeitada pela revisão automática sem razão detalhada; alternativa isolada permitida. Login/cabeçalho/HTTP A/B/importação/uploads/derivadas verificados; CUA ainda sem provider.\n',
'C33': '## C33 — fixture MFA incompatível com serialização Fortify — QA\n\nReprodução: primeira fixture de gestor cartográfico usava Crypt::encryptString no secret; POST /two-factor-challenge dava unserialize() offset error. Corrigida somente a fixture QA para Crypt::encrypt, compatível com Fortify. Login TOTP real passou; nenhuma credencial ativa ou controlo MFA alterado.\n',
'C34': '## C34 — prévia cartográfica recalculava hash do pai por elemento — P1 QA\n\nAo acrescentar verificação da edição/hash do pai, o hash era recalculado por feature. Ensaio de nível3 excedeu limite PHP e devolveu conteúdo fatal em resposta200, recusado pelo consumidor JSON. Corrigido para calcular hash uma vez por nível; transação preserva estado em falha. Repetir prova HTTP integral na revisão final e manter resultado visual pendente.\n',
'C35': '## C35 — cadastro documental falha500 quando campos opcionais são omitidos — P0\n\nAUD-LIB-01/SCAN-01. Reprodução: upload privado, depois POST /management/portal-services/resources com campos obrigatórios, omitindo document_date/issuer permitidos como nullable; o cadastro tenta aceder diretamente a document_date na atualização do documents. Observado: Undefined array key document_date em PortalServicesController:444 e transação revertida. Esperado: cadastro com data desconhecida/sem invenção, preservar emissor/data existentes quando ausentes. Corrigido acesso com ??null tanto na criação como na edição; teste mantém opcionais omitidos e cobre gravação/recuperação/scans reais/classificação/publicação separada, conflitos/dedup/hash/permissões. Sem alteração ativa nem aceitação fictícia de fonte oficial.\n',
'C36': '## C36 — original canónico cifrado devolve404 em consumidores — P0\n\nAUD-LIB-01/SCAN-01/DESK-02. Reprodução QA: documents com storage_encryption=laravel, bytes originais/hash, scan ClamD real clean; GET /management/documents/{id}/download. Esperado200 com original íntegro; observado404 (encrypted-consumer-before-qa.xml). Causa: gestão/biblioteca/Balcão comparavam hash do ficheiro cifrado ao hash original. Corrigido VerifiedDocumentBinary partilhado: mantém guardas do consumidor, scan/hash, path privado e cifra; decifra só em memória e verifica plaintext, sem copiar para disco público. Teste real verifica gestão, associação ao Balcão sem novo documents, download e view PDF após classificação/publicação explícitas, bloqueio antes de publicação, bytes/MIME/CSP e adulteração404 em todos os consumidores. document-consumers-real-qa.xml distingue os três ensaios ClamD de consumidores/worker das fixtures de outros testes. Sem observação visual nem escrita ativa.\n',
}
for id, block in additions.items():
if f'## {id} —' not in text:
text += '\n' + block
cases.write_text(text, encoding='utf-8')
print(json.dumps({'requirements': summary['requisitos'], 'source_lines': summary['linhas_origem'], 'requirements_closed': 0, 'visual': False, 'candidate_fixed': False, 'active_writes': 0}))