[$p,hash_file('sha256',base_path($p))],$this->files()); foreach(\Illuminate\Support\Facades\File::allFiles(public_path('build')) as $file){$files[]=['public/build/'.$file->getRelativePathname(),hash_file('sha256',$file->getPathname())];} usort($files,fn($a,$b)=>strcmp($a[0],$b[0])); return hash('sha256',json_encode($files,JSON_THROW_ON_ERROR)); } private function authorize(User $actor,bool $write=false): void { abort_unless($actor->hasVerifiedEmail() && app(ScopedPortalAdministration::class)->administrative($actor),403,'Gestão autorizada necessária.'); if($write){app(PortalProfileInstallation::class)->authorize($actor,true);} } private function evidence(array $target,string $code): array { $qa=app()->environment('testing') && (int)$target['port']===55441 && str_ends_with($target['database'],'_test') && config('database.connections.pgsql.host')==='127.0.0.1' && !config('database.connections.pgsql.url'); if($qa){return ['ready'=>true,'mode'=>'isolated_QA'];} if(app()->environment('testing')){return ['ready'=>false,'mode'=>'invalid_QA'];} $path=storage_path('app/private/investmoz-v2/park-profile/readiness.json'); $p=is_file($path)?json_decode(file_get_contents($path),true):[];$date=strtotime($p['verified_at'] ?? '');$auth=$p['active_authorization'] ?? []; $ready=$target['database']==='investemoz' && (int)$target['port']===5432 && !config('database.connections.pgsql.url') && in_array(config('database.connections.pgsql.host'),['127.0.0.1','localhost'],true) && ($p['target']['database'] ?? '')===$target['database'] && (int)($p['target']['port'] ?? 0)===(int)$target['port'] && ($p['code_hash'] ?? '')===$code && $date!==false && $date<=time() && $date>=time()-86400 && ($p['restore_verified'] ?? false) && ($p['files_verified'] ?? false) && ($p['qa_passed'] ?? false) && ($p['compatibility_verified'] ?? false) && ($p['restored_boot_verified'] ?? false) && (int)($p['restore_target']['port'] ?? 0)===55441 && str_ends_with($p['restore_target']['database'] ?? '','_test') && ($auth['decision_record'] ?? '')!=='' && ($auth['code_hash'] ?? '')===$code && ($auth['migration'] ?? '')===InvestmentZoneOperationalProfile::MIGRATION; foreach(['dump','files'] as $kind){$file=$p[$kind.'_path'] ?? ''; $ready=$ready && is_file($file) && hash_equals($p[$kind.'_sha256'] ?? '',hash_file('sha256',$file));} return ['ready'=>$ready,'mode'=>'active_requires_own_authorization_and_fresh_proof','proof_hash'=>is_file($path)?hash_file('sha256',$path):null]; } public function preview(User $actor): array { $this->authorize($actor);$target=(array)DB::selectOne('SELECT current_database() AS database,inet_server_port() AS port');$code=$this->fingerprint(); $ledgerCount=DB::table('migrations')->where('migration',InvestmentZoneOperationalProfile::MIGRATION)->count();$ledger=$ledgerCount===1;$column=Schema::hasColumn('investment_zones','editorial_draft'); $shape=DB::selectOne("SELECT data_type,is_nullable FROM information_schema.columns WHERE table_schema='public' AND table_name='investment_zones' AND column_name='editorial_draft'"); $correct=$shape && $shape->data_type==='text' && $shape->is_nullable==='YES'; $consistent=($ledger && $column && $correct) || ($ledgerCount===0 && !$column);$reasons=[]; if(!$consistent){$reasons[]='Ledger e coluna divergentes; reconciliar sem instalação parcial.';} try{$this->authorize($actor,true);}catch(HttpException $e){$reasons[]=$e->getMessage();} $evidence=$this->evidence($target,$code);if(!$evidence['ready']){$reasons[]='Decisão específica e backup/restauro/arranque frescos desta revisão necessários.';} $state=['target'=>$target,'migration'=>InvestmentZoneOperationalProfile::MIGRATION,'code_hash'=>$code,'installed'=>$ledger && $column, 'consistent'=>$consistent,'ready'=>$reasons===[],'reasons'=>$reasons,'evidence'=>$evidence]; return [...$state,'hash'=>hash_hmac('sha256',json_encode([$actor->id,session()->getId(),$state],JSON_THROW_ON_ERROR),config('app.key'))]; } public function apply(User $actor,array $input): array { $this->authorize($actor,true); abort_if(array_diff(array_keys($input),['hash','idempotency_key','confirm','_token']),422,'Ator, migration ou caminho arbitrário não permitido.'); $data=Validator::make($input,['hash'=>'required|string|size:64','idempotency_key'=>'required|uuid','confirm'=>'required|accepted'])->validate(); return DB::transaction(function()use($actor,$data){ DB::select('SELECT pg_advisory_xact_lock(161026,130000)');$requestHash=hash('sha256',json_encode($data,JSON_THROW_ON_ERROR)); $old=DB::table('audit_events')->where('source_system',self::SOURCE)->where('correlation_id',$data['idempotency_key'])->first(); if($old){$receipt=json_decode($old->after_data,true);abort_unless((int)$old->actor_user_id===$actor->id && hash_equals($receipt['request_hash'],$requestHash),409,'Chave já utilizada com outro ator ou conteúdo.');return [...$receipt['result'],'replayed'=>true];} $plan=$this->preview($actor);abort_unless(hash_equals($plan['hash'],$data['hash']),409,'Prévia alterada; consulte novamente.');abort_unless($plan['ready'],422,implode(' ',$plan['reasons'])); if(!$plan['installed']){ config(['investment-zone-profile.authenticated_installation'=>true]); try{abort_unless(Artisan::call('migrate',['--path'=>'database/migrations/'.InvestmentZoneOperationalProfile::MIGRATION.'.php','--force'=>true])===0,500,'Falha na instalação nominal.');} finally{config(['investment-zone-profile.authenticated_installation'=>false]);} } abort_unless($this->preview($actor)['installed'],500,'Esquema nominal inconsistente.'); $result=['migration'=>$plan['migration'],'code_hash'=>$plan['code_hash'],'target'=>$plan['target'],'changed'=>!$plan['installed'],'business_records_changed'=>0,'replayed'=>false,'receipt'=>$data['idempotency_key']]; DB::table('audit_events')->insert(['source_system'=>self::SOURCE,'entity_type'=>'investment-zone-profile','entity_id'=>null,'actor_user_id'=>$actor->id, 'correlation_id'=>$data['idempotency_key'],'action'=>'schema','data_classification'=>'restricted','after_data'=>json_encode(['request_hash'=>$requestHash,'result'=>$result],JSON_THROW_ON_ERROR)]); return $result; }); } }