| """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)) |
|
|