"""Canonical contracts/workflow through QA HTTP with real Fortify MFA and ClamD. Sanitized output.""" from portal_profiles_http_session import Session, actors, PRIVATE, BASE, EXPECTED, REASON, events import json import uuid import hashlib import zlib import struct from datetime import datetime, timezone fixtures = json.loads((PRIVATE / 'opportunity-workflow-fixtures-qa.json').read_text()) old = json.loads((PRIVATE / 'profiles-v1-http-receipts-qa.json').read_text()) path = PRIVATE / 'opportunity-workflow-http-qa.json' proof = {'target': EXPECTED, 'url': BASE, 'started_at': datetime.now(timezone.utc).isoformat(), 'interface_demonstrated': False, 'active_writes': 0, 'cases': []} sessions = {role: Session(role).login() for role in ['promoter', 'colleague', 'validator', 'publisher', 'outsider', 'multiple', 'super_first']} promoter = sessions['promoter']; validator = sessions['validator']; publisher = sessions['publisher']; superuser = sessions['super_first'] types = ['investment_project', 'real_estate', 'land_rights', 'business_sale', 'business_partnership', 'franchise', 'concession', 'financing', 'supply', 'other'] def persist(): proof['events'] = events path.write_text(json.dumps(proof, indent=2), encoding='utf-8') def prepare_roles(): assignments=superuser.call('GET','/perfis/gestao',inertia=True)['props']['assignments'] def available(role,access,responsibilities=()): return any(g['user_id']==actors[role]['id'] and g['role']==access and g['revoked_at'] is None and set(types).issubset(json.loads(g['opportunity_types'])) and set(responsibilities).issubset(json.loads(g['responsibilities'])) for g in assignments) if available('promoter','promoter') and available('validator','validator',['validation_coordinator','technical_reviewer','legal_reviewer']) and available('publisher','publisher') and available('multiple','publisher') and available('colleague','validator',['appeal_reviewer']): return # Additional scope uses the normal request and independent approval, preserving earlier grants. request = promoter.call('POST', '/perfil/promotor', {'creation_key': str(uuid.uuid4()), 'representation_type': 'company', 'organization_id': fixtures['organization_id'], 'opportunity_types': types, 'document_ids': [old['requests']['promoter']['document_id']], 'reason': REASON}, status=201) superuser.call('POST', f"/perfis/pedidos/{request['id']}/decisao", {'version': 1, 'decision': 'approved', 'approved_types': types, 'verified_identity': True, 'verified_representation': True, 'verified_documents': True, 'reason': 'Aprovação de dados estritamente sintéticos QA; não verifica identidade real'}) for role, access, responsibilities in [('validator','validator',['validation_coordinator','technical_reviewer','legal_reviewer']), ('publisher','publisher',[]), ('multiple','publisher',[]), ('colleague','validator',['appeal_reviewer'])]: superuser.call('POST', '/perfis/atribuicoes', {'user_id': actors[role]['id'], 'role': access, 'opportunity_types': types, 'responsibilities': responsibilities, 'reason': REASON}, status=201) def page(s, id): return s.call('GET', f'/minhas-oportunidades/{id}/view', inertia=True)['props'] def transition(s, id, action, status=200, **data): p = page(s, id) if action=='publish': data={'confirm_publication':True,'preview_hash':p['profileContext'].get('approval',{}).get('hash','0'*64),**data} return s.call('POST', f'/minhas-oportunidades/{id}/workflow', {'etag': p['etag'], 'action': action, 'reason': REASON, **data}, status=status) def save(s, id, canonical, status=200): p = page(s, id) return s.call('PUT', f'/minhas-oportunidades/{id}/draft', {'etag': p['etag'], 'canonical': canonical, 'reason': REASON}, status=status) def normalized(value): if isinstance(value,str): return value.strip() if isinstance(value,list): return [normalized(v) for v in value] if isinstance(value,dict): return {k:normalized(v) for k,v in value.items()} return value def png(seed): def chunk(t, raw): return struct.pack('>I', len(raw)) + t + raw + struct.pack('>I', zlib.crc32(t + raw) & 0xffffffff) pixels = b''.join(b'\x00' + bytes([20 + seed % 200, 90, 140]) * 512 for _ in range(512)) return b'\x89PNG\r\n\x1a\n' + chunk(b'IHDR', struct.pack('>IIBBBBB', 512, 512, 8, 2, 0, 0, 0)) + chunk(b'IDAT', zlib.compress(pixels)) + chunk(b'IEND', b'') def create(category): draft = promoter.call('POST', '/minhas-oportunidades/drafts', {'creation_key': str(uuid.uuid4()), 'organization_id': fixtures['organization_id'], 'opportunity_category': category, 'title': 'Oportunidade sintética QA ' + category, 'canonical': {'opportunity_category': category}, 'reason': REASON}, status=201) id = draft['id']; transition(promoter, id, 'submit', status=422) first = page(promoter, id) assert first['canonicalOptions']['version'] == 'opportunity-canonical-adapter-20261010-semantic1' provinces = promoter.call('GET', '/minhas-oportunidades/territory-options?type=province')['data'] province = provinces[0] districts = promoter.call('GET', '/minhas-oportunidades/territory-options?type=district&parent_id=' + province['id'])['data'] district = districts[0] location = promoter.call('POST', f'/minhas-oportunidades/{id}/resources/locations', {'etag': page(promoter,id)['etag'], 'creation_key': str(uuid.uuid4()), 'reason': REASON, 'payload': {'country_code':'MZ','province_id':province['id'],'district_id':district['id'],'role':'headquarters','is_primary':True}}, status=201) raw = png(id) document = promoter.call('POST', f'/minhas-oportunidades/{id}/resources/documents', {'etag':page(promoter,id)['etag'], 'creation_key':str(uuid.uuid4()), 'reason':REASON, 'payload':json.dumps({'title':'Documento de ensaio sintético — sem validade real','category':'qa-synthetic','classification':'restricted','visibility':'internal'})}, multipart=raw, status=201) resources = promoter.call('GET',f'/minhas-oportunidades/{id}/resources',inertia=True)['props']['resources'] doc = next(d for d in resources['documents'] if d['id']==document['id']) assert doc['file']['malware_scan_status']=='clean' and doc['file']['sha256'].strip()==hashlib.sha256(raw).hexdigest() payload = {'opportunity_category':category,'official_title':'Oportunidade sintética QA '+category, 'promoter_id':fixtures['organization_id'], 'promoter_type':'private', 'internal_contact_id':fixtures['contact_id'], 'sector_id':fixtures['sector_id'], 'subsector_id':fixtures['subsector_id'], 'economic_activity_id':fixtures['activity_id'], 'location_mode':'single','geographic_scope':'district','locations':[location['id']], 'executive_summary':('Cenário sintético exclusivamente QA, sem oferta ou decisão real. ' * 7).strip(), 'demand_statement':('Necessidade fictícia para verificar recuperação e decisões independentes. '*3).strip(),'documents':[document['id']], 'evidences':[{'title':'Comprovativo sintético','evidence_type':'qa-synthetic','document_id':document['id']}], 'publication_languages':[{'language_code':'pt-MZ','title':'Oportunidade sintética QA','summary':'Texto de demonstração QA'}], 'visibility':'internal','truth_declaration':True} details = {'investment_project': {'opportunity_type':'greenfield','maturity_stage':'idea','investment_modalities':['equity'],'investor_profiles':['institutional'], 'products':[{'name':'Produto QA','category':'qa'}], 'assets':[{'name':'Ativo QA','asset_type':'qa'}], 'markets':[{'name':'Mercado QA','market_type':'domestic','country_code':'MZ'}], 'competitive_advantage':'Diferenciação fictícia exclusivamente para ensaio QA. '*4,'employment':{'employment_type':'permanent','direct_jobs':2}, 'impact_indicators':[{'impact_type':'qa','description':'Impacto fictício QA'}],'total_investment':1000,'currency':'MZN', 'investment_breakdown':[{'label':'Rubrica QA','amount':1000,'currency':'MZN'}], 'secured_financing':[], 'funding_sought':1000, 'milestones':[{'title':'Preparação QA','due_date':'2027-01-01','status':'planned'},{'title':'Execução QA','due_date':'2027-02-01','status':'planned'}], 'licenses':[{'name':'Declaração QA de aplicabilidade','authority':'Entidade fictícia QA','status':'not_applicable'}], 'environmental_social':{'assessment_status':'qa','environmental_category':'qa'},'risks':[{'title':'Risco sintético QA','category':'qa','likelihood':1,'severity':1,'mitigation':'Medida de ensaio QA'}]}, 'real_estate':{'real_estate':{'transaction_type':'sale','property_type':'warehouse','construction_status':'completed','property_condition':'good','land_area':1000,'built_area':500,'area_unit':'sqm', 'utilities':[{'type':'water','status':'existing'}], 'occupancy_status':'vacant','title_type':'qa-synthetic','registration_number':'QA-'+str(id),'encumbrances':[{'type':'none'}], 'valuation':{'value':1000,'date':'2026-10-10','method':'qa-only','appraiser':'Synthetic QA','document_id':document['id']},'price':1000,'use_license_document_id':document['id']}}, 'land_rights':{'land_rights':{'operation_type':'investment_with_duat','duat_status':'application','holder_name':'Titular sintético QA','authorized_purpose':'Preparação sintética QA', 'parcels':[{'location_id':location['id'],'area':1000,'geometry':{'type':'MultiPolygon','coordinates':[[[[40,-13],[40.001,-13],[40.001,-12.999],[40,-12.999],[40,-13]]]]},'overlap_status':'qa-pending'}], 'overlaps_conflicts':[{'type':'none','status':'qa-only'}],'required_authorizations':[{'name':'Decisão externa ainda necessária','status':'pending'}]}}, } if category in ['business_sale','business_partnership','franchise']: transaction = {'business_sale':'full_sale','business_partnership':'partnership','franchise':'franchise'}[category] details[category] = {'business_transaction':{'transaction_type':transaction,'legal_entity_id':fixtures['organization_id'],'legal_form':'Synthetic QA','asking_price':1000, 'valuation':{'value':1000,'date':'2026-10-10','method':'QA'},'financial_periods':[{'year':2025,'document_id':document['id']}], 'assets_liabilities':{'assets':'QA','liabilities':'QA'}, 'employees':{'total':2}, 'contracts_licenses':[{'name':'QA','document_id':document['id']}],'litigation_contingencies':[], 'reason_for_transaction':'Operação fictícia para validar contratos, versões e permissões em QA. ', 'confidentiality_level':'restricted','due_diligence_status':'data_room_ready', **({'percentage_offered':100 if category=='business_sale' else 40,'governance_rights':{'voting':'QA'}} if category!='franchise' else {'franchise_terms':{'brand':'QA','territory':'QA','term':'QA'}}), **({'partner_contribution':{'capital':1000,'calendar':'QA'}} if category=='business_partnership' else {})}} payload.update(details.get(category,{'module_profile_version':'qa-unapproved'})) payload=normalized(payload) save(promoter,id,payload) new = Session('promoter').login(); recovered = page(new,id) assert recovered['values']['canonical']==payload and recovered['canonicalOptions']['resources']['locations'][0]['payload']['country_code']=='MZ' proof['cases'].append({'category':category,'id':id,'draft_saved_incomplete':True,'recovered_in_new_mfa_session':True,'document_id':document['id'],'location_id':location['id'],'clamd_real_clean':True}) persist() return id, payload, document['id'] def review(id, document): gates = page(validator,id)['canonicalOptions']['gates'] for gate in gates: transition(validator,id,'review_gate',gate=gate,gate_decision='approved',evidence_document_ids=[document]) transition(validator,id,'approve') if __name__ == '__main__': try: prepare_roles() # MFA session revisions may have changed when the new scope was granted. sessions.update({r:Session(r).login() for r in ['promoter','validator','publisher','colleague','multiple']}) promoter=sessions['promoter'];validator=sessions['validator'];publisher=sessions['publisher'] nav=promoter.call('GET','/dashboard',inertia=True)['props']['profileAccess'] promoter.call('GET',nav['mine'],inertia=True);promoter.call('GET',nav['create'],inertia=True) id,payload,doc=create('investment_project') transition(promoter,id,'submit') transition(validator,id,'assign_review',assignee_user_id=actors['validator']['id']) transition(validator,id,'approve',status=422) transition(validator,id,'request_correction',correction_items=['canonical.demand_statement']) payload['demand_statement']+=' Informação sintética corrigida pelo Promotor QA.' save(promoter,id,payload);transition(promoter,id,'submit');transition(validator,id,'start_review');review(id,doc) transition(sessions['multiple'],id,'approve',status=403) transition(publisher,id,'publish') published=page(promoter,id) assert published['workflowState']=='published' and published['profileContext']['publication']['visibility']=='internal' transition(promoter,id,'request_revision');payload['official_title']+=' — alteração material';save(promoter,id,payload) revised=page(promoter,id) assert revised['record']['title']==published['record']['title'] and 'approval' not in revised['profileContext'] and revised['workflowState']=='needs_information' transition(publisher,id,'publish',status=409);transition(publisher,id,'suspend') assert page(promoter,id)['record']['status']=='suspended' proof['positive_cycle']={'id':id,'promoter':actors['promoter']['id'],'validator':actors['validator']['id'],'publisher':actors['publisher']['id'], 'path':['create','save','new_session','recover','submit','assign_review','request_correction','correct','resubmit','review_gates','approve','publish','request_revision','material_edit','suspend'], 'publication_visibility':'internal','published_columns_preserved_during_revision':True,'approval_invalidated':True,'sla_unconfigured':True} for category in ['real_estate','land_rights','business_sale','business_partnership','franchise','concession','financing','supply','other']: other,p,document=create(category) if category in ['concession','financing','supply','other']: transition(promoter,other,'submit',status=422) proof['cases'][-1]['submission']='blocked-unapproved-module-profile' else: transition(promoter,other,'submit');proof['cases'][-1]['submission']='passed' if category=='real_estate': transition(validator,other,'reject',status=422,reason='curto') transition(validator,other,'reject');transition(promoter,other,'appeal') transition(validator,other,'appeal_reopen',status=403) # Same-organization colleague with another role is still conflicted, not an appeal instance. transition(sessions['colleague'],other,'appeal_reopen',status=403) proof['reasoned_rejection_and_appeal']={'id':other,'reason_persisted':True,'same_rejector_denied':True,'organization_conflict_denied':True,'independent_appeal_decision':'pending-authorized-independent-actor'} sessions['outsider'].call('GET',f'/minhas-oportunidades/{other}/view',status=403,inertia=True) persist() proof['completed_at']=datetime.now(timezone.utc).isoformat();proof['result']='passed';persist() print(json.dumps({'result':'passed','target':EXPECTED,'categories':len(proof['cases']),'positive_cycle_id':id,'promoter_qa_id':actors['promoter']['id'],'visual_pending':True,'active_writes':0})) except Exception: proof['result']='failed';persist();raise