40-validation/validate-contract-coverage.py

#!/usr/bin/env python3
from __future__ import annotations
import argparse, json, os, sys
from pathlib import Path
from evidence_lifecycle import seal_evidence

REPOS=['enterprise-architecture','solution-architecture','engineering-platform','engineering-tools','runtime']
CHAINS=['RC-0003','RC-0004','RC-0005','RC-0006']
ROLES={'canonical-source','specialization','realization','validation','materialization','evidence'}
VALIDATOR_REF='engineering-tools:40-validation/validate-contract-coverage.py'
REVIEW_REFS=[
 'enterprise-architecture:85-reference-artifacts/reference-model/contract-coverage-review-model.md',
 'solution-architecture:98-tests/MVP-001-contract-coverage-gap-review.md',
 'engineering-platform:96-documentation/CONTRACT_COVERAGE_GAP_REVIEW.md',
 'engineering-tools:40-validation/CONTRACT_COVERAGE_GAP_REVIEW.md',
]
EVIDENCE={
 'RC-0003':'runtime:test/reports/validation/build-contract.json',
 'RC-0004':'runtime:test/reports/validation/deployment-contract.json',
 'RC-0005':'runtime:test/reports/validation/workspace-contract.json',
 'RC-0006':'runtime:test/reports/validation/validation-contract.json',
}

def atomic_json(path:Path,data:dict)->None:
 path.parent.mkdir(parents=True,exist_ok=True); tmp=path.with_suffix(path.suffix+'.tmp')
 tmp.write_text(json.dumps(data,indent=2,ensure_ascii=False)+'\n',encoding='utf-8'); os.replace(tmp,path)

def roots(container:Path)->dict[str,Path]:
 out={}
 for rid in REPOS:
  p=container/rid; m=p/'manifest.json'
  if not m.is_file(): raise RuntimeError(f'missing repository manifest: {rid}')
  d=json.loads(m.read_text()); ident=d.get('artifactName') or d.get('repository',{}).get('id')
  if ident!=rid: raise RuntimeError(f'repository identity mismatch: {rid}')
  out[rid]=p
 return out

def resolve(rs,ref):
 rid,rel=ref.split(':',1); p=rs[rid]/rel
 if not p.is_file(): raise RuntimeError(f'missing required artifact: {ref}')
 return p

def passed(d):
 for key in ('status','result','overallStatus'):
  v=d.get(key)
  if isinstance(v,str) and v.upper() in {'PASSED','PASS','SUCCESS','SUCCEEDED','VALIDATED'}: return True
 return False

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-0017'); a=ap.parse_args()
 rs=roots(a.repository_roots.resolve()); rt=a.runtime_root.resolve()
 for ref in REVIEW_REFS: resolve(rs,ref)
 coverage=[]; refs=list(REVIEW_REFS)
 for cid in CHAINS:
  found={}; chain_refs=[]
  for rid in REPOS:
   manifest=json.loads((rs[rid]/'manifest.json').read_text())
   chain=next((c for c in manifest.get('realizationChains',[]) if c.get('id')==cid),None)
   if chain:
    for art in chain.get('artifacts',[]):
     role=art.get('role'); ref=art.get('reference')
     if not role or not ref: continue
     qualified=ref if ':' in ref else f'{rid}:{ref}'
     resolve(rs,qualified); found.setdefault(role,[]).append(qualified); chain_refs.append(qualified)
  missing=sorted(ROLES-set(found))
  evref=EVIDENCE[cid]; ev=json.loads(resolve(rs,evref).read_text())
  ev_ok=passed(ev) and ev.get('freshness')=='fresh'
  status='PASSED' if not missing and ev_ok else 'FAILED'
  coverage.append({'chain':cid,'status':status,'roles':sorted(found),'missingRoles':missing,'evidence':evref,'evidenceFresh':ev.get('freshness')=='fresh'})
  refs.extend(chain_refs+[evref])
 blockers=[x for x in coverage if x['status']!='PASSED']
 optional=[{'id':'OPT-CC-001','classification':'optional-improvement','title':'Dedicated promotion contract realization chain','roadmapImpact':'none','completionBlocking':False}]
 manifest={'schemaVersion':'1.0','reviewId':a.work_item,'workItem':a.work_item,'status':'PASSED' if not blockers else 'FAILED','mandatoryScope':CHAINS,'coverage':coverage,'completionBlockers':blockers,'optionalFindings':optional,'phase3CompletionCriteriaMet':not blockers,'mandatoryRoadmapChangeRequired':False,'deploymentPerformed':False,'promotionPerformed':False}
 mp=rt/'test/validations'/a.work_item/'contract-coverage-review-manifest.json'; atomic_json(mp,manifest)
 refs.append(f'runtime:test/validations/{a.work_item}/contract-coverage-review-manifest.json')
 ev=seal_evidence(manifest,a.repository_roots.resolve(),VALIDATOR_REF,sorted(set(refs)),work_item=a.work_item,chain='RC-0003-RC-0006')
 atomic_json(rt/'test/reports/validation/contract-coverage-gap-review.json',ev)
 print(json.dumps({'status':manifest['status'],'mandatoryChains':len(CHAINS),'completionBlockers':len(blockers),'optionalFindings':len(optional),'phase3CompletionCriteriaMet':not blockers},indent=2))
 if blockers: raise SystemExit(1)
if __name__=='__main__':
 try: main()
 except Exception as exc: print('ERROR: '+str(exc),file=sys.stderr); raise SystemExit(1)