50 lines
2.4 KiB
Python
50 lines
2.4 KiB
Python
"""R5 envelope: consume/count cached-stage stdin; preserve approved R4 behavior."""
|
|
from pathlib import Path
|
|
import argparse,hashlib,importlib.util,json
|
|
HERE=Path(__file__).resolve().parent
|
|
BASE=HERE.parent/'native-r4'
|
|
OUTPUT=Path('/tmp/forge-scanner-rollout-r5-receipt.json')
|
|
R4='64c73100f2347470fda1b8b3194dbf6a8009291d9d80223af463b3c5ecab52fd'
|
|
|
|
|
|
def sha(raw):return hashlib.sha256(raw).hexdigest()
|
|
|
|
def base_module():
|
|
s=importlib.util.spec_from_file_location('approved_r4_library',BASE/'rollout.py');b=importlib.util.module_from_spec(s);s.loader.exec_module(b)
|
|
if b.verify_packet()!=R4:raise RuntimeError('reviewed-r4-packet-drift')
|
|
return b
|
|
|
|
def verify_packet():
|
|
raw=(HERE/'packet.json').read_bytes();p=json.loads(raw)
|
|
for name,digest in p['files'].items():
|
|
if Path(name).name!=name or sha((HERE/name).read_bytes())!=digest:raise RuntimeError('r5-packet-drift')
|
|
base_module();return sha(raw)
|
|
|
|
def make_runner(base,module):
|
|
class Rollout(base.make_runner(module)):
|
|
def shell(self,pod,script,args=(),data=None,timeout=90):
|
|
staging=script==(base.ORIGINAL/'stage.sh').read_text()
|
|
if staging:script=(HERE/'stage.sh').read_text()
|
|
self._cached_stage_call=staging
|
|
try:return super().shell(pod,script,args,data,timeout)
|
|
finally:self._cached_stage_call=False
|
|
def event(self,name,**fields):
|
|
if name=='command-failed' and getattr(self,'_cached_stage_call',False):fields['operation']='artifact-staging'
|
|
return super().event(name,**fields)
|
|
return Rollout
|
|
|
|
def main():
|
|
p=argparse.ArgumentParser(description=__doc__);p.add_argument('--approved-digest',required=True)
|
|
p.add_argument('--binary',type=Path,default=Path('/tmp/sync-scanner-candidate/receive-secret-scan-candidate'));a=p.parse_args()
|
|
digest=verify_packet()
|
|
if a.approved_digest!=digest:raise RuntimeError('exact-founder-acknowledgement-required')
|
|
b=base_module();m=b.support();m.RECEIPT=OUTPUT;c=json.loads((b.ORIGINAL/'inputs.json').read_bytes())
|
|
runner=make_runner(b,m)(c);runner.receipt.update(revision=5,packet_sha256=digest,reused_r4_packet_sha256=R4,mode='apply')
|
|
with OUTPUT.open('x') as f:f.write(json.dumps(runner.receipt)+'\n')
|
|
runner.apply(a.binary)
|
|
|
|
if __name__=='__main__':
|
|
try:main()
|
|
except Exception as e:
|
|
print(json.dumps({'result':'refused-or-unverified','reason':str(e) if isinstance(e,RuntimeError) else type(e).__name__}))
|
|
raise SystemExit(1)
|