#!/usr/bin/env python3
"""Parity stripe catalog, Merkle proofs, and bounded one-chunk repair.

All evidence is local deterministic byte-integrity/redundancy evidence. The accepted
shard generation remains unchanged; successful repair must reproduce generation-1
bytes exactly and does not create a new generation.
"""
from __future__ import annotations
from pathlib import Path
from typing import Any
import argparse, copy, hashlib, json, tempfile

ROOT=Path(__file__).resolve().parents[1]
import sys
sys.path.insert(0,str(ROOT/'tools'))
import memory_patch_parity as parity
import memory_patch_shards as shards

CATALOG_REL='assets/data/memory-patch-stripe-catalog.json'
PROOFS_REL='assets/data/memory-patch-stripe-proofs.json'
REPAIR_REL='assets/data/memory-patch-stripe-repair-results.json'
JOURNAL_REL='assets/data/memory-patch-stripe-journal-results.json'
LINEAGE_REL='assets/data/memory-patch-shard-generation-lineage.json'
TEST_REL='assets/data/memory-patch-stripe-test-results.json'
PHP_REL='assets/data/memory-patch-stripe-php-results.json'

class StripeError(RuntimeError): pass

def canonical_bytes(obj:Any)->bytes:
    return json.dumps(obj,ensure_ascii=False,sort_keys=True,separators=(',',':')).encode('utf-8')
def sha_bytes(b:bytes)->str:return hashlib.sha256(b).hexdigest()
def sha_file(p:Path)->str:return hashlib.sha256(p.read_bytes()).hexdigest()
def load(p:Path)->dict[str,Any]:return json.loads(p.read_text('utf-8'))
def write_json(p:Path,obj:Any)->None:
    p.parent.mkdir(parents=True,exist_ok=True);p.write_text(json.dumps(obj,ensure_ascii=False,indent=2)+'\n','utf-8')

def record_core(rec:dict[str,Any])->dict[str,Any]:
    return {k:v for k,v in rec.items() if k not in ('stripe_digest_sha256','stripe_id')}
def record_digest(rec:dict[str,Any])->str:return sha_bytes(canonical_bytes(record_core(rec)))
def leaf_hash(digest_hex:str)->str:return sha_bytes(b'stripe-leaf\0'+bytes.fromhex(digest_hex))
def node_hash(a:str,b:str)->str:return sha_bytes(b'stripe-node\0'+bytes.fromhex(a)+bytes.fromhex(b))
def build_levels(digests:list[str])->list[list[str]]:
    if not digests: raise StripeError('empty_stripe_set')
    levels=[[leaf_hash(x) for x in digests]]
    while len(levels[-1])>1:
        cur=levels[-1]
        if len(cur)%2: cur=cur+[cur[-1]]
        levels.append([node_hash(cur[i],cur[i+1]) for i in range(0,len(cur),2)])
    return levels

def proof_for(levels:list[list[str]],idx:int)->list[dict[str,Any]]:
    out=[]
    for level_no,level in enumerate(levels[:-1]):
        if idx%2==0: sib=idx+1 if idx+1<len(level) else idx;side='right'
        else: sib=idx-1;side='left'
        out.append({'level':level_no,'side':side,'sha256':level[sib]});idx//=2
    return out

def verify_proof(digest_hex:str,proof:list[dict[str,Any]],root_hex:str)->bool:
    cur=leaf_hash(digest_hex)
    for level_no,p in enumerate(proof):
        if p.get('level')!=level_no or p.get('side') not in ('left','right'): raise StripeError('proof_structure')
        sib=p.get('sha256','')
        if len(sib)!=64: raise StripeError('proof_hash')
        cur=node_hash(sib,cur) if p['side']=='left' else node_hash(cur,sib)
    return cur==root_hex

def _chunk(data:bytes,start:int,length:int)->bytes:
    return data[start:start+length] if length else b''

