#!/usr/bin/env python3
"""Fail-closed verifier for retained split-origin auth evidence."""
import argparse, base64, hashlib, json, os, pathlib, re, stat, subprocess, sys, tempfile

ROOT=pathlib.Path(__file__).resolve().parent; SCHEMAS=ROOT/'schemas'
TEST_KEY_LABEL='NON-PRODUCTION TEST KEY'
TEST_PUBLIC_KEYS={'xTzSDBlljU30E9zhYnjcXspioOFliGaQQM+EqonH42A=','dpSO9r3ehAaPK7LUhRwSRdBzpKFlcLJpSTbYUiBI0xw='}
FIXTURE_ENV={'mode':'fixture','registry_id':'auth-split-fixture-v1'}
FORMS={'raw','url','base64','hex'}
SECRET_RE=re.compile(rb'(?i)(cookie|password|csrf|refresh[_-]?token|access[_-]?token|reset[_-]?token|authorization|set-cookie|request[_-]?body|response[_-]?body)\s*[:=]\s*(?!\[REDACTED:)[^\s,;}]{4,}')
ORACLE_REGISTRY=ROOT/'oracle-registry.json'
ROUTES=("/auth/register","/auth/login","/auth/logout","/auth/session","/auth/csrf","/auth/refresh-csrf","/auth/refresh","/auth/password-reset/request","/auth/password-reset/complete")
OPTIONS=("options-success","options-acr-method-absent","options-acr-method-malformed","options-acr-method-wrong","options-acr-header-extra")
SEAMS=(
"register.before-idempotency-claim","register.after-idempotency-claim-before-user","register.after-user-before-session","register.after-session-before-outcome","register.after-outcome-before-cookie-build","register.after-cookie-build-before-send","register.after-send-before-cookie-application","register.during-cookie-application",
"login.before-idempotency-claim","login.after-idempotency-claim-before-session","login.after-session-before-outcome","login.after-outcome-before-cookie-build","login.after-cookie-build-before-send","login.after-send-before-cookie-application","login.during-cookie-application",
"csrf.after-hash-persist-before-cookie-build","csrf.after-cookie-build-before-send","csrf.after-send-before-cookie-application","csrf.during-cookie-application",
"bootstrap.before-cas","bootstrap.after-cas-before-response-build","bootstrap.after-response-build-before-send","bootstrap.after-send-before-client-generation-cas",
"refresh.before-prelookup","refresh.after-prelookup-before-csrf","refresh.after-csrf-before-rotation","refresh.after-rotation-before-outcome-commit","refresh.after-outcome-commit-before-cookie-build","refresh.after-cookie-build-before-send","refresh.after-send-before-cookie-application","refresh.during-cookie-application","refresh.after-cookie-application-before-generation-cas",
"logout.after-prelookup-before-csrf","logout.after-csrf-before-revoke","logout.after-revoke-before-outcome","logout.after-outcome-before-clear-build","logout.after-clear-build-before-send","logout.after-send-before-cookie-application","logout.during-cookie-application",
"reset-request.after-token-persist-before-enqueue","reset-request.after-enqueue-before-accepted-outcome","reset-request.after-accepted-outcome-before-send","reset-request.after-send-connection-loss",
"password-reset.after-password-write-before-session-invalidate","password-reset.after-session-invalidate-before-clear-build","password-reset.after-clear-build-before-send","password-reset.after-send-before-cookie-application","password-reset.during-cookie-application")
REQUIRED={"environment.json":"environment-input.schema.json","summary.json":"summary.schema.json","sink-inventory.json":"sink-inventory.schema.json","artifact-manifest.json":"artifact-manifest.schema.json"}
NDJSON={"cases.ndjson":"case-oracle.schema.json","sink-search.ndjson":"sink-search.schema.json","db-assertions.ndjson":"db-assertions.schema.json","crash-injection.ndjson":"crash-injection.schema.json"}
CONTROL={"SHA256SUMS","artifact-manifest.json","summary.json"}
HOP_CLASSES={"browser-client","service-worker","browser-extension","browser-automation","packet-debug-capture","api-gateway-load-balancer","edge-cdn-waf","reverse-proxy","service-mesh-sidecar","app-runtime","request-response-log","observability","database-log","process-dump","crash-reporter","session-replay","support-ticket","warehouse-siem","backup-archive","deletion","vendor-transit-storage","mail-provider","mailbox","mail-queue","mail-dead-letter","mail-template-renderer","mail-template-preview-log","mail-bounce-handler","mail-complaint-handler","mail-link-scanner","mail-security-gateway","mail-link-rewriter"}
ACTUAL={
 "/auth/register":{"success","wrong-method","origin-absent","origin-disallowed","content-type-missing","content-type-unsupported","json-malformed","dto-invalid","idempotency-missing","idempotency-conflict","rate-limited","dependency-failure"},
 "/auth/login":{"success","wrong-method","origin-absent","origin-disallowed","content-type-missing","content-type-unsupported","json-malformed","dto-invalid","idempotency-missing","idempotency-conflict","auth-invalid","rate-limited","dependency-failure"},
 "/auth/logout":{"success","wrong-method","origin-absent","origin-disallowed","content-type-missing","content-type-unsupported","json-malformed","dto-invalid","idempotency-missing","idempotency-conflict","csrf-invalid","auth-invalid","rate-limited","dependency-failure","logout-partition","logout-overlap-consumed-inactive"},
 "/auth/session":{"success","wrong-method","origin-absent","origin-disallowed","content-type-unsupported","json-malformed","dto-invalid","auth-invalid","rate-limited","dependency-failure"},
 "/auth/csrf":{"success","wrong-method","origin-absent","origin-disallowed","content-type-missing","content-type-unsupported","json-malformed","dto-invalid","auth-invalid","rate-limited","dependency-failure"},
 "/auth/refresh-csrf":{"success","wrong-method","origin-absent","origin-disallowed","content-type-missing","content-type-unsupported","json-malformed","dto-invalid","auth-invalid","rate-limited","dependency-failure"},
 "/auth/refresh":{"success","wrong-method","origin-absent","origin-disallowed","content-type-missing","content-type-unsupported","json-malformed","dto-invalid","auth-invalid","csrf-invalid","rate-limited","dependency-failure","replay"},
 "/auth/password-reset/request":{"success","wrong-method","origin-absent","origin-disallowed","content-type-missing","content-type-unsupported","json-malformed","dto-invalid","rate-limited","dependency-failure","reset-non-enumeration"},
 "/auth/password-reset/complete":{"success","wrong-method","origin-absent","origin-disallowed","content-type-missing","content-type-unsupported","json-malformed","dto-invalid","auth-invalid","rate-limited","dependency-failure"}}
