set -euo pipefail release=/home/ps/knowledge-20260916-100045 docker exec -i im-admin-api-1 python -u - <<'PY' | tee "$release/after.json" import sqlite3,json,time started=time.monotonic() db=sqlite3.connect('file:/data/backend.db?mode=ro',uri=True,timeout=30) tables=['archive_message','archive_conversation','archive_person','archive_source_account','desktop_accounts','model_providers','users'] counts={t:db.execute('SELECT COUNT(*) FROM '+t).fetchone()[0] for t in tables} knowledge={t:db.execute('SELECT COUNT(*) FROM '+t).fetchone()[0] for t in ['knowledge_item','knowledge_job','knowledge_watch','knowledge_dirty','knowledge_retrieval_log']} enabled=db.execute('SELECT COUNT(*) FROM knowledge_settings WHERE enabled!=0').fetchone()[0] permissions=[r[0] for r in db.execute("SELECT permission_code FROM role_permissions WHERE role_code='admin' AND permission_code LIKE 'knowledge:%' ORDER BY permission_code")] index=db.execute("SELECT COUNT(*) FROM sqlite_master WHERE type='index' AND name='idx_knowledge_archive_scan'").fetchone()[0] triggers=db.execute("SELECT COUNT(*) FROM sqlite_master WHERE type='trigger' AND name LIKE 'knowledge_%'").fetchone()[0] check=db.execute('PRAGMA quick_check').fetchone()[0] assert check=='ok' and index==1 and triggers==4 assert enabled==0 and all(v==0 for v in knowledge.values()) and len(permissions)==5 print(json.dumps({'counts':counts,'knowledge':knowledge,'enabled_tenants':enabled,'admin_permissions':permissions,'archive_index':index,'source_triggers':triggers,'quick_check':check,'seconds':round(time.monotonic()-started,1)}),flush=True) PY python3 - <<'PY' | tee "$release/verification.json" from pathlib import Path import json,hashlib,subprocess,urllib.request,urllib.error release=Path('/home/ps/knowledge-20260916-100045') before=json.loads((release/'backup-verification.json').read_text())['counts'] after=json.loads((release/'after.json').read_text())['counts'] assert all(after[t]>=v for t,v in before.items()),(before,after) manifest=json.loads((release/'manifest.json').read_text(encoding='utf-8-sig')) for row in manifest: path=row['path'].replace('\\','/') assert hashlib.sha256((Path('/opt/im-admin')/path).read_bytes()).hexdigest().lower()==row['sha256'].lower(),path expected={row['path'].replace('\\','/'):row['sha256'].lower() for row in manifest if row['path'].endswith('.py') or '/dist/' in row['path'].replace('\\','/')} code='from pathlib import Path; import json,hashlib,sys; paths=json.load(sys.stdin); print(json.dumps({p:hashlib.sha256((Path("/app")/p).read_bytes()).hexdigest() for p in paths}))' 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,'Runtime files differ from the release' responses={} for path,wanted in [('/api/v2/health',200),('/api/v2/knowledge/overview',401),('/api/v2/knowledge/items',401),('/api/v2/knowledge/desktop/summary',401),('/knowledge/center',200),('/js/center-Bwv00pY-.js',200)]: url='http://127.0.0.1:18766'+path try: with urllib.request.urlopen(url,timeout=10) as response: status=response.status body=response.read() if path=='/js/center-Bwv00pY-.js': assert hashlib.sha256(body).hexdigest()==hashlib.sha256((release/'payload/admin-web/apps/web-antd/dist/js/center-Bwv00pY-.js').read_bytes()).hexdigest() except urllib.error.HTTPError as error: status=error.code assert status==wanted,(path,status,wanted) responses[path]=status names=['im-admin-api-1','im-admin-gateway-1','im-admin-knowledge-worker-1'] containers=json.loads(subprocess.check_output(['docker','inspect',*names])) states={} expected_image=subprocess.check_output(['docker','image','inspect','zyt/wecom-admin:knowledge-20260916-100045','--format','{{.Id}}'],text=True).strip() for container in containers: assert container['State']['Running'] and container['RestartCount']==0 assert container['Image']==expected_image states[container['Name'].lstrip('/')]={'running':True,'restarts':container['RestartCount'],'health':container['State'].get('Health',{}).get('Status','not_configured')} logs=subprocess.check_output(['docker','logs','--tail','150',container['Name']],stderr=subprocess.STDOUT,text=True) assert 'Traceback (most recent call last)' not in logs and 'ERROR:' not in logs,container['Name'] print(json.dumps({'source_files_verified':len(manifest),'runtime_files_verified':len(actual),'http':responses,'containers':states,'image':expected_image,'archive_counts_preserved':True})) PY