import hashlib
import importlib.util
import json
from pathlib import Path
import subprocess

REPO=Path('/workspace/contextra-analytics-runtime-lifecycle-refactor')
OUT=Path('/data/codex-artifacts/contextra/field-maintenance/2026/2026-10-07_ct-storage-maintenance_r01')
names=['ct.py','compose_backend.py','field_ui.py','tool_permissions.py','data_safety.py','data_worker.py','data_worker_sql.py','data_worker_redis.py','data_console.py','data_host.py','field_data.py','field_storage.py']
expected={n:hashlib.sha256((REPO/'tools/icerti_offline/maintenance'/n).read_bytes()).hexdigest() for n in names}
expected['storage_policy.py']=hashlib.sha256((REPO/'app/storage_policy.py').read_bytes()).hexdigest()
code=r'''
import hashlib,json,os,pwd,shutil,subprocess
from pathlib import Path
definition=json.loads(Path('/etc/contextra-maintenance/server.json').read_text())
prefix=Path(PREFIX)
hashes={name:hashlib.sha256((prefix/name).read_bytes()).hexdigest() for name in EXPECTED}
assert hashes==EXPECTED,'INSTALLED_SOURCE_MISMATCH'
state=Path(STATE)
receipts=[]
for p in sorted((state/'storage-updates').glob('*/receipt.json')):
    receipts.append(dict(path=str(p),receipt=json.loads(p.read_text())))
active=json.loads(Path(ACTIVE).read_text())
compose=json.loads(Path(active['compose']).read_text())
def run(args):
    r=subprocess.run(args,capture_output=True,text=True,timeout=20)
    assert r.returncode==0, 'READBACK_COMMAND_FAILED'
    return r.stdout
containers=run(['docker','ps','-aq','--filter','label=com.docker.compose.project='+PROJECT]).splitlines()
services=[]
for cid in containers:
    c=json.loads(run(['docker','inspect',cid]))[0]
    services.append(dict(service=c['Config']['Labels'].get('com.docker.compose.service'),status=c['State']['Status'],image=c['Image']))
assert len(services)==5 and all(r['status']==EXPECTED_STATE for r in services), 'SERVICE_STATE_DRIFT'
assert shutil.disk_usage('/').free>=15*1024**3,'ROOT_RESERVE'
# A separate editable snapshot never changes the root-owned execution installation.
editable=Path(SETUP)/'source-work'/'ct-storage-final-20261007'
editable.mkdir(mode=0o755,exist_ok=False)
account=pwd.getpwnam(OPERATOR)
for p in prefix.glob('*.py'):
    shutil.copyfile(p,editable/p.name)
    os.chown(editable/p.name,account.pw_uid,account.pw_gid)
    (editable/p.name).chmod(0o644)
os.chown(editable,account.pw_uid,account.pw_gid)
print(json.dumps(dict(server=PROJECT,hashes=hashes,active_transaction=active['transaction'],staged_image=compose['services']['analytics']['image'],services=services,root_free=shutil.disk_usage('/').free,storage=json.loads((Path(SETUP)/'storage.json').read_text()),editable_tools=str(editable),receipts=receipts,actual_data_deleted=False),ensure_ascii=False))
'''
spec=importlib.util.spec_from_file_location('remote','/tmp/codex/ltb1021/2026-10-06_icerti-ai2-deploy_r01/remote.py')
remote=importlib.util.module_from_spec(spec);spec.loader.exec_module(remote)
targets=[dict(name='ai2',PREFIX='/usr/local/lib/contextra-maintenance/portable-20261006T192646',STATE='/var/lib/contextra-maintenance/contextra-ai2',ACTIVE='/workspace/contextra/runtime/state/active.json',PROJECT='contextra-ai2',EXPECTED_STATE='exited',SETUP='/workspace/contextra/setup',OPERATOR='si'),dict(name='test',PREFIX='/usr/local/lib/contextra-maintenance/portable-20261001T182138',STATE='/var/lib/contextra-maintenance/contextra',ACTIVE='/workspace/contextra-runtime-config/ct/runtime/state/active.json',PROJECT='contextra',EXPECTED_STATE='running',SETUP='/workspace/contextra-runtime-config/ct/setup',OPERATOR='root')]
for target in targets:
    name=target.pop('name')
    program='EXPECTED='+repr(expected)+'\n'+'\n'.join(k+'='+repr(v) for k,v in target.items())+'\n'+code
    raw=remote.run(program,root=True,timeout=60) if name=='ai2' else subprocess.check_output(['python3','-B','-c',program],text=True,timeout=60)
    data=json.loads(raw)
    path=OUT/(name+'-final-readback.json');path.write_text(json.dumps(data,ensure_ascii=False,indent=2)+'\n');path.chmod(0o600)
    print(name,'hashes',len(data['hashes']),'services',[r['status'] for r in data['services']],'rootGiB',round(data['root_free']/1024**3,2),'snapshot',data['editable_tools'])