BROWSER={("/auth/register","cookie-policy"),("/auth/login","cookie-policy"),("/auth/session","expiry"),("/auth/refresh","expiry"),("/auth/refresh","two-tab-ordering"),("/auth/refresh","bounded-contention"),("/auth/refresh","connection-loss"),("/auth/logout","two-tab-ordering"),("/auth/password-reset/complete","cookie-policy")}

def die(reason): print(f"VERDICT=BLOCKED reason={reason}"); raise SystemExit(2)
def load(p):
 try: return json.loads(p.read_text(encoding='utf-8'))
 except Exception as e: die(f"invalid-json:{p.name}:{type(e).__name__}")
def rel_ok(s):
 p=pathlib.PurePosixPath(s); return bool(s) and not p.is_absolute() and '..' not in p.parts and str(p)==s and '\\' not in s

def canonical(v): return json.dumps(v,sort_keys=True,separators=(',',':'),ensure_ascii=False).encode()
def key_fingerprint(public_key):
 try: raw=base64.b64decode(public_key,validate=True)
 except Exception: die('invalid-trusted-key-registry')
 if len(raw)!=32: die('invalid-trusted-key-registry')
 return hashlib.sha256(raw).hexdigest()

def openssl_verify(public_raw,signature,payload):
 prefix=bytes.fromhex('302a300506032b6570032100')
 try:
  with tempfile.TemporaryDirectory(prefix='auth-split-verify-') as directory:
   d=pathlib.Path(directory); (d/'key.der').write_bytes(prefix+public_raw); (d/'sig').write_bytes(signature); (d/'msg').write_bytes(payload)
   return subprocess.run([os.environ.get('AUTH_SPLIT_OPENSSL','/usr/bin/openssl'),'pkeyutl','-verify','-rawin','-pubin','-keyform','DER','-inkey',str(d/'key.der'),'-sigfile',str(d/'sig'),'-in',str(d/'msg')],stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL).returncode==0
 except (FileNotFoundError,OSError):
  return None

