60 lines
3.0 KiB
Python
60 lines
3.0 KiB
Python
import datetime
|
|
import hashlib
|
|
import json
|
|
from pathlib import Path
|
|
import zipfile
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
AUDIT = ROOT / 'storage/app/private/investmoz-v2/portal-audit'
|
|
OUT = ROOT / 'outputs/investmoz-country02-candidate-20261011'
|
|
|
|
def read(path):
|
|
return json.loads(Path(path).read_text(encoding='utf-8-sig'))
|
|
|
|
def sha(path):
|
|
return hashlib.sha256(Path(path).read_bytes()).hexdigest()
|
|
|
|
def bounded(path):
|
|
path = Path(path).resolve()
|
|
assert path.is_relative_to(ROOT), 'Outside workspace refused'
|
|
return path
|
|
|
|
envelope = read(OUT / 'manifesto-pacote.json')
|
|
manifest = read(OUT / 'manifesto-candidata.json')
|
|
stage = read(AUDIT / 'country-production-stage-latest.json')
|
|
assert sha(OUT / 'manifesto-candidata.json') == envelope['manifesto_candidata_sha256']
|
|
assert sha(OUT / envelope['archive']) == envelope['archive_sha256']
|
|
assert manifest['code_hash'].startswith('36f7722ce5a5') and manifest['delivery_revision'].startswith('c68ceef9d4a3')
|
|
with zipfile.ZipFile(OUT / envelope['archive']) as z:
|
|
assert len(z.namelist()) == envelope['archive_entries']
|
|
for name, expected in envelope['verified_members_sha256'].items():
|
|
assert hashlib.sha256(z.read(name)).hexdigest() == expected, name
|
|
for name, expected in manifest['files'].items():
|
|
assert sha(bounded(ROOT / name)) == expected, name
|
|
for name, expected in manifest['production']['transitive_inputs'].items():
|
|
assert sha(bounded(ROOT / name)) == expected, name
|
|
for name, expected in manifest['production']['files'].items():
|
|
assert sha(bounded(ROOT / 'public/build' / name)) == expected, name
|
|
for name, expected in stage['active_assets'].items():
|
|
if name != 'manifest.json':
|
|
assert sha(bounded(ROOT / 'public/build' / name)) == expected, name
|
|
original = AUDIT / 'country-active-20261011_065027/manifest-before.json'
|
|
assert sha(original) == manifest['active_manifest_sha256']
|
|
receipt = {
|
|
'result': 'bundle_available_country_disabled',
|
|
'at': datetime.datetime.now(datetime.timezone.utc).isoformat(),
|
|
'delivery_revision': manifest['delivery_revision'], 'code_hash': manifest['code_hash'],
|
|
'production_manifest_sha256': manifest['production']['manifest_sha256'],
|
|
'previous_manifest_sha256': manifest['active_manifest_sha256'],
|
|
'preserved_immutable_previous_assets': len(stage['active_assets']) - 1,
|
|
'production_files': len(manifest['production']['files']),
|
|
'original_manifest_backup': str(original),
|
|
'country_flag_enabled': False, 'active_database_writes': 0, 'installer_applied': False,
|
|
'archive_members_verified': envelope['archive_entries'],
|
|
'receipt_recovery': 'Atomic replacement succeeded at 06:50:27 UTC. Receipt expression failed; all old/new hashes rechecked before recording, no replacement repeated.',
|
|
'reversal': 'Disable country and atomically restore manifest-before.json; retain assets, columns, values and audit.',
|
|
}
|
|
for path in [original.parent / 'bundle-receipt.json', AUDIT / 'country-active-bundle-latest.json']:
|
|
path.write_text(json.dumps(receipt, indent=2), encoding='utf-8')
|
|
print(json.dumps(receipt))
|