61 lines
4.3 KiB
Python
61 lines
4.3 KiB
Python
"""Prepare a read-only destination backup and an isolated restore for the fixed delta."""
|
|
from pathlib import Path
|
|
import hashlib, json, zipfile
|
|
|
|
R = Path(__file__).resolve().parent.parent
|
|
P = R / 'storage/app/private/investmoz-v2/portal-audit'
|
|
O = R / 'outputs/investmoz-operational-onboarding-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')
|
|
assert manifest['delta_sha256'].startswith('41a5ce778932')
|
|
archive = O / manifest['package']
|
|
assert sha(archive) == manifest['package_sha256']
|
|
expected = {x['path']: x['after_sha256'] for x in manifest['files']}
|
|
expected.update({x['path']: x['sha256'] for x in manifest['assets']})
|
|
with zipfile.ZipFile(archive) as z:
|
|
assert len(z.namelist()) == len(set(z.namelist())) == len(expected)
|
|
assert set(z.namelist()) == set(expected)
|
|
for name, value in expected.items():
|
|
assert hashlib.sha256(z.read(name)).hexdigest() == value
|
|
baseline = {x['path']: x['before_sha256'] for x in manifest['files']}
|
|
for relative, value in baseline.items():
|
|
assert (sha(R / relative) if (R / relative).is_file() else None) == value
|
|
scope = {'code_revision': manifest['delta_sha256'], 'active_base_hashes': baseline,
|
|
'production_manifest_sha256': manifest['production_manifest_sha256'],
|
|
'candidate': {'revision': manifest['delta_sha256'], 'zip_sha256': sha(archive),
|
|
'manifest_sha256': sha(O / 'manifesto.json')}}
|
|
(P / 'onboarding-release-backup-scope.json').write_text(json.dumps(scope, indent=2), encoding='utf-8')
|
|
source = (R / 'tools/portal-workflow-execution-target-backup.php').read_text(encoding='utf-8')
|
|
source = source.replace('workflow-execution', 'onboarding-release')
|
|
source = source.replace('workflow_consolidated_target_', 'onboarding_release_target_')
|
|
source = source.replace("['2026_10_10_050000_extend_organization_country']", "['2026_10_09_030000_extend_opportunity_resources','2026_10_10_040000_create_portal_profile_authorization','2026_10_10_050000_extend_organization_country']")
|
|
source = source.replace("['app','config','routes','bootstrap']", "['app','config','routes','bootstrap','resources']")
|
|
source = source.replace("['composer.json','composer.lock','package.json','package-lock.json']", "['composer.json','composer.lock','package.json','package-lock.json','artisan','public/index.php']")
|
|
start = source.index('// Service assets are copied from an independent READ ONLY OS snapshot.')
|
|
end = source.index('$zip->close();', start)
|
|
source = source[:start] + "// Services are unchanged by this delta; their preceding recovery archive is retained.\n" + source[end:]
|
|
start = source.index("$proof['resources_fingerprint']")
|
|
end = source.index("$proof['source_files_stable_at_snapshot_completion']", start)
|
|
source = source[:start] + source[end:]
|
|
source = source.replace("$proof['authenticated_installation_preview_still_required']=true;", """$proof['authenticated_installation_preview_still_required']=false;
|
|
$proof['no_migration_required']=true;
|
|
$proof['no_service_change_required']=true;
|
|
$proof['valid_until']=gmdate('c', time()+86400);
|
|
$proof['services_scope']='No service, certificate, PHP limit, worker, DLL or ACL change in this delta';
|
|
$proof['active_file_stability_rechecked']=true;
|
|
foreach ($fileHashes as $entry => $value) {
|
|
if (str_starts_with($entry,'active-code/')) $file=$root.'/'.substr($entry,12);
|
|
elseif (str_starts_with($entry,'runtime/active-build/')) $file=$root.'/public/build/'.substr($entry,21);
|
|
elseif (str_starts_with($entry,'private/')) $file=$root.'/storage/app/private/'.substr($entry,8);
|
|
else continue;
|
|
throw_unless(is_file($file) && hash_file('sha256',$file)===$value,RuntimeException::class,'Source file changed during snapshot.');
|
|
}
|
|
throw_unless(count($tables)===295 && $installationState['country_flag'],RuntimeException::class,'Post-installation baseline differs.');
|
|
""")
|
|
# The old immutable operation was a migration preflight; this release has no migrations.
|
|
source = source.replace("$proof['activation_ready']=false;", "$proof['activation_ready']=false;")
|
|
(R / 'tools/portal-onboarding-release-target-backup.php').write_text(source, encoding='utf-8')
|
|
print(json.dumps({'result': 'prepared', 'delta': manifest['delta_sha256'][:12], 'payload': len(expected),
|
|
'active_writes': 0, 'restore_port': 55441, 'services_changes': 0}))
|