def cryptography_verifier():
 if os.environ.get('AUTH_SPLIT_DISABLE_CRYPTOGRAPHY')=='1': return None
 try:
  from cryptography.exceptions import InvalidSignature
  from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
 except ImportError:
  return None
 def verify(public_raw,signature,payload):
  try: Ed25519PublicKey.from_public_bytes(public_raw).verify(signature,payload); return True
  except InvalidSignature: return False
  except (TypeError,ValueError): return False
 return verify

def probe_ed25519(force_openssl=False,force_cryptography=False):
 # RFC 8032 vector 2 uses a nonempty message supported by both cryptography and OpenSSL pkeyutl;
 # the corrupted-signature negative check rejects absent and always-success implementations.
 pub=bytes.fromhex('3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c')
 sig=bytes.fromhex('92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00')
 message=bytes.fromhex('72')
 verify=None if force_openssl else cryptography_verifier()
 if force_cryptography and verify is None: die('ed25519-validator-unavailable')
 if verify is None: verify=openssl_verify
 good=verify(pub,sig,message); bad=verify(pub,bytes([sig[0]^1])+sig[1:],message)
 if good is not True or bad is not False: die('ed25519-validator-unavailable')
 return verify

def verify_sig(pub,sig,payload,label,verify):
 try:
  public_raw=base64.b64decode(pub,validate=True); signature=base64.b64decode(sig,validate=True)
  if len(public_raw)!=32 or len(signature)!=64: raise ValueError()
 except Exception: die(f'invalid-ed25519-signature:{label}')
 if verify(public_raw,signature,payload) is not True: die(f'invalid-ed25519-signature:{label}')

def digest_file(p): return hashlib.sha256(p.read_bytes()).hexdigest()
def digest_value(v): return hashlib.sha256(canonical(v)).hexdigest()
def exact_ref(run, ref, claimed, label):
 if not rel_ok(ref) or not (run/ref).is_file(): die(f'{label}-reference')
 actual=digest_file(run/ref)
 if claimed!=actual or claimed=='0'*64: die(f'{label}-digest')
 return actual
def crash_route(seam):
 prefix=seam.split('.')[0]
 return {'reset-request':'/auth/password-reset/request','password-reset':'/auth/password-reset/complete','bootstrap':'/auth/refresh-csrf','register':'/auth/register','login':'/auth/login','csrf':'/auth/csrf','refresh':'/auth/refresh','logout':'/auth/logout'}[prefix]
def expected_identities():
 out={(route,'options',category,None) for route in ROUTES for category in OPTIONS}
 out|={(route,'actual',category,None) for route,categories in ACTUAL.items() for category in categories}
 out|={(route,'browser',category,None) for route,category in BROWSER}
 out|={(crash_route(seam),'crash','connection-loss',seam) for seam in SEAMS}
 return out
def parse_sums(p):
 rows={}
 for n,line in enumerate(p.read_text(encoding='utf-8').splitlines(),1):
  m=re.fullmatch(r'([0-9a-f]{64})  ([^\r\n]+)',line)
  if not m or not rel_ok(m.group(2)) or m.group(2) in rows: die(f'invalid-SHA256SUMS:{n}')
  rows[m.group(2)]=m.group(1)
 return rows

def scan_tree(run):
 if stat.S_IFMT(os.lstat(run).st_mode)!=stat.S_IFDIR: die('evidence-root-not-directory')
 root=run.resolve(); actual={}; inodes={}
 for parent,dirs,files in os.walk(run,followlinks=False):
  for name in [*dirs,*files]:
   p=pathlib.Path(parent)/name; st=os.lstat(p); rel=p.relative_to(run).as_posix(); mode=stat.S_IFMT(st.st_mode)
   if mode==stat.S_IFLNK: die(f'artifact-symlink:{rel}')
   if name in dirs and mode!=stat.S_IFDIR: die(f'artifact-nondirectory:{rel}')
   if name in files:
    if mode!=stat.S_IFREG: die(f'artifact-nonregular:{rel}')
    if st.st_nlink!=1 or (st.st_dev,st.st_ino) in inodes: die(f'artifact-hardlink:{rel}')
    inodes[(st.st_dev,st.st_ino)]=rel
    if not rel_ok(rel) or root not in p.resolve().parents: die(f'artifact-path-escape:{rel}')
    actual[rel]=(p,mode)
 return actual

