#!/usr/bin/env python3
"""Deterministic shard-set generation activation and bounded single-shard XOR parity.

The authoritative loose SHA-256 object store and accepted v2026.08.16.10 shard bytes
remain authoritative/preserved. Generation manifests, activation journals, and XOR
parity are secondary local byte-integrity/redundancy evidence only. They are not
signing, encryption, authenticated backup custody, timestamp authority, semantic
truth, public witness consensus, production disaster recovery, deployment, domain
control, or real-world authorization.
"""
from __future__ import annotations
from pathlib import Path
from typing import Any
import argparse, copy, hashlib, json, math, os, shutil, tempfile

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

GEN_REL='assets/data/memory-patch-shard-generation.json'
PARITY_REL='assets/data/memory-patch-shard-parity.json'
CHECKPOINT_REL='assets/data/memory-patch-shard-generation-checkpoints.json'
ACTIVATION_REL='assets/data/memory-patch-shard-activation-results.json'
RECOVERY_REL='assets/data/memory-patch-shard-parity-recovery-results.json'
TEST_REL='assets/data/memory-patch-shard-parity-test-results.json'
PHP_REL='assets/data/memory-patch-shard-parity-php-results.json'
PARITY_DIR_REL='assets/patch-object-parity'
CHUNK_SIZE=65536
GENERATION_ORDINAL=1
GENERATION_LABEL='v2026.08.16.10-accepted-shards'

