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

281 lines
35 KiB
Python
Raw 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.
"""Prepare JSON cell edits and reconcile existing Markdown from scoped visual QA receipts.
CSV authoring remains exclusively in the existing Artifact Tool builder.
"""
import collections
import csv
import hashlib
import json
import re
from datetime import datetime, timezone
from pathlib import Path
csv.field_size_limit(16000000)
ROOT = Path(__file__).resolve().parent.parent
DOC = ROOT / 'docs/investmoz/v2/auditoria-portal'
PRIVATE = ROOT / 'storage/app/private/investmoz-v2/portal-audit'
OUT = PRIVATE / 'visual-finalize-20261010'
OUT.mkdir(exist_ok=True)
stamp = datetime.now(timezone.utc).isoformat()
tag = '[Visual QA 10/10/2026] '
def receipt(name):
return json.loads((PRIVATE / name).read_text(encoding='utf-8'))
ab = receipt('investor-ab-chromium-20261010.json')
promoter = receipt('promoter-dashboard-chromium-20261010.json')
follow = receipt('visual-followup-chromium-20261010.json')
countries = receipt('consumers-mobile-accessibility-chromium-20261010.json')
manual = receipt('c37-qa18-post-mfa-destination-20261010.json')
denied = receipt('qa-report-access-chromium-20261010.json')
backup = receipt('profile-installation-backup-qa.json')
active = json.loads((ROOT / 'storage/app/private/virtual-desk/backup-proof.json').read_text(encoding='utf-8'))
assert ab['result'] == denied['result'] == 'passed'
assert all(ab['checks'].values()) and all(follow['checks'].values())
assert promoter['result'] == 'passed_scoped_restore_pending'
assert backup['restore_verified'] and backup['files_restore_verified'] and backup['files_verified']
assert active['restore_verified'] and active['files_verified']
assert manual['manual_access_confirmed_by_user'] and not manual['manual_refresh_logout_new_login_confirmed']
assert len(follow['accessibility']) == 15 and len(follow['maps']) == 4
rows = list(csv.reader((DOC / 'matriz-requisitos.csv').open(encoding='utf-8-sig', newline='')))
headers = rows[0]
canonical = {r[0]: dict(zip(headers, r)) for r in rows[1:]}
assert len(canonical) == len(rows)-1 == 3393
states = collections.Counter(r['Estado'] for r in canonical.values())
assert sum(int(r['Quantidade de linhas de origem']) for r in canonical.values()) == 6845
updates = {}
def update(ids, evidence, gap, interface=None, api=None, save=None, recover=None, result=None):
for rid in ids:
old = dict(canonical[rid])
# Re-running this preparation refines the same increment, without duplicating prose.
for field in ['Evidência funcional','Consolidação','Interface','API','Gravação','Recuperação','Resultado apresentado']:
old[field] = old[field].split(' | '+tag)[0]
if old['Lacuna'].startswith(tag) and ' | Histórico preservado: ' in old['Lacuna']:
old['Lacuna'] = old['Lacuna'].split(' | Histórico preservado: ', 1)[1]
patch = {'Evidência funcional': old['Evidência funcional'] + ' | ' + tag + evidence,
'Lacuna': tag + gap + ' | Histórico preservado: ' + old['Lacuna'],
'Consolidação': old['Consolidação'] + ' | ' + tag + 'Subcritérios atualizados por prova própria; estado global, IDs, fontes, versões e decisões preservados.'}
for field, value in [('Interface', interface), ('API', api), ('Gravação', save), ('Recuperação', recover), ('Resultado apresentado', result)]:
if value:
patch[field] = old[field] + ' | ' + tag + value
variants = json.loads(old['Critérios e versões por origem (JSON)'])
for variant in variants:
variant['incremento_visual_qa_20261010'] = {'atualizado_em': stamp, 'ambito': 'QA18097/55441 exclusivamente',
'evidencia': evidence, 'lacuna_atual': gap, 'estado_global_preservado': old['Estado'], 'ativacao': False}
patch['Critérios e versões por origem (JSON)'] = json.dumps(variants, ensure_ascii=False, separators=(',', ':'))
updates[rid] = patch
c37 = ('MFA manual18 aceite às17:40:21 e17:46:42UTC; sessão regenerada e autenticada18 persistida. '
'Destino inicialmente pretendido /qa-auditoria/legado-piloto-qa negado403 por perfil, sem perder sessão; /dashboard200 posterior. '
'Responsável confirmou acesso. Mensagem403QA e retorno visível corrigidos, com navegação Inertia409→GET403 única; ensaioQA21 preservou403 e abriu painel/investimentos/F5. '
'Rastreio: c37-qa18-post-mfa-destination-20261010.json; navegador: qa-report-access-chromium-20261010.json. '
'Ensaio18 isolado anterior de login/MFA/investimentos/F5/logout/nova sessão passou; não substitui o navegador habitual. Nenhum segredo/código/cookie registado nas provas entregáveis; MFA/chave/conta18 não alterados.')
update(['AUD-PROFILE-24','AUD-PROFILE-25','AUD-UI-01'], c37,
'C37: confirmar atualização, logout e novo login no navegador habitual; confirmação de acesso não encerra todo o ciclo. '
'ERR_TOO_MANY_REDIRECTS não foi demonstrado como ciclo persistente pelo rastreio; distinguir409 de atualização Inertia de rejeiçãoMFA e de autorização403. Outros perfis/dispositivos/consumidores pendentes.',
interface='QA18 acesso manual confirmado; QA21 retorno visível após relatório403 demonstrado. Ciclo manual C37 ainda parcial.',
api='POST MFA302, auth18 persistida; relatório403 e dashboard200. Não houve DecryptException nesses pedidos.',
recover='Sessão autenticada mantida após destino proibido; nova sessão manual18 chegou ao dashboard200.',
result='QA mostra acesso reservado e links úteis; nenhum privilégio administrativo concedido ao investidor.')
abevidence = ('Chromium desktop18: dois dossiers próprios A/B criados, guardados e recuperados noutra sessão; cinco fases e19 etapas preservadas. '
'A dispensaS10; B espera externaS10 comS02 independente. Plano contextual guardado/reaberto, resultados devolvidos e recuperados no dossier: ponto de equilíbrio20unidades; défice A0/B70000MZN. '
'Balcão preparado comoDRAFT, paísesPT/origemZA guardados; tarefa interna atribuída/assumida/iniciada/concluída no UI pelo gestor14 e recuperada pelo investidor. '
'Gestor sem acesso a resultados financeiros privados; QA23 dossier403/rascunho404; sem mistura entre projetos. investor-ab-chromium-20261010.json e visual-persistence-qa.json. '
'C80: bloquear edição/avanço enquanto GET do rascunho não terminou; diagnóstico guardado, correção e resposta deliberadamente atrasada no Chromium demonstrados em portal-simulator-loading-visual-qa.cjs/recibo.')
update(['AUD-GUIDE-01','AUD-DESK-01','AUD-DESK-02','AUD-CRM-01','AUD-SIM-02'], abevidence,
'A/B completos para o cenário interno aprovado, em desktop isolado; Balcão apenas preparaçãoDRAFT, não submissão/decisão externa. '
'Restantes contratos, documentos, fiscalidade/parâmetros legais, etapas externas e todos os consumidores/perfis não encerrados. '
'Móvel A/B teve achados anteriores: correção comum verificada com dossier próprioQA21, sem substituir repetição final A/B móvel ou aceitação manual.',
interface='A/B desktop demonstrados por ações visíveis, incluindo ferramenta, resultado, apoio e acompanhamento.',
api='Respostas guardadas por método/rota/status, sem corpos de autenticação; QA23 negado403/404.',
save='Casos, aplicabilidade, ferramenta, resultado, pedidoDRAFT e tarefa guardados pelo UI; originais/histórico preservados.',
recover='Logout e contexto novo: dossier, resultados, tarefa e inputs próprios da ferramenta recuperados para cada A/B.',
result='20unidades; défice financeiro A0/B70000MZN. Ferramenta e resultado ligados ao dossier correto, sem aprovação/score inventados.')
pevidence = (f'QA21 Chromium: dashboard mostra Minhas oportunidades e Nova oportunidade; abrir pelos links→criar rascunho incompleto#{promoter["opportunity"]["id"]}→guardar→logout/novaMFA→recuperar→editar/gravar. '
'Promotor solicita arquivo; Publicador25 arquiva por decisão fundamentada, estadoArquivado recuperado. '
'Restauração no workflow por perfis ausente; não atribuir essa função ao Promotor nem contornar com acesso legado. '
'promoter-dashboard-chromium-20261010.json. Leitura ativa dashboard-active-readonly.json: buildantigo, flags desligadas e030/040/09-190ausentes; não ativado.')
update(['AUD-DASH-01','AUD-PROFILE-05','AUD-PROFILE-32'], pevidence,
'Ciclo de restauração por perfil ainda não implementado; definir ator/regra coerentes com arquivo solicitado e histórico. '
'Workflow especializado completo, restantes perfis e disponibilização ativa continuam separados. Não conceder arquivo direto ao Promotor por inferência.',
interface='Dashboard QA21→links visíveis→criar/guardar/recuperar/editar; pedido de arquivo; Publicador arquiva.',
save='Rascunho e edição persistidos; pedido e decisão de arquivo fundamentados.',
recover='Recuperação noutra sessão e estadoArquivado após atualização.',
result='Oportunidade privada própria distinta do dossier e da publicação no catálogo; restauração explicitamente pendente.')
cevidence = ('Chromium QA18: residênciaPT no perfil de investimento guardada eF5 recuperada; negócio privado e apoio fundiárioDRAFT paísPT/origemFR guardados e recuperados por UI. '
'BalcãoA/B DRAFT paísPT/origemZA recuperados noutra sessão. Quatro formulários públicos/investidor/empresa/manifestação/apoio mostram249países (250opções incluindo vazio), mas sem submissão/gravação desta prova. '
'Gestor14: filtro origemZA eNãoinformado/unknown; UI estatísticas mostra4manifestaçõesNãoinformado e0pedidosZA submetidos, excluindo corretamente rascunhos A/B. '
'CRM filtrounknown renderizado. consumers-mobile-accessibility-chromium-20261010.json; visual-followup-chromium-20261010.json. Não inferir origem da residência ou constituição.')
update(['AUD-COUNTRY-01','AUD-COUNTRY-02','AUD-COUNTRY-03','AUD-COUNTRY-04','AUD-COUNTRY-06','AUD-COUNTRY-07'], cevidence,
'Concluir submissão e recuperação por nova sessão nos formulários públicos/empresa/manifestação/apoio; ensaio visual de estatísticas com pedidos submetidos de origem conhecida. '
'Negócios/fundiário deste lote sãoDRAFT e recuperaçãoF5, não provam submissão nem todos os perfis. País e origem são campos distintos, sem preencher faltas históricas por inferência.',
interface='Cobertura explicitada por formulário;249países nos quatro formulários públicos; seleções visíveis em perfil/negócios/fundiário/Balcão.',
save='ResidênciaPT; paísPT/origemFR nos dois rascunhos; BalcãoA/B paísPT/origemZA. Formulários públicos não submetidos nesta prova.',
recover='F5 perfil/negócios/fundiário; Balcão A/B noutra sessão. Não equiparar opções preenchidas a dados guardados.',
result='FiltrosZA/unknown e contagens visíveis;4manifestações sem origem; rascunhos excluídos. Falta estatística positiva por origem submetida neste ensaio visual.')
gevidence = ('Chromium: seletores e mapa de Cabo Delgado na oportunidade própriaQA21#51, criação deprojeto, cluster ecorredor pelo gestor14. '
'Cada consumidor: território registado, selected_geometry_available=true, context_geometry_count=1 e1polígonoSVG visível. '
'Seleção não gravada nesta prova; não fecha recuperação territorial ou hierarquia completa. Tiles externos bloqueados no browser isolado, basecartográfica externa não testada. '
'CorreçãoC78 regionARIA; visual-followup-chromium-20261010.json. A tentativa antiga deQA21 na oportunidade30 era403; não foi mapa demonstrado e foi substituída por recurso próprio51. '
'Nenhuma importação/publicação cartográfica neste lote;408associaçõesQA anteriores preservadas,173pendentes (26distritos/147postos), Mandie/Guro ambíguo; sem polígonos de localidades no exemplar2019.')
update(['AUD-GEO-01','AUD-GEO-02','AUD-GEO-03'], gevidence,
'Gravar/reabrir seleção e relações em cada consumidor; níveis descendentes e restantes mapas/perfis. Tiles externos/fallback sem ensaio conectado. '
'Resolver173correspondências inequívocas por pai/identidade, preservando Mandie/Guro ambíguo e ausência de polígonos de localidades. Publicação de camada permanece ação separada.',
interface='Quatro consumidores com seleção Cabo Delgado e1polígono visível cada; oportunidade30negada não contou como cobertura.',
api='API de contexto confirma1geometria acessível para o território selecionado em cada consumidor.',
result='Território cadastrado ≠ geometria disponível ≠ mapa apresentado ≠ seleção persistida; últimos3avaliados separadamente, gravação ainda pendente.')
for rid in ['AUD-GEO-01','AUD-GEO-02','AUD-GEO-03']:
updates[rid]['Evidência funcional'] += ' | Ensaio manual 17:52:33UTC na oportunidade51 recebeu403 com auth18, não21; recusa correta de acesso alheio. Sessão preservada, mapa visual manualQA21 continua pendente. Não conceder acesso nem contar esta recusa como mapa demonstrado.'
aevidence = ('FalhasC77–C83 corrigidas emQA: landmarks, nomeARIA do mapa, hierarquia de títulos do texto legado sem alteraroriginal/hash, '
'tabelas financeiras focáveis por teclado, reflow de tarefas/select/data no dossier, langpt-MZ apenas no routerQA e proteção da recuperação assíncrona do simulador. '
'Após correções15execuções axe/390–320–1440px,0violações automáticas/0overflow e idiomaPT; skiplink primeiroTab→conteúdo em3páginas; tabelas financeiras focadas/teclas no Chromium. '
'Não equivale a leitor de ecrã, zoom/dispositivo real ou cobertura de todo oportal. LegacyContentTest5/84 e rastreioMFA1/14 são provas específicas, não contagem substituta de percursos. '
'visual-followup-chromium-20261010.json e screenshots; históricos de falhas mantidos.')
update(['AUD-LEG-02','AUD-LEG-03','AUD-LEG-06','AUD-LEG-07'], aevidence,
'Cobertura integral, mais perfis/dispositivos, leitor de ecrã e editor administrativoC74 não demonstrados. '
'Mercado Financeiro permanece privado/corpo vazio confirmado na origem; rotas404. Sem associação automática Turismo→cadastro territorial, sem fundir Energia/Mineração.',
interface='Leitura pública/móvel dirigida, teclado e4consumidores do mapa demonstrados;15execuções de acessibilidade semviolação automática.',
result='Conteúdo/títulos originais preservados e hierarquia apresentada corrigida; imagem/pesquisa de setores nas provas prévias mantidas. Mercado Financeiro404.')
update(['AUD-DOC-01'],
'C84 corrigido em QA: recursos mostram scan e motor por ficheiro/versão; removida afirmação incondicional ClamD real pendente. '
'Chromium QA21 recursos51 confirmou apresentação sem texto obsoleto.22scans reais do lote legado anterior preservados; nenhum novo scan/documento/derivada neste ensaio. visual-followup-chromium-20261010.json.',
'PDFs, exportações, partilhas, derivadas e consumidores restantes precisam de operação/scan/permissão/recuperação próprios. '
'Um original limpo não prova scan das derivadas; disponibilização ativa separada.',
interface='Texto de scan individual corrigido e observado em QA; restantes ações documentais não encerradas.',
result='Estado e motor de scan pertencem a cada ficheiro/versão, sem invalidar ClamD real já demonstrado nem inventar scans.')
bevidence = (f'Backup/restauroQA renovado {backup["verified_at"]}:295tabelas/103ficheiros; restauro em{backup["restored_target"]["database"]}:55441, bytes extraídos e compatibilidade verificados. '
f'Ativo somenteREADONLY: backup{active["verified_at"]},288tabelas/5ficheiros incluindo runtime privado, restauroSQL em{active["restored_target"]["database"]}:55441 com todos valores/hashes iguais; bytes do arquivo verificados. '
'Não demonstra ainda migrações/código candidato no clone do ativo, nem aceitação global/corte. Regressão dirigida instalador/publicação9/93 eTypeScript passaram no alvo unitário isolado. '
'Sequência030000→040000→2026_10_09_190000_prepare_investor_path; QA instaladas não repetir, ativa ausentes porleitura. '
'Fingerprint técnico e manifesto privado vinculam provas; candidata global não fixada.0escritas ativas.')
update(['AUD-OPS-01'], bevidence,
'C37manual ciclo final, restauração porperfil, cobertura territorial/documental/países, decisões modulares/SLA e aceitações globais pendentes. '
'Clone fresco do ativo restaurado mas sem aplicar/desfazer sequência da futura candidata; preparar ensaio compatibilidade e ponto autenticado de importação em lote. '
'Backup renovado não autoriza ativação nem declaraI1–I3concluídos; repetir snapshot se dados/revisão mudarem.',
recover='QA295/103 com recuperação real de ficheiros; ativo288tabelas SQL recuperadas numa QA distinta e5ficheiros do arquivo verificados; sem escrita ativa.')
summary = f'''<!-- qa-visual-results-20261010:begin -->
## Ensaios visuais e pendências atuais — {stamp}
Trabalho exclusivo na QA da porta 18097, base `desk_restore_20261009_174726_test:55441`. Nenhuma alteração na base ativa. A mesma matriz mantém **3393 requisitos / 3393 linhas / 6845 ocorrências de origem**: 3235 parciais, 145 não implementados, 5 dependentes de decisão, 1 implementado mas não ativado e 7 concluídos e demonstrados. Os estados globais não mudaram; foram atualizados subcritérios com provas próprias. A ausência de requisitos globalmente classificados “com falha” não significa ausência de casos abertos.
| Requisito | Demonstrado em QA | Critérios ainda pendentes |
| --- | --- | --- |
| C37; UI01; PROFILE24–25 | MFA manual QA18 aceite às 17:40:21 e 17:46:42 UTC; autenticação persistida e dashboard 200. Responsável confirmou acesso. O 403 inicial era do relatório administrativo reservado. Mensagem e retorno ao painel corrigidos; Chromium QA21 provou recusa 403, retorno e F5 sem perder sessão. | F5, logout e novo login no navegador habitual QA18. Não declarar o loop anterior resolvido por inferência: o rastreio não demonstra um ciclo persistente. Uma atualização Inertia 409 isolada não é perda de autenticação. |
| A/B; GUIDE01; SIM02; DESK01–02; CRM01 | Chromium desktop QA18: criação → gravação → nova sessão → recuperação → aplicabilidade → ferramenta → resultado no dossier → preparação no Balcão → acompanhamento interno → nova recuperação. Cinco fases e 19 etapas preservadas. A dispensa S10; B aguarda informação externa em S10 enquanto S02 avança. Resultado: equilíbrio de 20 unidades e défice A=0 / B=70.000 MZN. QA23 recebe 403/404; gestor não vê resultados financeiros privados. | Balcão demonstrado em preparação DRAFT, sem submissão ou decisão externa. Restantes critérios especializados, fiscalidade, documentos e consumidores não encerrados. Repetição final A/B móvel ainda necessária. |
| DASH01; PROFILE05/32 | QA21, a partir do dashboard: “Minhas oportunidades” e “Nova oportunidade” → rascunho incompleto #50 → guardar → nova sessão → recuperar → editar. Promotor solicita arquivo; Publicador QA25 arquiva com fundamento; estado recuperado. | Restauração por perfil ausente no workflow. Definir e implementar ator/regra coerentes sem conceder arquivo direto ao Promotor por inferência. Workflow e contratos especializados ainda exigem demonstrações próprias. |
| COUNTRY01–04/06/07 | Residência PT guardada no perfil e recuperada após F5. Negócio e fundiário DRAFT: país PT / origem FR guardados e recuperados. Balcão A/B: PT / ZA recuperados noutra sessão. Quatro formulários públicos apresentam 249 países cada. Gestão: filtros ZA/unknown, quatro manifestações sem origem e zero pedidos submetidos ZA, com rascunhos excluídos. | Nos formulários públicos, a presença de opções não prova submissão ou recuperação. Concluir cada gravação e estatística positiva de origem conhecida no UI. Nos rascunhos de negócio/fundiário, F5 não substitui nova sessão. |
| GEO01–03 | Oportunidade própria QA21 #51 e criação de projeto, cluster e corredor: Cabo Delgado registado → API com uma geometria acessível → um polígono SVG visível em cada consumidor. | Seleções desse ensaio não guardadas. Recuperar relações por consumidor e testar descendentes, restantes mapas e perfis. Tiles externos bloqueados no ensaio, sem confirmação da base externa. 408 associações anteriores e 173 pendentes preservadas; Mandie/Guro ambíguo. |
| LEG02/03/06/07; C77–C83 | Corrigidos landmarks, ARIA, hierarquia de títulos, reflow, foco das tabelas e idioma HTML pt-MZ em QA. Quinze execuções dirigidas de axe, em 320/390/1440px, sem violações automáticas ou overflow. Três páginas com skiplink primeiro e tabelas financeiras operadas por teclado. | Cobertura integral, leitor de ecrã, dispositivos reais, mais perfis e editor C74. O dossier QA21 verifica o componente móvel corrigido, sem substituir a repetição final A/B. |
| DOC01; C84 | Retirada mensagem global falsa de ClamD pendente; scan e motor apresentados por ficheiro/versão. Os 22 scans reais anteriores permanecem demonstrados. | PDFs, exportações, partilhas, derivadas e operações restantes exigem provas próprias. Um original limpo não comprova a derivada. |
| OPS01 | Backup QA {backup['verified_at']}: 295 tabelas e 103 ficheiros restaurados. Ativo READ ONLY, backup {active['verified_at']}: 288 tabelas restauradas numa base QA distinta e cinco ficheiros conferidos no arquivo. Instalador/publicação: 9 testes / 93 asserções, além de TypeScript. | Aplicação, compatibilidade e reversão da futura revisão candidata no clone fresco do ativo. Revisão candidata global e ativação continuam por fixar. Contagens de testes não substituem critérios funcionais. |
**C80 corrigido:** a resposta tardia do GET do rascunho podia substituir preenchimento já iniciado. Edição e avanço ficam bloqueados até a recuperação terminar; o retorno exige o snapshot completo guardado. Chromium com resposta deliberadamente atrasada comprovou bloqueio, recuperação e libertação. A/B recuperaram os inputs corretos noutra sessão, preservando versões anteriores.
**Recusa manual do mapa às 17:52:33 UTC:** o pedido à oportunidade 51 foi feito com QA18, não QA21; recebeu 403 com sessão preservada. É uma prova de autorização, não de apresentação do mapa. Usar QA21 (`profiles-v1-promoter@example.invalid`) para esse consumidor; o Chromium anterior QA21 recebeu 200. Nenhuma permissão alargada para contornar a recusa.
**Provas privadas sanitizadas:** `investor-ab-chromium-20261010.json`, `promoter-dashboard-chromium-20261010.json`, `consumers-mobile-accessibility-chromium-20261010.json`, `visual-followup-chromium-20261010.json`, `qa-report-access-chromium-20261010.json`, `c37-qa18-post-mfa-destination-20261010.json`, `visual-persistence-qa.json`, `profile-installation-backup-qa.json` e `virtual-desk/backup-proof.json`. A tentativa antiga de QA21 na oportunidade 30 recebeu 403 e não contou como mapa; foi substituída pelo consumidor próprio 51. Tentativas e falhas de harness preservadas; sem códigos, segredos ou cookies nas evidências entregáveis.
Mercado Financeiro permanece privado, sem corpo inventado; rotas públicas 404. Energia e Mineração mantêm artigos distintos e Turismo não criou associações geográficas automáticas. Fontes oficiais aceites não foram reabertas. Próximo lote: C37 manual, restauração por perfil, submissão/estatística por formulário, gravação/recuperação territorial, documentos, Response/§27 e workflow. Perfis modulares/SLA continuam decisões separadas; não iniciar novas jornadas educativas.
As versões históricas abaixo ficam preservadas. Afirmações antigas sobre falta de navegador, A/B não testados ou ClamD global pendente são superadas apenas pelos subcritérios acima. Código preparado, QA demonstrada e disponibilidade ativa são estados distintos.
<!-- qa-visual-results-20261010:end -->
'''
activation = f'''<!-- qa-visual-activation-20261010:begin -->
## Ativação preparada, sem execução no ativo — {stamp}
Leitura ativa `investemoz:5432`: flags de dashboard/perfis/percurso desligadas; migrações 030000, 040000 e **2026_10_09_190000_prepare_investor_path** ausentes. A extensão 09/190000 continua necessária e não foi substituída pelo Balcão 07/190000. As três já estão instaladas na QA; não repetir.
1. Concluir os critérios pendentes e fixar revisão, hashes, build e flags candidatos. O snapshot técnico atual não representa aceitação global.
2. Backup ativo renovado em {active['verified_at']}: 288 tabelas / cinco ficheiros, restauro SQL em `{active['restored_target']['database']}:55441`, dados e bytes do arquivo conferidos. Preparar aplicação e reversão das migrações e compatibilidade da candidata neste clone fresco. Repetir o snapshot se dados/revisão mudarem antes do corte. A prova QA separada é 295 tabelas / 103 ficheiros em `{backup['restored_target']['database']}:55441`.
3. Disponibilizar código/build compatíveis com flags desligadas, preservando URLs, IDs, classificações, consentimentos, cinco fases e 19 etapas. Zero contas, dossiers ou oportunidades sintéticas transferidos.
4. Prévia e aplicação autenticadas **030000 → 040000 → 09/190000**, apenas se ausentes e com dependências coerentes. 030000 prepara recursos versionados. 040000 instala quatro tabelas, uma coluna em users e três em opportunities; não cria titulares ou grants. 09/190000 prepara duas colunas e uma definição; não cria dossiers ou tarefas. Conferir contagens na prévia real. Bootstrap separado, exatamente dois titulares reais ainda não designados.
5. Correções de interface, API e recuperação como lote próprio. Demonstrar C37 e dashboard por perfil; restauração de oportunidades continua por implementar e aceitar.
6. Conteúdos: prévia/hash/identidade/versão → armazenamento privado → ClamD → recuperação/replay. Lote de 22 originais (17 artigos / cinco imagens), reutilizando hashes existentes; 21 recursos divulgáveis e um exemplar vazio de Mercado Financeiro privado. Falta preparar e demonstrar o mecanismo autenticado de lote; CLI --actor QA não é autenticação ativa. Divulgação separada não ativa regras/cálculos.
7. Cartografia em lote independente: prévia, correspondências inequívocas, aplicação idempotente, auditoria e reversão. Retrato QA: 408 candidatas / 173 pendentes (26 distritos / 147 postos); Mandie/Guro ambíguo e ausência de polígonos de localidades no exemplar 2019. Publicação das camadas separada. Gravar/recuperar relações e demonstrar cada mapa consumidor.
8. Habilitar consumidores e percurso incrementalmente com permissões e provas próprias. `ready=true` significa preparação, não instalação, divulgação ou conclusão funcional.
**Reversão:** desligar flags e retirar divulgação por ações auditadas, preservando originais, versões, IDs, documentos, consentimentos, relações e eventos. Não executar DROP em tabelas utilizadas ou copiar fixtures QA. Restauro completo exige ensaio em QA distinta e reconciliação de escritas posteriores ao snapshot antes do corte. Não alterar chave nem retirar MFA para recuperar acesso. O backup ativo verificado não comprova ainda aplicação/reversão da futura candidata.
<!-- qa-visual-activation-20261010:end -->
'''
cases = '''<!-- qa-visual-cases-20261010:begin -->
## Casos adicionais C77–C84 e estado C37 — 10/10/2026
| Caso | Reprodução e observado | Esperado, causa e correção QA |
| --- | --- | --- |
| C37 | MFA manual aceite; destino pretendido era relatório administrativo, negado 403 com sessão autenticada. Nova tentativa 17:46:42 UTC chegou ao dashboard 200; responsável confirmou acesso. | Recusa administrativa correta. Página QA explica o acesso reservado e oferece retorno; pedido Inertia passa uma vez por 409 para renderizar a página completa, evitando modal na página MFA. QA21 provou 403 → painel → investimentos → F5. C37 habitual ainda pede F5/logout/novo login; ciclo anterior não demonstrado como falha persistente. |
| C77 / UI01, LEG02, DASH01 | axe: mains aninhados, conteúdo fora de landmarks e cabeçalho Sheet fora do Dialog. | Um main, navegação e apoio nomeados, SheetHeader dentro do SheetContent. Quinze verificações dirigidas posteriores sem violações automáticas. |
| C78 / GEO03 | Mapas com aria-label num div sem role. | Região nomeada. Cabo Delgado apresentou um polígono em quatro consumidores; seleção ainda não guardada e tiles externos não testados. |
| C79 / LEG02 | Turismo começava com h3 sob o título h1, omitindo h2. | Normalização apenas na apresentação para h2..h6 e saltos máximos de um nível, mantendo original, bytes, hash e texto. LegacyContentTest 5/84 e Chromium passaram. |
| C80 / SIM02, GUIDE01 | Preenchimento iniciado antes de terminar o GET podia ser sobrescrito pelo rascunho remoto; faltavam campos de A na recuperação. | Impedir edição e avanço até recuperar; cancelar efeito obsoleto; devolver resultado só após guardar snapshot completo. Chromium com GET deliberadamente atrasado e nova sessão A/B demonstraram recuperação. Histórico preservado. |
| C81 / SIM02 | Tabelas financeiras com scroll sem foco por teclado. | tabindex=0, região nomeada e foco visível. QA21 focou ambas e usou ArrowDown; axe passou. Leitor de ecrã real pendente. |
| C82 / GUIDE01 | Dossier a 390px alargava a página para 413px devido a labels/select/datetime da atribuição. | min-width:0, max-width:100% e quebra de linha, sem esconder ações. Dossier próprio QA21 corrigido sem overflow; repetir A/B móvel final. |
| C83 / UI01, LEG02 | HTML lang=en para interface em português. | Configuração pt-MZ somente no router QA e fallback anterior no template. Quinze verificações com pt-MZ; locale de validação preservado. |
| C84 / DOC01 | Mensagem global “ClamD real pendente” contrariava os scans reais. | Mostrar scan/motor por ficheiro/versão; retirar texto global. QA21 recursos51 confirmou apresentação; nenhuma derivada/PDF recebeu scan fictício. |
**Mapa manual 51, 17:52:33 UTC:** conta autenticada QA18 recebeu 403; QA21 havia recebido 200. Sessão mantida. É recusa correta por perfil/autoria e não um mapa funcional demonstrado pelo responsável. Credenciais e entradas MFA são distintas; não ampliar autorização para permitir o ensaio na conta errada.
Provas: `visual-followup-chromium-20261010.json`, `qa-report-access-chromium-20261010.json`, `investor-ab-chromium-20261010.json`, `visual-persistence-qa.json` e `layout-map-diagnosis-chromium-20261010.json`. Falhas do harness preservadas separadamente: ações antes de guardar, moeda não selecionada, esperas e oportunidade30 alheia a QA21. Não contar o 403 dessa oportunidade como cobertura do mapa. Nenhuma alteração ativa.
<!-- qa-visual-cases-20261010:end -->
'''
def readable(text):
return text
def prepend(file, text, marker):
old = file.read_text(encoding='utf-8')
old = re.sub(re.escape('<!-- '+marker+':begin -->') + r'.*?' + re.escape('<!-- '+marker+':end -->') + r'\s*', '', old, flags=re.S)
(OUT / (file.name+'.before')).write_text(old, encoding='utf-8')
file.write_text(readable(text).strip()+'\n\n'+old, encoding='utf-8')
prepend(DOC/'relatorio-executivo.md', summary, 'qa-visual-results-20261010')
prepend(DOC/'backlog.md', summary, 'qa-visual-results-20261010')
prepend(DOC/'plano-ativacao.md', activation, 'qa-visual-activation-20261010')
prepend(DOC/'casos-reproducao.md', cases, 'qa-visual-cases-20261010')
prepend(DOC/'legado-piloto-qa.md', summary, 'qa-visual-results-20261010')
prepend(ROOT/'docs/investmoz/v2/gestao-oportunidades-dashboard.md', summary, 'qa-visual-results-20261010')
for patch in updates.values():
for key,value in patch.items():
if key != 'Critérios e versões por origem (JSON)':
patch[key] = readable(value)
matrix_patches = [{'row':i,'values':updates[row[0]]} for i,row in enumerate(rows[1:],1) if row[0] in updates]
packet = {'files':[{'filename':'matriz-requisitos.csv','input_sha256':hashlib.sha256((DOC/'matriz-requisitos.csv').read_bytes()).hexdigest(),
'rows':rows,'patches':matrix_patches,'allowed_columns':list(next(iter(updates.values())).keys()) + ['Interface','API','Gravação','Recuperação','Resultado apresentado'],'append':[]}],
'proof_filename':'visual-csv-preservation-proof.json'}
backrows = list(csv.reader((DOC/'backlog-priorizado.csv').open(encoding='utf-8-sig', newline='')))
bh = backrows[0]
bp = []
for i,row in enumerate(backrows[1:],1):
rid = row[bh.index('ID')]
if rid not in updates:continue
old = dict(zip(bh,row)); bp.append({'row':i,'values':{'Pendência':updates[rid]['Lacuna'],
'Correção/componentes':old['Correção/componentes']+' | '+tag+updates[rid]['Evidência funcional'].split(tag)[-1],
'Ordem do próximo lote':old['Ordem do próximo lote']+' | '+tag+'C37manual→restauração porperfil→submissão/estatística→recuperação territorial→documentos/workflow; decisões e ativação separadas.'}})
packet['files'].append({'filename':'backlog-priorizado.csv','input_sha256':hashlib.sha256((DOC/'backlog-priorizado.csv').read_bytes()).hexdigest(),
'rows':backrows,'patches':bp,'allowed_columns':['Pendência','Correção/componentes','Ordem do próximo lote'],'append':[]})
sourcefile = DOC/'inventario-fontes.csv'
sr = list(csv.reader(sourcefile.open(encoding='utf-8-sig',newline=''))); sh=sr[0];sp=[];hashupdates=[]
sourcepaths = [ROOT/'docs/investmoz/v2/gestao-oportunidades-dashboard.md', DOC/'legado-piloto-qa.md']
for i,row in enumerate(sr[1:],1):
if Path(row[sh.index('Exemplar/caminho')]) not in sourcepaths:continue
old=dict(zip(sh,row));sp.append({'row':i,'values':{'Nota de leitura':old['Nota de leitura']+' | '+tag+'Versão atual com provas visuais dirigidas. SHA anterior '+old['SHA256']+', bytes anteriores '+old['Bytes']+'; histórico preservado.'}});hashupdates.append({'row':i})
assert len(hashupdates)==2
packet['files'].append({'filename':'inventario-fontes.csv','input_sha256':hashlib.sha256(sourcefile.read_bytes()).hexdigest(),
'rows':sr,'patches':sp,'allowed_columns':['Nota de leitura'],'append':[],'source_hash_updates':hashupdates})
(OUT/'csv-edits.json').write_text(json.dumps(packet,ensure_ascii=False,indent=2),encoding='utf-8')
proof={'at':stamp,'requirements':3393,'rows':3393,'source_occurrences':6845,'states_unchanged':dict(states),'updated_requirements':list(updates),
'active_writes':0,'global_candidate_fixed':False,'qa_backup_at':backup['verified_at'],'active_readonly_backup_at':active['verified_at']}
(OUT/'preparation-proof.json').write_text(json.dumps(proof,ensure_ascii=False,indent=2),encoding='utf-8')
print(json.dumps({'prepared':len(updates),'canonical_requirements':3393,'csv_authored':False,'global_candidate_fixed':False}))