def validate_tree(run, docs):
 actual=scan_tree(run)
 sums=parse_sums(run/'SHA256SUMS')
 if set(sums)!=(set(actual)-{'SHA256SUMS'}): die('SHA256SUMS-set-mismatch')
 for r,d in sums.items():
  if digest_file(actual[r][0])!=d: die(f'SHA256SUMS-digest:{r}')
 listed={x['path']:x for x in docs['artifact-manifest.json']['files']}; summary={x['path']:x for x in docs['summary.json']['artifacts']}; payload=set(actual)-CONTROL
 if set(listed)!=payload or set(summary)!=payload or set(listed)!=set(summary): die('artifact-closed-set-mismatch')
 for r in payload:
  p,mode=actual[r]; d=digest_file(p); b=os.lstat(p).st_size
  if listed[r]['sha256']!=d or listed[r]['bytes']!=b or summary[r]['sha256']!=d or summary[r]['bytes']!=b: die(f'artifact-manifest-mismatch:{r}')
 return {r:(mode,digest_file(p),os.lstat(p).st_size) for r,(p,mode) in actual.items()}

def main():
 ap=argparse.ArgumentParser(); ap.add_argument('--input',required=True); ap.add_argument('--manifest',required=True); ap.add_argument('--publication'); ap.add_argument('--trusted-key-registry'); ap.add_argument('--test-fixture',action='store_true',help=argparse.SUPPRESS); ap.add_argument('--force-openssl-fallback',action='store_true',help=argparse.SUPPRESS); ap.add_argument('--force-cryptography',action='store_true',help=argparse.SUPPRESS); a=ap.parse_args()
 if a.force_openssl_fallback and a.force_cryptography: die('ed25519-backend-selection-conflict')
 ed25519_verify=probe_ed25519(a.force_openssl_fallback,a.force_cryptography)
 run=pathlib.Path(a.input).resolve(); mp=pathlib.Path(a.manifest).resolve()
 try: import jsonschema
 except ImportError: die('jsonschema-validator-unavailable')
 if not run.is_dir() or mp.parent!=run or mp.name!='auth-sensitive-data-flow.json': die('production-evidence-directory-missing')
 schemas={p.name:load(p) for p in SCHEMAS.glob('*.schema.json')}
 needed=set(REQUIRED.values())|set(NDJSON.values())|{'redaction-manifest.schema.json'}
 if set(schemas)!=needed: die('required-schema-set-mismatch')
 scan_tree(run)
 for n,s in schemas.items():
  try: jsonschema.Draft202012Validator.check_schema(s)
  except Exception: die(f'invalid-metaschema:{n}')
 def validate(v,n):
  try: jsonschema.Draft202012Validator(schemas[n],format_checker=jsonschema.FormatChecker()).validate(v)
  except Exception as e: die(f'schema:{n}:{type(e).__name__}')
 docs={}
 for f,s in REQUIRED.items():
  if not (run/f).is_file(): die(f'missing:{f}')
  docs[f]=load(run/f); validate(docs[f],s)
 for f,s in NDJSON.items():
  if not (run/f).is_file(): die(f'missing:{f}')
  rows=[]
  for n,line in enumerate((run/f).read_text(encoding='utf-8').splitlines(),1):
   if not line.strip(): die(f'blank-ndjson:{f}:{n}')
   try: row=json.loads(line)
   except Exception: die(f'invalid-ndjson:{f}:{n}')
   validate(row,s); rows.append(row)
  if not rows: die(f'empty:{f}')
  docs[f]=rows
 manifest=load(mp); validate(manifest,'redaction-manifest.schema.json')
 digest=digest_file(mp); env=docs['environment.json']; inv=docs['sink-inventory.json']; summary=docs['summary.json']
 if env['redaction_manifest']['sha256']!='sha256:'+digest or inv['manifest_sha256']!=digest: die('manifest-digest-mismatch')
 if inv['entries']!=manifest['entries']: die('inventory-not-exact-manifest-copy')
 entries=manifest['entries']; ids=[e['hop_id'] for e in entries]
 if {e['class'] for e in entries}!=HOP_CLASSES: die('hop-class-coverage-not-exact')
 if len(ids)!=len(set(ids)): die('duplicate-hop-id')
 byid={e['hop_id']:e for e in entries}; probes=[]
 for e in entries:
  payload={k:v for k,v in e.items() if k!='owner_signature'}
  verify_sig(e['owner_public_key'],e['owner_signature'],canonical(payload),f"owner:{e['hop_id']}",ed25519_verify)
  for u in e['upstream']:
   if u not in byid or e['hop_id'] not in byid[u]['downstream']: die('nonreciprocal-upstream')
  for d in e['downstream']:
   if d not in byid or e['hop_id'] not in byid[d]['upstream']: die('nonreciprocal-downstream')
  if e['enabled']: probes.append(e['probe_id'])
 if len(probes)!=len(set(probes)): die('duplicate-enabled-probe')
 sec={k:v for k,v in manifest.items() if k!='security_signature'}
 verify_sig(manifest['security_public_key'],manifest['security_signature'],canonical(sec),'security-closure',ed25519_verify)
 fixture_labelled=TEST_KEY_LABEL in manifest['security_owner'] or any(TEST_KEY_LABEL in e['owner'] for e in entries)
 fixture_keyed=manifest['security_public_key'] in TEST_PUBLIC_KEYS or any(e['owner_public_key'] in TEST_PUBLIC_KEYS for e in entries)
 if a.test_fixture:
  if env.get('fixture_environment')!=FIXTURE_ENV: die('fixture-environment-metadata')
  if not fixture_keyed or {manifest['security_public_key'],*(e['owner_public_key'] for e in entries)}!=TEST_PUBLIC_KEYS: die('fixture-key-fingerprint-mismatch')
  trusted={'security_fingerprints':[key_fingerprint(manifest['security_public_key'])],'owner_fingerprints':[key_fingerprint(next(iter(entries))['owner_public_key'])]}
 else:
  if fixture_keyed: die('non-production-test-key')
  if not a.trusted_key_registry: die('trusted-key-registry-required')
  registry_path=pathlib.Path(a.trusted_key_registry)
  if run==registry_path.resolve() or run in registry_path.resolve().parents or registry_path.resolve() in run.parents: die('trusted-key-registry-inside-evidence')
  trusted=load(registry_path)
  if set(trusted)!={'security_fingerprints','owner_fingerprints'}: die('invalid-trusted-key-registry')
 if key_fingerprint(manifest['security_public_key']) not in trusted['security_fingerprints']: die('untrusted-security-key')
 if any(key_fingerprint(e['owner_public_key']) not in trusted['owner_fingerprints'] for e in entries): die('untrusted-owner-key')
 roots=[x for x in ids if not byid[x]['upstream']]
 if len(roots)!=1: die('redaction-graph-root-count')
 seen=set(roots); stack=list(roots)
 while stack:
  stack.extend(d for d in byid[stack.pop()]['downstream'] if d not in seen and not seen.add(d))
 if set(ids)!=seen: die('redaction-graph-not-root-connected')
 searches=docs['sink-search.ndjson']; got=[r['probe_id'] for r in searches]
 if sorted(got)!=sorted(probes) or len(got)!=len(set(got)): die('enabled-probe-coverage-not-exact')
 if any(r['hop_id'] not in byid or byid[r['hop_id']]['probe_id']!=r['probe_id'] for r in searches): die('probe-hop-reference')
 for r in searches:
  if set(r['forms_searched'])!=FORMS or len(r['forms_searched'])!=4: die('forms-searched-not-exact')
  required=sorted(byid[r['hop_id']]['data_classes'])
  if sorted(r['marker_classes'])!=required or r['marker_hits']!=len(required): die('marker-coverage-not-exact')
 cases=docs['cases.ndjson']; cids=[r['case_id'] for r in cases]
 oracle_doc=load(ORACLE_REGISTRY)
 if set(oracle_doc)!={'schema_version','oracles'} or oracle_doc['schema_version']!='1.0.0': die('oracle-registry-invalid')
 oracle_map={(o['route'],o['phase'],o['category'],o['crash_seam'],o['oracle_id']):o for o in oracle_doc['oracles']}
 if len(oracle_map)!=len(oracle_doc['oracles']): die('oracle-registry-duplicate')
 if len(cids)!=len(set(cids)): die('duplicate-current-case')
 identities=[(r['route'],r['phase'],r['category'],r['crash_seam']) for r in cases]
 if len(identities)!=len(set(identities)): die('duplicate-expected-identity')
 if set(identities)!=expected_identities(): die('expected-identity-set-mismatch')
 for r in cases:
  if r['canonical_outcome_sha256']!=digest_value(r['observed']) or r['canonical_outcome_sha256']=='0'*64: die(f'canonical-outcome-digest:{r["case_id"]}')
  exact_ref(run,r['trace_ref'],r['trace_sha256'],'trace')
  if not r['evidence_refs']: die('empty-evidence-refs')
  for ref in r['evidence_refs']: exact_ref(run,ref['path'],ref['sha256'],'evidence')
 if {r['route'] for r in cases}!=set(ROUTES): die('nine-route-coverage')
 for route in ROUTES:
  if {(r['phase'],r['category']) for r in cases if r['route']==route and r['phase']=='options'}!={("options",c) for c in OPTIONS}: die(f'options-coverage:{route}')
  if not any(r['route']==route and r['phase']=='actual' for r in cases): die(f'actual-coverage:{route}')
 for r in cases:
  oracle=oracle_map.get((r['route'],r['phase'],r['category'],r['crash_seam'],r['oracle_id']))
  if not oracle: die(f'oracle-missing:{r["case_id"]}')
  if oracle['expected']!=r['observed'] or oracle['expected_side_effects']!=r['observed_side_effects']: die(f'oracle-mismatch:{r["case_id"]}')
 dbrows=docs['db-assertions.ndjson']; dbids=[r['assertion_id'] for r in dbrows]; sinkids={r['probe_id'] for r in searches}
 if len(dbids)!=len(set(dbids)): die('duplicate-db-assertion')
 dbmap={r['assertion_id']:r for r in dbrows}; casemap={r['case_id']:r for r in cases}
 if any(not r['db_assertions'] or not r['sink_probes'] or set(r['db_assertions'])-set(dbids) or set(r['sink_probes'])-sinkids for r in cases): die('case-reference-closure')
 referenced=[x for r in cases for x in r['db_assertions']]
 if set(referenced)!=set(dbids) or len(referenced)!=len(set(referenced)): die('db-reference-not-exact')
 for d in dbrows:
  c=casemap.get(d['case_id'])
  if not c or d['assertion_id'] not in c['db_assertions'] or d['action']!=c['category']: die('db-case-action-reference')
  oracle=oracle_map[(c['route'],c['phase'],c['category'],c['crash_seam'],c['oracle_id'])]
  if d['observed_delta']!=oracle['expected_side_effects'] or d['observed_delta']!=c['observed_side_effects']: die('db-operation-delta')
  ctx=c['db_context']
  if any(d[k]!=ctx[k] for k in ('owner_id','session_id','family_id','generation_before','generation_after')): die('db-context-mismatch')
  derived=d['generation_before']+d['observed_delta'].get('family_generation',0)
  if d['generation_after']!=derived: die('db-generation-delta')
 seams=[r['crash_seam'] for r in cases if r['phase']=='crash']
 if set(seams)!=set(SEAMS) or len(seams)!=len(SEAMS): die('crash-seam-coverage-not-exact')
 ci=docs['crash-injection.ndjson']; fired=[r['seam'] for r in ci]
 if set(fired)!=set(SEAMS) or len(fired)!=len(SEAMS): die('crash-injection-coverage-not-exact')
 allrows=[*cases,*searches,*docs['db-assertions.ndjson'],*ci]
 referenced_paths={r['trace_ref'] for r in cases}|{x['path'] for r in cases for x in r['evidence_refs']}
 for ref in referenced_paths:
  data=(run/ref).read_bytes()
  if SECRET_RE.search(data): die(f'secret-bearing-retained-artifact:{ref}')
 counts={"pass":sum(r['verdict']=='PASS' for r in allrows),"fail":sum(r['verdict']=='FAIL' for r in allrows),"blocked":sum(r['verdict']=='BLOCKED' for r in allrows)}
 if summary['counts']!=counts: die('summary-count-mismatch')
 if not env['production_equivalent'] or not summary['production_equivalent'] or not summary['environment_valid'] or summary['verdict']!='PASS' or counts['fail'] or counts['blocked']: die('non-pass-record')
 tree=validate_tree(run,docs)
 if not a.publication: die('publication-copy-not-provided')
 pub=pathlib.Path(a.publication).resolve()
 if pub==run or not pub.is_dir(): die('publication-copy-missing-or-not-distinct')
 pubdocs={f:load(pub/f) for f in REQUIRED}; pubtree=validate_tree(pub,pubdocs)
 if tree!=pubtree or set(tree)!=set(pubtree): die('publication-copy-byte-mismatch')
 if a.test_fixture:
  print(f"FIXTURE_VERDICT=PASS run_id={env['run_id']} cases={len(cases)} sinks={len(entries)}")
 else:
  print(f"VERDICT=PASS run_id={env['run_id']} cases={len(cases)} sinks={len(entries)}")
if __name__=='__main__':
 try: main()
 except SystemExit: raise
 except (OSError,ValueError,TypeError,KeyError,json.JSONDecodeError) as exc: die(f'input-validation-error:{type(exc).__name__}')
