Add licensed public-resource archive plan and verified LFS tooling
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
"""Read-only source guard; write receipt only to a new publication path."""
|
||||
import argparse
|
||||
from datetime import datetime, timezone
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
from publish_archives import no_links, sha_file, canonical
|
||||
|
||||
ROOT = Path('/data/digital-drosophila')
|
||||
REGISTERED = 'b63e6281b6505ca3b60acb467dd0b3fae274cf513dcbfd653649ba52e9ba9740'
|
||||
SOURCE_COMMIT = '80aeb2093db655329774eff7b32fb6e417b8b40b'
|
||||
|
||||
|
||||
def git(*args, cwd=ROOT):
|
||||
return subprocess.check_output(['git', '-C', str(cwd), *args], text=True).strip()
|
||||
|
||||
|
||||
def capture(plan_path, output):
|
||||
output = no_links(output)
|
||||
if output == ROOT or ROOT in output.parents or output.exists():
|
||||
raise ValueError('Receipt must be new and outside the scientific source')
|
||||
plan = json.loads(Path(plan_path).read_bytes())
|
||||
path = ROOT / 'experiments/phase4ar-normal-conditioning/protocol/preregistered.json'
|
||||
raw = path.read_bytes()
|
||||
if hashlib.sha256(raw).hexdigest() != REGISTERED:
|
||||
raise ValueError('Phase 4AR registration hash mismatch')
|
||||
registration = json.loads(raw)
|
||||
records = []
|
||||
for filename, expected in sorted(registration['source_sha256'].items()):
|
||||
source = no_links(filename)
|
||||
if ROOT not in source.parents:
|
||||
raise ValueError('Registered source outside project')
|
||||
actual = sha_file(source)
|
||||
if actual['sha256'] != expected:
|
||||
raise ValueError('Phase 4AR source mismatch: ' + filename)
|
||||
records.append(dict(path=filename, **actual))
|
||||
if len(records) != 16:
|
||||
raise ValueError('Registered source inventory differs from reviewed scope')
|
||||
if git('cat-file', '-t', SOURCE_COMMIT) != 'commit':
|
||||
raise ValueError('Registered scientific commit unavailable')
|
||||
evidence = []
|
||||
for group in plan['groups']:
|
||||
licence = group['license']
|
||||
if 'evidence_file' in licence:
|
||||
source = ROOT / group['paths'][0] / licence['evidence_file']
|
||||
actual = sha_file(source)
|
||||
if actual['sha256'] != licence['evidence_sha256']:
|
||||
raise ValueError('License evidence hash mismatch: ' + group['id'])
|
||||
evidence.append(dict(group=group['id'], path=str(source), **actual))
|
||||
provenance = group['provenance']
|
||||
source = ROOT / group['paths'][0]
|
||||
if (source / '.git').exists():
|
||||
if git('rev-parse', 'HEAD', cwd=source) != provenance['commit']:
|
||||
raise ValueError('Upstream commit mismatch: ' + group['id'])
|
||||
if git('status', '--porcelain', cwd=source):
|
||||
raise ValueError('Upstream checkout changed: ' + group['id'])
|
||||
result = dict(schema='lfs-publication-source-guard-v1',
|
||||
observed_utc=datetime.now(timezone.utc).isoformat(),
|
||||
registration_sha256=REGISTERED, registered_scientific_commit=SOURCE_COMMIT,
|
||||
phase4ar_registered_sources=records, license_evidence=evidence,
|
||||
original_checkout_head=git('rev-parse', 'HEAD'),
|
||||
original_checkout_status=git('status', '--porcelain'),
|
||||
original_checkout_branch=git('branch', '--show-current'),
|
||||
disk_free_bytes={str(p): shutil.disk_usage(p).free for p in (ROOT, output.parent)},
|
||||
pass_all=True,
|
||||
scope='16 registered Phase 4AR source files, registration, pinned upstream checkout and license evidence; not a scientific rerun or full historical checksum audit')
|
||||
if result['original_checkout_status']:
|
||||
raise ValueError('Original checkout is no longer clean; preserve and investigate')
|
||||
if any(x < 100 * 1024**3 for x in result['disk_free_bytes'].values()):
|
||||
raise ValueError('Less than 100 GiB free reserve')
|
||||
with output.open('xb') as handle:
|
||||
handle.write(canonical(result))
|
||||
return dict(pass_all=True, receipt=str(output), registered_sources=len(records),
|
||||
original_checkout_head=result['original_checkout_head'])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--plan', required=True)
|
||||
parser.add_argument('--out', required=True)
|
||||
args = parser.parse_args()
|
||||
print(json.dumps(capture(args.plan, args.out), sort_keys=True))
|
||||
Reference in New Issue
Block a user