# SPDX-License-Identifier: MIT """Prepare, but never send, a bounded manual Clark return-route offer.""" from __future__ import annotations import argparse from datetime import date from hashlib import sha256 import json from pathlib import Path REQUEST="ab2a8887110cb058d496e0afe1563fe7e42ee260a63608efa2de03a62e58a534" ROLES={"first_source_reader","independent_reproducer"};HASH=set("0123456789abcdef") QUEUE_FIELDS=["schema_version","status","request_sha256","task_slug","entries","missing_roles","attachment_count","contains_sender_address","production_database_used","automatic_contact","submission_created","identity_verified","independence_verified","scientific_validation_claimed","queue_sha256"] DECISION_FIELDS=["schema_version","queue_sha256","role","worksheet_sha256","issued_on","expires_on","operator_reviewed","route_type","max_total_bytes"] ALLOWED_FILES=["worksheet.csv","validation-draft.json","reviewer-self-check.json","handoff-manifest.json","validator-output.txt"] def _hash(v):return isinstance(v,str) and len(v)==64 and not(set(v)-HASH) def _queue(v): if list(v)!=QUEUE_FIELDS or v["schema_version"]!="openlab-clark-return-queue-v1" or v["request_sha256"]!=REQUEST or v["task_slug"]!="clark-table-i":raise ValueError("queue identity or fields drift") if not _hash(v["queue_sha256"]):raise ValueError("queue hash invalid") body={k:v[k] for k in QUEUE_FIELDS[:-1]}; expected=sha256(json.dumps(body,ensure_ascii=False,separators=(",",":"),sort_keys=True).encode()).hexdigest() if v["queue_sha256"]!=expected:raise ValueError("queue seal mismatch") if not isinstance(v["entries"],list) or len(v["entries"])>2:raise ValueError("queue entry count invalid") roles=[];works=[];receipts=[] for entry in v["entries"]: if list(entry)!=["role","worksheet_sha256","preflight_input_sha256","prior_exposure_declared"] or entry["role"] not in ROLES or not _hash(entry["worksheet_sha256"]) or not _hash(entry["preflight_input_sha256"]) or not isinstance(entry["prior_exposure_declared"],bool):raise ValueError("queue entry invalid") roles.append(entry["role"]);works.append(entry["worksheet_sha256"]);receipts.append(entry["preflight_input_sha256"]) if len(set(roles))!=len(roles) or len(set(works))!=len(works) or len(set(receipts))!=len(receipts):raise ValueError("queue entries must remain distinct") missing=[role for role in ("first_source_reader","independent_reproducer") if role not in roles] expected_status="ready_for_manual_route_review_pair_not_validation" if not missing else "awaiting_required_role" if v["missing_roles"]!=missing or v["status"]!=expected_status:raise ValueError("queue role state drift") if v["attachment_count"]!=0 or v["contains_sender_address"] is not False or v["production_database_used"] is not False or v["automatic_contact"] is not False or v["submission_created"] is not False or v["identity_verified"] is not False or v["independence_verified"] is not False or v["scientific_validation_claimed"] is not False:raise ValueError("queue boundary promoted") return v def prepare(queue,decision): queue=_queue(queue) if list(decision)!=DECISION_FIELDS or decision["schema_version"]!="openlab-clark-route-offer-decision-v1":raise ValueError("decision fields drift") if decision["queue_sha256"]!=queue["queue_sha256"]:raise ValueError("decision queue binding drift") if decision["role"] not in ROLES or not _hash(decision["worksheet_sha256"]):raise ValueError("role or worksheet hash invalid") matches=[x for x in queue["entries"] if x["role"]==decision["role"] and x["worksheet_sha256"]==decision["worksheet_sha256"]] if len(matches)!=1:raise ValueError("decision must match one queue entry") try:issued=date.fromisoformat(decision["issued_on"]);expires=date.fromisoformat(decision["expires_on"]) except (TypeError,ValueError) as e:raise ValueError("offer dates invalid") from e if expires14:raise ValueError("offer expiry must be within 14 days") if decision["operator_reviewed"] is not True or decision["route_type"]!="managed_mailbox_reply" or decision["max_total_bytes"]!=1048576:raise ValueError("route decision boundary drift") offer={"schema_version":"openlab-clark-route-offer-v1","status":"manual_reply_draft_authorized_not_sent","request_sha256":REQUEST,"queue_sha256":queue["queue_sha256"],"role":decision["role"],"worksheet_sha256":decision["worksheet_sha256"],"issued_on":decision["issued_on"],"expires_on":decision["expires_on"],"route_type":"managed_mailbox_reply","allowed_files":ALLOWED_FILES,"max_total_bytes":1048576,"forbidden_content":["publisher_pdf_or_scan","credentials_or_secrets","other_people_personal_data","executable_or_archive"],"reply_subject_marker":"[KIWERB-CLARK-RETURN]","automatic_contact":False,"message_sent":False,"attachment_received":False,"submission_created":False,"identity_verified":False,"independence_verified":False,"scientific_validation_claimed":False} offer["offer_sha256"]=sha256(json.dumps(offer,ensure_ascii=False,separators=(",",":"),sort_keys=True).encode()).hexdigest();return offer def main(): p=argparse.ArgumentParser();p.add_argument("queue");p.add_argument("decision");p.add_argument("--out",required=True);a=p.parse_args();result=prepare(json.loads(Path(a.queue).read_text(encoding="utf-8")),json.loads(Path(a.decision).read_text(encoding="utf-8")));Path(a.out).write_text(json.dumps(result,ensure_ascii=False,indent=2)+"\n",encoding="utf-8");print(json.dumps(result,ensure_ascii=False)) if __name__=="__main__":main()