40-validation/validate-deployment-contract.py

#!/usr/bin/env python3
from __future__ import annotations
import argparse, hashlib, json, os, sys
from pathlib import Path
from evidence_lifecycle import seal_evidence
VALIDATOR_REF='engineering-tools:40-validation/validate-deployment-contract.py'

def sha256(p):
    h=hashlib.sha256()
    with p.open('rb') as f:
        for c in iter(lambda:f.read(1024*1024),b''): h.update(c)
    return h.hexdigest()

def atomic_json(p,d):
    p.parent.mkdir(parents=True,exist_ok=True); t=p.with_suffix(p.suffix+'.tmp'); t.write_text(json.dumps(d,indent=2,ensure_ascii=False)+'\n'); os.replace(t,p)

def main():
    ap=argparse.ArgumentParser(); ap.add_argument('repository_roots',type=Path); ap.add_argument('runtime_root',type=Path); ap.add_argument('--work-item',default='KT-0014'); a=ap.parse_args()
    roots=a.repository_roots.resolve(); rt=a.runtime_root.resolve()
    required=[roots/'enterprise-architecture/85-reference-artifacts/reference-model/deployment-contract-model.md',roots/'solution-architecture/90-deployment/MVP-001-deployment-transaction-contract.md',roots/'engineering-platform/00-overview/DEPLOYMENT_ARCHITECTURE.md',roots/'engineering-platform/00-overview/DEPLOYMENT_CONTRACT.md']
    missing=[str(p) for p in required if not p.is_file()]
    if missing: raise RuntimeError('missing deployment-contract artifacts: '+', '.join(missing))
    dep=rt/'test/installations/engineering-platform/deployment.json'; binary=rt/'test/installations/engineering-platform/bin/engineering-platform'
    if not dep.is_file() or not binary.is_file(): raise RuntimeError('missing deployment materialization')
    d=json.loads(dep.read_text())
    if d.get('status')!='SUCCESS': raise RuntimeError('deployment status is not SUCCESS')
    required_phases=['goTest','goVet','backendBuild','javascriptTest','portOwnership','serviceStop','install','serviceStart','service','health','smoke','frontendDeployment']
    phases=d.get('phases',{}); bad=[x for x in required_phases if phases.get(x)!='PASSED']
    if bad: raise RuntimeError('deployment phases not passed: '+', '.join(bad))
    actual=sha256(binary)
    if d.get('binarySha256')!=actual: raise RuntimeError('installed binary hash mismatch')
    result={'schemaVersion':'1.0','evidenceId':'engineering-platform-deployment-contract','workItem':a.work_item,'chain':'RC-0004','status':'SUCCESS','deploymentId':d.get('deploymentId'),'environment':d.get('environment'),'service':d.get('service'),'installedBinarySha256':actual,'frontendSha256':d.get('frontendSha256'),'frontendMode':d.get('frontendMode'),'phases':phases,'promotionPerformed':False}
    evidence=seal_evidence(result,roots,VALIDATOR_REF,['runtime:test/installations/engineering-platform/deployment.json','runtime:test/installations/engineering-platform/bin/engineering-platform'],work_item=a.work_item,chain='RC-0004')
    atomic_json(rt/'test/reports/validation/deployment-contract.json',evidence)
    print(json.dumps({'status':'PASSED','chain':'RC-0004','deploymentId':d.get('deploymentId')},indent=2))
if __name__=='__main__':
    try: main()
    except Exception as e: print('ERROR: '+str(e),file=sys.stderr); raise SystemExit(1)