def build_catalog(root:Path=ROOT)->dict[str,Any]:
    gen=load(root/parity.GEN_REL);meta=load(root/parity.PARITY_REL)
    parity.verify_generation(root,gen);parity.verify_parity(root,meta,gen=gen)
    chunk_size=meta['chunk_size_bytes'];padded=meta['padded_length_bytes'];pbytes=(root/meta['parity_path']).read_bytes()
    records=[]
    for ordinal,start in enumerate(range(0,padded,chunk_size)):
        end=start+chunk_size; contrib=[]
        for sd in gen['shards']:
            data=(root/sd['pack_path']).read_bytes();present=max(0,min(chunk_size,len(data)-start))
            b=_chunk(data,start,present)
            contrib.append({'shard_id':sd['shard_id'],'present_length_bytes':present,'chunk_sha256':sha_bytes(b),'whole_shard_sha256':sd['pack_sha256'],'whole_shard_length_bytes':sd['pack_size_bytes']})
        pb=pbytes[start:end]
        core={'schema':'memory-patch-parity-stripe/1','generation_id':gen['generation_id'],'generation_digest_sha256':gen['generation_digest_sha256'],'parity_id':meta['parity_id'],'stripe_ordinal':ordinal,'byte_start':start,'byte_end_exclusive':end,'chunk_size_bytes':chunk_size,'contributions':contrib,'parity_chunk_length_bytes':len(pb),'parity_chunk_sha256':sha_bytes(pb)}
        dg=sha_bytes(canonical_bytes(core));records.append({**core,'stripe_digest_sha256':dg,'stripe_id':'stripe-'+f'{ordinal:04d}'+'-'+dg[:12]})
    core={'schema':'memory-patch-parity-stripe-catalog/1','generation_id':gen['generation_id'],'generation_digest_sha256':gen['generation_digest_sha256'],'parity_id':meta['parity_id'],'parity_metadata_digest_sha256':meta['parity_metadata_digest_sha256'],'chunk_size_bytes':chunk_size,'padded_length_bytes':padded,'stripe_count':len(records),'shard_order':meta['shard_order'],'records':records}
    dg=sha_bytes(canonical_bytes(core));out={**core,'catalog_digest_sha256':dg,'catalog_id':'stripe-catalog-'+dg[:20],'truth_boundary':'Local deterministic chunk inventory only; not signing, authentication, encryption, secret sharing, production disaster-recovery proof, deployment, domain control, or real-world authorization.'}
    write_json(root/CATALOG_REL,out);return out

def verify_catalog(root:Path=ROOT,catalog:dict[str,Any]|None=None)->dict[str,Any]:
    c=copy.deepcopy(catalog or load(root/CATALOG_REL));gen=load(root/parity.GEN_REL);meta=load(root/parity.PARITY_REL)
    parity.verify_generation(root,gen);parity.verify_parity(root,meta,gen=gen)
    core={k:c[k] for k in ('schema','generation_id','generation_digest_sha256','parity_id','parity_metadata_digest_sha256','chunk_size_bytes','padded_length_bytes','stripe_count','shard_order','records')}
    dg=sha_bytes(canonical_bytes(core))
    if c.get('catalog_digest_sha256')!=dg or c.get('catalog_id')!='stripe-catalog-'+dg[:20]:raise StripeError('catalog_substitution')
    if c['generation_id']!=gen['generation_id'] or c['generation_digest_sha256']!=gen['generation_digest_sha256']:raise StripeError('catalog_generation_mismatch')
    if c['parity_id']!=meta['parity_id'] or c['parity_metadata_digest_sha256']!=meta['parity_metadata_digest_sha256']:raise StripeError('catalog_parity_mismatch')
    expected_count=meta['padded_length_bytes']//meta['chunk_size_bytes']
    if c['stripe_count']!=expected_count or len(c['records'])!=expected_count:raise StripeError('stripe_count')
    if c['shard_order']!=meta['shard_order']:raise StripeError('shard_order')
    pbytes=(root/meta['parity_path']).read_bytes(); by={s['shard_id']:s for s in gen['shards']}
    for i,r in enumerate(c['records']):
        if r.get('stripe_ordinal')!=i:raise StripeError('stripe_order')
        if r.get('byte_start')!=i*meta['chunk_size_bytes'] or r.get('byte_end_exclusive')!=(i+1)*meta['chunk_size_bytes']:raise StripeError('stripe_range')
        if r.get('stripe_digest_sha256')!=record_digest(r) or r.get('stripe_id')!='stripe-'+f'{i:04d}'+'-'+r['stripe_digest_sha256'][:12]:raise StripeError('stripe_digest')
        if [x['shard_id'] for x in r['contributions']]!=meta['shard_order']:raise StripeError('stripe_shard_order')
        for x in r['contributions']:
            sd=by[x['shard_id']];data=(root/sd['pack_path']).read_bytes();present=max(0,min(meta['chunk_size_bytes'],len(data)-r['byte_start']));b=_chunk(data,r['byte_start'],present)
            if x['present_length_bytes']!=present:raise StripeError('stale_length_table')
            if x['chunk_sha256']!=sha_bytes(b):raise StripeError('chunk_digest')
            if x['whole_shard_sha256']!=sd['pack_sha256'] or x['whole_shard_length_bytes']!=sd['pack_size_bytes']:raise StripeError('whole_shard_binding')
        pb=pbytes[r['byte_start']:r['byte_end_exclusive']]
        if len(pb)!=r['parity_chunk_length_bytes'] or sha_bytes(pb)!=r['parity_chunk_sha256']:raise StripeError('parity_chunk_digest')
    return {'pass':True,'catalog_id':c['catalog_id'],'stripe_count':c['stripe_count'],'catalog_digest_sha256':c['catalog_digest_sha256']}

