tricu

An interpreted language for exploring Tree Calculus
Log | Files | Refs | README | LICENSE

io_v2_provenance.py (6030B)


      1 #!/usr/bin/env python3
      2 """Real v2 recipe capture, producer removal and fresh explicitly trusted replay.
      3 Supply a retained-built candidate sourceBuildGraph bundle; never change defaults.
      4 """
      5 import argparse
      6 import json
      7 import os
      8 from pathlib import Path
      9 import re
     10 import shutil
     11 import subprocess
     12 import time
     13 from provenance import digest, put, object_path, snapshot, RECEIPT, SOURCE
     14 from source_order import Runner
     15 from http_headers import number
     16 
     17 
     18 def main():
     19     parser=argparse.ArgumentParser(description=__doc__)
     20     parser.add_argument('tricu',type=Path)
     21     parser.add_argument('--compiler-bundle',type=Path,required=True)
     22     parser.add_argument('--directory',type=Path,required=True)
     23     args=parser.parse_args()
     24     root=args.directory.resolve(); root.mkdir(parents=True,exist_ok=False)
     25     producer=root/'producer'; producer.mkdir()
     26     runner=Runner(args.tricu,producer)
     27     compiler=runner.imported(args.compiler_bundle.resolve(),'compiler')
     28     workspace=producer/'workspace'; workspace.mkdir()
     29     (workspace/'tricu.workspace').write_text('tricu.workspace.v1\n')
     30     (workspace/'dependency.tri').write_text('!export first\nfirst [x | _] = x\nfirst _ = 0\n')
     31     (workspace/'main.tri').write_text('!import dependency as D\n!export main\n-- raw Unicode: λ 😀 é\nmain = D.first [42 7]\n')
     32     runner.run('compile',['compile','--file',workspace/'main.tri','--toolchain',compiler,
     33         '--toolchain-io-version','2','--tool-store',runner.store,'--store',runner.store,
     34         '--output',producer/'program.arboricx'])
     35     records=[]; checks=[]
     36     def run(label,command,success=True):
     37         started=time.monotonic()
     38         result=subprocess.run([str(args.tricu.resolve()),*map(str,command),'+RTS','-N1','-M8192m','-T','-s','-RTS'],
     39             cwd=root,env=dict(os.environ,tricu_datadir=str(root/'NO-PACKAGED-COMPILER')),
     40             capture_output=True,text=True,timeout=180)
     41         records.append(dict(label=label,command=list(map(str,command)),seconds=time.monotonic()-started,
     42             code=result.returncode,stdout=result.stdout,stderr=result.stderr))
     43         (root/(label+'.log')).write_text(result.stdout+result.stderr)
     44         assert (result.returncode==0)==success,(label,result.stdout,result.stderr)
     45         return result.stdout+result.stderr
     46     pack=root/'pack'
     47     output=run('capture',['provenance','capture','--file',workspace/'main.tri','--store',runner.store,
     48         '--tool-store',runner.store,'--toolchain',compiler,'--toolchain-io-version','2','--output',pack])
     49     pin=re.search(r'Retained build inputs: ([0-9a-f]{64})',output)[1]
     50     data=(pack/'build-inputs').read_bytes()
     51     assert pin==digest('arboricx.build-inputs.v2',data)
     52     assert b'compiler-io\t2\n' in data
     53     compilation_records=runner.records
     54     shutil.rmtree(producer)
     55     before=snapshot(pack)
     56     assert 'Compiler IO version 2' in run('verify',['provenance','verify','--pack',pack,'--pin',pin])
     57     for label,trust,version in [('unauthorized', '0'*64,'2'),('wrong-version',compiler,'1')]:
     58         run(label,['provenance','rebuild','--pack',pack,'--pin',pin,'--trust-compiler',trust,
     59             '--trust-io-version',version,'--output',root/label],False)
     60         assert not (root/label).exists()
     61     replay=root/'replayed'
     62     response=run('rebuild',['provenance','rebuild','--pack',pack,'--pin',pin,'--trust-compiler',compiler,
     63         '--trust-io-version','2','--output',replay])
     64     assert 'Reproduced native module and receipt exactly' in response
     65     assert snapshot(pack)==before
     66     checks.append('offline-exact-module-and-receipt')
     67     module=(replay/'application/cache/source-build/main').read_text().split('\t')[2]
     68     # Evaluate the replay's actual stored executable independently, not a copied
     69     # packaged output or an expected receipt from the producer.
     70     reader=Runner(args.tricu,root/'execution'); reader.root.mkdir()
     71     reader.store=replay/'application'
     72     assert reader.invoke('value',module,'main',[])==number(42)
     73     checks.append('independent-replayed-value')
     74     # Retain valid but false producer claims and a matching poisoned cache. Verify
     75     # authenticates bytes only; fresh replay must detect the executable mismatch.
     76     forged=root/'forged'; shutil.copytree(pack,forged)
     77     store=forged/'store'
     78     rows=data.decode().splitlines()
     79     old_receipt=next(row.split('\t')[1] for row in rows if row.startswith('receipt\t'))
     80     receipt_rows=object_path(store,old_receipt).read_text().splitlines()
     81     src=put(store,SOURCE,b'!export main\nmain = 99\n')
     82     receipt_bytes=('\n'.join([RECEIPT,'source\t'+src,*receipt_rows[2:5]])+'\n').encode()
     83     receipt=put(store,RECEIPT,receipt_bytes)
     84     descriptor=('\n'.join([rows[0],'entry\tmain','receipt\t'+receipt,rows[3],rows[4],
     85         'unit\tmain\t'+receipt])+'\n').encode()
     86     (forged/'build-inputs').write_bytes(descriptor)
     87     forged_pin=digest('arboricx.build-inputs.v2',descriptor)
     88     key_bytes=('\n'.join(['arboricx.source-build-key.v1','source\t'+src,*receipt_rows[2:4]])+'\n').encode()
     89     key=digest('arboricx.source-build-key.v1',key_bytes)
     90     cache=store/'cache/source-build/main'; cache.parent.mkdir(parents=True)
     91     cache.write_text('\t'.join(['arboricx.source-build-cache.v1',key,receipt_rows[4].split('\t')[1],receipt]))
     92     run('verify-false-claim',['provenance','verify','--pack',forged,'--pin',forged_pin])
     93     response=run('reject-false-claim',['provenance','rebuild','--pack',forged,'--pin',forged_pin,
     94         '--trust-compiler',compiler,'--trust-io-version','2','--output',root/'mismatch'],False)
     95     assert 'Rebuild mismatch' in response and 'tree different' in response
     96     checks.append('authenticated-false-claim-and-cache-poison-rejected')
     97     (root/'report.json').write_text(json.dumps(dict(checks=checks,compiler=compiler,io_version=2,
     98         producer_removed=True,pack_unchanged=True,records=records,compilation=compilation_records),indent=2)+'\n')
     99     print('PASS v2 offline replay, explicit authority, exact receipts, independent output and false-claim/cache controls')
    100 
    101 
    102 if __name__=='__main__': main()