185 lines
21 KiB
Python
185 lines
21 KiB
Python
"""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))
|