Evidence

verify cycle.py

Download source fileOpen in research workspace
#!/usr/bin/env python3
"""Portable, independent replay of C1483--C1582.

Uses only files below this script's directory. Does not import research.py or
runner.py, edit source models, or amend the ordered journal. Decimal reversal
uses integer divmod on original durations. --require-100 closes final checks;
the default accepts an ordered completed prefix of the cycle.
"""
from __future__ import annotations

import argparse
from datetime import datetime
from fractions import Fraction
import hashlib
import json
from pathlib import Path
import subprocess
import sys

BASE=Path(__file__).resolve().parent
CHECKS=[]
CHECKED=set()
NOT_PRESENT=[]

def encode(value):
    if isinstance(value,Fraction):
        return value.numerator if value.denominator==1 else str(value)
    if isinstance(value,dict): return {str(k):encode(v) for k,v in value.items()}
    if isinstance(value,(list,tuple)): return [encode(v) for v in value]
    return value

def check(label,actual,expected=True):
    actual,expected=encode(actual),encode(expected)
    CHECKS.append({'name':label,'actual':actual,'expected':expected,'pass':actual==expected})

def read_json(path): return json.loads(path.read_text(encoding='utf-8'))
def sha(payload): return hashlib.sha256(payload).hexdigest()
def record_hash(record):
    core={k:v for k,v in record.items() if k!='sha256'}
    return sha(json.dumps(core,sort_keys=True,separators=(',',':'),ensure_ascii=False).encode())

def reverse_original(n):
    assert type(n) is int and n>0
    scale=1
    while n % 10 == 0:
        n//=10
        scale*=10
    result=0
    while n:
        n,digit=divmod(n,10)
        result=result*10+digit
    return result*scale

def rebuild(anchor,original_legs):
    out=[anchor]
    for duration in original_legs:
        out.append(out[-1]+reverse_original(duration))
    return out

def verify_model(name,expected,subset=False,optional=True):
    filename=name+'.json'
    path=BASE/'model'/filename
    if not path.exists():
        NOT_PRESENT.append(filename)
        if not optional: check('required model exists: '+filename,False)
        return
    actual=read_json(path)
    if subset: actual={key:actual.get(key) for key in expected}
    check('model/'+name,actual,expected)
    CHECKED.add(filename)

def verify_sources():
    binding_path=BASE/'model/source_bindings.json'
    bindings=read_json(binding_path)
    CHECKED.add('source_bindings.json')
    for name,binding in bindings.items():
        target=(BASE/'evidence'/name).resolve()
        check('source remains inside evidence: '+name,target.is_relative_to((BASE/'evidence').resolve()))
        check('source exists: '+name,target.is_file())
        if target.is_file():
            data=target.read_bytes()
            check('source bound bytes and SHA: '+name,[len(data),sha(data)],
                  [binding['bytes'],binding['sha256']])
    return bindings

def verify_journal(require100):
    parent=read_json(BASE/'evidence/C1482_parent_record.json')
    journal_bytes=(BASE/'journal.json').read_bytes()
    journal=json.loads(journal_bytes)
    check('parent is completed C1482',parent.get('step'),1482)
    check('parent canonical record hash',record_hash(parent),parent.get('sha256'))
    check('journal is ordered list',isinstance(journal,list))
    check('journal count bounds',0<len(journal)<=100)
    if require100:
        check('one hundred completed actions',len(journal),100)
        check('no pending action at close',not (BASE/'pending.json').exists())
    previous=parent['sha256']
    prior_closed=None
    required={'step','title','question','sources','opened_utc','predecessor_sha256',
              'results','finding','reassessment','checks','closed_utc','sha256'}
    for expected_step,record in enumerate(journal,1483):
        prefix='C'+str(expected_step)
        check(prefix+' complete fields',required.issubset(record))
        check(prefix+' sequence',record.get('step'),expected_step)
        check(prefix+' predecessor',record.get('predecessor_sha256'),previous)
        check(prefix+' canonical record hash',record_hash(record),record.get('sha256'))
        check(prefix+' explicit reassessment',bool(str(record.get('reassessment','')).strip()))
        declared=record.get('checks',{})
        check(prefix+' declared check results',all(v is True for v in declared.values()))
        opened=datetime.fromisoformat(record['opened_utc'])
        closed=datetime.fromisoformat(record['closed_utc'])
        check(prefix+' closure after opening',closed>=opened)
        if prior_closed is not None: check(prefix+' order in time',opened>=prior_closed)
        previous,prior_closed=record['sha256'],closed
        result=record.get('results',{})
        if isinstance(result,dict) and 'review_file' in result and 'review_sha256' in result:
            review=(BASE/result['review_file']).resolve()
            check(prefix+' review is portable',review.is_relative_to(BASE))
            check(prefix+' review exists',review.is_file())
            if review.is_file(): check(prefix+' review content hash',sha(review.read_bytes()),result['review_sha256'])
        if isinstance(result,dict) and 'strategy_sha256' in result:
            check(prefix+' bound Strategy edition',sha((BASE/'evidence/Strategy.md').read_bytes()),result['strategy_sha256'])
    if len(journal)>=50:
        check('C1532 Strategy checkpoint present',journal[49]['step'],1532)
        check('C1532 Strategy review recorded','review_sha256' in journal[49].get('results',{}))
    if require100:
        check('C1582 final step',journal[-1]['step'],1582)
        check('C1582 Strategy review recorded','review_sha256' in journal[-1].get('results',{}))
    return {'actions':len(journal),'first':journal[0]['step'],'last':journal[-1]['step'],
            'parent_record_sha256':parent['sha256'],'last_record_sha256':previous,
            'journal_file_sha256':sha(journal_bytes)}

