make(\Illuminate\Contracts\Console\Kernel::class)->bootstrap(); use Illuminate\Support\Facades\DB; use Symfony\Component\Process\Process; if (DB::connection()->getDatabaseName() !== $database || DB::selectOne('select current_database() as db')->db !== $database) throw new LogicException('Effective QA database mismatch.'); require __DIR__.'/host-support.php'; $worker = in_array('--worker',$argv,true); if ($worker) { $input = json_decode(stream_get_contents(STDIN),true,64,JSON_THROW_ON_ERROR); DB::select('select set_config(?, ?, false)',['application_name',$input['label']]); try { budgetQaRecord($mode,$input['fixture'],$input['kind'],$input['amount']); $status = 200; } catch (\Tempo\Finance\Budget\BudgetFailure|\Illuminate\Validation\ValidationException $e) { $status = 422; } echo json_encode(['status'=>$status,'pid'=>DB::selectOne('select pg_backend_pid() as pid')->pid],JSON_THROW_ON_ERROR); exit; } if ($mode === 'compras') { \Illuminate\Support\Facades\Artisan::call('migrate',['--force'=>true]); } else { require __DIR__.'/sgf-schema.php'; } $prefix = $mode==='sgf'?'cb_':''; $f = budgetQaFixture($mode); $budget = budgetQaCreate($mode,$f); $steps = []; foreach ([['reserve',30000],['award',35000],['commit',35000]] as [$kind,$amount]) { budgetQaRecord($mode,$f,$kind,$amount); budgetQaRecord($mode,$f,$kind,$amount); $steps[$kind] = budgetQaEngine($mode,$f)->balance($budget); } // Real invoice foreign key in Compras, source-reference-only consumption in SGF. if ($mode==='compras') { budgetQaSelect($mode,$f); $sourceInvoice = \App\Models\Invoice::query()->create(['tenant_id'=>$f['tenant'],'supplier_id'=>\App\Models\PurchaseOrder::findOrFail($f['invoice'])->supplier_id,'purchase_order_id'=>$f['invoice'],'created_by'=>$f['actor'],'number'=>'TEST-BUDGET-ONLY','issued_on'=>now()->format('Y-m-d'),'subtotal_minor'=>10000,'tax_minor'=>0,'total_minor'=>10000,'snapshot'=>[]]); $f['invoice'] = $sourceInvoice->id; } $engine = budgetQaEngine($mode,$f); $engine->consume($f['origin'],$f['invoice'],10000,10000); $engine->consume($f['origin'],$f['invoice'],10000,10000); $steps['consume'] = $engine->balance($budget); budgetQaAssert($steps['consume']['reserved_minor']===0 && $steps['consume']['committed_minor']===25000 && $steps['consume']['consumed_minor']===10000 && $steps['consume']['available_minor']===65000,'Partial consumption differs.'); budgetQaReject(fn()=>budgetQaRecord($mode,$f,'reserve',30001),'changed replay'); budgetQaReject(fn()=>budgetQaRecord($mode,$f,'release'),'release committed source'); budgetQaReject(fn()=>$engine->consume($f['origin'],$f['invoice'],10001,10001),'changed consumption replay'); $rid = $engine->proposeReinforcement($budget,20000,'REF-QA','Synthetic reinforcement'); budgetQaAssert($engine->balance($budget)['available_minor']===65000,'Pending reinforcement changed availability.'); budgetQaReject(fn()=>$engine->reviewReinforcement($rid,'approved','Self approval'),'self approval'); $review = budgetQaEngine($mode,$f,'validator'); $review->reviewReinforcement($rid,'approved','Distinct synthetic validation'); $review->reviewReinforcement($rid,'approved','Distinct synthetic validation'); $steps['reinforcement'] = $review->balance($budget); budgetQaAssert($steps['reinforcement']['available_minor']===85000,'Reinforcement differs.'); budgetQaSelect($mode,$f); $foreign = budgetQaFixture($mode); budgetQaCreate($mode,$foreign); budgetQaSelect($mode,$f); budgetQaReject(fn()=>budgetQaEngine($mode,$f)->balance($budget+1),'foreign allocation'); if ($mode === 'sgf') { budgetQaReject(fn()=>app(\App\Modules\Purchasing\BudgetAdapter::class)->record(budgetQaSelect($mode,$f),$foreign['origin'],'reserve',100,100),'foreign mapped source'); budgetQaReject(fn()=>app(\App\Shared\Context\CompanyContext::class)->selectFor($f['actor'],$f['tenant'],$f['company'],$f['unit']-1),'reserved unit'); $stale = budgetQaEngine($mode,$f); budgetQaSelect($mode,$foreign); budgetQaReject(fn()=>$stale->balance($budget),'stale context'); } // Release is allowed once before commitment. $release = budgetQaFixture($mode); $releaseBudget = budgetQaCreate($mode,$release,10000); budgetQaRecord($mode,$release,'reserve',4000); budgetQaRecord($mode,$release,'release'); budgetQaRecord($mode,$release,'release'); budgetQaAssert(budgetQaEngine($mode,$release)->balance($releaseBudget)['available_minor']===10000,'Release differs.'); // The below-tax basis is preserved, without a document recalculation. $net = budgetQaFixture($mode); $netBudget = budgetQaCreate($mode,$net,10000,false); budgetQaEngine($mode,$net)->record($net['origin'],$net['centre'],'operating','reserve',11600,10000); budgetQaAssert(budgetQaEngine($mode,$net)->balance($netBudget)['reserved_minor']===10000,'Net basis differs.'); // Failed audit must roll back the ledger and make retry safe. $failure = budgetQaFixture($mode); $failureBudget = budgetQaCreate($mode,$failure,10000); $auditTable = $mode==='compras'?'audit_logs':'cb_audit'; DB::unprepared("CREATE FUNCTION budget_qa_fail() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN IF NEW.action='budget.reserve' THEN RAISE EXCEPTION 'QA_AUDIT_FAILURE'; END IF; RETURN NEW; END $$; CREATE TRIGGER budget_qa_fail BEFORE INSERT ON {$auditTable} FOR EACH ROW EXECUTE FUNCTION budget_qa_fail()"); try { budgetQaRecord($mode,$failure,'reserve',4000); throw new RuntimeException('Audit fault was not observed.'); } catch (\Illuminate\Database\QueryException $expected) { budgetQaAssert(str_contains($expected->getMessage(),'QA_AUDIT_FAILURE'),'Wrong injected failure.'); } DB::unprepared("DROP TRIGGER budget_qa_fail ON {$auditTable}; DROP FUNCTION budget_qa_fail()"); budgetQaAssert(budgetQaEngine($mode,$failure)->balance($failureBudget)['available_minor']===10000,'Fault left an orphan reservation.'); budgetQaRecord($mode,$failure,'reserve',4000); budgetQaRecord($mode,$failure,'reserve',4000); budgetQaAssert(budgetQaEngine($mode,$failure)->balance($failureBudget)['reserved_minor']===4000,'Recovery duplicated effect.'); // Revalidate host permission even when the command is an idempotent replay. if ($mode==='compras') DB::table('users')->where('id',$f['validator'])->update(['purchasing_role'=>'warehouse']); else DB::table('permission_grants')->where('user_id',$f['validator'])->where('permission','purchasing_budget.reinforcement.review')->delete(); budgetQaReject(fn()=>budgetQaEngine($mode,$f,'validator')->reviewReinforcement($rid,'approved','Replay after revocation'),'revoked reviewer replay'); $races=[]; $monitorConfig=DB::connection()->getConfig(); config(['database.connections.budget_monitor'=>$monitorConfig]); $monitor=DB::connection('budget_monitor'); foreach (['over_reserve','same_reserve','same_commit','same_release'] as $scenario) { $r = budgetQaFixture($mode); $rb = budgetQaCreate($mode,$r,10000); $second=$r; if ($scenario==='over_reserve') { if ($mode==='compras') { budgetQaSelect($mode,$r); $clone=\App\Models\Requisition::findOrFail($r['origin'])->replicate(); $clone->number='REQ-QA-'.bin2hex(random_bytes(6)); $clone->save(); $second['origin']=$clone->id; } else $second['origin']=DB::table('cb_origins')->insertGetId($r['scope']+['cost_center_id'=>$r['centre'],'required_kind'=>'operating','reference'=>'REQ-RACE-'.bin2hex(random_bytes(6))]); } if (in_array($scenario,['same_commit','same_release'],true)) budgetQaRecord($mode,$r,'reserve',6000); $kind=match($scenario){'same_commit'=>'commit','same_release'=>'release',default=>'reserve'}; $db=DB::connection(); $db->beginTransaction(); $db->table($mode==='compras'?'tenants':'companies')->where('id',$mode==='compras'?$r['tenant']:$r['company'])->lockForUpdate()->firstOrFail(); $processes=[]; try { foreach ([$r,$second] as $i=>$fixture) { $p=new Process([PHP_BINARY,__FILE__,$root,$mode,'--worker'],$root); $p->setInput(json_encode(['fixture'=>$fixture,'label'=>'common_budget_'.$mode.'_'.$i,'kind'=>$kind,'amount'=>6000],JSON_THROW_ON_ERROR)); $p->setTimeout(35); $p->start(); $processes[]=$p; } $deadline=microtime(true)+15; $waits=0; do { $waits=(int)$monitor->selectOne("select count(*) as n from pg_stat_activity where datname=? and application_name in (?,?) and wait_event_type='Lock'",[$database,'common_budget_'.$mode.'_0','common_budget_'.$mode.'_1'])->n; if ($waits===2) break; foreach($processes as $p) if(!$p->isRunning()) throw new RuntimeException('Race worker exited before overlap: '.$p->getOutput().$p->getErrorOutput()); usleep(20000); } while(microtime(true)<$deadline); budgetQaAssert($waits===2,'Two overlapping PostgreSQL lock waits not observed.'); $db->commit(); $results=[]; foreach($processes as $p){$p->wait();budgetQaAssert($p->isSuccessful(),'Race worker failed: '.$p->getOutput().$p->getErrorOutput());$results[]=json_decode($p->getOutput(),true,32,JSON_THROW_ON_ERROR);} $statuses=array_column($results,'status');sort($statuses); budgetQaAssert($statuses===($scenario==='over_reserve'?[200,422]:[200,200]),'Race statuses differ.'); budgetQaAssert(count(array_unique(array_column($results,'pid')))===2,'Independent DB workers required.'); $b=budgetQaEngine($mode,$r)->balance($rb); budgetQaAssert($b['available_minor']===($scenario==='same_release'?10000:4000),'Race duplicated budget effect.'); $races[]=['scenario'=>$scenario,'passed'=>true,'observed_lock_waits'=>$waits,'balance'=>$b]; } finally { if($db->transactionLevel())$db->rollBack(); foreach($processes as $p)if($p->isRunning())$p->stop(); } } $reflection = new ReflectionClass(\Tempo\Finance\Budget\BudgetControl::class); if($mode==='compras')budgetQaAssert(str_contains(str_replace('\\','/',$reflection->getFileName()),'common-budget-artifact-'),'Standalone did not load the detached package artifact.'); echo json_encode(['host'=>$mode,'passed'=>true,'detached_package_artifact'=>$mode==='compras','core_sha256'=>hash_file('sha256',$reflection->getFileName()),'steps'=>$steps,'races'=>$races,'checks'=>['independent_approval','partial_consumption','same_command_replay','changed_replay_rejected','committed_release_refused','reinforcement_pending_and_approved','tenant_isolation','release_once','net_tax_basis','audit_rollback_and_retry','revoked_reviewer_replay'],'sgf_context_checks'=>$mode==='sgf'?['foreign_source_refused','reserved_unit_refused','stale_context_refused']:[],'visual_review'=>false],JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR).PHP_EOL;