"""Fail-closed admission check for a candidate image-only GitOps release. This is a validator, not an authority issuer or a cluster/Git credential broker. Evidence must be supplied by the separately admitted release identity. """ import copy from datetime import datetime, timedelta, timezone import re import yaml IMAGE=re.compile(r'forgejo\.coulomb\.social/coulomb/activity-core@sha256:[0-9a-f]{64}') SHA=re.compile(r'[0-9a-f]{40}') DEPLOYS={'actcore-api','actcore-worker','actcore-event-router'} def require(condition,message): if not condition: raise ValueError(message) def indexed(docs): out={} for d in docs: require(isinstance(d,dict),'invalid resource') key=(d['kind'],d['metadata']['name']) require(key not in out and d['metadata'].get('namespace')=='activity-core','duplicate or foreign resource') out[key]=copy.deepcopy(d) return out def validate(before,after,evidence,now=None): now=now or datetime.now(timezone.utc) require(evidence.get('schema_version')==1,'unknown evidence schema') require(evidence.get('identity_admitted') is True,'release identity not admitted') require(evidence.get('authority')=='ACTIVITY-WP-0041-image-only-v1','unknown authority') require(evidence.get('review_result')=='pass' and evidence.get('checks')=='pass','checks/review not passed') require(evidence.get('reviewer') and evidence.get('reviewer')!=evidence.get('producer'),'independent review required') require(bool(SHA.fullmatch(evidence.get('candidate_commit',''))),'full candidate revision required') require(bool(SHA.fullmatch(evidence.get('rollback_commit',''))),'rollback revision required') require(evidence['candidate_commit']!=evidence['rollback_commit'],'rollback must name prior revision') observed=datetime.fromisoformat(evidence['healthy_since'].replace('Z','+00:00')) measured=datetime.fromisoformat(evidence['observed_at'].replace('Z','+00:00')) require(observed.tzinfo is not None and measured.tzinfo is not None,'timestamps need timezone') require(now-timedelta(minutes=5)<=measured<=now,'health evidence stale or future') require(measured-observed>=timedelta(hours=24),'24-hour healthy observation period incomplete') require(evidence.get('argo_synced') is True and evidence.get('argo_healthy') is True,'Argo not healthy/synced') left,right=indexed(before),indexed(after) require(left.keys()==right.keys(),'resource inventory change') changed=[] for key,a in left.items(): b=right[key] if a==b: continue require(key[0]=='Deployment' and key[1] in DEPLOYS,'only runtime images may change') ac=a['spec']['template']['spec']['containers'];bc=b['spec']['template']['spec']['containers'] require(len(ac)==len(bc)==1,'container inventory change') require(bool(IMAGE.fullmatch(bc[0].get('image',''))),'registry digest required') require(bc[0].get('imagePullPolicy')=='IfNotPresent','pull policy must support registry recovery') ac[0]['image']=bc[0]['image'];ac[0]['imagePullPolicy']=bc[0]['imagePullPolicy'] require(a==b,'non-image deployment change') changed.append(key[1]) require(bool(changed),'no release change') return {'admitted':True,'deployments':changed,'candidate_commit':evidence['candidate_commit'],'rollback_commit':evidence['rollback_commit']}