Files
Investidor-V2/tools/portal-next-results-prepare.py
T
nala 287e991486
tests / ci (push) Waiting to run
oportunidades 7
2026-10-13 11:40:29 +02:00

121 lines
19 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.
"""Bind this bounded QA increment and patch only the existing audit tables."""
from pathlib import Path
import collections, csv, hashlib, json, sys
from datetime import datetime, timezone
csv.field_size_limit(50_000_000)
ROOT=Path(__file__).resolve().parent.parent
DOC=ROOT/'docs/investmoz/v2/auditoria-portal'
PRIVATE=ROOT/'storage/app/private/investmoz-v2/portal-audit'
OUT=PRIVATE/'next-consumers-finalize-20261010'
OUT.mkdir(exist_ok=True)
def sha(p):return hashlib.sha256(p.read_bytes()).hexdigest()
def text(p):
b=p.read_bytes();return b.decode('utf-16' if b.startswith(b'\xff\xfe') else 'utf-8-sig')
def load(n):return json.loads(text(PRIVATE/n))
TAG='[QA país mestre/workflow/revogação/acessibilidade 10/10/2026] '
gaps={
'AUD-COUNTRY-02':'Critério QA demonstrado: residência distinta, país no mestre organizations existente, cópia explícita identificada, versões/submissões anteriores preservadas, recuperação noutra sessão e outra organização403; viewer/revogação/Auditor cobertos por política. Ativo sem 050000/colunas e sem organizações (leitura21:23UTC); futura instalação autenticada e dados/representações autorizados continuam necessários. Não exige nova validação da credibilidade das fontes oficiais.',
'AUD-OP-01':'52 conserva criação/dashboard/correção/publicação anteriores. Novas provas visuais: alterações pós-aprovação31–35 e quatro módulos42/44/46/48. Restam coedição visual, variações de filtros/perfis e arquivo/restauração conforme decisão; dossier privado permanece separado. Rascunhos36–39 preservados com versões históricas, sem atualização silenciosa.',
'AUD-OP-02':'Recursos52 e recusas anteriores preservados. Documentos próprios existentes sustentam os pareceres visuais por categoria, sem reupload ou divulgação automática. Restam partilhas/revogação e derivados de consumidores diferentes da sala de parceria1, perfis/formatos adicionais; instalação030000 ativa ausente.',
'AUD-OP-03':'Demonstrados: alterações materiais/aprovação independente/republicação31/33/34/35, intenção de concessão42, procura de financiamento44, pedido de compra46 e outros48; 32 aprovado mas publicação422 por dependência externa. Suspensão31→nova revisão sem edição→aprovação→republicação e rejeição34→recurso independente29→nova aprovação24/publicação25 demonstrados. Restam variantes de oferta43/45/47 na interface, coedição/múltiplos papéis/atribuições/pareceres por consumidor, materialidade por revisões afetadas, coordenação/SLA configurados e arquivo/restauração definidos.',
'AUD-OP-04':'Contratos canónicos e revisão documental utilizados nas nove categorias adicionais; rascunhos incompletos mantêm as provas anteriores. C102 remove a validação duplicada do resumo e preserva texto canónico integral. Restam Response/§27 integral, condições/coleções semânticas ainda inventariadas e exemplos visuais específicos de direito atribuído43/oferta45/oferta47; perfis modulares QA não substituem decisões funcionais ativas.',
'AUD-PART-01':'Sala1: autorização explícita e revogação visual PDF/PNG/JPG; URLs200→404 imediatamente na sessão previamente autorizada e noutra sessão; proprietário200 e hashes/scans originais preservados. Restam revogação de outros âmbitos/perfis/consumidores, candidatura/consentimento/retirada, mensagens, acordo/marcos e demais critérios individuais; não fechar todo o módulo com três ficheiros.',
'AUD-SCAN-01':'ClamD real e scans próprios anteriores preservados. PDF/PNG/JPG partilhados/revogados mantêm sha256=scanned_sha256 e clean; não houve nova geração nem scan fictício. Restam scans próprios e permissões de novas derivadas/formatos/consumidores ainda não ensaiados; indisponibilidade do scanner bloqueia somente operações afetadas.',
'AUD-LIB-02':'Originais e versões não duplicados/reimportados neste lote. Revogação da sala não equivale a retirada pública da biblioteca. Restam conflitos de identidade/versão, exports/derivadas/retirada pública por consumidor, cobertura documental integral e importações pendentes já inventariadas; divulgação e cálculo continuam separados.',
'AUD-UI-01':'Teclado, atalho para conteúdo, menu/Escape/foco, nomes dos países, erro associado/focado e estados foram revistos nas cinco rotas delimitadas. Provas visuais por categoria/partilha acumuladas; A/B320/390px e C37 conservam fecho. Restam acessibilidade integral nas outras rotas, leitura por leitor de ecrã, estados condicionais, documentos etiquetados, móvel dos consumidores novos e dispositivos reais.',
'AUD-OPS-01':'Incremento delimitado por hashes de fontes/build/recibos, sem candidata global aprovada para corte. 050000 demonstrado em QA e instalador autenticado preparado; preservar030000→040000→09/190000. Ativo consultado READ ONLY, quatro migrations ausentes e organizations=0. Renovar backup/restauro/compatibilidade/reversão no clone correspondente antes de ação ativa; provas históricas não cobrem esta revisão.',
'AUD-DOC-01':'Esta linha reconcilia documentação e diferenças de versões, não representa todas as operações de documentos. Novo retrato datado liga C97–C103, critérios e lacunas aos requisitos funcionais próprios. Restam afirmações históricas sem evidência individual e diferenças ainda inventariadas; preservar decisões/fontes oficiais e as versões antigas identificadas.'
}
notes={
'country':'next-organization-chromium-20261010.json + next-final-persistence-qa.json: QA21 mestreFR/v2→cópia explícita na declaração/manifestação→mestreZA/v3→nova sessão; manifesto antigoMZ/v1 e novoFR/fontev2 não foram reescritos; QA23 PUT403. Fonte/source_system/external_id preservados. 050000: duas colunas, zero tabelas/organizações/contas. next-installer-preview-chromium-20261010.json demonstra Auditor31/múltiplos papéis sem edição do mestre, prévia autenticada instaladaQA/readyfalse e Promotor21 sem acesso403 ao instalador.',
'workflow':'next-workflow-categories/modular/suspension/appeal-chromium-20261010.json: Promotor21 prepara alteração/submete; Validador24 revê/aprova; Publicador25 disponibiliza. 32bloqueado422 por dependência externa;31 suspensão/reaprovação/republicação;34rejeição24/recurso29/nova aprovação24/publicação25. SQL conserva hashes, decisões, publicações e tarefa anterior. C99 limite CRM80;C101 ciclo de revisão separado do conteúdo;C102 resumo integral no canónico.',
'sharing':'next-sharing-chromium-20261010.json: sala1, QA21 autoriza/retira, QA22 URLs200→404 sem logout e numa nova sessão; originalPDF/PNG/JPG proprietário200/hash exato, scans próprios históricos preservados. Estado de documentos nomeado; sem novos ficheiros/derivadas no ensaio web. next-real-document-final.xml: dois casos técnicos isolados com ClamD real/GD, três WebP320/640/1280 com scans próprios, originais preservados, exclusão lógica/recuperação e derivada adulterada404; quarentena423. Não é demonstração visual de todos os consumidores.',
'a11y':'next-accessibility-chromium-20261010.json: cinco rotas, Chromium/axe e teclado, zero violações automáticas; atalho→conteúdo, menuEnter/Escape/foco, três países distintos e cópia identificada, erro do mestre associado e focado, estados nomeados. C103 corrigiu três violações decorrentes de main aninhado na sala de parceria. Não demonstra leitor de ecrã, todas as rotas ou dispositivos reais.',
'activation':'next-active-readonly-20261010.json: ativo investemoz5432 READ ONLY/ROLLBACK,030000/040000/09-190000/050000 ausentes, zero organizações/colunas país. next-country-install-qa.json e testes do instalador: extensão somenteQA; prévia/dependências/hash/MFA/idempotência/auditoria/consistência. Backup ativo não renovado.'
}
mapping={
'AUD-COUNTRY-02':['country','activation'],'AUD-OP-01':['workflow'],'AUD-OP-02':['workflow','sharing'],
'AUD-OP-03':['workflow'],'AUD-OP-04':['workflow'],'AUD-PART-01':['sharing'],
'AUD-SCAN-01':['sharing'],'AUD-LIB-02':['sharing'],'AUD-UI-01':['a11y','country','workflow','sharing'],
'AUD-OPS-01':['activation'],'AUD-DOC-01':['country','workflow','sharing','a11y','activation']}
if '--verify' in sys.argv:
packet=json.loads((OUT/'csv-edits.json').read_text(encoding='utf-8'));changed={}
for edit in packet['files']:
now=list(csv.reader((DOC/edit['filename']).open(encoding='utf-8-sig',newline='')));old=edit['rows'];headers=now[0]
assert len(now)==len(old)==3394 and headers==old[0]
patches={p['row']:p['values']for p in edit['patches']};ids=[]
for i,(before,after)in enumerate(zip(old,now)):
for j,(a,b)in enumerate(zip(before,after)):
if a!=b:assert i in patches and headers[j] in patches[i] and b==patches[i][headers[j]]
if i in patches:
ids.append(after[headers.index('ID')])
if edit['filename'].startswith('matriz'):
k=headers.index('Critérios e versões por origem (JSON)');a=json.loads(before[k]);b=json.loads(after[k]);assert len(a)==len(b)
for p,n in zip(a,b):assert all(n[k]==v for k,v in p.items())
assert set(ids)==set(gaps);changed[edit['filename']]=len(ids)
rows=list(csv.DictReader((DOC/'matriz-requisitos.csv').open(encoding='utf-8-sig',newline='')))
states=dict(collections.Counter(r['Estado']for r in rows));assert states=={'parcial':3234,'não implementado':145,'dependente de decisão concreta':5,'implementado mas não ativado':2,'concluído e demonstrado':7}
scope=load('next-final-scope-qa.json');assert sha(ROOT/'public/build/manifest.json')==scope['active_manifest_sha256']
assert all(sha(ROOT/f)==h for f,h in scope['tested_sources'].items())
proof={'result':'passed','requirements':len(rows),'rows':len(rows),'source_occurrences':sum(int(r['Quantidade de linhas de origem'])for r in rows),'states':states,'changed':changed,'versions_references_and_other_cells_preserved':True,'only_state_change':'AUD-COUNTRY-02 partial -> implemented but not activated','ab_and_c37_acceptance_preserved':True,'active_writes':0}
(OUT/'preservation-verification.json').write_text(json.dumps(proof,ensure_ascii=False,indent=2),encoding='utf-8');print(json.dumps(proof,ensure_ascii=False));sys.exit()
receipts=['next-organization','next-sharing','next-workflow-categories','next-workflow-modular','next-workflow-suspension','next-workflow-appeal','next-accessibility','next-installer-preview']
proofs={}
for n in receipts:
name=n+'-chromium-20261010.json';p=load(name);assert p['result']=='passed' and p['active_writes']==0 and p['target']=='desk_restore_20261009_174726_test:55441',name
assert not p.get('javascript_errors'),(name,p.get('javascript_errors'));proofs[name]=sha(PRIVATE/name)
sql=load('next-final-persistence-qa.json');assert sql['result']=='passed' and sql['read_only']
active=load('next-active-readonly-20261010.json');assert active['active_writes']==0 and active['identity']['read_only']=='on' and not active['migrations'] and active['organization_count']==0
assert sha(ROOT/'public/build/manifest.json')==active['active_manifest_sha256']
build=load('next-final-build-confirmed.json');assert build['native_exit']==0
regression=load('next-final-regression-summary.json');assert regression['result']=='passed'
types=text(PRIVATE/'next-final-types.log');assert 'tsc --noEmit' in types and 'error TS' not in types
sources=['app/Services/OrganizationCountry.php','app/Services/OrganizationCountryInstallation.php','app/Services/OpportunityResourceInstallation.php','app/Services/OpportunityWorkflowTasks.php','app/Services/OpportunityProfileWorkflow.php','app/Services/OpportunityManagement.php','app/Http/Controllers/InvestmentWorkspaceController.php','app/Http/Controllers/InvestmentSubmissionController.php','app/Http/Controllers/OpportunityResourceController.php','database/migrations/2026_10_10_050000_extend_organization_country.php','routes/investment.php','routes/web.php','resources/js/pages/investment/profile.tsx','resources/js/pages/investment/submission.tsx','resources/js/components/country-select.tsx','resources/js/pages/investment/business-interest.tsx','resources/js/pages/desk/request.tsx','resources/js/pages/land-assistance/form.tsx','resources/js/pages/portfolio/workspace.tsx','resources/js/pages/management/shared.tsx','resources/js/pages/management/opportunity-draft.tsx','resources/js/pages/management/opportunity-install.tsx','resources/js/pages/partnerships/room.tsx','tests/Feature/OrganizationCountryTest.php','tests/Feature/OrganizationCountryInstallationTest.php','tests/Feature/OpportunityCanonicalWorkflowTest.php','public/build-qa-opportunities/manifest.json']
sources += ['resources/js/components/opportunity-canonical-fields.tsx','resources/js/lib/value-text.ts','resources/js/lib/profile-json.ts','resources/js/pages/portfolio/shared.tsx','resources/js/pages/partnerships/shared.tsx','resources/js/pages/management/opportunity-resources.tsx','app/Services/VerifiedDocumentBinary.php','app/Services/LegacyContent.php','app/Http/Controllers/InvestorPathController.php','app/Http/Controllers/TerritoryGeometryImportController.php','tests/Feature/OpportunityRealDocumentOperationsTest.php']
sources += ['tools/portal-next-installer-preview-qa.cjs','tools/portal-next-accessibility-qa.cjs','tools/portal-next-workflow-lifecycle-qa.cjs','tools/portal-next-consumers-visual-qa.cjs','tools/portal-visual-qa-lib.cjs']
# These outputs are reconciled after the CSV; they are not executable candidate sources.
mutable_reports={f'docs/investmoz/v2/auditoria-portal/{n}' for n in ['relatorio-executivo.md','backlog.md','casos-reproducao.md','plano-ativacao.md']}
# Style-only updates have their byte originals preserved; JSON domain data stays equal.
formatting=[]
for folder in ['next-format-originals-20261010','next-php-format-originals-20261010']:
base=PRIVATE/folder
for original in base.rglob('*'):
if not original.is_file():continue
relative=original.relative_to(base).as_posix();current=ROOT/relative
if current.suffix=='.json':assert json.loads(original.read_text(encoding='utf-8-sig'))==json.loads(current.read_text(encoding='utf-8-sig')),relative
formatting.append({'file':relative,'original_sha256':sha(original),'current_sha256':sha(current),'original_preserved':True})
if relative not in sources and relative not in mutable_reports:sources.append(relative)
quality={'phpstan':json.loads([s for s in text(PRIVATE/'next-phpstan-final-qa.log').splitlines()if s.startswith('{')][-1]),'frontend':text(PRIVATE/'next-check-final.log'),'pint':json.loads(text(PRIVATE/'next-pint-final.log'))}
assert quality['phpstan']['totals']=={'errors':0,'file_errors':0}
assert 'Found no warnings or lint errors' in quality['frontend'] and 'Formatting issues found' not in quality['frontend']
assert quality['pint']['result']=='passed'
stamp=datetime.now(timezone.utc).isoformat()
scope={'at':stamp,'target':'desk_restore_20261009_174726_test:55441','active_writes':0,'active_manifest_sha256':active['active_manifest_sha256'],'tested_sources':{f:sha(ROOT/f)for f in sources},'browser_receipts':proofs,'sql_sha256':sha(PRIVATE/'next-final-persistence-qa.json'),'regression':regression,'typescript_exit':0,'build_exit':0,'quality_checks_passed':True,'format_originals':formatting,'corrections':['C97','C98','C99','C100','C101','C102','C103'],'candidate':'Bounded QA increment only; global active candidate not approved','preserved':['A/B integrated internal acceptance','C37 QA18 authentication','opportunity52','prior sources and versions'],'separate':['other consumers/conditional semantics','full accessibility/screen reader','real devices','active backup/restore and activation']}
scope['increment_revision_sha256']=hashlib.sha256(json.dumps(scope['tested_sources'],sort_keys=True).encode()).hexdigest()
(PRIVATE/'next-final-scope-qa.json').write_text(json.dumps(scope,ensure_ascii=False,indent=2),encoding='utf-8')
files=[]
for filename in ['matriz-requisitos.csv','backlog-priorizado.csv']:
p=DOC/filename;rows=list(csv.reader(p.open(encoding='utf-8-sig',newline='')));headers=rows[0];patches=[]
for i,row in enumerate(rows[1:],1):
old=dict(zip(headers,row));key=old['ID']
if key not in gaps:continue
evidence=' | '.join(notes[n]for n in mapping[key])+' Recibos/SQL/fontes:next-final-scope-qa.json; sem ativação.'
if filename.startswith('matriz'):
updates={'Evidência funcional':evidence,'Interface':evidence,'API':'Pedidos reais do Chromium no servidor18097 com alvo QA conferido antes de escrita; respostas/recusas por operação constam dos recibos. Casos API anteriores preservados, sem os apresentar como visual novo.','Gravação':'Mestre/entidades e serviços existentes; duas colunas opcionais050000, nenhuma tabela/organização/conta nova. SQL READ ONLY confirma versões, cópias, tarefas, publicações, hashes e revogação.','Recuperação':'Sessões novas no mestre/cópias e nas partilhas; atores21/24/25/29 independentes no workflow. SQL confirma preservação de A/B52 e tarefas/histórico anteriores.','Resultado apresentado':evidence,'Consolidação':'Somente critérios demonstrados acrescentados. COUNTRY02 preparado/demonstradoQA mas instalação ativa ausente; estados globais dos outros requisitos e fechosA/B/C37 preservados.'}
values={f:old[f]+' | '+TAG+v for f,v in updates.items()};values['Lacuna']=TAG+gaps[key]
if key in ['AUD-COUNTRY-02','AUD-OPS-01']:
values['Disponibilidade ativa']=old['Disponibilidade ativa']+' | '+TAG+notes['activation']
if key=='AUD-COUNTRY-02':
values['Implementação encontrada']=old['Implementação encontrada']+' | '+TAG+'OrganizationCountry/OrganizationCountryInstallation e extensão050000 no mestre organizations existente; memberships autoriza, cópias e audit_events preservam valores/fontes. Sem cadastro paralelo; interface degrada de forma controlada quando colunas ausentes.'
variants=json.loads(old['Critérios e versões por origem (JSON)'])
for v in variants:v['next_consumers_20261010']={'at':stamp,'evidencia':evidence,'criterios_restantes':gaps[key],'active':False}
values['Critérios e versões por origem (JSON)']=json.dumps(variants,ensure_ascii=False,separators=(',',':'))
else:values={'Pendência':TAG+gaps[key]+' | Histórico preservado: '+old['Pendência'],'Aceitação':old['Aceitação']+' | '+TAG+evidence,'Correção/componentes':old['Correção/componentes']+' | '+TAG+'Reutilizar organizations/memberships/audit_events/contratos/tasks/documentos existentes. Executar critérios restantes por rota/perfil; não repetir os critérios A/B/C37 aceites nem instalar novamente QA.'}
if key=='AUD-COUNTRY-02':assert old['Estado']=='parcial';values['Estado']='implementado mas não ativado'
patches.append({'row':i,'values':values})
assert len(patches)==len(gaps)
files.append({'filename':filename,'input_sha256':sha(p),'rows':rows,'patches':patches,'allowed_columns':sorted({f for p in patches for f in p['values']})})
(OUT/'csv-edits.json').write_text(json.dumps({'files':files},ensure_ascii=False),encoding='utf-8')
(OUT/'criterion-evidence.json').write_text(json.dumps({'at':stamp,'notes':notes,'remaining':gaps,'scope':scope,'sql':sql},ensure_ascii=False,indent=2),encoding='utf-8')
print(json.dumps({'prepared':len(gaps),'same_matrix':True,'csv_authored':False,'increment':scope['increment_revision_sha256'],'active_writes':0}))