#!/usr/bin/env python3 """Read-only artifact integrity gate. Success NEVER accepts original P08-02.""" import argparse import hashlib import importlib.util import json import math import os from pathlib import Path import re import sys sys.dont_write_bytecode = True HERE = Path(__file__).resolve().parent CONTRACT = 'docs/plans/original-completion-2026-10-02/execution/completion_marker_contracts.json' SPEC_SHA = '19f02bb7912a4d7354e565186f02366fc1d4dd698468d26142e8204fd14ebfb0' CONDITIONS = ('mixed_construction','occupancy_change','actual_actor_positions', 'combined_rescue_logistics_ev','no_overlap_orphan_loss','unsolvable_preservation') MEMBERS = ('world.bin','manifest.json','thumbnail.png','journal.log') CASES = ('off-positive','off-refusal','on-positive','on-refusal') LABELS = ['checkpoint'] + ['minute-%03d' % n for n in range(1,161)] + ['events'] def sha(path): return hashlib.sha256(Path(path).read_bytes()).hexdigest() def load(path): return json.loads(Path(path).read_bytes()) def module(name, path): spec = importlib.util.spec_from_file_location(name,path) obj = importlib.util.module_from_spec(spec); spec.loader.exec_module(obj); return obj def decode(node): """Only decode for semantic checks; whole typed nodes remain comparison authority.""" t = node['type'] if t == 'Dictionary': pairs = [(decode(k),decode(v)) for k,v in node['entries']] keys = [json.dumps(k,sort_keys=True) for k,v in pairs] if len(keys) != len(set(keys)): raise ValueError('duplicate typed dictionary key') return {k:v for k,v in pairs} if t == 'Array': return [decode(n) for n in node['items']] text = node['text'] if t in ('String','StringName'): return json.loads(text.removeprefix('&')) if t == 'int': return int(text) if t == 'float': return float(text) if t == 'bool': if text not in ('true','false'): raise ValueError('invalid bool') return text == 'true' if t == 'Nil': return None if t in ('Vector2i','Vector3i','Vector2','Vector3'): match = re.fullmatch(re.escape(t)+r'\(([^()]*)\)',text) parts = match.group(1).split(',') if match else [] integer = t.endswith('i') number = r'[+-]?[0-9]+' if integer else r'[+-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)(?:[eE][+-]?[0-9]+)?' if len(parts) != int(t[6]) or any(not re.fullmatch(number,x.strip()) for x in parts): raise ValueError('invalid typed vector '+t) values = [(int if integer else float)(x.strip()) for x in parts] if not integer and not all(math.isfinite(x) for x in values): raise ValueError('invalid typed vector '+t) return values # Preserve unfamiliar scalar types verbatim; never silently drop them. return (t,text) def indexed(rows, key): result = {row[key]:row for row in rows} if len(result) != len(rows): raise ValueError('duplicate '+key) return result def semantics(snapshot, identity, initial=False): """Independent ID/cell/reference checks on actual serialized Runtime state.""" r = snapshot['systems']['runtime'] guests = indexed(r['guests']['guests'],'guest_id') staff = indexed(r['staff']['staff'],'staff_id') if set(guests) & set(staff): raise ValueError('duplicate actor across domains') jobs = indexed(r['construction']['jobs'],'job_id') facilities = indexed(r['construction']['facilities'],'instance_id') operational = indexed(r['facilities']['instances'],'instance_id') owners = {k:v['occupied_cells'] for k,v in facilities.items()} owners.update({k:v['reserved_cells'] for k,v in jobs.items() if v['state'] not in ('COMPLETED','CANCELLED')}) occupied = indexed(r['world']['occupancies'],'entity_id') cells = set() for owner,row in occupied.items(): actual = {tuple(c) for c in row['cells']} if len(actual) != len(row['cells']) or cells & actual: raise ValueError('overlap occupancy') cells |= actual if owner not in owners or actual != {tuple(c) for c in owners[owner]}: raise ValueError('orphan occupancy/ownership') if set(owners) != set(occupied): raise ValueError('lost occupancy owner') for fid,facility in operational.items(): if fid not in facilities or facility['occupied_cells'] != facilities[fid]['occupied_cells']: raise ValueError('operational/construction ownership mismatch') for jid in identity['jobs']: if jid not in jobs: raise ValueError('lost paid job') starting = jid == identity['starting_job'] stage_ok = (jobs[jid]['progress'] == 0 and jobs[jid]['state'] == 'PLANNED') if starting else (jobs[jid]['progress'] > 0 and jobs[jid]['state'] == 'BUILDING') if initial and (not stage_ok or not jobs[jid]['construction_cost'] or len(jobs[jid]['reserved_cells']) != 6): raise ValueError('missing paid old-size progress/reservation') if identity['starting_job'] not in identity['jobs']: raise ValueError('missing starting stage') budget = identity['diagnostic_budget'] total = {key:sum(row['cost'].get(key,0) for row in budget['costs']) for key in ('gold','materials')} total['gold'] += budget['signing'] + budget['food_cost'] deficit = {key:max(0,value-budget['initial'][key]) for key,value in total.items() if value > budget['initial'][key]} if budget['seed'] != 42588 or budget['food_quantity'] != 8 or total != budget['needed'] or deficit != budget['deltas']: raise ValueError('diagnostic resource derivation mismatch') if len(identity['jobs']) < 2 or identity['dorm'] not in facilities: raise ValueError('mixed jobs/completed facility missing') tasks = indexed(r['staff']['tasks'],'task_id') if 'patient' not in staff or 'carrier' not in staff: raise ValueError('lost patient/carrier') rescue = tasks[identity['rescue_task']] if rescue['target_entity_id'] != 'patient' or rescue['rescue_facility_id'] not in facilities: raise ValueError('rescue patient/destination reference') if initial and (rescue['state'] != 'MOVING' or rescue['assigned_staff_id'] != 'carrier' or staff['patient']['health'] != 0): raise ValueError('rescue is not actually in flight') inventories = indexed(r['inventory']['inventories'],'owner_inventory_id') requests = indexed(r['logistics_requests'],'request_id') reservations = indexed(r['inventory']['reservations'],'reservation_id') if not identity['logistics_requests']: raise ValueError('missing original food request') for rid in identity['logistics_requests']: req = requests[rid] for endpoint in ('source_inventory_id','destination_inventory_id'): value = req[endpoint] if not value.startswith('facility.inventory:') or value.removeprefix('facility.inventory:') not in operational: raise ValueError('orphan cargo endpoint') state = req['state'] carrier_id = req['assigned_carrier_id'] if state == 'RESERVED' and carrier_id: raise ValueError('RESERVED carrier contradiction') if state in ('RESERVED','DELIVERING'): ids = req['reservation_ids'] if not ids or len(ids) != len(set(ids)) or any(x not in reservations for x in ids): raise ValueError(state+' missing/duplicate reservation') totals = {} for reservation_id in ids: row = reservations[reservation_id] if (row['requester_id'] != rid or row['owner_inventory_id'] != req['source_inventory_id'] or row['stack_id'] not in req['reserved_stack_ids'] or row['state'] != ('ACTIVE' if state == 'RESERVED' else 'CONSUMED') or type(row['quantity']) is not int or row['quantity'] <= 0): raise ValueError(state+' reservation crosslink mismatch') totals[row['item_id']] = totals.get(row['item_id'],0)+row['quantity'] if totals != req['item_requirements'] or set(req['reserved_stack_ids']) != {reservations[x]['stack_id'] for x in ids}: raise ValueError(state+' reservation quantity mismatch') if state == 'RESERVED': source_stacks = indexed(inventories.get(req['source_inventory_id'],{}).get('stacks',[]),'stack_id') for reservation_id in ids: row = reservations[reservation_id]; stack = source_stacks.get(row['stack_id'],{}) if (stack.get('item_id') != row['item_id'] or stack.get('quantity',0) < row['quantity'] or stack.get('reserved_quantity',0) < row['quantity']): raise ValueError('RESERVED cargo/reservation mismatch') else: carrier = staff.get(carrier_id,{}) cargo = inventories.get(carrier.get('carried_inventory_id'),{}) cargo_totals = {} for stack in cargo.get('stacks',[]): cargo_totals[stack['item_id']] = cargo_totals.get(stack['item_id'],0)+stack['quantity'] if not carrier or cargo_totals != totals: raise ValueError('DELIVERING carrier/cargo mismatch') trips = indexed(r['elevators']['trips'],'request_id') actors = set(guests) | set(staff) for trip in trips.values(): actor = trip['actor_id'] if actor not in actors and actor.removeprefix('guest:').removeprefix('staff:') not in actors: raise ValueError('orphan EV actor') if initial: if len(identity['ev_requests']) != 5 or not set(identity['ev_requests']) <= set(trips): raise ValueError('lost EV request') if sum(len(c['boarded']) for c in r['elevators']['shafts']) != 4: raise ValueError('EV passengers/queue absent') if 'p08.walker' not in guests or not r['movement_credits']: raise ValueError('mid-route guest/credit absent') return r def typed_differences(a, b, path=()): if a == b: return [] if a.get('type') != b.get('type'): return [path] if a['type'] == 'Dictionary': aa = {str(decode(k)):(k,v) for k,v in a['entries']}; bb = {str(decode(k)):(k,v) for k,v in b['entries']} if aa.keys() != bb.keys(): return [path] differences = [] for k in aa: if aa[k][0] != bb[k][0]: differences.append(path+(k,'@key-type')) differences += typed_differences(aa[k][1],bb[k][1],path+(k,)) return differences if a['type'] == 'Array' and a.get('element_type') == b.get('element_type') and a.get('element_class') == b.get('element_class'): if len(a['items']) != len(b['items']): return [path] return [p for i,(x,y) in enumerate(zip(a['items'],b['items'])) for p in typed_differences(x,y,path+(str(i),))] return [path] def migration_revisions(before, after, refusal): """WorldService._mark_dirty assigns domain counters the global epoch. r04: (world, occupancy, support) (144,144,142) -> (150,150,150). Only an actual occupancy rewrite can justify the support epoch delta. Whole typed comparison still rejects every other unapproved field. """ old, new = before['world']['revisions'], after['world']['revisions'] keys = ('world','occupancy','support') if any(type(row[k]) is not int or row[k] < 0 for row in (old,new) for k in keys): raise ValueError('invalid revision epoch') if refusal: if any(old[k] != new[k] for k in keys): raise ValueError('refusal revision changed') return changed = before['world']['occupancies'] != after['world']['occupancies'] if (not changed or not old['support'] <= old['world'] or not old['occupancy'] <= old['world'] or new['world'] <= old['world'] or new['occupancy'] != new['world'] or new['support'] != new['world'] or new['support'] < old['support']): raise ValueError('support epoch/occupancy mismatch') # Explicit geometry changes only. No actor, economy, request, event or route wildcard. # This fixture deliberately places actors away from removed cells: NO route changes approved. GEOMETRY = [ r'systems/runtime/world/occupancies/\d+/cells(?:/.*)?', r'systems/runtime/world/dirty_cells(?:/.*)?', r'systems/runtime/world/revisions/(world|occupancy)', r'systems/runtime/construction/jobs/\d+/reserved_cells(?:/.*)?', r'systems/runtime/construction/facilities/\d+/occupied_cells(?:/.*)?', r'systems/runtime/facilities/instances/\d+/occupied_cells(?:/.*)?', ] def check(project_root, evidence_dir): root, evidence = Path(project_root).resolve(), Path(evidence_dir).resolve() deficits = [] def need(ok, reason): if not ok: deficits.append(reason) def guarded(label, fn): try: return fn() except (KeyError,ValueError,TypeError,OSError,IndexError,AttributeError) as exc: deficits.append(label+': '+str(exc)); return None index = guarded('index',lambda:load(evidence/'index.json')) if not isinstance(index,dict): return deficits or ['index is not an object'] def artifact(name): if not isinstance(name,str) or Path(name).is_absolute() or '..' in Path(name).parts: raise ValueError('unsafe artifact path') path = evidence/name if not path.is_file() or any(p.is_symlink() for p in [path,*path.parents] if p != evidence.parent): raise ValueError('missing/aliased artifact '+name) pin = index['artifacts'][name] if pin != {'bytes':path.stat().st_size,'sha256':sha(path)}: raise ValueError('artifact hash '+name) return path def document(name): return load(artifact(name)) need(index.get('schema') == 'p08-evidence-v1','schema') need(index.get('acceptance') == 'pending','integrity is not unit acceptance') need(index.get('collection_error') is None,'collection failure') def source_gate(): runner = module('p08_readonly_source',root/'tools/run_all_tests.py') actual = {n:p['sha256'] for n,p in runner.source_refs(root).items()} need(index['source_map'] == actual,'wrong/stale/incomplete final source map') spec = load(root/'docs/spec/acceptance/P08-02.json') need(index['spec_sha256'] == SPEC_SHA == hashlib.sha256((root/'docs/spec/acceptance/P08-02.json').read_text().strip().encode()).hexdigest(),'frozen spec SHA') for name,pin in spec['current_source_pins'].items(): need(actual.get(name) == pin,'frozen production source '+name) inputs = {str(p.relative_to(root)):sha(p) for p in (root/'docs/spec/inputs/p08-02').iterdir() if p.is_file()} need(index['inputs'] == inputs,'frozen input map') need(index['marker_contract_sha256'] == sha(root/CONTRACT),'marker contract source') need(re.fullmatch('[0-9a-f]{40}',index['commit']) is not None,'submitted commit identity') guarded('source',source_gate) for name in index.get('artifacts',{}): guarded('artifact '+name,lambda name=name:artifact(name)) markers = module('p08_evidence_markers',root/'tools/strict_test_markers.py') contracts = markers.load_contracts(root/CONTRACT) pids = set() def phase(name, role): receipt = document(name); pid = receipt['actual_pid'] need(type(pid) is int and pid > 0 and pid not in pids,'distinct native PID '+name); pids.add(pid) parent = str(Path(name).parent) prefix = '' if parent == '.' else parent+'/' folder = prefix+receipt['phase']+'/' start,end,outer = [document(folder+n) for n in ('started.json','ended.json','outer-owned-finally.json')] for row in (start,end): need(row['actual_pid'] == pid and row['command'] == receipt['command'],'raw lifecycle PID/argv '+name) need(end.get('exit_code') == 0 and end.get('engine_ended') is True,'ended exit '+name) need(start.get('live_identity_exact') is True or start.get('tracked_child_ended_before_initial_probe') is True,'native Popen identity '+name) need(outer['actual_captured_pids'] == [pid] and outer['all_captured_ended'] is True and not outer['outer_cleanup_errors'] and outer['foreign_process_or_group_signals'] == 0,'owned cleanup '+name) native = receipt['native_process'] need(native['exit_code'] == 0 and native['helper_exit_code'] == 0 and native['timed_out'] is False and native['lifecycle_error'] is None,'native exit/errors '+name) need(receipt['exit_code'] == 0 and receipt['engine_ended'] is True,'phase ended '+name) if role: test = 'tests/fixtures/p08-02/'+role+'.gd' need(receipt['test'] == test and receipt['source_sha256'] == index['source_map'][test],'phase source '+name) raw = artifact(prefix+receipt['log']).read_text() parsed = markers.parse_raw(test,raw,{test:{'template':'P08 mixed '+role+': checks, failures'}},receipt['source_sha256']) need(not parsed['errors'],'strict completion '+name+': '+str(parsed['errors'])) profile = receipt['actual_private_profile']['report'] need(profile['fresh_before_parent_initialize'] is True and profile['profile_writes_before_guard'] == 0 and profile['actual_user_data_dir'] == profile['expected_user_data_dir'],'guarded fresh profile '+name) return receipt def case_gate(case): name = case['id']; prefix = case['directory']+'/' need(name == case['operations']+'-'+case['kind'],'case identity '+name) need(len(case['phases']) == 2,'writer and reader phases '+name) receipts = [phase(n,r) for n,r in zip(case['phases'],('writer','reader'))] def typed(name): artifact(prefix+name+'.var') return document(prefix+name+'.json') identity = decode(typed('identity')) original = typed('original'); after_live = typed('after-live') need(original == after_live,'live partial batch '+name) before_members,after_members = [decode(typed(n)) for n in ('before-members','after-members')] for package in ('original','result','backup'): for member in MEMBERS: artifact(prefix+package+'/formal/'+member) for member in MEMBERS: need(before_members['/'+member] == sha(artifact(prefix+'original/formal/'+member)),'original overwritten '+name+'/'+member) need(after_members['/'+member] == sha(artifact(prefix+'result/formal/'+member)),'result member '+name+'/'+member) need(after_members['.pre_migration.bak/'+member] == sha(artifact(prefix+'backup/formal/'+member)),'backup overwritten '+name+'/'+member) refusal = case['kind'] == 'refusal' if refusal: need(before_members == after_members,'refusal original/backup overwritten '+name) source = semantics(decode(original),identity,True) need(source['operations']['enabled'] == (case['operations']=='on'),'missing actual Operations mode '+name) for role,receipt in zip(('writer','reader'),receipts): proc = decode(typed(role+'-process')) need(proc['pid'] == receipt['actual_pid'] and proc['failures'] == 0 and proc['operations'] == (case['operations']=='on') and proc['refusal'] == refusal,'fixture/native binding '+name+'/'+role) checkpoint = typed('writer-checkpoint') migrated = decode(checkpoint)['systems']['runtime'] migration_revisions(source,migrated,refusal) if refusal: need(original == checkpoint,'partial earlier candidate '+name) else: for path in typed_differences(original,checkpoint): support_epoch = path == ('systems','runtime','world','revisions','support') need(support_epoch or any(re.fullmatch(pattern,'/'.join(path)) for pattern in GEOMETRY),'unapproved typed migration delta '+name+'/'+ '/'.join(path)) migrated = decode(checkpoint)['systems']['runtime'] jobs = indexed(migrated['construction']['jobs'],'job_id') need(all(len(jobs[j]['reserved_cells']) == 4 for j in identity['jobs']),'positive batch not applied '+name) for label in LABELS: writer,reader = typed('writer-'+label),typed('reader-'+label) need(writer == reader,'cross-PID full typed state/events '+name+'/'+label) need(sha(artifact(prefix+'writer-'+label+'.var')) == sha(artifact(prefix+'reader-'+label+'.var')),'cross-PID raw Variant '+name+'/'+label) if label != 'events': semantics(decode(writer),identity) final = decode(typed('writer-minute-160'))['systems']['runtime'] need(indexed(final['staff']['staff'],'staff_id')['patient']['health'] == 100,'patient lost/untreated '+name) final_requests = indexed(final['logistics_requests'],'request_id') need(all(final_requests[i]['state']=='COMPLETED' for i in identity['logistics_requests']),'original cargo completion '+name) events = decode(typed('writer-events')) for rid in identity['ev_requests']: need(sum(e['event_type']=='ELEVATOR_ARRIVED' and e['payload'].get('request_id')==rid for e in events)==1,'EV original request exactly once '+name+'/'+rid) cases = index.get('cases',[]) need([c.get('id') for c in cases] == list(CASES),'missing/duplicate ON/OFF positive/refusal case') for case in cases: guarded('case '+str(case.get('id')),lambda case=case:case_gate(case)) for condition in CONDITIONS: expected = [c for c in CASES if condition != 'unsolvable_preservation' or c.endswith('refusal')] need(index.get('conditions',{}).get(condition) == expected,'condition mapping '+condition) def os_gate(): proof = document(index['os_proof']) need(proof['commit'] == index['commit'] and proof['source_map'] == index['source_map'],'OS current candidate') need(proof['input_method'] == 'native_os' and proof['setup'] == 'controlled_old_save','OS input provenance/setup') actions = proof['actions'] need([a['action'] for a in actions] == ['preview','cancel','confirm','changed_source_rejection'],'OS actions missing') for action in actions: raw = document(action['input_receipt']) need(raw['method'] == 'native_os' and raw['event_type'] in ('mouse','keyboard') and type(raw['target_pid']) is int and raw['target_pid'] > 0 and raw['timestamp_ns'] > 0,'fake OS signal') for key in ('screen_before','screen_after','model_before','model_after','formal_before','formal_after'): artifact(action[key]) if action['action'] in ('preview','cancel','changed_source_rejection'): need(document(action['formal_before']) == document(action['formal_after']),'OS formal preservation '+action['action']) guarded('OS proof',os_gate) def regression_gate(): proof = document(index['regression']); run_input = document(proof['input']); ended = document(proof['ended']) need(run_input['selected_source_map'] == index['source_map'],'final regression source map') need(proof['commit'] == index['commit'],'final regression commit') need(not ended['error'] and not ended['cleanup_errors'] and not ended['guard_errors'],'regression failure') runner = module('p08_regression_roster',root/'tools/run_all_tests.py') roster = runner.roster(root) need(run_input['roster'] == roster,'full final roster') rows = [x for x in ended['phases'] if x.get('test')] need([x['test'] for x in rows] == roster,'full final regression receipt') for row in rows: raw = artifact(proof['directory']+'/'+row['log']).read_text() need(not markers.parse_raw(row['test'],raw,contracts,index['source_map'][row['test']])['errors'],'regression strict '+row['test']) need(row['exit_code'] == 0 and row['engine_ended'] is True,'regression native exit '+row['test']) guarded('final regression',regression_gate) return sorted(set(deficits)) def main(argv=None): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--verify-only',action='store_true',required=True) parser.add_argument('--project-root',type=Path,default=HERE.parent) args = parser.parse_args(argv) build = os.environ.get('BUILD_OUTPUT_DIR') deficits = ['BUILD_OUTPUT_DIR missing'] if not build else check(args.project_root,Path(build)/'p08-evidence') print(json.dumps({'integrity':'deficient' if deficits else 'verified','deficits':deficits, 'acceptance':'pending','conditions':{c:'pending' for c in CONDITIONS}},ensure_ascii=False,indent=2)) return 1 if deficits else 0 if __name__ == '__main__': raise SystemExit(main())