import hashlib,json
def canon(x): return json.dumps(x,sort_keys=True,separators=(',',':'))
def digest(r): return hashlib.sha256(canon({k:v for k,v in r.items() if k!='hash'}).encode()).hexdigest()
def make_packet(fields):
 r={'version':1,'provenance':{'source':'session-42'},'fields':fields,'next_steps':['run consumer replay']};r['hash']=digest(r);return [r]
def validate(p):
 g=[]
 for r in p:
  if r.get('hash')!=digest(r):g+=['HASH_SELF']
  if not r.get('provenance'):g+=['PROVENANCE_REQUIRED']
  for n,s in r.get('fields',{}).items():
   if s.get('kind')=='FACT' and s.get('source')=='INHERITED':g+=['INHERITED_NOT_FACT:'+n]
   if s.get('conditional') is True and not s.get('condition'):g+=['FIELD_CONDITIONALITY_REQUIRED:'+n]
 return sorted(set(g))
def consume(p):
 g=validate(p)
 if g:return {'status':'REFUSED','gates':g}
 r=p[-1];return {'status':'RESUMED','gates':[],'facts':{k:v['value'] for k,v in r['fields'].items() if v.get('kind')=='FACT'},'next_steps':r['next_steps']}
def run():
 c={'owner':{'kind':'FACT','source':'OBSERVED','value':'terry'}};q={'next_step':{'kind':'FACT','source':'OBSERVED','value':'invite trial','conditional':True,'condition':'adopter available'}};i={'context':{'kind':'FACT','source':'INHERITED','value':'unverified'}}
 cases={'valid':(make_packet(c),[],'RESUMED'),'declared_conditional':(make_packet(q),[],'RESUMED'),'undeclared_conditional':(make_packet({'next_step':{**q['next_step'],'condition':None}}),['FIELD_CONDITIONALITY_REQUIRED:next_step'],'REFUSED'),'inherited_fact':(make_packet(i),['INHERITED_NOT_FACT:context'],'REFUSED'),'missing_provenance':(make_packet(c),['PROVENANCE_REQUIRED'],'REFUSED')}
 p=cases['missing_provenance'][0][0];p.pop('provenance');p['hash']=digest(p);out={}
 for n,(packet,e,s) in cases.items():
  x=consume(packet);assert x['status']==s and x['gates']==e,(n,x);assert x==consume(json.loads(json.dumps(packet)));out[n]=x
 print(json.dumps({'ok':True,'contract':'continuity-resume-v1','cases':out},sort_keys=True))
if __name__=='__main__':run()
