Files
Investidor-V2/tools/portal-functional-materiality-qa.php
nala 287e991486
tests / ci (push) Waiting to run
oportunidades 7
2026-10-13 11:40:29 +02:00

42 lines
4.7 KiB
PHP

<?php
// Actual isolated canonical snapshots; semantic checks make no database mutations.
use App\Models\Opportunity;
use App\Services\OpportunityCanonicalContract;
use App\Services\OpportunityReviewMateriality;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Arr;
$root=dirname(__DIR__);$dir=$root.'/storage/app/private/investmoz-v2/portal-audit';
$plan=json_decode(file_get_contents($dir.'/portal-stability-latest.json'),true,512,JSON_THROW_ON_ERROR);
putenv('STABILITY_QA_MODE=consumers');$app=require $plan['source'].'/tools/portal-stability-runtime.php';
$proof=DB::transaction(function():array {
DB::statement('SET TRANSACTION READ ONLY');
$m=app(OpportunityReviewMateriality::class);$checks=[];
$changes=[30=>['total_investment',10,['technical_review']],31=>['real_estate.title_holder_name','QA changed holder',['registry_urban_review']],32=>['land_rights.holder_document_id',null,['land_legal_review']],33=>['business_transaction.entity_registration.registration_number','QA changed registry',['corporate_screening']],34=>['business_transaction.governance_rights.voting','QA changed voting',['partnership_governance_review','legal_tax_due_diligence']],35=>['business_transaction.entity_registration.registration_number','QA changed registry',['franchise_legal_review']]];
foreach($changes as $id=>[$field,$value,$expected]) {
$record=Opportunity::findOrFail($id);$before=app(OpportunityCanonicalContract::class)->validateSubmission($record);$reviews=[];
foreach(app(OpportunityCanonicalContract::class)->gates($record) as $gate) {
$reviews[$gate]=['decision'=>'approved','hash'=>$before['hash'],'actor_id'=>24,'at'=>'2026-10-10T20:00:00Z','scope_rule'=>OpportunityReviewMateriality::PREVIOUS_VERSION];
$reviews[$gate]['scope_hash']=$m->hash($before,$gate,$reviews[$gate],false,OpportunityReviewMateriality::PREVIOUS_VERSION);
throw_unless($m->matches($before,$gate,$reviews[$gate]),RuntimeException::class,'Original v2 signature must remain readable.');
}
$after=$before;Arr::set($after['payload'],$field,$value);$after['hash']=hash('sha256',json_encode($after,JSON_THROW_ON_ERROR));
$result=$m->reconcile($before,$after,$reviews,app(OpportunityCanonicalContract::class)->gates($record));
$actual=$result['reopened'];sort($actual);sort($expected);throw_unless($actual===$expected,RuntimeException::class,'Incorrect affected gates for '.$record->opportunity_category.': '.implode(',',$actual));
foreach($result['reviews'] as $gate=>$review) { throw_unless($review['hash']===$reviews[$gate]['hash']&&$review['at']===$reviews[$gate]['at']&&$review['actor_id']===$reviews[$gate]['actor_id'],RuntimeException::class,'Signed decision changed.'); }
$editorial=$before;$editorial['payload']['official_title'].=' QA typo';$editorial['hash']=hash('sha256',json_encode($editorial,JSON_THROW_ON_ERROR));
throw_unless($m->reconcile($before,$editorial,$reviews,array_keys($reviews))['reopened']===[],RuntimeException::class,'Editorial title must not reopen reviews.');
$checks[]=['category'=>$record->opportunity_category,'id'=>$id,'field'=>$field,'reopened'=>$actual,'retained'=>array_keys($result['reviews']),'editorial_retained'=>true,'v2_signed_decisions_verified'=>true];
}
// A file referenced by the contract must invalidate the gate relying on it,
// even if it was not the manually selected supporting document.
$before=app(OpportunityCanonicalContract::class)->validateSubmission(Opportunity::findOrFail(33));$originalId=$before['payload']['business_transaction']['entity_registration']['legal_document_id'];
$id='aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa'; // In-memory isolated reference probe, never persisted or certified.
$before['payload']['business_transaction']['entity_registration']['legal_document_id']=$id;
$before['resources']['documents'][$id]=$before['resources']['documents'][$originalId];
$after=$before;$after['resources']['documents'][$id]['sha256']=str_repeat('f',64);
throw_unless($m->hash($before,'corporate_screening')!==$m->hash($after,'corporate_screening'),RuntimeException::class,'Referenced file change was ignored.');
throw_unless($m->hash($before,'financial_review')===$m->hash($after,'financial_review'),RuntimeException::class,'Unrelated review reopened.');
return ['result'=>'passed','at'=>gmdate('c'),'rule'=>OpportunityReviewMateriality::VERSION,'checks'=>$checks,'nested_reference_material'=>true,'active_writes'=>0];
});
file_put_contents($dir.'/functional-materiality-semantic-qa-20261011.json',json_encode($proof,JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE|JSON_THROW_ON_ERROR));echo json_encode($proof,JSON_UNESCAPED_UNICODE|JSON_THROW_ON_ERROR).PHP_EOL;