getFile()).':'.$e->getLine().PHP_EOL); exit(1); }); $phase=$argv[1]??'before'; if (!in_array($phase,['before','resources','installed','bootstrap'],true)) throw new RuntimeException('Unknown verification phase'); $root=dirname(__DIR__);$private=$root.'/storage/app/private/investmoz-v2/portal-audit'; require $root.'/vendor/autoload.php'; $revision='41caea0f900fe0705e84e065a65f9534ff3a585ee34098f860666f522875a3a5'; $candidate=$root.'/outputs/investmoz-workflow-consolidated-20261011'; $manifest=json_decode(file_get_contents($candidate.'/manifesto.json'),true,512,JSON_THROW_ON_ERROR); $backup=json_decode(file_get_contents($private.'/workflow-execution-target-backup.json'),true,512,JSON_THROW_ON_ERROR); $stage=json_decode(file_get_contents($private.'/workflow-activation-stage.json'),true,512,JSON_THROW_ON_ERROR); throw_unless($manifest['revision']===$revision && $stage['revision']===$revision && $backup['candidate_revision']===$revision,RuntimeException::class,'Revision differs'); throw_unless(hash_file('sha256',$candidate.'/investmoz-workflow-41caea0f900f.zip')==='1d6dece89817459f012909f309f688e85e40752e845469855b2dce101879c102' && hash_file('sha256',$candidate.'/manifesto.json')==='092ea57b5eeecef8ae1c44f4c1a8dd6c95d63af5e4448524c46a9e251b1d9b63',RuntimeException::class,'Package differs'); throw_unless($backup['restore_verified'] && $backup['files_verified'] && $backup['file_count']===867 && time()-strtotime($backup['verified_at'])<86400 && strtotime($backup['verified_at'])<=time(),RuntimeException::class,'Recovery proof expired'); throw_unless(hash_file('sha256',$backup['dump_path'])===$backup['dump_sha256'],RuntimeException::class,'Recovery dump differs'); foreach($manifest['payload'] as $name=>$hash) throw_unless(is_file($root.'/'.$name) && hash_file('sha256',$root.'/'.$name)===$hash,RuntimeException::class,'Deployed payload differs'); foreach($manifest['unchanged_dependencies'] as $name=>$hash) throw_unless(hash_file('sha256',$root.'/'.$name)===$hash,RuntimeException::class,'Preserved dependency differs'); throw_unless(hash_file('sha256',$root.'/.env')===$stage['unchanged_env_sha256'],RuntimeException::class,'Environment/cipher configuration differs'); $app=require $root.'/bootstrap/app.php'; (new Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables)->bootstrap($app); (new Illuminate\Foundation\Bootstrap\LoadConfiguration)->bootstrap($app); $c=config('database.connections.'.config('database.default')); throw_unless($c['driver']==='pgsql' && empty($c['url']) && in_array($c['host'],['127.0.0.1','localhost'],true) && (int)$c['port']===5432 && $c['database']==='investemoz' && !$app->configurationIsCached(),RuntimeException::class,'Exact destination required'); throw_unless(config('app.url')==='https://portal-investidor.test' && config('portal.organization_country_enabled') && !config('portal.notifications_enabled') && count(config('portal.notification_recipients',[]))===0,RuntimeException::class,'Preserved URL/country/notification configuration differs'); foreach(['portal.profile_access_enabled','portal.opportunity_dashboard_access_enabled','portal.opportunity_specialized_drafts_enabled','portal.opportunity_modular_profiles_qa_enabled','portal.legacy_content_qa_enabled','investor-path.enabled'] as $flag) throw_unless(!config($flag),RuntimeException::class,'Operational flows must remain OFF'); $pdo=new PDO('pgsql:host='.$c['host'].';port=5432;dbname=investemoz',$c['username'],$c['password'],[PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION]); $cloneName=$backup['restored_target']['database']; throw_unless(preg_match('/^workflow_consolidated_target_\d{8}_\d{6}_test$/D',$cloneName) && $backup['restored_target']['port']===55441,RuntimeException::class,'Exact restored QA clone required'); $clone=new PDO('pgsql:host=127.0.0.1;port=55441;dbname='.$cloneName,'postgres','',[PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION]); foreach([$pdo,$clone] as $connection) $connection->exec('BEGIN ISOLATION LEVEL REPEATABLE READ READ ONLY'); $identity=$pdo->query("SELECT current_database() AS database,inet_server_port() AS port,current_setting('transaction_read_only') AS read_only")->fetch(PDO::FETCH_ASSOC); throw_unless($identity['database']==='investemoz' && (int)$identity['port']===5432 && $identity['read_only']==='on' && (int)$clone->query('SELECT inet_server_port()')->fetchColumn()===55441,RuntimeException::class); $quote=static fn(string $v):string=>'"'.str_replace('"','""',$v).'"'; $cloneTables=$clone->query("SELECT schemaname,tablename FROM pg_tables WHERE schemaname NOT IN ('pg_catalog','information_schema') ORDER BY schemaname,tablename")->fetchAll(PDO::FETCH_ASSOC); throw_unless(count($cloneTables)===288,RuntimeException::class,'Restored previous schema differs'); $transient=['sessions','cache','cache_locks'];$appendOnly=['audit_events','migrations'];$preserved=0;$transientChanged=[];$appendCounts=[]; foreach($cloneTables as $table){ $name=$quote($table['schemaname']).'.'.$quote($table['tablename']); $stmt=$clone->prepare('SELECT column_name FROM information_schema.columns WHERE table_schema=? AND table_name=? ORDER BY ordinal_position');$stmt->execute([$table['schemaname'],$table['tablename']]); $columns=implode(',',array_map($quote,$stmt->fetchAll(PDO::FETCH_COLUMN))); $sql='SELECT row_to_json(t)::text FROM (SELECT '.$columns.' FROM '.$name.') t ORDER BY row_to_json(t)::text'; $original=$clone->query($sql)->fetchAll(PDO::FETCH_COLUMN);$actual=$pdo->query($sql)->fetchAll(PDO::FETCH_COLUMN); throw_unless(count($original)===$backup['tables'][$name]['count'] && hash('sha256',implode("\n",$original))===$backup['tables'][$name]['sha256'],RuntimeException::class,'Physical recovery clone rows differ from verified snapshot'); if($table['schemaname']==='public' && in_array($table['tablename'],$transient,true)){ if($actual!==$original) $transientChanged[]=$table['tablename']; }elseif($table['schemaname']==='public' && in_array($table['tablename'],$appendOnly,true)){ throw_unless(count(array_diff($original,$actual))===0 && count($actual)>=count($original),RuntimeException::class,'Existing audit/migration history changed'); $appendCounts[$table['tablename']]=count($actual)-count($original); }else{throw_unless($actual===$original,RuntimeException::class,'Original non-transient rows changed');$preserved++;} } $actualTables=$pdo->query("SELECT schemaname,tablename FROM pg_tables WHERE schemaname NOT IN ('pg_catalog','information_schema') ORDER BY schemaname,tablename")->fetchAll(PDO::FETCH_ASSOC); $expectedNew=['opportunity_documents','opportunity_images','opportunity_location_drafts','portal_profile_requests','portal_role_assignments','portal_superuser_pair','portal_security_approvals'];sort($expectedNew); $baselineNames=array_map(static fn($t)=>$t['schemaname'].'.'.$t['tablename'],$cloneTables); $new=array_values(array_diff(array_map(static fn($t)=>$t['schemaname'].'.'.$t['tablename'],$actualTables),$baselineNames));sort($new); $expectedTables=$phase==='before'?[]:($phase==='resources'?['opportunity_documents','opportunity_images','opportunity_location_drafts']:$expectedNew); throw_unless($new===array_map(static fn($t)=>'public.'.$t,$expectedTables),RuntimeException::class,'Unexpected schema additions'); throw_unless(count($actualTables)===($phase==='before'?288:($phase==='resources'?291:295)),RuntimeException::class,'Unexpected table count'); $migration030='2026_10_09_030000_extend_opportunity_resources';$migration040='2026_10_10_040000_create_portal_profile_authorization';$migration050='2026_10_10_050000_extend_organization_country'; $actualMigrations=$pdo->query('SELECT migration FROM migrations ORDER BY migration')->fetchAll(PDO::FETCH_COLUMN); $oldMigrations=$clone->query('SELECT migration FROM migrations ORDER BY migration')->fetchAll(PDO::FETCH_COLUMN); $newMigrations=array_values(array_diff($actualMigrations,$oldMigrations));sort($newMigrations); $allowedMigrations=$phase==='before'?[]:($phase==='resources'?[$migration030]:[$migration030,$migration040]);sort($allowedMigrations); throw_unless($newMigrations===$allowedMigrations && in_array($migration050,$actualMigrations,true) && !in_array('2026_10_09_190000_prepare_investor_path',$actualMigrations,true),RuntimeException::class,'Unexpected migration application'); $sequences=$pdo->query("SELECT schemaname,sequencename FROM pg_sequences WHERE schemaname NOT IN ('pg_catalog','information_schema') ORDER BY schemaname,sequencename")->fetchAll(PDO::FETCH_ASSOC); $oldSequences=$clone->query("SELECT schemaname,sequencename FROM pg_sequences WHERE schemaname NOT IN ('pg_catalog','information_schema') ORDER BY schemaname,sequencename")->fetchAll(PDO::FETCH_ASSOC); throw_unless($sequences===$oldSequences && count($sequences)===31,RuntimeException::class,'Sequence inventory differs'); foreach($sequences as $sequence){ $name=$quote($sequence['schemaname']).'.'.$quote($sequence['sequencename']); $oldSequence=$clone->query('SELECT last_value,is_called FROM '.$name)->fetch(PDO::FETCH_ASSOC); $actualSequence=$pdo->query('SELECT last_value,is_called FROM '.$name)->fetch(PDO::FETCH_ASSOC); if($sequence['schemaname']==='public' && $sequence['sequencename']==='migrations_id_seq'){ throw_unless((int)$actualSequence['last_value']===(int)$oldSequence['last_value']+count($allowedMigrations) && $actualSequence['is_called'],RuntimeException::class,'Migration sequence differs from exactly two authorized insertions'); }else throw_unless($actualSequence===$oldSequence,RuntimeException::class,'Original non-migration sequence changed'); } $counts=[];foreach(['users','organizations','memberships','documents','opportunities','jobs','failed_jobs'] as $name) $counts[$name]=(int)$pdo->query('SELECT count(*) FROM '.$quote($name))->fetchColumn(); throw_unless($counts['users']===13 && $counts['organizations']===0 && $counts['memberships']===0 && $counts['documents']===4 && $counts['jobs']===0 && $counts['failed_jobs']===0,RuntimeException::class,'Destination identities/documents/queue no longer match scope'); $originalBytes=0; foreach($backup['active_files_manifest'] as $entry=>$hash){if(str_starts_with($entry,'private/')){ $file=$root.'/storage/app/private/'.substr($entry,8);throw_unless(is_file($file) && hash_file('sha256',$file)===$hash,RuntimeException::class,'Original binary changed');$originalBytes++; }} throw_unless($originalBytes===4,RuntimeException::class,'Original file inventory differs'); $mfa=$pdo->query('SELECT id FROM users WHERE two_factor_secret IS NOT NULL ORDER BY id')->fetchAll(PDO::FETCH_COLUMN); throw_unless(array_map('intval',$mfa)===array_map('intval',$backup['original_mfa_user_ids']),RuntimeException::class,'MFA coverage changed'); $pair=null;$audit=[];$addedColumns=0; if($phase==='resources'){ foreach($expectedTables as $name) throw_unless((int)$pdo->query('SELECT count(*) FROM '.$quote($name))->fetchColumn()===0,RuntimeException::class,'Operational resource records outside scope'); throw_unless((int)$pdo->query('SELECT count(*) FROM documents WHERE storage_encryption IS NOT NULL')->fetchColumn()===0,RuntimeException::class,'Unexpected re-encryption'); $addedColumns=1; $audit=$pdo->query("SELECT id,source_system,action,actor_user_id FROM audit_events WHERE source_system='opportunity-resources-install' ORDER BY id")->fetchAll(PDO::FETCH_ASSOC); throw_unless(count($audit)===1 && $audit[0]['action']==='opportunity_resources_installed' && (int)$audit[0]['actor_user_id']===2,RuntimeException::class,'Resource install audit differs'); }elseif($phase!=='before'){ foreach(['opportunity_documents','opportunity_images','opportunity_location_drafts','portal_profile_requests','portal_role_assignments'] as $name) throw_unless((int)$pdo->query('SELECT count(*) FROM '.$quote($name))->fetchColumn()===0,RuntimeException::class,'Operational records/grants created outside scope'); throw_unless((int)$pdo->query('SELECT count(*) FROM documents WHERE storage_encryption IS NOT NULL')->fetchColumn()===0 && (int)$pdo->query('SELECT count(*) FROM users WHERE portal_access_revision<>0')->fetchColumn()===0,RuntimeException::class,'Unexpected re-encryption/access revision'); $stmt=$pdo->query("SELECT table_name,column_name FROM information_schema.columns WHERE table_schema='public' AND ((table_name='documents' AND column_name='storage_encryption') OR (table_name='users' AND column_name='portal_access_revision') OR (table_name='opportunities' AND column_name IN('owner_user_id','organization_id','workflow_context'))) ORDER BY table_name,column_name"); $addedColumns=count($stmt->fetchAll(PDO::FETCH_ASSOC));throw_unless($addedColumns===5,RuntimeException::class,'Expected additive columns missing'); $audit=$pdo->query("SELECT id,source_system,action,actor_user_id FROM audit_events WHERE source_system IN('opportunity-resources-install','portal-profile-installation','portal-profiles-v1') ORDER BY id")->fetchAll(PDO::FETCH_ASSOC); $actions=array_column($audit,'action');throw_unless(count(array_keys($actions,'opportunity_resources_installed',true))===1 && count(array_keys($actions,'install',true))===1,RuntimeException::class,'Installation audit differs'); $pairs=$pdo->query('SELECT id,first_user_id,second_user_id,approved_by,version FROM portal_superuser_pair')->fetchAll(PDO::FETCH_ASSOC); if($phase==='bootstrap'){ throw_unless(count($pairs)===1,RuntimeException::class,'Exactly one designated pair required');$pair=array_map('intval',$pairs[0]); // The real session identifies holder 13 as requester and holder 2 as // independent confirmer. Preserve the same explicitly designated set. throw_unless($pair===['id'=>1,'first_user_id'=>13,'second_user_id'=>2,'approved_by'=>2,'version'=>1],RuntimeException::class,'Designated independent pair differs'); $approvals=$pdo->query('SELECT kind,requested_by,subject_user_id,approved_by,consumed_at IS NOT NULL AS consumed FROM portal_security_approvals')->fetchAll(PDO::FETCH_ASSOC); throw_unless(count($approvals)===1 && $approvals[0]['kind']==='bootstrap' && (int)$approvals[0]['requested_by']===13 && (int)$approvals[0]['subject_user_id']===2 && (int)$approvals[0]['approved_by']===2 && $approvals[0]['consumed'],RuntimeException::class,'Bootstrap approval receipt differs'); foreach(['bootstrap-request','bootstrap-confirm','superuser_pair_initialized'] as $action) throw_unless(count(array_keys($actions,$action,true))===1,RuntimeException::class,'Bootstrap audit missing or duplicated'); }else{throw_unless($pairs===[],RuntimeException::class,'Unexpected prior bootstrap');} } $pdo->exec('ROLLBACK');$clone->exec('ROLLBACK'); $proof=['at'=>gmdate('c'),'result'=>'passed','phase'=>$phase,'revision'=>$revision,'target'=>$identity,'schema_tables'=>count($actualTables),'new_tables'=>$new,'added_columns'=>$addedColumns, 'new_migrations'=>$newMigrations,'050000_preserved'=>true,'190000_absent'=>true,'original_tables_compared'=>count($cloneTables),'non_transient_tables_preserved'=>$preserved,'transient_tables_changed'=>$transientChanged,'append_only_counts'=>$appendCounts, 'original_files_unchanged'=>4,'MFA_envelopes_unchanged'=>count($mfa),'COUNTRY02_enabled'=>true,'operational_flags_enabled'=>false,'external_notifications_enabled'=>false, 'counts'=>$counts,'sequences_compared'=>count($sequences),'new_sequences'=>0,'resources_installed'=>$phase!=='before','profiles_installed'=>in_array($phase,['installed','bootstrap'],true),'superuser_pair'=>$pair,'audits'=>$audit,'read_only'=>true,'active_writes'=>0,'queue_jobs_consumed'=>0]; $destination=$private.'/workflow-activation-'.($phase==='installed'?'schema-verified':$phase.'-verified').'.json'; if(is_file($destination)) copy($destination,$destination.'.before-'.gmdate('Ymd_His')); file_put_contents($destination,json_encode($proof,JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR)); echo json_encode($proof,JSON_THROW_ON_ERROR).PHP_EOL;