# SPDX-License-Identifier: MIT """Bind a sealed Clark admission pairing to the existing private curator case.""" from __future__ import annotations import argparse,json from hashlib import sha256 from pathlib import Path if __package__:from .prepare_clark_curator_case import build else:from prepare_clark_curator_case import build REQUEST="ab2a8887110cb058d496e0afe1563fe7e42ee260a63608efa2de03a62e58a534";ROLES=("first_source_reader","independent_reproducer");HASH=set("0123456789abcdef") FIELDS=["schema_version","status","request_sha256","roles","admission_sha256s","worksheet_sha256s","required_disclosures","semantic_rechecks_passed","distinct_role_artifacts","manual_private_curator_review_required","production_database_used","submission_created","identity_verified","independence_verified","scientific_validation_claimed","discovery_claimed","pairing_sha256"] def _hash(v):return isinstance(v,str) and len(v)==64 and not(set(v)-HASH) def _pairing(path): raw=Path(path).read_bytes();v=json.loads(raw.decode("utf-8")) if list(v)!=FIELDS or v["schema_version"]!="openlab-clark-admission-pairing-v1" or v["status"]!="paired_for_private_curator_conflict_review_not_submission" or v["request_sha256"]!=REQUEST:raise ValueError("pairing identity or fields drift") if v["roles"]!=list(ROLES) or set(v["admission_sha256s"])!=set(ROLES) or set(v["worksheet_sha256s"])!=set(ROLES):raise ValueError("pairing roles drift") if not all(_hash(x) for x in [*v["admission_sha256s"].values(),*v["worksheet_sha256s"].values(),v["pairing_sha256"]]):raise ValueError("pairing hash invalid") expected=sha256(json.dumps({k:v[k] for k in FIELDS[:-1]},ensure_ascii=False,separators=(",",":"),sort_keys=True).encode()).hexdigest() if v["pairing_sha256"]!=expected:raise ValueError("pairing seal mismatch") if v["semantic_rechecks_passed"] is not True or v["distinct_role_artifacts"] is not True or v["manual_private_curator_review_required"] is not True:raise ValueError("pairing review boundary drift") for f in ["production_database_used","submission_created","identity_verified","independence_verified","scientific_validation_claimed","discovery_claimed"]: if v[f] is not False:raise ValueError("pairing promotion refused") return raw,v def prepare(pairing_path,first_files,second_files): raw,pairing=_pairing(pairing_path);case=build(first_files,second_files) if case["worksheet_sha256s"]!=pairing["worksheet_sha256s"]:raise ValueError("pairing to curator case worksheet binding drift") if case["disclosures_requiring_curator_review"]!=pairing["required_disclosures"]:raise ValueError("pairing disclosures changed before curator case") body={"schema_version":"openlab-clark-pairing-curator-case-v1","status":"pairing_bound_to_complete_private_curator_source_case","pairing_sha256":pairing["pairing_sha256"],"pairing_file_sha256":sha256(raw).hexdigest(),"curator_case":case,"source_cell_review_required":45,"identity_review_required":True,"independence_review_required":True,"domain_review_required":True,"production_database_used":False,"submission_created":False,"identity_verified":False,"independence_verified":False,"scientific_validation_claimed":False,"discovery_claimed":False} body["binding_sha256"]=sha256(json.dumps(body,ensure_ascii=False,separators=(",",":"),sort_keys=True).encode()).hexdigest();return body def main(): p=argparse.ArgumentParser();p.add_argument("pairing"); for n in ("first_csv","first_draft","first_self_check","first_manifest","second_csv","second_draft","second_self_check","second_manifest"):p.add_argument(n) p.add_argument("--out",required=True);a=p.parse_args();r=prepare(a.pairing,(a.first_csv,a.first_draft,a.first_self_check,a.first_manifest),(a.second_csv,a.second_draft,a.second_self_check,a.second_manifest));Path(a.out).write_text(json.dumps(r,ensure_ascii=False,indent=2)+"\n",encoding="utf-8");print(json.dumps(r,ensure_ascii=False)) if __name__=="__main__":main()