class ParityError(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 ceil_chunk(n:int)->int:return ((n+CHUNK_SIZE-1)//CHUNK_SIZE)*CHUNK_SIZE

def protected_anchor_digest(root:Path)->str:
    entries=[]
    for name in ('totem','taboo','talisman'):
        p=root/'.uai'/f'{name}.uai';entries.append({'path':p.relative_to(root).as_posix(),'sha256':sha_file(p),'size_bytes':p.stat().st_size})
    return sha_bytes(canonical_bytes(entries))

def retention_digest(root:Path)->str:
    p=root/'assets/data/memory-patch-object-retention.json';return sha_file(p)

def build_generation(root:Path=ROOT)->dict[str,Any]:
    sup=load(root/shards.SUPER_REL); shards.verify_super(root,sup); shards.verify_checkpoints(root)
    desc=[]
    for d in sup['shards']:
        pp=root/d['pack_path']; ip=root/d['index_path']
        desc.append({'shard_id':d['shard_id'],'ordinal':d['ordinal'],'pack_path':d['pack_path'],'pack_sha256':sha_file(pp),'pack_size_bytes':pp.stat().st_size,'index_path':d['index_path'],'index_file_sha256':sha_file(ip),'index_digest_sha256':d['index_digest_sha256'],'first_digest':d['first_digest'],'last_digest':d['last_digest'],'object_count':d['object_count']})
    core={'schema':'memory-patch-shard-generation/1','generation_ordinal':GENERATION_ORDINAL,'generation_label':GENERATION_LABEL,'predecessor_generation_id':None,'authority':'assets/patch-object-store/sha256','super_index_path':shards.SUPER_REL,'super_index_id':sup['super_index_id'],'super_index_digest_sha256':sup['super_index_digest_sha256'],'super_index_file_sha256':sha_file(root/shards.SUPER_REL),'checkpoint_path':shards.CHECKPOINT_REL,'checkpoint_file_sha256':sha_file(root/shards.CHECKPOINT_REL),'checkpoint_root_sha256':load(root/shards.CHECKPOINT_REL)['super_index_checkpoint']['merkle_root_sha256'],'catalog_id':sup['catalog_id'],'catalog_digest_sha256':sup['catalog_digest_sha256'],'object_count':sup['object_count'],'object_set_digest_sha256':sup['object_set_digest_sha256'],'retention_file_sha256':retention_digest(root),'protected_anchor_set_digest_sha256':protected_anchor_digest(root),'shard_count':sup['shard_count'],'shards':desc}
    dg=sha_bytes(canonical_bytes(core));out={**core,'generation_digest_sha256':dg,'generation_id':'shard-generation-'+dg[:20],'truth_boundary':'Local deterministic shard-generation metadata only; not signing, authentication, timestamp authority, semantic truth, production disaster recovery, deployment, domain control, or real-world authorization.'}
    write_json(root/GEN_REL,out);return out

def verify_generation(root:Path=ROOT,gen:dict[str,Any]|None=None, shard_bytes:dict[str,bytes]|None=None)->dict[str,Any]:
    gen=copy.deepcopy(gen or load(root/GEN_REL)); shard_bytes=shard_bytes or {}
    core={k:gen[k] for k in ('schema','generation_ordinal','generation_label','predecessor_generation_id','authority','super_index_path','super_index_id','super_index_digest_sha256','super_index_file_sha256','checkpoint_path','checkpoint_file_sha256','checkpoint_root_sha256','catalog_id','catalog_digest_sha256','object_count','object_set_digest_sha256','retention_file_sha256','protected_anchor_set_digest_sha256','shard_count','shards')}
    dg=sha_bytes(canonical_bytes(core))
    if gen.get('generation_digest_sha256')!=dg or gen.get('generation_id')!='shard-generation-'+dg[:20]:raise ParityError('generation_substitution')
    sup=load(root/shards.SUPER_REL);shards.verify_super(root,sup)
    if gen['super_index_id']!=sup['super_index_id'] or gen['super_index_digest_sha256']!=sup['super_index_digest_sha256'] or gen['super_index_file_sha256']!=sha_file(root/shards.SUPER_REL):raise ParityError('generation_super_index_mismatch')
    cp=load(root/shards.CHECKPOINT_REL)
    if gen['checkpoint_file_sha256']!=sha_file(root/shards.CHECKPOINT_REL) or gen['checkpoint_root_sha256']!=cp['super_index_checkpoint']['merkle_root_sha256']:raise ParityError('generation_checkpoint_mismatch')
    if gen['catalog_id']!=sup['catalog_id'] or gen['catalog_digest_sha256']!=sup['catalog_digest_sha256'] or gen['object_count']!=129 or gen['object_set_digest_sha256']!=sup['object_set_digest_sha256']:raise ParityError('generation_object_set_drift')
    if gen['retention_file_sha256']!=retention_digest(root):raise ParityError('generation_retention_drift')
    if gen['protected_anchor_set_digest_sha256']!=protected_anchor_digest(root):raise ParityError('generation_protected_anchor_drift')
    if gen['shard_count']!=len(sup['shards']) or len(gen['shards'])!=len(sup['shards']):raise ParityError('generation_shard_count')
    for gd,sd in zip(gen['shards'],sup['shards']):
        for k in ('shard_id','ordinal','pack_path','index_path','index_digest_sha256','first_digest','last_digest','object_count'):
            if gd[k]!=sd[k]:raise ParityError('generation_shard_binding')
        b=shard_bytes.get(gd['shard_id'],(root/gd['pack_path']).read_bytes())
        if len(b)!=gd['pack_size_bytes']:raise ParityError('generation_shard_length')
        if sha_bytes(b)!=gd['pack_sha256']:raise ParityError('generation_shard_digest')
        if sha_file(root/gd['index_path'])!=gd['index_file_sha256']:raise ParityError('generation_index_file_digest')
    return {'pass':True,'generation_id':gen['generation_id'],'generation_digest_sha256':gen['generation_digest_sha256'],'shard_count':gen['shard_count'],'object_count':gen['object_count']}

def parity_path(gen:dict[str,Any])->str:return f"{PARITY_DIR_REL}/{gen['generation_id']}.xor"

def build_parity(root:Path=ROOT,gen:dict[str,Any]|None=None)->dict[str,Any]:
    gen=gen or load(root/GEN_REL);verify_generation(root,gen)
    max_len=max(s['pack_size_bytes'] for s in gen['shards']); padded=ceil_chunk(max_len); parity=bytearray(padded)
    for sd in gen['shards']:
        data=(root/sd['pack_path']).read_bytes()
        for base in range(0,padded,CHUNK_SIZE):
            chunk=data[base:base+CHUNK_SIZE]
            for i,v in enumerate(chunk):parity[base+i]^=v
    pp=root/parity_path(gen);pp.parent.mkdir(parents=True,exist_ok=True);pp.write_bytes(parity)
    core={'schema':'memory-patch-shard-parity/1','algorithm':'xor-single-missing-shard','chunk_size_bytes':CHUNK_SIZE,'padded_length_bytes':padded,'generation_id':gen['generation_id'],'generation_digest_sha256':gen['generation_digest_sha256'],'super_index_digest_sha256':gen['super_index_digest_sha256'],'object_set_digest_sha256':gen['object_set_digest_sha256'],'retention_file_sha256':gen['retention_file_sha256'],'protected_anchor_set_digest_sha256':gen['protected_anchor_set_digest_sha256'],'shard_order':[s['shard_id'] for s in gen['shards']],'original_lengths_bytes':{s['shard_id']:s['pack_size_bytes'] for s in gen['shards']},'expected_shard_sha256':{s['shard_id']:s['pack_sha256'] for s in gen['shards']},'parity_path':parity_path(gen),'parity_size_bytes':len(parity),'parity_sha256':sha_bytes(parity)}
    dg=sha_bytes(canonical_bytes(core));out={**core,'parity_metadata_digest_sha256':dg,'parity_id':'shard-parity-'+dg[:20],'truth_boundary':'XOR parity is bounded local redundancy only. It is not encryption, authentication, signing, secret sharing, authenticated backup custody, or production disaster-recovery proof.'};write_json(root/PARITY_REL,out);return out

def verify_parity(root:Path=ROOT,meta:dict[str,Any]|None=None,parity_bytes:bytes|None=None,gen:dict[str,Any]|None=None)->dict[str,Any]:
    meta=copy.deepcopy(meta or load(root/PARITY_REL));gen=gen or load(root/GEN_REL);verify_generation(root,gen)
    core={k:meta[k] for k in ('schema','algorithm','chunk_size_bytes','padded_length_bytes','generation_id','generation_digest_sha256','super_index_digest_sha256','object_set_digest_sha256','retention_file_sha256','protected_anchor_set_digest_sha256','shard_order','original_lengths_bytes','expected_shard_sha256','parity_path','parity_size_bytes','parity_sha256')}
    dg=sha_bytes(canonical_bytes(core))
    if meta.get('parity_metadata_digest_sha256')!=dg or meta.get('parity_id')!='shard-parity-'+dg[:20]:raise ParityError('parity_metadata_substitution')
    if meta['generation_id']!=gen['generation_id'] or meta['generation_digest_sha256']!=gen['generation_digest_sha256']:raise ParityError('parity_generation_mismatch')
    if meta['super_index_digest_sha256']!=gen['super_index_digest_sha256'] or meta['object_set_digest_sha256']!=gen['object_set_digest_sha256']:raise ParityError('parity_object_set_drift')
    if meta['retention_file_sha256']!=gen['retention_file_sha256']:raise ParityError('parity_retention_drift')
    if meta['protected_anchor_set_digest_sha256']!=gen['protected_anchor_set_digest_sha256']:raise ParityError('parity_protected_anchor_drift')
    expected_order=[s['shard_id'] for s in gen['shards']]
    if meta['shard_order']!=expected_order:raise ParityError('parity_shard_order')
    if meta['chunk_size_bytes']!=CHUNK_SIZE or meta['padded_length_bytes']!=ceil_chunk(max(s['pack_size_bytes'] for s in gen['shards'])):raise ParityError('parity_length_table')
    for s in gen['shards']:
        if meta['original_lengths_bytes'].get(s['shard_id'])!=s['pack_size_bytes'] or meta['expected_shard_sha256'].get(s['shard_id'])!=s['pack_sha256']:raise ParityError('parity_length_table')
    b=parity_bytes if parity_bytes is not None else (root/meta['parity_path']).read_bytes()
    if len(b)!=meta['parity_size_bytes'] or len(b)!=meta['padded_length_bytes']:raise ParityError('parity_truncated')
    if sha_bytes(b)!=meta['parity_sha256']:raise ParityError('parity_digest')
    return {'pass':True,'parity_id':meta['parity_id'],'parity_sha256':meta['parity_sha256'],'parity_size_bytes':len(b),'chunk_size_bytes':meta['chunk_size_bytes']}

def recover_one(root:Path, missing_shard_id:str, available:dict[str,bytes]|None=None, parity_bytes:bytes|None=None, meta:dict[str,Any]|None=None, gen:dict[str,Any]|None=None)->bytes:
    meta=meta or load(root/PARITY_REL);gen=gen or load(root/GEN_REL);verify_parity(root,meta,parity_bytes,gen)
    ids=[s['shard_id'] for s in gen['shards']]
    if missing_shard_id not in ids:raise ParityError('unknown_missing_shard')
    available=available or {sid:(root/next(s['pack_path'] for s in gen['shards'] if s['shard_id']==sid)).read_bytes() for sid in ids if sid!=missing_shard_id}
    missing=[sid for sid in ids if sid not in available]
    if missing!=[missing_shard_id]:raise ParityError('exactly_one_missing_required')
    pbytes=parity_bytes if parity_bytes is not None else (root/meta['parity_path']).read_bytes();out=bytearray(pbytes)
    for sid in ids:
        if sid==missing_shard_id:continue
        data=available[sid]
        expected=meta['original_lengths_bytes'][sid]
        if len(data)!=expected:raise ParityError('available_shard_wrong_length')
        if sha_bytes(data)!=meta['expected_shard_sha256'][sid]:raise ParityError('available_shard_digest')
        for base in range(0,len(out),CHUNK_SIZE):
            chunk=data[base:base+CHUNK_SIZE]
            for i,v in enumerate(chunk):out[base+i]^=v
    want_len=meta['original_lengths_bytes'][missing_shard_id];recovered=bytes(out[:want_len])
    if len(recovered)!=want_len:raise ParityError('recovered_length')
    if sha_bytes(recovered)!=meta['expected_shard_sha256'][missing_shard_id]:raise ParityError('reconstructed_byte_mismatch')
    # Verify full shard semantics, digest range, local index, and super-index membership before admission.
    target=next(s for s in gen['shards'] if s['shard_id']==missing_shard_id)
    idx=load(root/target['index_path']);shards.verify_shard_index(root,idx,recovered)
    sb={missing_shard_id:recovered};shards.verify_super(root,shard_bytes=sb)
    return recovered

def generation_checkpoint(gen:dict[str,Any])->dict[str,Any]:
    leaves=[]
    for s in gen['shards']:
        leaves.append(sha_bytes(canonical_bytes({'kind':'shard','id':s['shard_id'],'pack_sha256':s['pack_sha256'],'size_bytes':s['pack_size_bytes'],'index_digest_sha256':s['index_digest_sha256']})))
    leaves += [sha_bytes(canonical_bytes({'kind':'super-index','digest':gen['super_index_digest_sha256']})),sha_bytes(canonical_bytes({'kind':'catalog','digest':gen['catalog_digest_sha256'],'object_set':gen['object_set_digest_sha256']})),sha_bytes(canonical_bytes({'kind':'retention','digest':gen['retention_file_sha256']})),sha_bytes(canonical_bytes({'kind':'protected-anchors','digest':gen['protected_anchor_set_digest_sha256']}))]
    return {'leaf_count':len(leaves),'leaf_digests_sha256':leaves,'merkle_root_sha256':merkle_root(leaves)}
def merkle_root(leaves:list[str])->str:
    if not leaves:return sha_bytes(b'')
    layer=[bytes.fromhex(x) for x in leaves]
    while len(layer)>1:
        if len(layer)%2:layer.append(layer[-1])
        layer=[hashlib.sha256(b'node\0'+layer[i]+layer[i+1]).digest() for i in range(0,len(layer),2)]
    return layer[0].hex()

def build_checkpoints(root:Path=ROOT)->dict[str,Any]:
    gen=load(root/GEN_REL);meta=load(root/PARITY_REL);verify_generation(root,gen);verify_parity(root,meta,gen=gen)
    gc=generation_checkpoint(gen);pl=[sha_bytes(canonical_bytes({'kind':'parity-metadata','digest':meta['parity_metadata_digest_sha256']})),sha_bytes(canonical_bytes({'kind':'parity-bytes','digest':meta['parity_sha256'],'size':meta['parity_size_bytes']})),sha_bytes(canonical_bytes({'kind':'generation','digest':gen['generation_digest_sha256']}))];pc={'leaf_count':len(pl),'leaf_digests_sha256':pl,'merkle_root_sha256':merkle_root(pl)}
    core={'schema':'memory-patch-shard-generation-checkpoints/1','generation_id':gen['generation_id'],'parity_id':meta['parity_id'],'generation_checkpoint':gc,'parity_checkpoint':pc}
    dg=sha_bytes(canonical_bytes(core));out={**core,'checkpoint_digest_sha256':dg,'checkpoint_id':'shard-generation-checkpoint-'+dg[:20]};write_json(root/CHECKPOINT_REL,out);return out

def verify_checkpoints(root:Path=ROOT,cp:dict[str,Any]|None=None)->dict[str,Any]:
    cp=copy.deepcopy(cp or load(root/CHECKPOINT_REL));gen=load(root/GEN_REL);meta=load(root/PARITY_REL)
    core={k:cp[k] for k in ('schema','generation_id','parity_id','generation_checkpoint','parity_checkpoint')};dg=sha_bytes(canonical_bytes(core))
    if cp.get('checkpoint_digest_sha256')!=dg or cp.get('checkpoint_id')!='shard-generation-checkpoint-'+dg[:20]:raise ParityError('generation_checkpoint_substitution')
    if cp['generation_id']!=gen['generation_id'] or cp['parity_id']!=meta['parity_id']:raise ParityError('generation_checkpoint_binding')
    if cp['generation_checkpoint']!=generation_checkpoint(gen):raise ParityError('generation_checkpoint_root')
    pl=[sha_bytes(canonical_bytes({'kind':'parity-metadata','digest':meta['parity_metadata_digest_sha256']})),sha_bytes(canonical_bytes({'kind':'parity-bytes','digest':meta['parity_sha256'],'size':meta['parity_size_bytes']})),sha_bytes(canonical_bytes({'kind':'generation','digest':gen['generation_digest_sha256']}))]
    if cp['parity_checkpoint']!={'leaf_count':len(pl),'leaf_digests_sha256':pl,'merkle_root_sha256':merkle_root(pl)}:raise ParityError('parity_checkpoint_root')
    return {'pass':True,'checkpoint_id':cp['checkpoint_id'],'generation_root_sha256':cp['generation_checkpoint']['merkle_root_sha256'],'parity_root_sha256':cp['parity_checkpoint']['merkle_root_sha256']}

def activation_digest(j:dict[str,Any])->str:
    return sha_bytes(canonical_bytes({k:v for k,v in j.items() if k not in ('activation_digest_sha256','activation_id')}))

def activate(root:Path, state_dir:Path, gen:dict[str,Any]|None=None, interrupt_at:str|None=None)->dict[str,Any]:
    gen=gen or load(root/GEN_REL)
    if state_dir.exists() and any(state_dir.iterdir()):raise ParityError('activation_state_not_fresh')
    state_dir.mkdir(parents=True,exist_ok=True)
    journal={'schema':'memory-patch-shard-activation/1','requested_generation_id':gen['generation_id'],'predecessor_generation_id':gen['predecessor_generation_id'],'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/'activation-journal.json',journal)
        if interrupt_at==name:raise ParityError('simulated_activation_interruption:'+name)
    try:
        step('prepare',{'generation_digest_sha256':gen['generation_digest_sha256']})
        verify_generation(root,gen);verify_parity(root,gen=gen);verify_checkpoints(root)
        step('verify',{'verified':True,'object_count':gen['object_count'],'shard_count':gen['shard_count']})
        active={'schema':'memory-patch-active-generation/1','generation_id':gen['generation_id'],'generation_digest_sha256':gen['generation_digest_sha256'],'super_index_digest_sha256':gen['super_index_digest_sha256'],'object_set_digest_sha256':gen['object_set_digest_sha256'],'retention_file_sha256':gen['retention_file_sha256'],'protected_anchor_set_digest_sha256':gen['protected_anchor_set_digest_sha256']};ad=sha_bytes(canonical_bytes(active));active['active_state_digest_sha256']=ad
        write_json(state_dir/'active-generation.json',active);step('commit',{'active_state_digest_sha256':ad});journal['status']='committed'
    except ParityError as e:
        if str(e).startswith('simulated_activation_interruption'):
            journal['status']='interrupted';write_json(state_dir/'activation-journal.json',journal);raise
        journal['states'].append({'state':'rollback','reason':str(e)});journal['status']='rolled_back';(state_dir/'active-generation.json').unlink(missing_ok=True)
    dg=activation_digest(journal);journal['activation_digest_sha256']=dg;journal['activation_id']='shard-activation-'+dg[:20];write_json(state_dir/'activation-journal.json',journal);return journal

def rollback_to(root:Path,state_dir:Path,target_generation_id:str)->dict[str,Any]:
    gen=load(root/GEN_REL)
    if target_generation_id!=gen.get('predecessor_generation_id') or target_generation_id is None:raise ParityError('undeclared_predecessor_rollback')
    return {'pass':True}

def build_activation_results(root:Path=ROOT)->dict[str,Any]:
    gen=load(root/GEN_REL);fixtures=[]
    with tempfile.TemporaryDirectory() as td:
        j=activate(root,Path(td)/'ok',gen);fixtures.append({'fixture':'clean_commit','pass':j['status']=='committed','status':j['status'],'activation_digest_sha256':j['activation_digest_sha256']})
    for st in ('prepare','verify'):
        with tempfile.TemporaryDirectory() as td:
            d=Path(td)/st
            try:activate(root,d,gen,st);ok=False
            except ParityError as e:ok=str(e)==f'simulated_activation_interruption:{st}' and not (d/'active-generation.json').exists()
            fixtures.append({'fixture':'interrupt_'+st,'pass':ok,'status':'interrupted'})
    bad=copy.deepcopy(gen);bad['super_index_digest_sha256']='0'*64; core={k:bad[k] for k in ('schema','generation_ordinal','generation_label','predecessor_generation_id','authority','super_index_path','super_index_id','super_index_digest_sha256','super_index_file_sha256','checkpoint_path','checkpoint_file_sha256','checkpoint_root_sha256','catalog_id','catalog_digest_sha256','object_count','object_set_digest_sha256','retention_file_sha256','protected_anchor_set_digest_sha256','shard_count','shards')};dg=sha_bytes(canonical_bytes(core));bad['generation_digest_sha256']=dg;bad['generation_id']='shard-generation-'+dg[:20]
    try:
        with tempfile.TemporaryDirectory() as td:j=activate(root,Path(td)/'bad',bad);ok=j['status']=='rolled_back'
    except Exception:ok=False
    fixtures.append({'fixture':'wrong_super_index_rolls_back','pass':ok,'status':'rolled_back'})
    try:rollback_to(root,Path('.'),'undeclared-generation');ok=False
    except ParityError as e:ok=str(e)=='undeclared_predecessor_rollback'
    fixtures.append({'fixture':'undeclared_predecessor_rejected','pass':ok,'status':'rejected'})
    core={'schema':'memory-patch-shard-activation-results/1','generation_id':gen['generation_id'],'fixtures':fixtures,'committed_generation_preserves_object_set':verify_generation(root,gen)['object_count']==129,'automatic_delete_count':0,'protected_anchor_mutation_count':0};dg=sha_bytes(canonical_bytes(core));out={**core,'results_digest_sha256':dg,'results_id':'shard-activation-results-'+dg[:20],'pass':all(x['pass'] for x in fixtures)};write_json(root/ACTIVATION_REL,out);return out

def build_recovery_results(root:Path=ROOT)->dict[str,Any]:
    gen=load(root/GEN_REL);meta=load(root/PARITY_REL);fixtures=[]
    for s in gen['shards']:
        b=recover_one(root,s['shard_id']);fixtures.append({'fixture':'recover_'+s['shard_id'],'pass':sha_bytes(b)==s['pack_sha256'],'shard_id':s['shard_id'],'size_bytes':len(b),'sha256':sha_bytes(b)})
    # negative two missing
    ids=[s['shard_id'] for s in gen['shards']];avail={s['shard_id']:(root/s['pack_path']).read_bytes() for s in gen['shards'][2:]}
    try:recover_one(root,ids[0],avail);ok=False
    except ParityError as e:ok=str(e)=='exactly_one_missing_required'
    fixtures.append({'fixture':'two_missing_rejected','pass':ok})
    # mutated parity
    pb=bytearray((root/meta['parity_path']).read_bytes());pb[len(pb)//2]^=1
    try:recover_one(root,ids[0],parity_bytes=bytes(pb));ok=False
    except ParityError as e:ok=str(e)=='parity_digest'
    fixtures.append({'fixture':'mutated_parity_rejected','pass':ok})
    # truncated parity
    try:recover_one(root,ids[0],parity_bytes=(root/meta['parity_path']).read_bytes()[:-1]);ok=False
    except ParityError as e:ok=str(e)=='parity_truncated'
    fixtures.append({'fixture':'truncated_parity_rejected','pass':ok})
    core={'schema':'memory-patch-shard-parity-recovery-results/1','generation_id':gen['generation_id'],'parity_id':meta['parity_id'],'fixtures':fixtures,'recoverable_missing_shards':1,'unrecoverable_missing_shards':2,'automatic_delete_count':0,'protected_anchor_mutation_count':0};dg=sha_bytes(canonical_bytes(core));out={**core,'results_digest_sha256':dg,'results_id':'shard-parity-recovery-'+dg[:20],'pass':all(x['pass'] for x in fixtures)};write_json(root/RECOVERY_REL,out);return out

def build_all(root:Path=ROOT)->dict[str,Any]:
    g=build_generation(root);p=build_parity(root,g);c=build_checkpoints(root);a=build_activation_results(root);r=build_recovery_results(root)
    return {'generation_id':g['generation_id'],'generation_digest_sha256':g['generation_digest_sha256'],'parity_id':p['parity_id'],'parity_sha256':p['parity_sha256'],'parity_size_bytes':p['parity_size_bytes'],'chunk_size_bytes':p['chunk_size_bytes'],'checkpoint_id':c['checkpoint_id'],'activation_results_id':a['results_id'],'recovery_results_id':r['results_id'],'pass':a['pass'] and r['pass']}

if __name__=='__main__':
    ap=argparse.ArgumentParser();ap.add_argument('--root',type=Path,default=ROOT);ap.add_argument('--build',action='store_true');ns=ap.parse_args();print(json.dumps(build_all(ns.root) if ns.build else {'generation':verify_generation(ns.root),'parity':verify_parity(ns.root),'checkpoints':verify_checkpoints(ns.root)},indent=2))