def build_proofs(root:Path=ROOT)->dict[str,Any]:
    c=load(root/CATALOG_REL);verify_catalog(root,c)
    digests=[r['stripe_digest_sha256'] for r in c['records']];levels=build_levels(digests);rootdg=levels[-1][0]
    proofs={}
    for i,r in enumerate(c['records']):
        pr=proof_for(levels,i)
        if not verify_proof(r['stripe_digest_sha256'],pr,rootdg):raise StripeError('self_proof')
        proofs[str(i)]={'stripe_id':r['stripe_id'],'stripe_digest_sha256':r['stripe_digest_sha256'],'proof':pr}
    core={'schema':'memory-patch-parity-stripe-proofs/1','catalog_id':c['catalog_id'],'catalog_digest_sha256':c['catalog_digest_sha256'],'generation_id':c['generation_id'],'stripe_count':c['stripe_count'],'leaf_domain':'stripe-leaf','node_domain':'stripe-node','odd_node_rule':'duplicate_last_hash','merkle_root_sha256':rootdg,'proofs':proofs}
    dg=sha_bytes(canonical_bytes(core));out={**core,'proof_set_digest_sha256':dg,'proof_set_id':'stripe-proofs-'+dg[:20]};write_json(root/PROOFS_REL,out);return out

def verify_proofs(root:Path=ROOT,proofs:dict[str,Any]|None=None,catalog:dict[str,Any]|None=None)->dict[str,Any]:
    c=catalog or load(root/CATALOG_REL);verify_catalog(root,c);p=copy.deepcopy(proofs or load(root/PROOFS_REL))
    core={k:p[k] for k in ('schema','catalog_id','catalog_digest_sha256','generation_id','stripe_count','leaf_domain','node_domain','odd_node_rule','merkle_root_sha256','proofs')};dg=sha_bytes(canonical_bytes(core))
    if p.get('proof_set_digest_sha256')!=dg or p.get('proof_set_id')!='stripe-proofs-'+dg[:20]:raise StripeError('proof_set_substitution')
    if p['catalog_id']!=c['catalog_id'] or p['catalog_digest_sha256']!=c['catalog_digest_sha256'] or p['generation_id']!=c['generation_id']:raise StripeError('proof_catalog_binding')
    if set(p['proofs'])!={str(i) for i in range(c['stripe_count'])}:raise StripeError('proof_set_incomplete')
    for i,r in enumerate(c['records']):
        rec=p['proofs'][str(i)]
        if rec['stripe_id']!=r['stripe_id'] or rec['stripe_digest_sha256']!=r['stripe_digest_sha256']:raise StripeError('proof_record_binding')
        if not verify_proof(r['stripe_digest_sha256'],rec['proof'],p['merkle_root_sha256']):raise StripeError('proof_invalid')
    return {'pass':True,'proof_set_id':p['proof_set_id'],'merkle_root_sha256':p['merkle_root_sha256'],'proof_count':len(p['proofs'])}

def _record(c:dict[str,Any],ordinal:int)->dict[str,Any]:
    if not isinstance(ordinal,int) or ordinal<0 or ordinal>=c['stripe_count']:raise StripeError('wrong_stripe_ordinal')
    r=c['records'][ordinal]
    if r['stripe_ordinal']!=ordinal:raise StripeError('wrong_stripe_ordinal')
    return r

