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

47 lines
4.2 KiB
Python

"""Final read-only verification of sealed artifacts and audit preservation."""
from pathlib import Path
from datetime import datetime,timezone
import json,csv,hashlib,zipfile,collections
csv.field_size_limit(20000000)
root=Path(__file__).resolve().parent.parent;private=root/'storage/app/private/investmoz-v2/portal-audit';doc=root/'docs/investmoz/v2/auditoria-portal';out=root/'outputs/investmoz-opportunity-workflow-candidate-20261011'
sha=lambda p:hashlib.sha256(p.read_bytes()).hexdigest()
receipt=json.loads((out/'pacote.json').read_text());manifest=json.loads((out/'manifesto.json').read_text());plan=json.loads((private/'portal-stability-latest.json').read_text());source=Path(plan['source'])
assert sha(out/receipt['archive'])==receipt['sha256'] and sha(out/'manifesto.json')==receipt['manifest_sha256']
core={k:v for k,v in manifest.items()if k not in ['revision','sealed_at']}
assert hashlib.sha256(json.dumps(core,sort_keys=True,separators=(',',':'),ensure_ascii=False).encode()).hexdigest()==receipt['revision']
with zipfile.ZipFile(out/receipt['archive'])as z:
assert z.testzip() is None
for n,h in manifest['payload'].items():
assert sha(out/'payload'/n)==h and sha(source/n)==h
assert hashlib.sha256(z.read('payload/'+n)).hexdigest()==h
for n in ['manifesto.json','relatorio.md','procedimento.md','evidencias-por-criterio.json']:assert z.read(n)==(out/n).read_bytes()
for n,h in manifest['unchanged_dependencies'].items():assert sha(root/n)==h
for n,h in json.loads((private/'functional-active-files-before-20261011.json').read_text()).items():assert sha(root/n)==h
for n,h in manifest['QA_evidence'].items():assert sha(private/n)==h
original=json.loads((private/'functional-finalize-20261011/csv-edits.json').read_text())
packets=[json.loads((private/folder/'csv-edits.json').read_text())for folder in ['functional-finalize-20261011','functional-candidate-finalize-20261011','functional-fixed-finalize-20261011']]
changed_ids=set()
for file in original['files']:
with (doc/file['filename']).open(encoding='utf-8-sig',newline='')as stream:rows=list(csv.reader(stream))
before=file['rows'];assert len(rows)==len(before) and rows[0]==before[0];headers=rows[0];allowed={}
for packet in packets:
for entry in packet['files']:
if entry['filename']!=file['filename']:continue
for patch in entry['patches']:allowed.setdefault(patch['row'],set()).update(patch['values'])
for number,(a,b)in enumerate(zip(before[1:],rows[1:]),1):
assert len(a)==len(b)
for column,(old,new)in enumerate(zip(a,b)):
if old!=new:
assert headers[column]in allowed.get(number,set()),(file['filename'],number,headers[column]);changed_ids.add(b[headers.index('ID')])
if file['filename']=='matriz-requisitos.csv':
states=dict(collections.Counter(row[headers.index('Estado')]for row in rows[1:]));assert states==dict(collections.Counter(row[headers.index('Estado')]for row in before[1:]));assert len(rows)-1==3393
occurrences=sum(int(row[headers.index('Quantidade de linhas de origem')])for row in rows[1:]);assert occurrences==6845
assert len(changed_ids)==25
snapshot=json.loads((private/'functional-final-persistence-qa-20261011.json').read_text())
assert len(snapshot['categories'])==6
for row in snapshot['categories']:assert row['state']=='published'and row['approval_actor']==24 and row['publication_actor']==25 and row['current_hash']==row['approval_hash']==row['publication_hash']
backup=json.loads((private/'functional-backup-restoration-qa-20261011.json').read_text())
assert backup['result']=='passed'and sha(Path(backup['dump_path']))==backup['dump_sha256']and sha(Path(backup['files_path']))==backup['files_sha256']
result={'result':'passed','at':datetime.now(timezone.utc).isoformat(),'candidate':receipt['revision'],'code_revision':receipt['code_revision'],'package_sha256':receipt['sha256'],'source_payload_bundle_and_proof_hashes_verified':True,'requirements':3393,'source_occurrences':6845,'states':states,'updated_requirements':25,'unmodified_IDs_sources_acceptance_and_closed_states':True,'six_current_approval_publication_hashes_match':True,'active_baseline_preserved':True,'activation_ready':False,'active_writes':0}
(out/'verificacao-final.json').write_text(json.dumps(result,ensure_ascii=False,indent=2)+'\n',encoding='utf-8');print(json.dumps(result,ensure_ascii=False))