true]);$u=App\Models\User::findOrFail($f['actors']['read']);auth()->setUser($u);app(CompanyContext::class)->selectFor($u->id,$scope['tenant_id'],$scope['company_id'],$scope['unit_id']); $checks=0;$cases=[];$capture=false;$queries=[];$readState=[]; DB::listen(function($event)use(&$capture,&$queries,&$readState){if(!$capture)return;$queries[]=$event->sql;if(!$readState&&str_contains($event->sql,'"pa_imported_sources"')){$pdo=$event->connection->getPdo();$readState=['isolation'=>$pdo->query('SHOW transaction_isolation')->fetchColumn(),'read_only'=>$pdo->query('SHOW transaction_read_only')->fetchColumn()];}}); $assert=function(bool $value,string $why)use(&$checks){$checks++;if(!$value)throw new RuntimeException($why);}; $case=function(string $name,callable $test)use(&$cases,&$checks){$before=$checks;DB::beginTransaction();try{$test();$cases[]=['name'=>$name,'assertions'=>$checks-$before,'passed'=>true];}finally{while(DB::transactionLevel()>0)DB::rollBack();}echo 'Passed page case: '.$name."\n";}; $positions=function(array $d):string{$rows=$d['obligations'];usort($rows,fn($a,$b)=>$a['id']<=>$b['id']);return hash('sha256',Payables::canonical($rows));}; $case('stable-source-window-and-independent-evidence-pages',function()use($assert,$service,$u,$scope){ $first=$service->listing($u);$second=$service->listing($u,['page'=>2]);$third=$service->listing($u,['page'=>3]);$assert(count($first['documents'])===25&&count($second['documents'])===25&&count($third['documents'])===1,'25/25/1 source pages');$assert($first['pagination']['documents']['total']===51,'Scoped total');$assert(!array_intersect(array_column($first['documents'],'local_id'),array_column($second['documents'],'local_id')),'Disjoint ordered pages');$assert($first['documents'][24]['local_id']>$second['documents'][0]['local_id'],'Stable descending document order'); $source=DB::table('pa_sources')->where($scope)->where('local_source_id',$first['documents'][0]['local_id'])->value('id');$row=(array)DB::table('pa_proposals')->where('source_id',$source)->first();unset($row['id']);for($n=2;$n<=61;$n++)DB::table('pa_proposals')->insert(array_replace($row,['capture_version'=>$n])); $a=$service->listing($u);$b=$service->listing($u,['proposal_page'=>2,'event_page'=>2]);$assert($a['pagination']['proposals']['total']===85,'All source versions retained behind pager');$assert(!array_intersect($a['proposals']->pluck('id')->all(),$b['proposals']->pluck('id')->all()),'Disjoint proposal pages');$assert(!array_intersect($a['events']->pluck('id')->all(),$b['events']->pluck('id')->all()),'Disjoint event pages');$assert(count($a['documents'])===count($b['documents']),'Evidence pager does not change document page'); }); $case('filters-before-pagination-and-literal-search',function()use($assert,$service,$u){ $a=$service->listing($u,['q'=>'Synthetic supplier Alpha']);$assert($a['pagination']['documents']['total']===25,'Supplier filter applies before count/page');$b=$service->listing($u,['q'=>'BENCH-','per_page'=>100]);$assert(count($b['documents'])===50,'Number search');$assert($service->listing($u,['q'=>'%'])['pagination']['documents']['total']===0,'Percent is literal');$assert($service->listing($u,['q'=>'_'])['pagination']['documents']['total']===0,'Underscore is literal');$assert($service->listing($u,['recognition'=>'recognised'])['pagination']['documents']['total']===51,'Recognition filter');$assert($service->listing($u,['recognition'=>'unrecognised'])['pagination']['documents']['total']===0,'Unrecognised filter');$empty=$service->listing($u,['page'=>99]);$assert(!$empty['documents']&&!$empty['obligations']&&$empty['proposals']->isEmpty()&&$empty['events']->isEmpty(),'Out-of-range is empty, not whole book'); }); $case('full-position-parity-with-original-reader',function()use($assert,$positions,$service,$u,$scope){ $ids=DB::table('pa_imported_sources')->where($scope)->orderByDesc('id')->pluck('id')->map(fn($id)=>(int)$id)->all();$old=$service->engine($u)->listing($ids);$new=$service->listing($u,['per_page'=>100]);$assert($positions($old)===$positions($new),'All fields of all financial positions match');$assert(count($old['documents'])===count($new['documents']),'Documents unchanged');$assert($old['proposals']->count()===$new['proposals']->count(),'Proposal retrieval parity within page'); }); $case('batch-queries-do-not-grow-with-the-page',function()use($assert,$service,$u,&$capture,&$queries,&$readState){ $queries=[];$readState=[];$capture=true;$service->listing($u);$capture=false;$small=count($queries);$assert(!array_filter($queries,fn($q)=>str_contains(strtolower($q),'for update')),'Read page takes no writer locks'); $queries=[];$readState=[];$capture=true;$large=$service->listing($u,['per_page'=>100]);$capture=false;$assert(count($large['documents'])===51,'Larger valid window');$assert(count($queries)===$small,'Constant SQL count for 25 versus 51 documents');$assert(count(array_filter($queries,fn($q)=>str_contains($q,'from "pa_source_histories"')))===1,'One batch historical-source read');$assert(count(array_filter($queries,fn($q)=>str_contains($q,'from "pa_imported_payments"')))===1,'One batch imported-payments read'); }); $case('tenant-company-unit-isolation-and-access-revocation',function()use($assert,$service,$u,$scope,$f){ $doc=$service->document($u,$f['source_id']);unset($doc['local_id']);$doc['number']='FOREIGN-SECRET';$foreign=DB::table('pa_imported_sources')->insertGetId(['tenant_id'=>$scope['tenant_id']+100,'company_id'=>$scope['company_id']+100,'unit_id'=>$scope['unit_id']+100,'payload'=>Payables::canonical($doc),'receipt_hash'=>hash('sha256',Payables::canonical($doc))]);$assert($service->listing($u,['q'=>'FOREIGN-SECRET'])['pagination']['documents']['total']===0,'Foreign source excluded before count'); try{$service->document($u,$foreign);throw new RuntimeException('Foreign read accepted');}catch(Symfony\Component\HttpKernel\Exception\HttpExceptionInterface $e){$assert($e->getStatusCode()===403,'Foreign source refused');} DB::table('pa_access')->where($scope)->where('user_id',$u->id)->delete();try{$service->listing($u);throw new RuntimeException('Revoked read accepted');}catch(Symfony\Component\HttpKernel\Exception\HttpExceptionInterface $e){$assert($e->getStatusCode()===403,'Current read right revalidated');} }); $case('receipt-changes-do-not-reuse-a-previous-page-snapshot',function()use($assert,$service,$u,$scope,$positions){ $page=$service->listing($u);$id=$page['documents'][0]['local_id'];$row=DB::table('pa_source_histories')->where($scope)->where('local_source_id',$id)->first();$p=json_decode($row->payload,true,64,JSON_THROW_ON_ERROR);$p['receipt_version']=2;DB::table('pa_source_histories')->insert($scope+['local_source_id'=>$id,'version'=>2,'payload'=>Payables::canonical($p),'receipt_hash'=>str_repeat('0',64),'created_at'=>now()]);$next=$service->listing($u);$bad=array_filter($next['obligations'],fn($o)=>$o['source_conflict']);$assert(count($bad)===1&&array_values($bad)[0]['balance_minor']===null,'Only changed receipt becomes indeterminate');$assert(count($next['documents'])===25,'Other documents remain readable');$all=DB::table('pa_imported_sources')->where($scope)->pluck('id')->map(fn($v)=>(int)$v)->all();$legacy=$service->engine($u)->listing($all);$ids=array_column($next['obligations'],'id');$legacy['obligations']=array_values(array_filter($legacy['obligations'],fn($o)=>in_array($o['id'],$ids,true)));$assert($positions($legacy)===$positions($next),'Refusals/indeterminate state identical to original reader'); }); $case('allocation-reversal-aggregation-is-equivalent',function()use($assert,$service,$u,$scope,$positions){ foreach(['ps_allocations','ps_reversals'] as $table)DB::statement("CREATE TABLE $table(tenant_id bigint,company_id bigint,unit_id bigint,obligation_id bigint,amount_minor bigint)");$page=$service->listing($u,['per_page'=>100]);$ob=$page['obligations'][0]['id'];DB::table('ps_allocations')->insert([$scope+['obligation_id'=>$ob,'amount_minor'=>500],$scope+['obligation_id'=>$ob,'amount_minor'=>200]]);DB::table('ps_reversals')->insert($scope+['obligation_id'=>$ob,'amount_minor'=>500]);$ids=DB::table('pa_imported_sources')->where($scope)->pluck('id')->map(fn($v)=>(int)$v)->all();$old=$service->engine($u)->listing($ids);$new=$service->listing($u,['per_page'=>100]);$assert($positions($old)===$positions($new),'Allocation/reversal positions match');$o=array_values(array_filter($new['obligations'],fn($r)=>$r['id']===$ob))[0];$assert($o['allocated_payments_minor']===200&&$o['balance_minor']===2800,'Net allocation still belongs to existing balance formula'); }); $case('historical-decision-chain-is-validated-in-both-readers',function()use($assert,$service,$u,$scope,$positions){ PaymentDecisionSchema::install();$page=$service->listing($u);$doc=$page['documents'][0];unset($doc['revision'],$doc['missing']);$origin=DB::table('pa_sources')->where($scope)->where('local_source_id',$doc['local_id'])->first();$import=DB::table('pa_history_imports')->where($scope)->where('source_id',$origin->id)->first();$payments=json_decode($import->snapshot,true,64,JSON_THROW_ON_ERROR)['payments'];$pending=null;foreach($payments as &$p)if($p['status']==='pending'){$pending=$p['id'];$p['status']='confirmed';$p['reviewed_by']=900000;$p['reviewed_at']='2026-11-02 12:00:00';$p['decision_audit_id']=900001;}unset($p);$capture=HistoricalPayments::capture($doc,$payments);(new PaymentDecisions(DB::connection(),$scope))->record($import,$capture,$pending,900000); $payload=['version'=>SupplierHistoryReceipts::VERSION,'receipt_version'=>2,'source_key'=>Payables::identity($doc),'source_revision'=>Payables::revision($doc),'history_hash'=>$capture['history_hash'],'payments'=>$capture['payments']];DB::table('pa_source_histories')->insert($scope+['local_source_id'=>$doc['local_id'],'version'=>2,'payload'=>Payables::canonical($payload),'receipt_hash'=>hash('sha256',Payables::canonical($payload)),'created_at'=>now()]);$ids=DB::table('pa_imported_sources')->where($scope)->pluck('id')->map(fn($v)=>(int)$v)->all();$old=$service->engine($u)->listing($ids);$new=$service->listing($u,['per_page'=>100]);$assert($positions($old)===$positions($new),'Complete decision-chain parity');$o=array_values(array_filter($new['obligations'],fn($r)=>$r['source_id']===$origin->id))[0];$assert($o['confirmed_payments_minor']===4000&&$o['pending_payments_minor']===0&&$o['balance_minor']===2000,'Existing decision-chain effect remains exact'); }); // This final read starts outside any write transaction, as the real HTTP endpoint does. $queries=[];$readState=[];$capture=true;$service->listing($u);$capture=false;$assert($readState===['isolation'=>'repeatable read','read_only'=>'on'],'Coherent read-only HTTP-style transaction'); $report=['kind'=>'financial_page_regression','cases'=>$cases,'scenarios'=>count($cases),'assertions'=>$checks,'read_transaction'=>$readState,'last_read_queries'=>count($queries),'benchmark_fixture_documents'=>51,'no_new_decision_capability'=>true,'decision_and_allocation_rows_rollback_only'=>true];file_put_contents($s['root'].'/page-cases.json',json_encode($report,JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR));echo json_encode(['scenarios'=>count($cases),'assertions'=>$checks,'passed'=>true])."\n";