"""Read-only baseline or deployed-code verification; prints hashes/status only.""" from pathlib import Path import argparse, hashlib, json, subprocess, urllib.request, urllib.error parser=argparse.ArgumentParser();parser.add_argument('mode',choices=['baseline','candidate','deployed']);args=parser.parse_args() folder=Path(__file__).resolve().parent;project=Path('/opt/im-admin') base=json.loads((folder/'server-baseline-patient.json').read_text()) manifest=json.loads((folder/'patient-manifest.json').read_text()) image=lambda ref:subprocess.check_output(['docker','image','inspect',ref,'--format','{{.Id}}'],text=True).strip() expected=dict(base['expected']) if args.mode=='baseline': assert image('zyt/wecom-admin:current')==base['image'],'Live image changed' for container in ['im-admin-api-1','im-admin-gateway-1','im-admin-knowledge-worker-1']: actual_image=subprocess.check_output(['docker','inspect',container,'--format','{{.Image}}'],text=True).strip() assert actual_image==base['image'],'Running image changed: '+container else: for row in manifest: if row['path'].startswith('wechat_rpa/'):expected[Path(row['path']).name]=row['sha256'] for row in manifest: path=folder/('payload/'+row['path'] if row['path'].startswith('wechat_rpa/') else row['path']) assert path.resolve().is_relative_to(folder.resolve()) assert path.stat().st_size==row['bytes'] and hashlib.sha256(path.read_bytes()).hexdigest()==row['sha256'],row['path'] if args.mode!='candidate': for name,wanted in expected.items(): assert hashlib.sha256((project/'wechat_rpa'/name).read_bytes()).hexdigest()==wanted,'Host drift: '+name code='from pathlib import Path;import hashlib,json,sys;names=json.load(sys.stdin);print(json.dumps({n:hashlib.sha256((Path("/app/wechat_rpa")/n).read_bytes()).hexdigest() for n in names}))' actual=json.loads(subprocess.check_output(['docker','exec','-i','im-admin-api-1','python','-c',code],input=json.dumps(list(expected)).encode())) assert actual==expected,'Container code differs from expected' if args.mode=='deployed': wanted=image('zyt/wecom-admin:patient-session-20260918') assert image('zyt/wecom-admin:current')==wanted for name in ['im-admin-api-1','im-admin-gateway-1','im-admin-knowledge-worker-1']: raw=json.loads(subprocess.check_output(['docker','inspect',name]))[0] assert raw['Image']==wanted and raw['State']['Running'] and raw['RestartCount']==0,name assert raw['State'].get('Health',{}).get('Status','not_configured') in ('healthy','not_configured'),name for url in ['http://127.0.0.1:18766/api/v2/health','http://127.0.0.1:18770/health']: with urllib.request.urlopen(url,timeout=15) as response:assert response.status==200 # Existing patient route must still reject anonymous reads; do not query real patients. try: urllib.request.urlopen('http://127.0.0.1:18766/api/v2/archive/desktop/patient-context?external_account_id=synthetic&conversation_external_id=synthetic',timeout=15) except urllib.error.HTTPError as exc:assert exc.code in (401,403),exc.code else:raise AssertionError('Patient API unexpectedly accessible without authentication') print(json.dumps({'mode':args.mode,'files':len(expected),'status':'passed','patientDataRead':False}))