85 lines
8.3 KiB
Python
85 lines
8.3 KiB
Python
"""Full-Project checkpoint comparison with one preregistered fresh placement binding."""
|
|
from pathlib import Path
|
|
import argparse,copy,datetime,json,math,subprocess,sys,uuid
|
|
sys.dont_write_bytecode=True
|
|
from stage import HERE,child_env,copy_new,read,record,verify,write,canonical
|
|
|
|
def identities(value):
|
|
found=set()
|
|
def walk(v):
|
|
if isinstance(v,dict):
|
|
for k,x in v.items():walk(k);walk(x)
|
|
elif isinstance(v,list):
|
|
for x in v:walk(x)
|
|
elif isinstance(v,str):
|
|
try:found.add(str(uuid.UUID(v)))
|
|
except ValueError:pass
|
|
walk(value);return found
|
|
|
|
def main():
|
|
p=argparse.ArgumentParser();p.add_argument('--expected',required=True,choices=['B','P']);p.add_argument('--output-prefix',required=True,type=Path);p.add_argument('--actual',type=Path);p.add_argument('--calibration',type=Path);a=p.parse_args()
|
|
staging=read(HERE/'staging-record.json');verify(staging['manifest']);manifest=read(HERE/'input-manifest.json')
|
|
if staging['source_commit']!=manifest['source_commit']:raise RuntimeError('Stage pin mismatch')
|
|
for r in manifest['immutable_files']:verify(r)
|
|
catalog_record=record(HERE/'expected/catalog.json');catalog=read(catalog_record['path']);verify(catalog['contract']);contract=read(catalog['contract']['path'])
|
|
for r in catalog['checkpoints'].values():verify(r)
|
|
baseline=read(catalog['checkpoints']['B']['path']);template=read(catalog['checkpoints']['P-template']['path']);reserved=catalog['reserved_node_uuid'];diagram=contract['diagram'];binding_path=HERE/'fresh-node-binding.json'
|
|
binding_record=record(binding_path) if binding_path.exists() else None;binding=read(binding_path) if binding_record else None
|
|
if binding:
|
|
for field,r in [('manifest',staging['manifest']),('catalog',catalog_record),('evaluator',manifest['evaluator'])]:
|
|
if any(binding[field][k]!=r[k] for k in ('bytes','sha256')):raise RuntimeError('Binding input changed')
|
|
verify(binding['first_checkpoint_receipt']);verify(binding['calibration'])
|
|
first=read(binding['first_checkpoint_receipt']['path'])
|
|
if first['failure'] or not first['full_project_exact'] or not first['inputs_unchanged'] or first['expected']!='P' or binding['source_commit']!=manifest['source_commit']:raise RuntimeError('Invalid first binding receipt')
|
|
if any(binding[key]!=first['proposed_binding'][key] for key in ('node_uuid','position','signed_error')):raise RuntimeError('Binding no longer matches its first accepted checkpoint')
|
|
prefix=a.output_prefix.resolve();outputs={suffix:Path(str(prefix)+suffix) for suffix in ('.arcadia','.project.json','.stdout.log','.stderr.log','.receipt.json')}
|
|
if any(path.exists() for path in outputs.values()):raise RuntimeError('Fresh checkpoint prefix required')
|
|
actual_path=(a.actual or Path(manifest['saved_working_path'])).resolve()
|
|
if actual_path.suffix.lower()!='.arcadia':raise RuntimeError('Save As the declared normalized source before checks')
|
|
actual=record(actual_path);prefix.parent.mkdir(parents=True,exist_ok=True);copy_new(actual_path,outputs['.arcadia'])
|
|
started=datetime.datetime.now(datetime.timezone.utc).isoformat();exit_code=None;failure=None;exact=False;unchanged=False;candidate_binding=None;calibration=None
|
|
try:
|
|
with outputs['.stdout.log'].open('xb') as stdout,outputs['.stderr.log'].open('xb') as stderr:
|
|
proc=subprocess.run([manifest['evaluator']['path'],str(outputs['.arcadia']),str(outputs['.project.json'])],cwd=HERE/'unrelated-cwd',env=child_env(),stdout=stdout,stderr=stderr)
|
|
exit_code=proc.returncode
|
|
if exit_code or outputs['.stderr.log'].stat().st_size:raise RuntimeError('Evaluator failed or emitted stderr')
|
|
observed=read(outputs['.project.json']);expected=baseline
|
|
if a.expected=='P':
|
|
if binding:
|
|
node_id=binding['node_uuid'];position=binding['position']
|
|
else:
|
|
if a.calibration is None:raise RuntimeError('First P requires the pre-input calibration record')
|
|
calibration=record(a.calibration);cal=read(a.calibration)
|
|
if cal['target']!=contract['position_rule']['target'] or cal['maximum_absolute_error_per_axis_world_units']!=1.0:raise RuntimeError('Calibration contract differs')
|
|
stamp=datetime.datetime.fromisoformat(cal['declared_utc'].replace('Z','+00:00'))
|
|
if stamp.tzinfo is None or stamp>=datetime.datetime.fromisoformat(started):raise RuntimeError('Pre-input calibration timestamp required')
|
|
if not all(k in cal for k in ('canvas_mapping','physical_pixel_scale','chosen_integer_pixel','predicted_error_bound_world')):raise RuntimeError('Calibration evidence incomplete')
|
|
pixel=cal['chosen_integer_pixel'];bound=cal['predicted_error_bound_world']
|
|
if set(pixel)!={'x','y'} or any(type(v) is not int for v in pixel.values()) or set(bound)!={'x','y'} or any(type(v) not in (int,float) or not math.isfinite(v) or not 0<=v<=1 for v in bound.values()):raise RuntimeError('Invalid predeclared targeting evidence')
|
|
nodes=observed['diagrams'][diagram]['nodes'];new_ids=set(nodes)-set(baseline['diagrams'][diagram]['nodes'])
|
|
if len(new_ids)!=1:raise RuntimeError('Require exactly one new node identity in target view')
|
|
node_id=next(iter(new_ids));u=uuid.UUID(node_id)
|
|
if str(u)!=node_id or u.version!=4 or u.variant!=uuid.RFC_4122 or node_id in identities(baseline):raise RuntimeError('New identity must be canonical fresh UUIDv4')
|
|
if nodes[node_id]['id']!=node_id:raise RuntimeError('Node key/id mismatch')
|
|
position=nodes[node_id]['position'];target=contract['position_rule']['target']
|
|
if set(position)!={'x','y'} or any(type(v) not in (int,float) or not math.isfinite(v) or abs(v-target[axis])>1.0 for axis,v in position.items()):raise RuntimeError('Placement outside fixed prelaunch precision box')
|
|
expected=copy.deepcopy(template);node=expected['diagrams'][diagram]['nodes'].pop(reserved);node['id']=node_id;node['position']=copy.deepcopy(position);expected['diagrams'][diagram]['nodes'][node_id]=node
|
|
if not binding:candidate_binding={'node_uuid':node_id,'position':position,'signed_error':{axis:position[axis]-contract['position_rule']['target'][axis] for axis in ('x','y')}}
|
|
exact=canonical(observed)==canonical(expected)
|
|
if not exact:raise RuntimeError('Entire Project differs from frozen B/P contract')
|
|
verify(actual)
|
|
for r in manifest['immutable_files']:verify(r)
|
|
verify(staging['manifest'])
|
|
if binding_record:verify(binding_record)
|
|
if calibration:verify(calibration)
|
|
unchanged=True
|
|
except Exception as e:failure=str(e)
|
|
artifacts=[record(path) for suffix,path in outputs.items() if suffix!='.receipt.json' and path.exists()]
|
|
receipt={'source_commit':manifest['source_commit'],'evaluator_source_commit':manifest['evaluator_source_commit'],'started_utc':started,'finished_utc':datetime.datetime.now(datetime.timezone.utc).isoformat(),'expected':a.expected,'catalog':catalog_record,'contract':catalog['contract'],'manifest':staging['manifest'],'evaluator':manifest['evaluator'],'input':actual,'exit_code':exit_code,'failure':failure,'inputs_unchanged':unchanged,'full_project_exact':exact,'artifacts':artifacts,'prior_binding':binding_record,'proposed_binding':candidate_binding if exact and unchanged else None,'calibration':calibration,'scope':'Complete saved Project; only first P may bind fresh UUID plus fixed-box position after full equality. Native input/freshness not proved here.'}
|
|
write(outputs['.receipt.json'],receipt)
|
|
if failure or not exact or not unchanged:raise SystemExit('Comparison failed; preserve outputs: '+str(failure))
|
|
if candidate_binding:
|
|
write(binding_path,{**candidate_binding,'source_commit':manifest['source_commit'],'manifest':staging['manifest'],'catalog':catalog_record,'baseline':catalog['checkpoints']['B'],'contract':catalog['contract'],'evaluator':manifest['evaluator'],'calibration':calibration,'first_checkpoint_receipt':record(outputs['.receipt.json']),'input':actual,'position_rule':contract['position_rule'],'scope':'Created only after full Project equality; subsequent P uses exact UUID and exact x/y without tolerance'})
|
|
print(a.expected+': complete Project exact; immutable inputs unchanged')
|
|
if __name__=='__main__':main()
|