Files
digital-drosophila/tools/lfs-publication/capture_source_guard.py
2026-10-01 03:21:48 +08:00

86 lines
4.2 KiB
Python

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