from pathlib import Path
import json,hashlib,stat
def sha(p):return hashlib.sha256(p.read_bytes()).hexdigest()
def seal(here,raw,status,exclude=()):
 out=here/'manifest.json';assert not out.exists(),'Immutable seal once';adjust=[]
 def entries(root,public=False):
  result=[]
  for p in sorted(root.rglob('*')):
   if not p.is_file():continue
   n=p.relative_to(root).as_posix()
   if p==out or public and any(n==x or n.startswith(x+'/') for x in exclude):continue
   assert not p.is_symlink();result.append({'path':n,'sha256':sha(p),'bytes':p.stat().st_size,'mode':oct(stat.S_IMODE(p.stat().st_mode))})
  return result
 groups={}
 for lane in ['B','C']:
  root=raw/f'private-{lane}-final';assert stat.S_IMODE(root.stat().st_mode)==0o700
  log=root/'runtime/log/20261011_sql.log';before=stat.S_IMODE(log.stat().st_mode);digest=sha(log);assert before in [0o644,0o600]
  if before!=0o600:log.chmod(0o600);adjust.append({'path':str(log),'from':oct(before),'to':'0o600','sha256':digest,'bytes_unchanged':sha(log)==digest,'canonical_copy_only':True})
  files=entries(root);assert all(f['mode']=='0o600' for f in files);groups[lane]={'root':str(root),'directory_mode':'0o700','files':files}
 old={}
 base=here
 while not (base/'source-frozen-sha.json').exists():base=base.parent
 for name,digest in [('parallel-ai-core-scan-pay-20261011','cfda90d3fb84df1c73920f7b831c64d85e28bdfbd4a4b965086fe05a96d57b15'),('parallel-ai-direct002-20261011','45a587e9304011fa66ead8c07f1c876e5f86109a64848877ef3fddf65d5537dc')]:
  m=base.parent/name/'manifest.json';assert sha(m)==digest;d=json.loads(m.read_text());count=0
  for root,files in [(m.parent,d['public_files'])]+[(Path(g['root']),g['files']) for g in d['canonical_private_groups'].values()]:
   for f in files:p=root/f['path'];assert sha(p)==f['sha256'] and p.stat().st_size==f['bytes'] and oct(stat.S_IMODE(p.stat().st_mode))==f['mode'];count+=1
  old[name]={'manifest_SHA':digest,'immutable_assets':count}
 data={'status':status,'public_root':str(here),'public_excluded_independent_subbatches':list(exclude),'public_files':entries(here,True),'canonical_private_groups':groups,'canonical_permission_adjustments':adjust,'old_seals_verified_immutable':old,'business_DMLDDL':0,'original_rows_retained':True,'new_resources_cleaned':True}
 out.write_text(json.dumps(data,ensure_ascii=False,indent=2)+'\n');print(json.dumps({'public_files':len(data['public_files']),'private_files':{k:len(g['files']) for k,g in groups.items()},'manifest_SHA':sha(out)}))
if __name__=='__main__':seal(Path(__file__).resolve().parent,Path('/tmp/cdx-ai-api-customer-read12-20261011'),'SEALED_READ10_EMPTY_LIMITED_WEIGHT_TOOL_BLOCKED',('failed-weight-retest','final-weight-read'))
