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