"""Bounded AI-only readback and evidence collection; no channel control."""
import json,hashlib
from pathlib import Path
import deploy

audit="""import json,yaml
from pathlib import Path
from fall_r2_settings import migrate_document
root=Path('/work');rows=[]
for entry in json.loads((root/'settings-inputs.json').read_text()):
 def load(stage):
  p=root/stage/entry['name'];return json.loads(p.read_text()) if entry['format']=='json' else yaml.safe_load(p.read_text())
 before=load('settings-before');after=load('settings-after')
 assert migrate_document(before,entry['kind'])==after
 assert migrate_document(after,entry['kind'])==after
 key={'base':'streams','runtime':'added'}.get(entry['kind'])
 olds=before[key] if key else [before];news=after[key] if key else [after]
 for a,b in zip(olds,news):
  assert {k:v for k,v in a.items() if k not in ('fall','event_cooldowns')}=={k:v for k,v in b.items() if k not in ('fall','event_cooldowns')}
  assert {k:v for k,v in a.get('event_cooldowns',{}).items() if k!='FALL'}=={k:v for k,v in b['event_cooldowns'].items() if k!='FALL'}
 rows.append(dict(kind=entry['kind'],count=len(news),unrelated_preserved=True,idempotent=True))
result=dict(status='PASS',items=rows)
(root/'settings-audit.json').write_text(json.dumps(result,indent=2));print(json.dumps(result))
"""
code="""import sys,json,subprocess,re,shutil
from pathlib import Path
sys.path.insert(0,ROOT)
from install import FallR2Installer
i=FallR2Installer(ROOT,'icerti-ai2');root=Path(ROOT);image=json.loads((root/'built.json').read_text())['image']
code="import sys;sys.path.insert(0,'/work');"+AUDIT
print(i.cpu_job('ct-fall-r2-final-audit',image,['-c',code],60).decode())
rows=i.rows();runtime=i.runtime(rows['analytics']['Id'])
logs=subprocess.run(['docker','logs','--since','5m','--tail','500',rows['analytics']['Id']],capture_output=True,timeout=20)
data=logs.stdout+logs.stderr
(root/'restricted-post-readiness.log').write_bytes(data)
bad=[line for line in data.decode(errors='replace').splitlines() if re.search(r'Traceback|CRITICAL|FALL_R2_.*(?:INVALID|ERROR)|ModuleNotFoundError|ImportError',line)]
result=dict(status='PASS' if not bad else 'REVIEW',stream_counts=runtime['streamCounts'],critical_patterns=len(bad),
 services={k:dict(running=v['State']['Running'],health=v['State'].get('Health',{}).get('Status'),image=v['Image']) for k,v in rows.items()},
 root_free_bytes=shutil.disk_usage('/').free,camera_start_calls=0,alarm_induction_tests=0,browser_acceptance='UNVERIFIED_USER_OWNED')
i.save('bounded-after',result);print(json.dumps(result))
assert result['root_free_bytes']>=15*1024**3
assert not bad and rows['ai-gateway']['State']['Running']
""".replace('ROOT',repr(deploy.REMOTE)).replace('AUDIT',repr(audit))
print(deploy.r.python(code,sudo=True,timeout=100).decode())
names=['manifest.json','built.json','plan.json','config-targets.json','isolated-tests.json','isolated-pytest.log',
       'settings-inputs.json','settings-migration.json','settings-commit.json','settings-audit.json',
       'activation.json','deployed.json','verified.json','verified-before-gateway.json','bounded-after.json',
       'install.py','fall_r2_settings.py']
dest=deploy.ROOT/'evidence';dest.mkdir(exist_ok=True)
code='import json,hashlib,base64;from pathlib import Path;root=Path('+repr(deploy.REMOTE)+');names='+repr(names)+';print(json.dumps({n:dict(sha256=hashlib.sha256((root/n).read_bytes()).hexdigest(),data=base64.b64encode((root/n).read_bytes()).decode()) for n in names}))'
import base64
items=json.loads(deploy.r.python(code,sudo=True))
for name,item in items.items():
 raw=base64.b64decode(item['data']);assert hashlib.sha256(raw).hexdigest()==item['sha256'];(dest/name).write_bytes(raw)
(deploy.ROOT/'evidence-transfer.json').write_text(json.dumps({n:v['sha256'] for n,v in items.items()},indent=2))
print('TRANSFER_SHA256_PASS',len(items))
