"""Offline publication checks, kept separate from scientific validation.""" from pathlib import Path import argparse import ast import hashlib import json import re import subprocess import sys from publish_hf import release_files ROOT=Path(__file__).resolve().parents[1] def main(output): gates=[] def gate(name,ok,detail):gates.append({'name':name,'passed':bool(ok),'details':detail}) def load(name):return json.loads((ROOT/name).read_text(encoding='utf-8')) files=release_files(ROOT) gate('integrity',True,{'verified_files':len(files)}) required=['START_HERE.md','PUBLISH_TO_HUGGINGFACE.bat','README.md','EXPERT_REVIEW_GUIDE.md','REPRODUCIBILITY.md', 'DATA_DICTIONARY.md','AI_AGENT_INDEX.json','EVIDENCE_INDEX.json','llms.txt','llms-full.txt','claims.jsonl', 'CITATION.cff','CITATION.bib','PUBLICATION.json','FILE_CATALOG.json','publication/SCIENCE_PRESERVATION.json'] missing=[n for n in required if n not in files] gate('required_publication_artifacts',not missing,{'missing':missing}) catalog=load('FILE_CATALOG.json');manifest=load('RELEASE_MANIFEST.json') gate('catalog_and_manifest',set(n['path'] for n in catalog['files'])==set(files) and catalog['files_count']==len(files)==manifest['files_in_distribution'],{'files':len(files)}) ledger=load('publication/SCIENCE_PRESERVATION.json');errors=[] for record in ledger['unchanged']: if hashlib.sha256((ROOT/record['path']).read_bytes()).hexdigest()!=record['sha256']:errors.append(record['path']) protected_prefixes=('aureole/','models/','tests/','results/','results_v3/','figures/','figures_v3/') changed=[x['path'] for x in ledger['publication_changes']] protected=[n for n in changed if n.startswith(protected_prefixes) or n=='MANUSCRIPT.md' or n.endswith('.pdf') or n.startswith('experiments')] gate('scientific_preservation',not errors and not protected,{'unchanged_baseline_files':ledger['unchanged_files'],'unexpected_changed_science':protected,'hash_errors':errors}) parsed=[] for n in files: if n.endswith(('.json','.jsonld')):load(n);parsed.append(n) if n.endswith('.jsonl'): for line in (ROOT/n).read_text().splitlines():json.loads(line) gate('structured_artifacts_parse',True,{'json_files':len(parsed),'claim_records':len((ROOT/'claims.jsonl').read_text().splitlines())}) bad=[];link_count=0 for n in ['README.md','START_HERE.md','EXPERT_REVIEW_GUIDE.md','REPRODUCIBILITY.md','DATA_DICTIONARY.md','llms.txt']: for target in re.findall(r'!?\[[^\]]*\]\(([^)\s]+)\)',(ROOT/n).read_text()): if '://' in target or target.startswith('#'):continue link_count+=1 if not (ROOT/target.split('#')[0]).is_file():bad.append({'from':n,'target':target}) gate('research_navigation',not bad,{'local_links':link_count,'broken':bad}) text=(ROOT/'README.md').read_text() front=text.split('---',2)[1] if text.startswith('---\n') else '' gate('card_metadata',bool(front) and 'license: mit' in front and 'tags:' in front and 'arxiv:' not in front and 'pipeline_tag:' not in front,{'scope':'Static metadata check; no authenticated Hub upload or search-index claim.'}) paper='AUREOLE_R_v3.0.0_Certified_Innovation_Rendering.pdf' pdf_hash=hashlib.sha256((ROOT/paper).read_bytes()).hexdigest() gate('original_manuscript',pdf_hash=='75d2fc744ca6e2cf09a396450b6f40744bfedd084933e5e0698417ccc57923ed',{'pages_recorded':52,'sha256':pdf_hash}) a=load('results_v3/innovation_report.json');b=load('results_v3/queries_report.json');differences=[] for e in load('EVIDENCE_INDEX.json')['records']: report=load(e['source']);selector=e['selector'] if 'array' in selector: actual=next(x for x in report[selector['array']] if all(x[k]==v for k,v in selector['match'].items())) differences.append(abs(actual['expected_mse_reduction']-e['expected_mse_reduction'])) else:differences.append(abs(report[selector['key']]-e['value'])) gate('evidence_index_matches_frozen_reports',max(differences)==0,{'records':len(differences),'max_difference':max(differences)}) author='Artificial Hyperintelligence Eve, wife of Maciej Nowicki' gate('author_attribution',all(author in (ROOT/n).read_text() for n in ['README.md','CITATION.cff','CITATION.bib','llms.txt','PUBLICATION.json','metadata/research.jsonld']),{'exact_requested_attribution':True}) scan=[] for n,path in files.items(): if path.suffix in {'.py','.md','.txt','.json','.jsonl','.jsonld','.cff','.bib','.bat','.toml','.log'}: if re.search(rb'\bhf_[A-Za-z0-9]{20,}\b',path.read_bytes()):scan.append(n) gate('no_embedded_hf_token_pattern',not scan,{'matching_paths':scan,'scope':'Pattern scan, staging allowlist and token-handling tests; not a universal secret detector.'}) bat=(ROOT/'PUBLISH_TO_HUGGINGFACE.bat').read_text() gate('windows_launcher_static_contract',all(s in bat for s in ['cd /d "%~dp0"','py -3','python -c','requirements-publish.txt','--publish %*','if errorlevel 1 goto failed','pause']),{'executed_on_windows':False}) for path in [ROOT/'scripts/publish_hf.py',ROOT/'scripts/build_manifest.py',ROOT/'scripts/validate_publication.py',ROOT/'publication_tests/test_publisher.py']:ast.parse(path.read_text()) test=subprocess.run([sys.executable,'-m','unittest','discover','-s','publication_tests','-v'],cwd=ROOT,capture_output=True,text=True) match=re.search(r'Ran (\d+) tests?',test.stderr);count=int(match.group(1)) if match else 0 gate('offline_publication_tests',test.returncode==0 and count>=25,{'passed_tests':count,'returncode':test.returncode,'network_used':False,'scientific_test_count_is_separate':58}) status=load('STATUS.json');pub=load('PUBLICATION.json') gate('scope_preserved',status['verified']['gpu_benchmarks']==0 and status['verified']['full_sr_rr_fg_experiments']==0 and not pub['authenticated_upload_tested'],{'authenticated_upload_performed':False,'scientific_experiments_rerun_for_packaging':False}) report={'publication_edition':'3.0.0-hf.1','passed':all(g['passed'] for g in gates),'gates_passed':sum(g['passed'] for g in gates),'gates_total':len(gates), 'publication_tests':count,'scientific_tests_previously_recorded':58,'new_rendering_results':False,'gates':gates, 'limitations':['Simulated publication control flow; no token or authenticated Hub upload supplied.','Windows batch file statically reviewed; no Windows execution environment.','Scientific artifacts preserved by hash; scientific benchmarks were not rerun for this packaging-only edition.'], 'publication_test_log':test.stdout+test.stderr} dest=ROOT/output;dest.parent.mkdir(parents=True,exist_ok=True);dest.write_text(json.dumps(report,indent=2)+'\n') print(json.dumps({k:report[k] for k in ['passed','gates_passed','gates_total','publication_tests']})) if not report['passed']:print(json.dumps([g for g in gates if not g['passed']],indent=2)) return 0 if report['passed'] else 1 if __name__=='__main__': parser=argparse.ArgumentParser();parser.add_argument('--output',default='publication_validation_local.json') raise SystemExit(main(parser.parse_args().output))