#!/usr/bin/env python3 """Offline curator intake for one de Vos return; no network, writes or admission.""" import datetime,hashlib,json,re,subprocess,sys from pathlib import Path TASK='devos-1954-tungsten-curve20-independent-review' VALIDATOR_SHA256='16a8ae16a1001ed6a3a369a89e703831ff25c340c1b2f4f84e796c5096ffdebc' HEX=re.compile(r'^[0-9a-f]{64}$');UTC_SECOND=re.compile(r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$') FIELDS={'schema_version','task_slug','received_at_utc','route_evidence_sha256','identity_status','reviewer_identity_evidence_sha256','independence_status','independence_evidence_sha256','source_access_status','protected_full_text_retained','scientific_comparison_status','reviewed_by_role','automatic_admission','external_contact','production_database_used'} def emit(status,reason=None,**extra): out={'schema_version':'openlab-devos-curator-intake-check-v1','status':status,**extra,'automatic_admission':False,'external_contact':False,'production_database_used':False,'independent_validation_claimed':False,'scientific_discovery_claimed':False,'files_written':0,'network_requests':0} if reason:out['reason']=reason print(json.dumps(out,sort_keys=True,separators=(',',':')));return 1 if status=='rejected' else 0 def sha(path):return hashlib.sha256(path.read_bytes()).hexdigest() def timestamp(value): if not isinstance(value,str) or not UTC_SECOND.fullmatch(value):return False try:return datetime.datetime.fromisoformat(value.replace('Z','+00:00')).isoformat(timespec='seconds').replace('+00:00','Z')==value except ValueError:return False def main(argv): if len(argv)!=6:return emit('rejected','usage: devos-curator-intake.py FILLED.csv COMPLETED-RETURN.json RETURN-CHECK.json CURATOR-REVIEW.json VALIDATOR.py') worksheet,returned,check_path,review_path,validator=map(Path,argv[1:]) if any(not p.is_file() for p in [worksheet,returned,check_path,review_path,validator]):return emit('rejected','all five local files are required') if any(not 1<=p.stat().st_size<=65536 for p in [worksheet,returned,check_path,review_path,validator]):return emit('rejected','file size outside 1..65536 bytes') if sha(validator)!=VALIDATOR_SHA256:return emit('rejected','return validator SHA-256 mismatch; execution refused') try:provided=json.loads(check_path.read_text(encoding='utf-8'));review=json.loads(review_path.read_text(encoding='utf-8')) except (UnicodeDecodeError,json.JSONDecodeError):return emit('rejected','valid UTF-8 JSON evidence required') run=subprocess.run([sys.executable,str(validator),str(worksheet),str(returned)],capture_output=True,text=True,timeout=15) try:actual=json.loads(run.stdout) except json.JSONDecodeError:return emit('rejected','return validator produced no valid JSON') if run.returncode!=0:return emit('rejected','return validator rejected the supplied files') if provided!=actual:return emit('rejected','provided return-check JSON does not exactly match fresh validator output') if actual.get('schema_version')!='openlab-devos-return-check-v2' or actual.get('status')!='structurally_ready_for_manual_route_review_not_admitted' or actual.get('worksheet_full_structure_rechecked') is not True or actual.get('sealed_at_whole_second_utc') is not True:return emit('rejected','return-check status or safeguards mismatch') if actual.get('worksheet_sha256')!=sha(worksheet) or actual.get('return_sha256')!=sha(returned):return emit('rejected','return-check file hashes mismatch') if not isinstance(review,dict) or set(review)!=FIELDS:return emit('rejected','exact curator-review fields required') if review['schema_version']!='openlab-devos-curator-review-v1' or review['task_slug']!=TASK:return emit('rejected','review schema or task mismatch') if not timestamp(review['received_at_utc']):return emit('rejected','received_at_utc must be a real whole-second UTC time') for key in ['route_evidence_sha256','independence_evidence_sha256']: if not isinstance(review[key],str) or not HEX.fullmatch(review[key]):return emit('rejected',f'{key} must be 64 lowercase hex digits') if review['identity_status'] not in ['reviewed_verified','reviewed_not_verified']:return emit('rejected','identity_status invalid') if review['identity_status']=='reviewed_verified': if not isinstance(review['reviewer_identity_evidence_sha256'],str) or not HEX.fullmatch(review['reviewer_identity_evidence_sha256']):return emit('rejected','verified identity requires evidence hash') elif review['reviewer_identity_evidence_sha256'] is not None:return emit('rejected','unverified identity must not carry a verification hash') if review['independence_status'] not in ['reviewed_independent','reviewed_uncertain','reviewed_conflict']:return emit('rejected','independence_status invalid') if review['source_access_status'] not in ['reviewed_lawful_bounded','held']:return emit('rejected','source_access_status invalid') if review['protected_full_text_retained'] is not False:return emit('rejected','protected full text must not be retained in intake') if review['scientific_comparison_status']!='pending':return emit('rejected','scientific comparison must remain pending at intake') if not isinstance(review['reviewed_by_role'],str) or not 3<=len(review['reviewed_by_role'].strip())<=100:return emit('rejected','reviewed_by_role must be bounded text') if review['automatic_admission'] is not False or review['external_contact'] is not False or review['production_database_used'] is not False:return emit('rejected','intake must remain offline and manual') eligible=review['identity_status']=='reviewed_verified' and review['independence_status']=='reviewed_independent' and review['source_access_status']=='reviewed_lawful_bounded' status='eligible_for_manual_scientific_comparison_review_not_validated' if eligible else 'held_for_manual_followup' return emit(status,worksheet_sha256=actual['worksheet_sha256'],return_sha256=actual['return_sha256'],return_check_sha256=sha(check_path),curator_review_sha256=sha(review_path),identity_status=review['identity_status'],independence_status=review['independence_status'],source_access_status=review['source_access_status'],scientific_comparison_status='pending') if __name__=='__main__':raise SystemExit(main(sys.argv))