Files
Investidor-V2/tools/portal-audit-legacy-browser-update.py
nala 20971d523f
tests / ci (push) Canceled after 0s
oportunidades 6
2026-10-10 21:17:46 +02:00

185 lines
21 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.
"""Reconcile browser evidence into the sole existing audit, preserving source references."""
import collections
import csv
import hashlib
import json
import re
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
DOC = ROOT / 'docs/investmoz/v2/auditoria-portal'
PRIVATE = ROOT / 'storage/app/private/investmoz-v2/portal-audit'
csv.field_size_limit(5_000_000)
browser = json.loads((PRIVATE / 'legacy-content-browser-qa.json').read_text(encoding='utf-8'))
persist = json.loads((PRIVATE / 'legacy-content-browser-persistence-qa.json').read_text(encoding='utf-8'))
assert browser['result'] == 'passed' and browser['interface_observed'] and browser['checks']['qa_browser_title']
assert persist['dossier_id'] == browser['dossier_id'] and persist['reference_count'] == persist['reference_event_count'] == 1
assert browser['creation_evidence']['receipt'] and browser['checks']['created_from_ui']
assert persist['transaction_read_only'] and not persist['active_access'] and browser['active_writes'] == 0
matrix = DOC / 'matriz-requisitos.csv'
rows = list(csv.DictReader(matrix.open(encoding='utf-8-sig', newline='')))
headers = list(rows[0])
protected = ['ID', 'Pedido/trecho', 'Fonte/ocorrências', 'Critério de aceitação', 'Versões divergentes',
'IDs das linhas de origem', 'Quantidade de linhas de origem']
before = {r['ID']: {k: r[k] for k in protected} for r in rows}
tag = '[Interface legado QA 10/10/2026] '
evidence = ('Chromium instalado, contextos isolados, desktop 1440 px / móvel 390 px: fotografia/fontes/paleta, pesquisa hit/miss, '
'Enter/foco de 3 px, links carteira/zonas/biblioteca/oportunidade. Promotor QA21: login→MFA→dossier→fonte→retorno→'
'logout→novo contexto/MFA→recuperação/F5→associação repetida. Conta23:403 e artigo público sem título privado; '
'anónimo sem contexto privado. UI observada; referência/evento únicos e originais/hash/scan confirmados '
'em leitura QA. legacy-content-browser-qa.json (creation_evidence aponta a criação inicial); legacy-content-browser-persistence-qa.json; legado-piloto-qa.md.')
gaps = {
'AUD-LEG-01': 'Cobertura histórica, paginação, destinos externos e restantes exemplares/versões não importados. C68 continua identificado.',
'AUD-LEG-02': 'Piloto desktop/móvel e foco por teclado demonstrados. Falta cobertura sistemática dos restantes controlos/tecnologias assistivas e expansão das adaptações ao restante legado; não declarar conformidade integral de acessibilidade.',
'AUD-LEG-03': 'Percurso do piloto demonstrado na interface. Relações cartográficas concretas dos seis corredores ainda não fundamentadas; link para mapas gerais não comprova delimitação/integração territorial do artigo. A/B globais permanecem independentes.',
'AUD-LEG-04': 'Nenhum critério pendente na consulta e proteção do piloto QA demonstrado. C37/QA18 no navegador do utilizador e disponibilização ativa são requisitos/ações independentes, não fechados por este ensaio.',
'AUD-LEG-05': 'Relações territoriais concretas e ampliação por etapa/setor ainda por fundamentar. Fonte única no piloto, sem criar oportunidades ou geometrias pelo artigo.',
'AUD-LEG-06': 'Contexto/retorno do piloto observados; mapa de migração dos restantes URLs ainda pendente. C37/QA18 e cenários A/B completos não foram executados neste ensaio.',
'AUD-LEG-07': 'Imagens/links/pesquisa/permissões/deduplicação do piloto demonstrados. Anexos/referências dos restantes conteúdos e imagem histórica404 por resolver; cobertura global permanece parcial.',
}
for row in rows:
if row['ID'] not in gaps:
continue
# Old findings remain as dated evidence; source requests and acceptance wording are immutable.
for key in ['Interface', 'Evidência funcional', 'Resultado apresentado', 'Recuperação']:
if tag not in row[key]:
value = evidence if key == 'Evidência funcional' else (
'Render/cliques observados no Chromium QA, com recuperação noutra sessão/F5 e negação à conta23. '
'Não é o navegador pessoal do utilizador nem o ensaio C37/QA18.')
row[key] = tag + value + ' | Evidência anterior preservada: ' + row[key]
row['Lacuna'] = gaps[row['ID']]
row['Dependência'] = 'Biblioteca/documentos existentes; Balcão+09/190000 para dossier. Expansão cartográfica exige correspondências fundamentadas. C37/A-B/SLA/perfis e ativação mantêm âmbito próprio.'
if row['ID'] == 'AUD-LEG-04':
old_state = row['Estado']
row['Estado'] = 'concluído e demonstrado'
variants = json.loads(row['Critérios e versões por origem (JSON)'])
for variant in variants:
if variant.get('estado') != row['Estado']:
variant.setdefault('historico_estado', []).append({'estado': variant.get('estado'), 'motivo': 'Sem prova visual no ensaio inicial'})
variant['estado'] = row['Estado']
variant['ambito_demonstrado'] = 'Piloto agronegócio exclusivamente QA; não ativação, C37 ou cobertura histórica'
variant['evidencia'] = 'legacy-content-browser-qa.json; legacy-content-browser-persistence-qa.json'
row['Critérios e versões por origem (JSON)'] = json.dumps(variants, ensure_ascii=False)
if tag not in row['Consolidação']:
row['Consolidação'] += ' | ' + tag + 'Estado anterior ' + old_state + '; critérios do piloto QA agora demonstrados; ativo indisponível e requisitos globais preservados.'
else:
if tag not in row['Consolidação']:
row['Consolidação'] += ' | ' + tag + 'Critérios visuais do piloto comprovados sem encerrar as lacunas globais enumeradas.'
# A later backup probe discovered a separate QA infrastructure fault. Keep the UI proof,
# but do not allow a historical readiness receipt to imply a current verified backup.
failure = json.loads((PRIVATE / 'profile-installation-backup-failure-qa.json').read_text(encoding='utf-8'))
preservation = json.loads((PRIVATE / 'qa-readable-preservation.json').read_text(encoding='utf-8'))
assert not failure['restore_verified'] and f"{failure['target']['database']}:{failure['target']['port']}" == browser['target']
assert preservation['transaction_read_only'] and not preservation['restore_verified'] and preservation['database_writes'] == 0
fault_tag = '[C71 QA 10/10/2026] '
for row in rows:
if row['ID'] not in ['AUD-OPS-01', 'AUD-PROFILE-31']:
continue
if fault_tag not in row['Evidência funcional']:
row['Evidência funcional'] += ' | ' + fault_tag + 'Novo pg_dump falha: global/2964 (pg_db_role_setting) ausente no cluster QA55441. Prova anterior13:35:39UTC conservada, não é renovação. 295 tabelas/34902 linhas/31 sequências preservadas por leitura;26 ficheiros de catálogos partilhados ausentes. Preservação não é restauro verificado. qa-readable-preservation.json; profile-installation-backup-failure-qa.json.'
row['Lacuna'] = fault_tag + 'Restauro atual bloqueado pela integridade do cluster QA; origem da remoção não identificada. Recuperar em cluster isolado preservando todos os dados, conferir schema/seq/ficheiros/MFA e repetir backup/restauro e percursos afetados. Readiness QA invalidada; candidata não fixada. | Lacunas anteriores preservadas: ' + row['Lacuna']
row['Dependência'] = fault_tag + 'Restabelecer backup/restauro QA antes de novas escritas de ensaio ou candidata; nenhum catálogo copiado/recriado às cegas e nenhuma ação no PostgreSQL ativo5432. | ' + row['Dependência']
if row['ID'] == 'AUD-OPS-01':
row['Estado'] = 'com falha'
row['Prioridade'] = 'P0'
assert before == {r['ID']: {k: r[k] for k in protected} for r in rows}
assert len({r['ID'] for r in rows}) == len(rows)
with matrix.open('w', encoding='utf-8-sig', newline='') as f:
writer = csv.DictWriter(f, fieldnames=headers); writer.writeheader(); writer.writerows(rows)
counts = {'requirements': len(rows), 'matrix_rows': len(rows), 'source_occurrences': sum(int(r['Quantidade de linhas de origem']) for r in rows),
'states': dict(collections.Counter(r['Estado'] for r in rows)), 'legacy_states': {r['ID']: r['Estado'] for r in rows if r['ID'] in gaps}}
(PRIVATE / 'legacy-browser-audit-update-qa.json').write_text(json.dumps(counts, ensure_ascii=False, indent=2), encoding='utf-8')
marker = 'qa-legacy-browser-20261010'
def prepend(filename, content):
file = DOC / filename
text = file.read_text(encoding='utf-8')
text = re.sub(r'\n*<!-- ' + marker + r':begin -->.*?<!-- ' + marker + r':end -->\n*', '\n\n', text, flags=re.S)
first, rest = text.split('\n', 1)
file.write_text(first + '\n\n<!-- ' + marker + ':begin -->\n' + content + '\n<!-- ' + marker + ':end -->\n\n' + rest.lstrip(), encoding='utf-8')
summary = '''## Verificação do piloto na interface — 10/10/2026, exclusivamente QA
**O piloto existente foi demonstrado em Chromium instalado**, com contextos separados, sem usar o perfil pessoal do utilizador. O controlo nativo continua indisponível (`native pipe`, os error 2, duas sondagens); esse problema não impediu renderização, cliques e capturas por Playwright. C37/QA18 permanece pendente; este ensaio usou Promotor QA21 e outra conta sintética QA23.
Página pública→pesquisa com/sem resultado→artigo→carteira/mapas/biblioteca/oportunidade publicada→CTA→login/MFA da conta QA21→dossier guardado→fonte/retorno→logout→novo contexto/MFA→recuperação e F5→continuação/repetição passaram. Leitura anónima e QA23 não recebem o título/ID do contexto privado; dossier negado 403 à QA23. A criação na interface consta da tentativa inicial preservada; as repetições recuperam o mesmo dossier sem criar outro. Cinco fases / 19 etapas observadas. Persistência em transação READ ONLY: uma referência, um evento, um documento por hash; originais e scan preservados; projeto independente com zero referências.
Desktop 1440 px / móvel 390 px: fotografia 900×506, Noto Sans / Noto Serif carregadas, cores reais adaptadas, texto integral de 4.702 carateres, foco de 3 px por teclado e nenhum deslocamento horizontal. Sete capturas privadas. **C70 corrigido:** título de separador “Laravel” substituído por “InvesteMoz QA” apenas no build QA; bundle ativo preservado. Isto não certifica acessibilidade integral nem demonstra todas as geometrias/consumidores do portal.
**AUD-LEG-04 concluído e demonstrado em QA**, por consulta pública/proteção privada. AUD-LEG-01/02/03/05/06/07 continuam parciais pelas lacunas explicitadas na matriz: cobertura histórica, controlo assistivo sistemático, relações territoriais concretas, restante mapa de URLs e anexos. Nenhum I0–I3/A-B/workflow/C37 é encerrado por esta prova.
Recibos: `legacy-content-browser-qa.json`, `legacy-content-browser-persistence-qa.json`; testes `tools/legacy-content-browser-qa.cjs` e `tools/legacy-content-browser-persistence-qa.php`. Capturas e credenciais permanecem privadas; não foram registados Cookie, X-XSRF-TOKEN ou corpo com o código MFA. O ZIP anteriormente entregue é um snapshot anterior à verificação, não prova de execução remota.'''
backup_note = """
**C71 / P0 — novo backup QA bloqueado:** `pg_dump` falhou às14:48–14:49UTC: `global/2964` ausente, catálogo `pg_db_role_setting`; diagnóstico identifica26 ficheiros de catálogos partilhados ausentes no cluster55441, situado em Temp. Não atribuir a remoção à limpeza automática sem evidência. O backup/restauro válido de13:35:39UTC (295 tabelas/81 ficheiros) permanece histórico. Preservação posterior READ ONLY:295 tabelas/34.902 linhas/31 sequências; não equivale a backup/restauro verificado. Readiness QA foi invalidada e o CLI passa a devolver exit1 controlado. Próxima ação: recuperação em ambiente isolado, preservação/conferência integral e nova prova de restauro antes de retomar escritas. Nenhum catálogo recriado às cegas nem acesso/escrita na base ativa. Ver `qa-readable-preservation.json`, `profile-installation-backup-failure-qa.json` e C71 em `casos-reproducao.md`."""
summary += backup_note
prepend('legado-piloto-qa.md', summary)
prepend('relatorio-executivo.md', summary + f'''
**Contagens da mesma matriz:** {counts['requirements']} requisitos/{counts['matrix_rows']} linhas; {counts['source_occurrences']} ocorrências de origem. Estados: ''' + '; '.join(f"{v} {k}" for k, v in counts['states'].items()) + '.')
prepend('backlog.md', '''## Próximo lote após a demonstração do piloto
| Requisito | Demonstrado | Falta / próxima ação |
| --- | --- | --- |
| AUD-OPS-01 / C71 — P0 | Diagnóstico e preservação READ ONLY; backup anterior intacto, readiness atual inválida. | Recuperar cluster QA e renovar backup/restauro antes de novas escritas; não fixar candidata. |
| AUD-LEG-04 | Consulta pública e login apenas para dossier; MFA da conta QA21/nova sessão/F5; anónimo/QA23 sem contexto; QA23 negado 403. Concluído exclusivamente QA. | Ativação separada; não assumir C37/QA18 resolvido. |
| AUD-LEG-02 | Desktop/móvel/fotografia/fontes/paleta, pesquisa, teclado; C70 título QA corrigido. | Verificação sistemática de controlos/tecnologias assistivas e adaptação dos outros setores. |
| AUD-LEG-03/05 | Artigo→consumidores→dossier/fonte/retorno; referência/evento únicos; sem mistura. | Fundamentar correspondências dos seis corredores; mapas gerais não equivalem a geometrias relacionadas com o artigo. |
| AUD-LEG-01/06/07 | Piloto visual e fontes preservadas. | Reconciliar os demais 50 exemplares observados, sitemap misto/paginação, outros URLs/anexos; C68 imagem 404 continua aberto. |
| C37; A/B; dashboard QA21 | Ensaio QA21 do piloto não os substitui. | C37 no contexto problemático QA18; A/B completos com ferramentas/Balcão/acompanhamento; CRUD de oportunidades pelo dashboard. |
Continuar países/origem/estatísticas, 173 lacunas cartográficas (Mandie/Guro ambíguo), documentação/derivadas, Response/materialidade e workflow na mesma matriz. Nenhum responsável real ou compromisso SLA foi designado. Base ativa fora do âmbito; sem repetir instalações QA030/040/09-190. C71 bloqueia novas escritas QA até recuperar a integridade e o restauro, sem revogar as provas já obtidas.''')
prepend('casos-reproducao.md', '''## C70 — identificação errada no título do navegador
**Requisito:** AUD-LEG-02; P2. **Reprodução:** abrir anónimamente `/explorar/agronegocio` na QA 18097 e consultar o título do separador. **Esperado:** identidade do portal e distinção QA. **Observado:** “Explorar agronegócio - Laravel”. **Causa:** `resources/js/app.tsx` usa o nome padrão da compilação; a QA herdava `VITE_APP_NAME` do ambiente local. **Correção:** `vite.config.ts` define “InvesteMoz QA” exclusivamente sob `INVESTMOZ_QA_BUILD=1`; não altera .env nem build ativo. **Evidência:** título verificado no Chromium, percurso renderizado após recompilação; manifesto ativo inalterado. Código preparado eQA demonstrada; não ativado.
As primeiras tentativas do ensaio de browser terminaram por falta de espera da navegação Inertia no próprio teste, antes de inspecionar o URL atualizado. O runner foi corrigido para aguardar o destino; recibos das tentativas preservados privadamente. Não classificar essas falhas de instrumentação como falhas do portal. C37 não foi repetido; C68 permanece aberto.
## C71 — falha do novo backup, cluster PostgreSQL QA
**Requisito:** AUD-OPS-01, dependência de AUD-PROFILE-31; P0, aberto. **Reprodução:** guarda identifica `desk_restore_20261009_174726_test:55441`; executar `tools/portal-profile-installation-backup-qa.php`. **Esperado:** dump atual, restauro noutra base QA e comparação integral. **Observado:** `pg_dump` exit1 ao consultar `pg_roles`, pois não consegue abrir `global/2964`; nenhum novo restauro criado. **Causa identificada:** ficheiro de `pg_db_role_setting` ausente; inspeção READ ONLY identifica26 ficheiros partilhados ausentes no clusterQA em Temp. O processo que os removeu não foi identificado. **Mitigação:** originais/backups anteriores preservados;295 tabelas/34.902 linhas/31 sequências extraídas privadamente por leitura; readiness atual invalidada e erro do CLI controlado. **Correção pendente:** recuperar clusterQA preservando schema, IDs, relações, sequências e chave estável, conferir todos os dados/ficheiros e repetir backup/restauro antes de ensaios com escrita. Não declarar preservação NDJSON ou backup13:35 como restauro renovado. Nenhum `key:generate`, catálogo inventado, reinício do PostgreSQL ativo ou escrita ativa.
Referência técnica: [PostgreSQL17 — pg_db_role_setting](https://www.postgresql.org/docs/17/catalog-pg-db-role-setting.html), catálogo partilhado por todas as bases de um cluster. Diagnósticos completos e extrações permanecem privados.''')
prepend('plano-ativacao.md', '''## Atualização após a prova visual do piloto
O piloto legado foi demonstrado no Chromium QA com Promotor QA21; C37/QA18, CRUD de oportunidades no dashboard e A/B completos continuam pendentes. Não fixar uma revisão candidata global apenas por esta prova. A identificação “InvesteMoz QA” é compilada somente no build QA; não transferir esse rótulo automaticamente para produção.
Preparação preservada: corrigir critérios globais→fixar revisão/hashes/configuração→conferir base ativa por leitura e renovar backup/restauro correspondente→código/bundle compatíveis com flags desligadas→030000→040000→09/190000 apenas se ausentes→importações privadas por prévia/hash/ClamD→divulgação por recurso/camada→disponibilização do percurso. Fonte editorial pública usa biblioteca; guardar contexto requer Balcão/09-190. Cartografia continua lote distinto.
Contagens do legado inalteradas: um artigo + uma imagem, três associações fundamentadas, zero oportunidades/geometrias criadas pelo artigo e zero contas/dossiers QA transferidos. Reversão: ocultar piloto/flag e usar bundle compatível; retirar divulgação em ação auditada preservando originais, versões, relações e histórico. Ensaio/restauro QA não substitui backup da base ativa/revisão candidata; nenhuma ativação autorizada.
C71 é agora pré-condição técnica: recuperar o cluster QA e renovar backup/restauro, retomar as escritas e repetir os percursos afetados; depois fixar candidata e preparar a prova específica do alvo ativo. O restauroQA anterior13:35 permanece histórico; readiness invalidada. A falha nova não autoriza alterar ou reiniciar a base ativa.''')
pilot = DOC / 'legado-piloto-qa.md'
text = pilot.read_text(encoding='utf-8')
text = text.replace('**Aceitação visual pendente:** `cua.getState()`', '**Histórico do ensaio inicial, anterior ao Chromium:** `cua.getState()`')
text = text.replace('Promotor QA21 visual, A/B, workflow normativo integral', 'CRUD do dashboard com Promotor QA21, A/B, workflow normativo integral')
pilot.write_text(text, encoding='utf-8')
# Preserve older versions as history, with explicit precedence for the latest evidence.
for filename in ['legado-piloto-qa.md', 'relatorio-executivo.md', 'backlog.md', 'casos-reproducao.md', 'plano-ativacao.md']:
file = DOC / filename
content = file.read_text(encoding='utf-8')
content = content.replace('## Frente atual — legado agronegócio, exclusivamente em QA', '## Histórico — legado agronegócio antes do ensaio Chromium')
content = content.replace('## Próximo lote — expansão do legado mantendo I0–I3/C37', '## Histórico do lote planeado antes do ensaio Chromium')
content = content.replace('## Complemento legado — preparaçãoQA, sem autorização ativa', '## Histórico — complemento legado anterior ao ensaio Chromium')
content = content.replace('**Navegador integrado indisponível; aceitação visual pendente**', '**Estado histórico, antes do ensaio Chromium: navegador integrado indisponível; aceitação visual então pendente**')
# The remainder of these old blocks retains its original findings and counts.
for a, b in {'às14:48': 'às 14:48', 'identifica26': 'identifica 26', 'cluster55441': 'cluster 55441', 'de13:35': 'de 13:35', 'ONLY:295': 'ONLY: 295', 'exit1': 'exit 1', 'identifica26': 'identifica 26', 'clusterQA': 'cluster QA', 'privadamente por leitura;295': 'privadamente por leitura; 295', 'leitura;295': 'leitura; 295', 'preservados;295': 'preservados; 295', 'leitura;26': 'leitura; 26', 'de catálogos partilhados ausentes no cluster55441': 'de catálogos partilhados ausentes no cluster 55441'}.items():
content = content.replace(a, b)
file.write_text(content, encoding='utf-8')
sources = DOC / 'inventario-fontes.csv'
records = list(csv.DictReader(sources.open(encoding='utf-8-sig', newline='')))
for filename in ['legado-piloto-qa.md']:
file = DOC / filename; data = file.read_bytes()
record = next(r for r in records if r['Exemplar/caminho'].replace('\\', '/').endswith('/' + filename))
old = record['SHA256']; new = hashlib.sha256(data).hexdigest()
if old != new: record['Edição/versão identificada'] += ' | exemplar anterior SHA256=' + old
edition = record['Edição/versão identificada']
if ' | interface QA10/10/2026' not in edition:
edition += ' | interface QA10/10/2026'
record.update({'SHA256': new, 'Bytes': str(len(data)), 'Nota de leitura': 'Prova real Chromium QA; limites históricos/cartográficos/C37/A-B preservados, sem ativação.',
'Edição/versão identificada': edition})
with sources.open('w', encoding='utf-8-sig', newline='') as f:
writer = csv.DictWriter(f, fieldnames=list(records[0])); writer.writeheader(); writer.writerows(records)
print(json.dumps(counts, ensure_ascii=True))