107 lines
19 KiB
Python
107 lines
19 KiB
Python
"""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}))
|