from pathlib import Path import json,hashlib,csv,shutil,zipfile from datetime import datetime,timezone from collections import Counter R=Path(__file__).resolve().parent.parent;P=R/'storage/app/private/investmoz-v2/portal-audit' O=R/'outputs/investmoz-workflow-consolidated-20261011' load=lambda p:json.loads(p.read_text(encoding='utf-8-sig')) sha=lambda p:hashlib.sha256(p.read_bytes()).hexdigest() manifest=load(O/'manifesto.json');revision=manifest['revision'][:12] preview=load(P/'consolidated-preview-qa.json');holder=load(P/'consolidated-holder-compatibility-qa.json') assert preview['result']=='passed' and holder['result']=='passed' assert len(preview['screenshots'])==1 shots=O/'capturas-qa';shots.mkdir(exist_ok=True) shot=R/preview['screenshots'][0];assert shot.is_file() shutil.copyfile(shot,shots/shot.name) public_preview={k:preview[k] for k in ['result','started_at','base','target','browser','checks','active_writes']} public_preview['screenshot']='capturas-qa/'+shot.name public_preview['test_harness_correction']='First attempted legacy manager14 after initialized pair received403; corrected authorized holder15, no application change. Historical failed attempt retained privately.' (O/'preview-qa.json').write_text(json.dumps(public_preview,indent=2,ensure_ascii=False),encoding='utf-8') (O/'compatibilidade-titulares-qa.json').write_text(json.dumps(holder,indent=2,ensure_ascii=False),encoding='utf-8') # Improve authored prose only, without changing scope, hashes, paths or service proposal. spacing={' +159':' + 159','a030000':'a 030000','somente030000':'somente 030000', 'somente040000':'somente 040000','somente030':'somente 030', 'pedido15→confirmação16':'pedido 15 → confirmação 16', 'PDF20MiB':'PDF de 20 MiB','Fila4→3→2→1→0':'Fila 4 → 3 → 2 → 1 → 0','repetição0→0':'repetição 0 → 0', 'CLI256M':'CLI 256M','pico148MiB':'pico de 148 MiB','scanner13310/ClamAV1.5.4':'scanner 13310 / ClamAV 1.5.4', 'assinaturas11/10':'assinaturas de 11/10','às08:26UTC':'às 08:26 UTC','DBQA':'DB QA','destino3310':'destino 3310', 'real180s/recuperação210s':'real de 180 s / recuperação de 210 s','nos191':'nos 191', 'aumento288→295':'aumento 288 → 295','tabelas030000+040000':'tabelas 030000 + 040000', 'contém181':'contém 181','fixtures.191':'fixtures. 191','ID2':'ID 2','ID13':'ID 13','IDs3/18':'IDs 3/18', 'legado e190000':'legado e 190000','atuais3310':'atuais 3310','provasLocalService':'provas LocalService', 'reconciliação191':'reconciliação dos 191 ficheiros','tabelaB3':'tabela B3', '→030000→040000':' → 030000 → 040000', '→ flags':' → flags','escopo': 'âmbito','SHA-256:':'SHA-256:', 'Dest​ino':'Destino','050000 não se repete':'050000 não se repete', 'COUNTRY02 permaneceON':'COUNTRY02 permanece ON','code/bundle':'código/bundle', 'destino real**':'destino real**','flagsOFF':'flags OFF','COUNTRY02 ON':'COUNTRY02 ON', 'fila default partilhada':'fila default partilhada','upload20M':'upload 20M','POST24M':'POST 24M', 'memória256M':'memória 256M','execução180s':'execução 180 s','cadência60s':'cadência 60 s', 'externo180s':'externo 180 s','lease210s':'lease 210 s','prazo15min':'prazo 15 min', 'em030000':'em 030000','em040000':'em 040000','050000/190000':'050000/190000', 'somente030000/040000':'somente 030000/040000','somente030000':'somente 030000', '190000 fica':'190000 fica','notificações enfileiradas':'notificações enfileiradas', 'OFF.050000':'OFF. 050000','revisão/ZIP':'revisão/ZIP', '|288/31 |799 |':'| 288 / 31 | 799 |','|295/31 |188 |':'| 295 / 31 | 188 |','|295/31 |191 |':'| 295 / 31 | 191 |', 'Destino,15:11:50UTC':'Destino, 15:11:50 UTC','anterior,15:11:55UTC':'anterior, 15:11:55 UTC', 'delta,16:29:27UTC':'delta, 16:29:27 UTC','QA renovada,':'QA renovada, ', '+476':' + 476','+312':' + 312','+2TLS':' + 2 TLS','+5Apache':' + 5 Apache', '+1env':' + 1 env','+17 código':' + 17 código','+158bundle':' + 158 bundle', '+22 código':' + 22 código','+159bundle':' + 159 bundle',',9 envelopes':', 9 envelopes', 'investment_project,real_estate,land_rights,business_sale,business_partnership,franchise':'investment_project, real_estate, land_rights, business_sale, business_partnership, franchise'} for name in ['relatorio-consolidacao.md','procedimento-unico.md','designacoes-b3.md']: p=O/name;text=p.read_text(encoding='utf-8') for a,b in spacing.items():text=text.replace(a,b) p.write_text(text,encoding='utf-8') report=O/'relatorio-consolidacao.md' report.write_text(report.read_text(encoding='utf-8')+'''\n## Verificações afetadas da consolidação\n\nChromium HTTPS/MFA normal confirmou a prévia040000 com o novo fingerprint/readiness e a dependência030000 com fingerprint/readiness correspondente ao restauro renovado. Interface de instalação renderizada pelo bundle consolidado. Não houve repetição da instalação: a recusa403 ao gestor legado que não integra a dupla QA foi verificada antes da leitura permitida ao titular15. As contas reais clonadas2/13 passaram validateBootstrapHolder, incluindo leitura do envelope MFA normal; zero alterações às contas. [Prévia QA](preview-qa.json), [compatibilidade dos titulares](compatibilidade-titulares-qa.json).\n''',encoding='utf-8') block=f''' ## Candidata consolidada {revision} — QA, sem ativação [Relatório e bloqueios](../../../../outputs/investmoz-workflow-consolidated-20261011/relatorio-consolidacao.md), [procedimento único](../../../../outputs/investmoz-workflow-consolidated-20261011/procedimento-unico.md) e [manifesto](../../../../outputs/investmoz-workflow-consolidated-20261011/manifesto.json). Delta 117d2d889701 integrado: 22 ficheiros de código + 159 assets (181 no ZIP), referência 98a8e0b70892 preservada. Compilação de produção do delta verificada por bytes; nenhuma alteração de código adicional. Prévia Chromium/MFA 040000 com fingerprint novo e dependência030000 corresponde ao restauro renovado; gestor legado fora da dupla QA recebe403, titular autorizado recebe200. Instalações/funcionalidades válidas não repetidas. LocalService S-1-5-19 demonstrada no clone55441: configuração lida, ClamD13310, quatro scans PDF20MiB/trêsWebP, fila4→0 e repetição0→0, zero recursos novos/falhas e hashes/cifra recuperados; escrita só nas áreas QA autorizadas, negada em código/vendor/config/.env e leitura alheia negada. Tarefa própria removida. Limite180s/lease210s anteriores preservados como prova distinta de utilizador interativo. Não é instalação/prova do destino3310. 191 ficheiros reconciliados:22código+159bundle+1env+9documentais, incluindo os4originais do destino com hashes iguais e5ficheiros sintéticos. Backup799 do destino tem âmbito maior; os8documentais exclusivos da QA188 continuam no backup anterior. RestauroQA renovado16:49:42UTC:295tabelas/31seq/191ficheiros/9MFA; renovar o backup/restauro do destino real e associar à revisão antes da intervenção. B3: responsável confirmou nala@tempo.co.mz (**ID2**) e onala@me.com (**ID13**); email/MFA/gestão legada verificados READ ONLY e compatibilidade do envelope validada no clone. IDs3/18 iniciais reconciliados. Designação registada, sem conceder privilégios. Ainda faltam autorização/cerimónia cruzada e contas/organização/tipos/âmbito operacionais. B1/B2: serviços3310, supervisão/limites e ACLs efetivas do destino por autorizar/verificar; RX adicional não neutraliza escrita herdada. Notificações OFF e nova leitura da default imediatamente antes de iniciar worker; parar perante classe desconhecida ou notificação enfileirada. Mesma matriz3401requisitos/7739ocorrências, três evidências complementadas, zero IDs/estados gerais novos. COUNTRY02 ativo e fechos A/B/C37 preservados. Restauração/partilha individual/190000/legado/modulares/ofertas/SLA permanecem fora. Os blocos seguintes mantêm o histórico. ''' for a,b in {**spacing,'dependência030000':'dependência 030000','recebe403':'recebe 403','recebe200':'recebe 200', 'clone55441':'clone 55441','ClamD13310':'ClamD 13310','fila4→0':'fila 4 → 0','quatro scans PDF20MiB/trêsWebP':'quatro scans PDF de 20 MiB / três WebP', 'Limite180s/lease210s':'Limite 180 s / lease 210 s','191 ficheiros reconciliados:22código+159bundle+1env+9documentais':'191 ficheiros reconciliados: 22 código + 159 bundle + 1 env + 9 documentais', 'os4originais':'os 4 originais','e5ficheiros':'e 5 ficheiros','Backup799':'Backup 799','os8documentais':'os 8 documentais', 'QA188':'QA 188','RestauroQA renovado16:49:42UTC:295tabelas/31seq/191ficheiros/9MFA':'Restauro QA renovado 16:49:42 UTC: 295 tabelas / 31 sequências / 191 ficheiros / 9 MFA', 'IDs3/18':'IDs 3/18','serviços3310':'serviços 3310','matriz3401requisitos/7739ocorrências':'matriz: 3401 requisitos / 7739 ocorrências'}.items():block=block.replace(a,b) for name in ['backlog.md','relatorio-executivo.md','plano-ativacao.md','workflow-criterios-saida-20261011.md']: p=R/'docs/investmoz/v2/auditoria-portal'/name;old=p.read_text(encoding='utf-8') assert '' not in old p.write_text(block+old,encoding='utf-8') before=load(P/'consolidated-preservation-before.json');checks={} for kind,base in [('fixed_candidate',R/'outputs/investmoz-workflow-upgrade-20261011'),('delta_reference',R/'outputs/investmoz-service-followup-20261011'),('service_plan',R/'outputs/investmoz-workflow-service-plan-20261011')]: changed=[key for key,value in before[kind].items() if not (base/key).is_file() or sha(base/key)!=value] assert not changed,(kind,changed);checks[kind]={'files':len(before[kind]),'changed':0} active=load(P/'service-followup-preservation-before.json')['active_hashes'] assert all(sha(R/key)==value for key,value in active.items()) checks['active_monitored_files']={'files':len(active),'changed':0} csv.field_size_limit(16777216) old=list(csv.reader((P/'consolidated-matrix-before.csv').open(encoding='utf-8-sig',newline=''))) new=list(csv.reader((R/'docs/investmoz/v2/auditoria-portal/matriz-requisitos.csv').open(encoding='utf-8-sig',newline=''))) assert len(old)==len(new)==3402 and len({r[0] for r in new[1:]})==3401 differences=[] for a,b in zip(old,new): assert len(a)==len(b) for c,(x,y) in enumerate(zip(a,b)): if x!=y: assert old[0][c]=='Evidência funcional' and b[0] in ['AUD-OPS-01','AUD-PROFILE-15','AUD-PROFILE-31'] differences.append({'id':b[0],'column':old[0][c]}) assert len(differences)==3 for key,value in manifest['service_proposal'].items():assert sha(O/key)==value assert sha(O/manifest['zip'])==manifest['zip_sha256'] with zipfile.ZipFile(O/manifest['zip']) as z: assert len(z.namelist())==181 for key,value in manifest['payload'].items(): assert hashlib.sha256(z.read(key)).hexdigest()==value for key,value in manifest['unchanged_dependencies'].items(): assert sha(R/key)==value, key backup=load(P/'consolidated-backup-restoration-qa.json') assert backup['code_manifest']=={key:value for key,value in manifest['payload'].items() if not key.startswith('public/build/')} assert backup['production_manifest_sha256']==manifest['production_manifest_sha256'] result={'result':'passed','verified_at':datetime.now(timezone.utc).isoformat(),'candidate':manifest['revision'], 'code_revision':manifest['code_revision'],'zip_sha256':manifest['zip_sha256'], 'manifest_sha256':sha(O/'manifesto.json'), 'preservation':checks,'matrix':{'requirements':3401,'source_occurrences':sum(int(dict(zip(new[0],row))['Quantidade de linhas de origem']) for row in new[1:]),'changes':differences,'new_ids':0,'states_sources_versions_preserved':True}, 'QA_restore_bound_to_exact_candidate_code_and_production_bundle':True, 'changed_preview_and_holder_compatibility_passed':True,'LocalService_QA_passed':True, 'destination_account_ids_designated':[2,13], 'active_writes':0,'activation_ready':False,'activation_authorized':False, 'delivery_files':{p.relative_to(O).as_posix():sha(p) for p in sorted(O.rglob('*')) if p.is_file()}} (O/'verificacao-final.json').write_text(json.dumps(result,indent=2,ensure_ascii=False),encoding='utf-8') print(json.dumps({k:result[k] for k in ['result','candidate','manifest_sha256','LocalService_QA_passed','active_writes','activation_ready']}))