Blum / scripts /verify_deployment.py
SignalLayer Labs
fix: coherent recovery snapshots and public deployment readiness (#8)
9b989e1 unverified
Raw History Blame Contribute Delete
3.79 kB
"""Verify source revision, JSON APIs, readiness and public read-only boundary."""
import argparse
import json
import subprocess
import sys
from pathlib import Path
import time
import urllib.error
import urllib.request
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--backend-only', action='store_true')
parser.add_argument('--timeout', type=int, default=120)
args = parser.parse_args()
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / 'backend'))
from app.core.release import source_content_hash
expected_content_hash = source_content_hash()
revision = subprocess.check_output(['git', 'rev-parse', 'HEAD'], text=True).strip()
backend = 'https://italianhype-blum.hf.space'
site = 'https://blum.signallayerlabs.workers.dev'
verification_headers = {
'User-Agent': 'BLUM-Deployment-Verification/2.3.0 (+https://github.com/SignalLayerLabs/Blum)',
}
def request(url, **kwargs):
# Identify the maintained verifier explicitly; workers.dev rejects Python's default agent.
return urllib.request.Request(url, headers=verification_headers, **kwargs)
def get_json(origin, path):
response = urllib.request.urlopen(request(origin+path), timeout=15)
if 'application/json' not in response.headers.get('Content-Type', ''):
raise ValueError('Expected a JSON API response: '+path)
return json.load(response)
deadline = time.monotonic() + args.timeout
last_error = ''
while time.monotonic() < deadline:
try:
startup = get_json(backend, '/startup/status')
deployed = get_json(backend, '/api/runtime/release')
if not startup.get('api_ready') or not startup.get('ui_ready') or startup.get('last_error'):
raise ValueError('Backend is restoring or unavailable.')
if deployed.get('source_revision') != revision or deployed.get('version') != '2.3.0':
raise ValueError('Backend revision has not finished deploying.')
if deployed.get('source_content_hash') != expected_content_hash:
raise ValueError('Backend protected source content does not match the verified checkout.')
if not args.backend_only:
public = get_json(site, '/api/release')
if public.get('source_revision') != revision or public.get('source_content_hash') != expected_content_hash or not public.get('backend_ready'):
raise ValueError('Cloudflare revision or backend readiness has not settled.')
break
except (OSError, ValueError) as exc:
last_error = str(exc)
time.sleep(10)
else:
raise SystemExit(('Backend' if args.backend_only else 'Release')+' verification failed: '+last_error)
print('Backend ready at', revision, flush=True)
if not args.backend_only:
for path in ['/api/brain/snapshot', '/api/training/snapshot', '/api/paper-trading/snapshot?limit=1',
'/api/alpha/snapshot', '/api/alpha/desk', '/api/ascent/status']:
payload = get_json(site, path)
if not isinstance(payload, dict): raise SystemExit('Invalid snapshot: '+path)
print('Verified JSON snapshot', path, flush=True)
try:
urllib.request.urlopen(request(site+'/api/ascent/run', data=b'{}', method='POST'), timeout=15)
raise SystemExit('Public research mutation was unexpectedly allowed.')
except urllib.error.HTTPError as exc:
if exc.code != 405: raise
for path in ['/brain/', '/training-ground/', '/paper-trading/', '/alpha/', '/evolution/']:
response = urllib.request.urlopen(request(site+path), timeout=15)
if response.status != 200 or 'text/html' not in response.headers.get('Content-Type', ''):
raise SystemExit('Product page unavailable: '+path)
print('Cloudflare pages, live API snapshots and write protection verified.', flush=True)