def repair_one_chunk(root:Path,target_shard_id:str,ordinal:int,candidate_bytes:bytes,*,peer_chunk_overrides:dict[str,bytes]|None=None,parity_chunk_override:bytes|None=None,catalog:dict[str,Any]|None=None,proofs:dict[str,Any]|None=None)->bytes:
    c=copy.deepcopy(catalog or load(root/CATALOG_REL));p=copy.deepcopy(proofs or load(root/PROOFS_REL));verify_proofs(root,p,c)
    gen=load(root/parity.GEN_REL);meta=load(root/parity.PARITY_REL);parity.verify_generation(root,gen);parity.verify_parity(root,meta,gen=gen)
    r=_record(c,ordinal);pr=p['proofs'][str(ordinal)]
    if not verify_proof(r['stripe_digest_sha256'],pr['proof'],p['merkle_root_sha256']):raise StripeError('proof_invalid')
    contrib={x['shard_id']:x for x in r['contributions']}
    if target_shard_id not in contrib:raise StripeError('unknown_target_shard')
    target=contrib[target_shard_id];tlen=target['present_length_bytes']
    if tlen<=0:raise StripeError('target_not_present_in_stripe')
    peer_chunk_overrides=peer_chunk_overrides or {}
    start=r['byte_start'];chunk_size=r['chunk_size_bytes']
    parity_bytes=(root/meta['parity_path']).read_bytes();pb=parity_chunk_override if parity_chunk_override is not None else parity_bytes[start:r['byte_end_exclusive']]
    if len(pb)!=r['parity_chunk_length_bytes']:raise StripeError('parity_chunk_length')
    if sha_bytes(pb)!=r['parity_chunk_sha256']:raise StripeError('parity_proof_substitution')
    recovered=bytearray(pb)
    gen_by={s['shard_id']:s for s in gen['shards']}
    for sid in meta['shard_order']:
        if sid==target_shard_id:continue
        x=contrib[sid];sd=gen_by[sid]
        expected_len=x['present_length_bytes']
        if sid in peer_chunk_overrides: chunk=peer_chunk_overrides[sid]
        else: chunk=_chunk((root/sd['pack_path']).read_bytes(),start,expected_len)
        if len(chunk)!=expected_len:raise StripeError('peer_chunk_length')
        if sha_bytes(chunk)!=x['chunk_sha256']:raise StripeError('two_damaged_contributions')
        for i,v in enumerate(chunk):recovered[i]^=v
    recovered_chunk=bytes(recovered[:tlen])
    if sha_bytes(recovered_chunk)!=target['chunk_sha256']:raise StripeError('reconstructed_chunk_digest')
    expected_len=target['whole_shard_length_bytes']
    if len(candidate_bytes)==expected_len:
        old=candidate_bytes[start:start+tlen]
        if len(old)!=tlen:raise StripeError('candidate_chunk_range')
        if sha_bytes(old)==target['chunk_sha256']:raise StripeError('target_chunk_not_damaged')
        repaired=candidate_bytes[:start]+recovered_chunk+candidate_bytes[start+tlen:]
    elif len(candidate_bytes)==expected_len-tlen:
        repaired=candidate_bytes[:start]+recovered_chunk+candidate_bytes[start:]
    else:raise StripeError('candidate_shard_length')
    if len(repaired)!=expected_len:raise StripeError('repaired_shard_length')
    if sha_bytes(repaired)!=target['whole_shard_sha256']:raise StripeError('whole_shard_digest_mismatch')
    sd=gen_by[target_shard_id];idx=load(root/sd['index_path']);shards.verify_shard_index(root,idx,repaired);shards.verify_super(root,shard_bytes={target_shard_id:repaired})
    parity.verify_generation(root,gen,shard_bytes={target_shard_id:repaired})
    return repaired