def verify_cumulative(require100):
    # Declared source inputs independently transcribed from the frozen material.
    nodes={
        'MT':{'C':14006,'F':4836,'S':5436,'N':6386,'A':1406},
        'LXX':{'C':14896,'F':5746,'S':6346,'N':7296,'A':1406},
        'SP':{'C':13396,'F':4716,'S':5316,'N':6266,'A':1406},
    }
    verify_model('cumulative_source_nodes',nodes,optional=not require100)
    names=['Adam','Seth','Enosh','Kenan','Mahalalel','Jared','Enoch','Methuselah','Lamech']
    actual={'MT':[930,912,905,910,895,962,365,969,777],
            'LXX':[930,912,905,910,895,962,365,969,753],
            'SP':[930,912,905,910,895,847,365,720,653]}
    rounded={t:[5*((n+2)//5) for n in rows] for t,rows in actual.items()}
    sums={t:sum(rows) for t,rows in rounded.items()}
    residuals={t:[r-a for r,a in zip(rounded[t],actual[t])] for t in actual}
    verify_model('prefix_source_rows',{'names':names,'actual':actual,'rounded':rounded,
                                     'sums':sums,'residuals':residuals},optional=not require100)
    spec={'F':['F'],'S':['S'],'N':['N'],'FS':['F','S'],'FN':['F','N'],
          'SN':['S','N'],'FSN':['F','S','N']}
    verify_model('partition_spec',{'order':['A','F','S','N','C'],'partitions':spec,
                                  'head_and_anchor':'alwaysretained',
                                  'reversal':'eachoriginallegonce',
                                  'empty_selection':'notpartofthisnamedspineextension'},optional=not require100)
    partitions={}
    for tradition,v in nodes.items():
        check(tradition+' retained serial Noah and Shem lives',[v['N']-v['S'],v['S']-v['F']],[950,600])
        check(tradition+' rounded prefix agrees with selected head',v['C']-v['N'],sums[tradition]-(5 if tradition=='SP' else 0))
        paths={}
        for name,interior in spec.items():
            source=[v['A']]+[v[k] for k in interior]+[v['C']]
            legs=[b-a for a,b in zip(source,source[1:])]
            inv=[reverse_original(n) for n in legs]
            path1406=rebuild(1406,legs)
            path6=rebuild(6,[1400]+legs)
            paths[name]={'source_nodes':source,'source_legs':legs,'inverse_legs':inv,
                         'path1406':path1406,'path6':path6,'total':sum(inv)}
            check(tradition+'/'+name+' source total',[sum(legs),source[-1]-source[0]],[v['C']-1406]*2)
            check(tradition+'/'+name+' common stage translation',
                  [b-a for a,b in zip(path1406,path6[1:])],[2700]*len(path1406))
        verify_model('partitions_'+tradition,paths,optional=not require100)
        partitions[tradition]=paths
    lower={}
    for t,v in nodes.items():
        a=v['F']-1406
        lower[t]={'a':a,'merged_original':a+1550,'merged_inverse':reverse_original(a+1550),
                  'split_inverse':reverse_original(a)+reverse_original(1550),
                  'defect':reverse_original(a+1550)-reverse_original(a)-reverse_original(1550)}
        check(t+' named Noah lower merge',[lower[t]['defect'],partitions[t]['N']['total']-partitions[t]['FN']['total']],[0,0])
    verify_model('lower_1550_law',lower,optional=not require100)
    for t,p in partitions.items():
        check(t+' common explicit lifespan split',p['FN']['total']-p['FSN']['total'],4320)
    check('LXX refinement/completion join',partitions['LXX']['FSN']['path1406'][-1],partitions['LXX']['F']['path6'][-1])
    check('LXX joined coordinate',partitions['LXX']['FSN']['path1406'][-1],13636)
    check('SP source to completed refined head',nodes['SP']['C']-partitions['SP']['FSN']['path6'][-1],3600)
    check('MT inherited secondary Flood/Noah head',partitions['MT']['FN']['path1406'][-1],13016)
    check('LXX prefix reversal register contrast',reverse_original(7600)-reverse_original(7620),4030)
    split_loss=reverse_original(1550)-reverse_original(600)-reverse_original(950)
    verify_model('lifespan_split_law',{'original_combined':600+950,'original_separate':[600,950],
                 'inverse_combined':reverse_original(1550),
                 'inverse_separate':[reverse_original(600),reverse_original(950)],
                 'loss':split_loss},optional=not require100)
    upper={}
    refinement={}
    scope={}
    for t,v in nodes.items():
        prefix=v['C']-v['N']
        primary_upper=reverse_original(v['C']-v['F'])
        grouped_upper=reverse_original(1550)+reverse_original(prefix)
        gain=grouped_upper-primary_upper
        upper[t]={'prefix':prefix,'primary_upper':primary_upper,
                  'Noah_grouped_upper':grouped_upper,'gain':gain}
        refinement[t]={'upper_gain':gain,'split_loss':split_loss,
                       'predicted_change':gain-split_loss,
                       'observed_change':partitions[t]['FSN']['total']-partitions[t]['F']['total']}
        held=partitions[t]['FSN']['path1406'][-1]
        completed=partitions[t]['F']['path6'][-1]
        scope[t]={'refined_held':held,'primary_completed':completed,'signed_difference':held-completed}
    verify_model('upper_1550_law',upper,optional=not require100)
    verify_model('refinement_law',refinement,optional=not require100)
    verify_model('refinement_scope',scope,optional=not require100)
    lxx=partitions['LXX']
    verify_model('LXX_completion_join',{'refined_held_path':lxx['FSN']['path1406'],
                 'primary_completed_path':lxx['F']['path6'],
                 'refinement_gain':lxx['FSN']['total']-lxx['F']['total'],
                 'stage_gain':reverse_original(1400)-1400,
                 'common_head':lxx['FSN']['path1406'][-1]},optional=not require100)
    sp=partitions['SP']['FSN']
    source_branch=sp['source_nodes'][1:]
    inverse_branch=sp['path1406'][1:]
    verify_model('SP_refinement_scope',{'source_branch':source_branch,'inverse_branch':inverse_branch,
                 'node_shifts':[b-a for a,b in zip(source_branch,inverse_branch)],
                 'new_local_contractions':[950-reverse_original(950),7130-reverse_original(7130)]},
                 optional=not require100)
    def defect(u,v):
        return reverse_original(u+v)-reverse_original(u)-reverse_original(v)
    grammar={}
    for t,v in nodes.items():
        a,c=v['F']-1406,v['C']-v['N']
        total=sum(reverse_original(n) for n in (a,600,950,c))
        d_a600=defect(a,600)
        d_pair=defect(600,950)
        d_950c=defect(950,c)
        d_a1550=defect(a,1550)
        d_1550c=defect(1550,c)
        d_a600_950=defect(a+600,950)
        grammar[t]={'full_total':total,'D_a_600':d_a600,'D_600_950':d_pair,
                    'D_950_c':d_950c,'D_a_1550':d_a1550,'D_1550_c':d_1550c,
                    'D_a600_950':d_a600_950}
        generated={'F':total+d_pair+d_1550c,
                   'S':total+d_a600+d_950c,
                   'N':total+d_pair+d_a1550,
                   'FS':total+d_950c,
                   'FN':total+d_pair,
                   'SN':total+d_a600,
                   'FSN':total}
        check(t+' all seven totals regenerated from merging defects',generated,
              {name:p['total'] for name,p in partitions[t].items()})
        check(t+' lower merge cocycle',d_a600+d_a600_950,d_pair+d_a1550)
        check(t+' upper merge cocycle',d_pair+d_1550c,d_950c+defect(600,950+c))
        check(t+' cocycle alternative Noah total',total+d_a600+d_a600_950,partitions[t]['N']['total'])
    verify_model('merge_defect_grammar',grammar,optional=not require100)
    return nodes,partitions

def verify_4176_source_role():
    text29=(BASE/'evidence/File29.md').read_text()
    text17=(BASE/'evidence/File17.md').read_text()
    check('File29 appoints4176 Year6+60 state','`4176 BC` Year-6 +60 / Real state' in text29)
    check('File29 distinguishes4116 Year6 and4114 endpoint',
          '`4116 BC` Year-6 state' in text29 and '`4114 BC` endpoint state' in text29)
    check('File17 appoints4176 MT+275 Year6 state',
          '`4176 BC`, Year 6, MT `+275`' in text17)
    check('File17 names3901 Year6 variant base','4176n BC → 3901n BC = 275' in text17)
    check('Year6 variant descriptions agree',[4116+60,3901+215+60,3901+275],[4176]*3)
    check('Year6 source pair is translated SP pair',[2736+1440,6+1440],[4176,1446])
    check('Year6 source interval retains2730',4176-1446,2736-6)

def verify_babylonian():
    script=BASE/'agents/verify_babylonian.py'
    run=subprocess.run([sys.executable,str(script)],cwd=BASE,capture_output=True,text=True)
    check('Babylonian verifier exits successfully',run.returncode,0)
    result_path=BASE/'agents/babylonian_root_verification.json'
    if not result_path.is_file():
        check('Babylonian verification result exists',False)
        return {}
    child=read_json(result_path)
    check('Babylonian arithmetic status',child.get('status'),'PASS')
    for item in child.get('checks',[]):
        CHECKS.append({**item,'name':'Babylonian/'+item['name']})
    CHECKED.update(child.get('models_checked',[]))
    return {k:child.get(k) for k in ('status','grouped_check_count','models_checked','additional_model_files_not_checked')}

def main():
    parser=argparse.ArgumentParser(description=__doc__)
    parser.add_argument('--require-100',action='store_true',help='Require C1483 through C1582 and no pending action')
    parser.add_argument('--output',type=Path,default=BASE/'verification.json')
    args=parser.parse_args()
    journal_info={}
    child={}
    try:
        verify_sources()
        journal_info=verify_journal(args.require_100)
        nodes,partitions=verify_cumulative(args.require_100)
        verify_4176_source_role()
        child=verify_babylonian()
    except Exception as error:
        check('verification execution',type(error).__name__+': '+str(error),'successful replay')
    # These models record research interpretation, not additional numeric claims.
    narrative={'babylonian_kernel.json','claim_ledger_first50.json',
               'claim_ledger_second50.json','final_kernel.json'}
    present={p.name for p in (BASE/'model').glob('*.json')}
    for name in sorted(present & narrative):
        value=read_json(BASE/'model'/name)
        check('narrative model is nonempty object: '+name,isinstance(value,dict) and bool(value))
    unverified=sorted(present-CHECKED-narrative)
    check('all arithmetic model categories covered',unverified,[])
    failed=[r for r in CHECKS if not r['pass']]
    result={'status':'PASS' if not failed else 'FAIL','require_100':args.require_100,
            'journal':journal_info,'grouped_check_count':len(CHECKS),'failed_check_count':len(failed),
            'models_checked':sorted(CHECKED),'narrative_models':sorted(present & narrative),
            'unverified_models':unverified,'not_yet_present':NOT_PRESENT,
            'Babylonian_verifier':child,
            'method':'Portable source SHA bindings, canonical linked-journal hashes, independent integer divmod reversal and exact Fraction arithmetic. Counts are verification groups, not discoveries.',
            'checks':CHECKS}
    args.output.parent.mkdir(parents=True,exist_ok=True)
    args.output.write_text(json.dumps(result,indent=2,ensure_ascii=False)+'\n')
    print(json.dumps({k:result[k] for k in ('status','require_100','journal','grouped_check_count','failed_check_count','unverified_models')}))
    if failed:
        for failure in failed: print(json.dumps(failure,ensure_ascii=False))
    return 0 if not failed else 1

if __name__=='__main__': raise SystemExit(main())

Linked sources and evidence

Edition and provenance

verify_cycle.py

SHA-256 90bc46337462e8c807c381b0287f43edac177a5a58be7b26181ec6a8a1fc8fb6

C480–C1634/Research_Cycles/C1483_C1582/c1483_c1582/verify_cycle.py