from pathlib import Path
import subprocess,sys,json,os,re
R=Path(__file__).resolve().parents[1]
cp=subprocess.run([sys.executable,str(R/'src/static_preflight_q1.py')],cwd=R,text=True,capture_output=True,env={**os.environ,'PYTHONDONTWRITEBYTECODE':'1'}); print(cp.stdout,end='')
m=re.findall(r'(\d+)/(\d+) PASS',cp.stdout); bp,bt=(map(int,m[-1]) if m else (0,1)); checks=[]
def ck(n,v): checks.append((n,bool(v)))
b=json.loads((R/'fixture/baseline-system.json').read_text()); pol=json.loads((R/'fixture/golden-policy.json').read_text()); cases=json.loads((R/'fixture/probe-cases.json').read_text())['cases']; req=json.loads((R/'requirements.json').read_text()); tmpl=json.loads((R/'evidence/Bounded_Threat_Model_Record_v1.template.json').read_text()); src=(R/'src/threat_model_fixture.py').read_text()
fields=['Asset','Security Objective','Actor','Entry Point','Data Flow','Trust Boundary','Threat Scenario','Control','Evidence Check','Residual Risk','Owner Decision Needed']
ck('actors exact',b['actors']==['owner','collaborator','unrelated_authenticated','unauthenticated'])
ck('component identifiers exact',list(b['components'])==['application_service','metadata_store','storage_adapter'])
ck('all baseline components app controlled',all(v['zone']=='APP_CONTROLLED' for v in b['components'].values()))
ck('trust boundary exact',b['trust_boundaries']==[{'id':'TB-INTERNET-APP','from':'client','to':'application_service'}])
ck('record schema exact',b['record_fields']==fields)
ck('bounded objective exact',pol['bounded_objective']=='Only owner or named collaborator can retrieve document content; unauthenticated callers cannot retrieve metadata.')
ck('negative paths exact',pol['required_negative_paths']==['unrelated_authenticated document read denied','unauthenticated metadata read denied'])
ck('forbidden conclusions exact',pol['forbidden_conclusions']==['A threat model proves the system is secure.','A generic security checklist is a complete threat model.','One green path proves production authorization.','Moving a component across a trust boundary leaves the old threat model automatically valid.'])
ck('probe ids exact',[x['id'] for x in cases]==['baseline_bounded_document_flow','unauthorized_document_read','external_storage_boundary_mutation','unauthenticated_metadata_endpoint','control_name_without_evidence_check','evidence_field_removed'])
ck('probe classes exact',[x['expected_class'] for x in cases]==['BASELINE_BOUNDED_EVIDENCE_OBSERVED','AUTHORIZATION_DENIED','TRUST_BOUNDARY_CHANGED_REVIEW_REQUIRED','UNAUTHENTICATED_METADATA_EXPOSURE','CONTROL_UNVERIFIABLE','EVIDENCE_INCOMPLETE'])
ck('defensive requirements exact',req=={'python':'>=3.10','external_network_required':False,'real_credentials_required':False,'external_target_required':False,'purpose':'deterministic synthetic defensive threat-model fixture'})
ck('evidence template core exact',list(tmpl.keys())[:11]==fields)
ck('code auth guards exact',"if p['document_read_requires_authentication'] and not authenticated:" in src and "actor not in [doc['owner'],*doc['collaborators']]" in src and "if p['metadata_requires_authentication'] and not authenticated:" in src)
ck('code semantic outputs exact',"row['Control'] and not row['Evidence Check']" in src and "['actor_and_entry_point','trust_boundary','threat_scenario','control_and_evidence']" in src)
for n,v in checks: print(('PASS' if v else 'FAIL'),n)
p=bp+sum(v for _,v in checks); t=bt+len(checks); print(f'{p}/{t} PASS'); sys.exit(0 if p==t else 1)