def journal_digest(j:dict[str,Any])->str:return sha_bytes(canonical_bytes({k:v for k,v in j.items() if k not in ('journal_digest_sha256','journal_id')}))
def repair_transaction(root:Path,state_dir:Path,target_shard_id:str,ordinal:int,candidate_bytes:bytes,interrupt_at:str|None=None)->dict[str,Any]:
    if state_dir.exists() and any(state_dir.iterdir()):raise StripeError('repair_state_not_fresh')
    state_dir.mkdir(parents=True,exist_ok=True);c=load(root/CATALOG_REL);gen=load(root/parity.GEN_REL);r=_record(c,ordinal)
    journal={'schema':'memory-patch-stripe-repair-journal/1','generation_id':gen['generation_id'],'target_shard_id':target_shard_id,'stripe_ordinal':ordinal,'states':[],'status':'in_progress'}
    def step(name:str,detail:dict[str,Any]|None=None):
        journal['states'].append({'state':name,**(detail or {})});write_json(state_dir/'repair-journal.json',journal)
        if interrupt_at==name: journal['status']='interrupted';write_json(state_dir/'repair-journal.json',journal);raise StripeError('simulated_interrupt:'+name)
    try:
        step('prepare',{'stripe_id':r['stripe_id']})
        verify_catalog(root);verify_proofs(root);step('verify stripe',{'merkle_root_sha256':load(root/PROOFS_REL)['merkle_root_sha256']})
        repaired=repair_one_chunk(root,target_shard_id,ordinal,candidate_bytes);(state_dir/'candidate-repaired.pack').write_bytes(repaired);step('reconstruct',{'candidate_sha256':sha_bytes(repaired)})
        target=next(x for x in gen['shards'] if x['shard_id']==target_shard_id)
        if sha_bytes(repaired)!=target['pack_sha256']:raise StripeError('verify_shard_digest')
        step('verify shard',{'shard_sha256':target['pack_sha256']})
        parity.verify_generation(root,gen,shard_bytes={target_shard_id:repaired});step('verify generation',{'generation_id':gen['generation_id']})
        (state_dir/'committed-shard.pack').write_bytes(repaired);step('commit',{'committed_sha256':sha_bytes(repaired)});journal['status']='committed'
    except StripeError as e:
        if str(e).startswith('simulated_interrupt:'):
            (state_dir/'committed-shard.pack').unlink(missing_ok=True);return _finalize_journal(state_dir,journal)
        (state_dir/'candidate-repaired.pack').unlink(missing_ok=True);(state_dir/'committed-shard.pack').unlink(missing_ok=True);journal['states'].append({'state':'rollback','reason':str(e)});journal['status']='rolled_back'
    return _finalize_journal(state_dir,journal)

def _finalize_journal(state_dir:Path,journal:dict[str,Any])->dict[str,Any]:
    dg=journal_digest(journal);journal['journal_digest_sha256']=dg;journal['journal_id']='stripe-repair-journal-'+dg[:20];write_json(state_dir/'repair-journal.json',journal);return journal

def _corrupt_chunk(data:bytes,start:int,length:int)->bytes:
    b=bytearray(data);pos=start if length else 0;b[pos]^=1;return bytes(b)

def build_repair_results(root:Path=ROOT)->dict[str,Any]:
    gen=load(root/parity.GEN_REL);c=load(root/CATALOG_REL);fixtures=[]
    # corrupt middle chunk in shard-0002
    sid='shard-0002';ordinal=5;sd=next(x for x in gen['shards'] if x['shard_id']==sid);r=_record(c,ordinal);t=next(x for x in r['contributions'] if x['shard_id']==sid);orig=(root/sd['pack_path']).read_bytes();bad=_corrupt_chunk(orig,r['byte_start'],t['present_length_bytes']);rep=repair_one_chunk(root,sid,ordinal,bad);fixtures.append({'fixture':'corrupt_middle_chunk','pass':rep==orig,'shard_id':sid,'stripe_ordinal':ordinal,'repaired_sha256':sha_bytes(rep)})
    # missing middle chunk in shard-0003
    sid2='shard-0003';ordinal2=10;sd2=next(x for x in gen['shards'] if x['shard_id']==sid2);r2=_record(c,ordinal2);t2=next(x for x in r2['contributions'] if x['shard_id']==sid2);orig2=(root/sd2['pack_path']).read_bytes();missing=orig2[:r2['byte_start']]+orig2[r2['byte_start']+t2['present_length_bytes']:];rep2=repair_one_chunk(root,sid2,ordinal2,missing);fixtures.append({'fixture':'missing_middle_chunk','pass':rep2==orig2,'shard_id':sid2,'stripe_ordinal':ordinal2,'repaired_sha256':sha_bytes(rep2)})
    core={'schema':'memory-patch-stripe-repair-results/1','generation_id':gen['generation_id'],'catalog_id':c['catalog_id'],'fixtures':fixtures,'new_generation_created':False,'automatic_delete_count':0,'protected_anchor_mutation_count':0}
    dg=sha_bytes(canonical_bytes(core));out={**core,'results_digest_sha256':dg,'results_id':'stripe-repair-results-'+dg[:20],'pass':all(x['pass'] for x in fixtures)};write_json(root/REPAIR_REL,out);return out

def build_journal_results(root:Path=ROOT)->dict[str,Any]:
    gen=load(root/parity.GEN_REL);c=load(root/CATALOG_REL);sid='shard-0002';ordinal=5;sd=next(x for x in gen['shards'] if x['shard_id']==sid);r=_record(c,ordinal);t=next(x for x in r['contributions'] if x['shard_id']==sid);orig=(root/sd['pack_path']).read_bytes();bad=_corrupt_chunk(orig,r['byte_start'],t['present_length_bytes']);fixtures=[]
    with tempfile.TemporaryDirectory() as td:
        j=repair_transaction(root,Path(td)/'ok',sid,ordinal,bad);fixtures.append({'fixture':'clean_commit','pass':j['status']=='committed','status':j['status'],'journal_digest_sha256':j['journal_digest_sha256']})
    for state in ('prepare','verify stripe','reconstruct','verify shard','verify generation'):
        with tempfile.TemporaryDirectory() as td:
            d=Path(td)/('i-'+state.replace(' ','-'));j=repair_transaction(root,d,sid,ordinal,bad,interrupt_at=state);ok=j['status']=='interrupted' and not (d/'committed-shard.pack').exists();fixtures.append({'fixture':'interrupt_'+state.replace(' ','_'),'pass':ok,'status':j['status'],'journal_digest_sha256':j['journal_digest_sha256']})
    core={'schema':'memory-patch-stripe-journal-results/1','generation_id':gen['generation_id'],'state_machine':['prepare','verify stripe','reconstruct','verify shard','verify generation','commit / rollback'],'fixtures':fixtures,'partial_install_count':0,'new_generation_created':False}
    dg=sha_bytes(canonical_bytes(core));out={**core,'results_digest_sha256':dg,'results_id':'stripe-journal-results-'+dg[:20],'pass':all(x['pass'] for x in fixtures)};write_json(root/JOURNAL_REL,out);return out

def build_lineage(root:Path=ROOT)->dict[str,Any]:
    gen=load(root/parity.GEN_REL);c=load(root/CATALOG_REL);p=load(root/PROOFS_REL)
    core={'schema':'memory-patch-shard-generation-lineage/1','accepted_generations':[{'generation_ordinal':gen['generation_ordinal'],'generation_id':gen['generation_id'],'generation_digest_sha256':gen['generation_digest_sha256'],'shard_count':gen['shard_count'],'object_count':gen['object_count']}],'transition_count':0,'current_generation_id':gen['generation_id'],'new_generation_created':False,'reason':'Selective chunk repair reconstructs exact accepted generation-1 shard bytes; no byte-level shard-set change exists, so no generation transition is invented.','stripe_catalog_id':c['catalog_id'],'stripe_merkle_root_sha256':p['merkle_root_sha256']}
    dg=sha_bytes(canonical_bytes(core));out={**core,'lineage_digest_sha256':dg,'lineage_id':'shard-generation-lineage-'+dg[:20]};write_json(root/LINEAGE_REL,out);return out

def build_all(root:Path=ROOT)->dict[str,Any]:
    c=build_catalog(root);p=build_proofs(root);r=build_repair_results(root);j=build_journal_results(root);l=build_lineage(root)
    return {'pass':r['pass'] and j['pass'],'catalog_id':c['catalog_id'],'stripe_count':c['stripe_count'],'merkle_root_sha256':p['merkle_root_sha256'],'repair_results_id':r['results_id'],'journal_results_id':j['results_id'],'lineage_id':l['lineage_id']}

def main():
    ap=argparse.ArgumentParser();ap.add_argument('--root',type=Path,default=ROOT);ap.add_argument('--build',action='store_true');ns=ap.parse_args()
    if ns.build: print(json.dumps(build_all(ns.root),indent=2))
    else: print(json.dumps({'catalog':verify_catalog(ns.root),'proofs':verify_proofs(ns.root)},indent=2))
if __name__=='__main__':main()
