select("SELECT tablename FROM pg_tables WHERE schemaname='public' ORDER BY tablename"),'tablename'); $out=[]; foreach ($tables as $table) { if (!preg_match('/^[a-z_][a-z0-9_]*$/D',$table)) throw new LogicException('Table'); $out[$table]=(array)$db->selectOne('SELECT count(*) rows, md5(coalesce(string_agg(row_to_json(t)::text, chr(10) ORDER BY row_to_json(t)::text),\'\')) fingerprint FROM "'.$table.'" t'); } $out['sequences']=$db->select("SELECT sequencename,last_value FROM pg_sequences WHERE schemaname='public' ORDER BY sequencename"); return hash('sha256',json_encode($out,JSON_THROW_ON_ERROR)); }; $selected=json_decode(file_get_contents($run['root'].'/private/receipt-composition-cc50a276c10c/state.json'),true); config(['database.connections.sgc_protected_host'=>array_replace(config('database.connections.pgsql'),[ 'host'=>'127.0.0.1','port'=>55439,'database'=>$selected['env']['DB_DATABASE'],'username'=>$selected['env']['DB_USERNAME'],'password'=>$selected['env']['DB_PASSWORD'],'url'=>null]), 'database.connections.sgc_protected_native'=>$selected['nativeConnection']]); $protectedBefore=[$snapshot(DB::connection('sgc_protected_host')),$snapshot(DB::connection('sgc_protected_native'))]; $hostBefore=$snapshot($host); $financialTables=['budget_entries','budget_consumptions','stock_movements','pa_obligations']; $effectsBefore=[]; foreach ($financialTables as $table) if ($native->getSchemaBuilder()->hasTable($table)) $effectsBefore[$table]=$native->table($table)->count(); $existingInvoices=$native->table('invoices')->orderBy('id')->get()->toJson(); $priorIds=$native->table('invoices')->pluck('id')->all(); // SQL fixtures only: a distinct order/accepted line to exercise invoice intake. These // are not claimed as newly executed request/award/receipt/physical-stock workflows. $fixtureFile=$proofRoot.'/source-fixture.json'; if(!is_file($fixtureFile)){ $sourceOrder=$native->table('purchase_orders')->where('tenant_id',$nativeFixture['tenant'])->first(); $sourceLine=$native->table('purchase_order_items')->where('purchase_order_id',$sourceOrder->id)->first(); $copy=function($table,$sourceId,$replace=[])use($native){$row=(array)$native->table($table)->where('id',$sourceId)->first();unset($row['id']);return $native->table($table)->insertGetId(array_replace($row,$replace));}; $fixture=$native->transaction(function()use($native,$copy,$sourceOrder,$sourceLine,$proofRoot,$f){ $ref='QA-SGC-'.substr(basename($proofRoot),-12); $request=$copy('requisitions',$sourceOrder->requisition_id,['number'=>$ref]); $requestLine=$copy('requisition_items',$sourceLine->requisition_item_id,['requisition_id'=>$request]); $oldAward=$native->table('purchase_awards')->where('id',$sourceOrder->purchase_award_id)->first(); $quote=$copy('quotations',$oldAward->quotation_id,['requisition_id'=>$request]); $award=$copy('purchase_awards',$oldAward->id,['requisition_id'=>$request,'quotation_id'=>$quote]); $order=$copy('purchase_orders',$sourceOrder->id,['number'=>$ref,'requisition_id'=>$request,'purchase_award_id'=>$award]); $line=$copy('purchase_order_items',$sourceLine->id,['purchase_order_id'=>$order,'requisition_item_id'=>$requestLine]); $receiptSource=$native->table('goods_receipt_items')->where('purchase_order_item_id',$sourceLine->id)->first(); $receipt=$copy('goods_receipts',$receiptSource->goods_receipt_id,['purchase_order_id'=>$order,'number'=>$ref,'idempotency_key'=>(string)Str::uuid(),'delivery_note'=>$ref]); $copy('goods_receipt_items',$receiptSource->id,['goods_receipt_id'=>$receipt,'purchase_order_item_id'=>$line]); $oldOrigin=$native->table('audit_logs')->where('requisition_id',$sourceOrder->requisition_id)->first(); $copy('audit_logs',$oldOrigin->id,['requisition_id'=>$request,'action'=>'sgf.request.command','before'=>null, 'after'=>json_encode(['fixture_only'=>true,'request_scope'=>hash('sha256',json_encode($f['scope'],JSON_THROW_ON_ERROR))])]); return ['request'=>$request,'order'=>$order,'line'=>$line,'receipt'=>$receipt,'sql_fixture_only'=>true]; });file_put_contents($fixtureFile,json_encode($fixture,JSON_THROW_ON_ERROR)); } $fixture=json_decode(file_get_contents($fixtureFile),true); $order=$native->table('purchase_orders')->where('id',$fixture['order'])->first(); $line=$native->table('purchase_order_items')->where('id',$fixture['line'])->first(); $orderId=(int)$order->id; Route::post('/api/_sgc_test_state', fn(Illuminate\Http\Request $r)=>app(App\Modules\Purchasing\InvoiceController::class)->show($orderId,app(App\Modules\Purchasing\ReceiptRuntime::class))) ->middleware(App\Http\Middleware\AuthenticateSgcInvoice::class); $state=fn()=> $send([],null,[], '/api/_sgc_test_state'); $document=function(string $suffix)use($line,$proofRoot) { return ['number'=>'QA-SGC-'.basename($proofRoot).'-'.$suffix, 'issued_on'=>'2026-11-10','subtotal'=>'100.00','tax'=>'0.00','total'=>'100.00', 'items'=>[['purchase_order_item_id'=>(int)$line->id,'quantity'=>'1.000','unit'=>$line->unit,'unit_price'=>'100.00','tax_rate'=>'0.00']]]; }; $preview=function(string $suffix, ?string $key=null)use($state,$send,$orderId,$document){ $view=$state(); if($view['status']!==200)throw new RuntimeException('Native read refused: '.$view['status']); $body=['phase'=>'preview','order_id'=>$orderId,'revision'=>$view['data']['revision'],'reason'=>'Conferência sintética SGC, sem aprovação financeira.', 'data'=>$document($suffix)]; $key??=(string)Str::uuid();return [$send($body,$key),$key,$body]; }; $confirm=fn($p,$key)=>$send(['phase'=>'confirm','order_id'=>$orderId,'preview'=>$p],$key); $firstState=$state(); $ok($firstState['status']===200,'existing regression native order readable with mapped identity'); [$p,$key,$body]=$preview('main'); file_put_contents($proofRoot.'/initial-preview.json', json_encode($p,JSON_THROW_ON_ERROR)); $ok($p['status']===200&&isset($p['data']['preview']),'actual invoice preview via API transport'); $ok($p['data']['impact']['budget_consumption_minor']===0&&!$p['data']['impact']['payable_created'],'preview has no budget consumption or payable'); $ok($native->table('invoices')->count()===count($priorIds),'preview rolls all business rows back'); $ok($p['no_store'],'successful response is not cached'); config(['sgc_invoice.enabled'=>false]);$ok($send($body,$key)['status']===404,'disabled endpoint refuses even valid credentials');config(['sgc_invoice.enabled'=>true]); foreach ([['HTTP_AUTHORIZATION'=>null],['HTTP_AUTHORIZATION'=>'Bearer '.str_repeat('x',64)],['HTTP_X_SGC_CLIENT'=>'unknown']] as $headers) $ok($send($body,$key,$headers)['status']===401,'missing or wrong principal/token refused'); $ok($send($body,$key,['HTTP_X_SGC_CONTEXT'=>str_repeat('0',64)])['status']===409,'stale context version refused'); $ok($send($body,$key,['REMOTE_ADDR'=>'192.0.2.1'])['status']===403,'non-TLS remote request refused'); $ok($send($body,$key,['CONTENT_TYPE'=>'text/plain'])['status']===415,'non JSON refused'); $ok($send($body,$key,[], '/api/v1/integration/sgc/invoices',str_repeat('x',131073))['status']===413,'oversized body refused'); $ok($send($body,$key,[], '/api/v1/integration/sgc/invoices','{bad')['status']===422,'malformed JSON refused'); $bad=$body; $bad['tenant_id']=1;$ok($send($bad,$key)['status']===422,'client cannot choose tenant'); $bad=$body; $bad['data']['supplier_id']=1;$ok($send($bad,$key)['status']===422,'supplier cannot be silently associated'); $bad=$body; $bad['data']['total']=100.0;$ok($send($bad,$key)['status']===422,'floating point amounts refused'); $bad=$body; $bad['data']['total']='101.00';$ok($send($bad,$key)['status']===422,'inconsistent total refused by original writer'); $bad=$body; $bad['data']['items'][0]['purchase_order_item_id']=$nativeFixture['foreign_order_line'];$ok($send($bad,$key)['status']===422,'foreign native line refused'); $bad=$body; $bad['order_id']=$nativeFixture['foreign_order'];$ok($send($bad,$key)['status']===403,'foreign native order refused'); $ok($send($body,'not-a-uuid')['status']===422,'malformed idempotency key refused'); $changed=$principal; $changed['auth_version']=2;config(['sgc_invoice.principals.synthetic'=>$changed]);$ok($send($body,$key)['status']===401,'revoked authentication version refused'); $changed=$principal;$changed['unit_id']=$f['foreign_unit'];config(['sgc_invoice.principals.synthetic'=>$changed]);$ok($send($body,$key)['status']===403,'unauthorised unit mapping refused'); config(['sgc_invoice.principals.synthetic'=>$principal]); $host->beginTransaction();try{$host->table('permission_grants')->where('user_id',$f['issuer'])->where('permission','purchasing_invoices.prepare')->delete();$ok($send($body,$key)['status']===403,'revoked prepare right refused');}finally{$host->rollBack();} $result=$confirm($p['data']['preview'],$key);$ok($result['status']===201&&$result['data']['status']==='pending','confirmation creates pending invoice only'); $invoiceId=$result['data']['invoice_id']; $replay=$confirm($p['data']['preview'],$key);$ok($replay['status']===200&&$replay['data']['invoice_id']===$invoiceId&&$replay['data']['replayed'],'lost response recovered by same confirmation'); $ok($send($body,$key)['status']===200,'same request recovered through preview after commit'); $bad=$body; $bad['reason']='Outro fundamento sintético, conteúdo diferente.';$ok($send($bad,$key)['status']===409,'same key different content conflicts'); $ok($confirm($p['data']['preview'],(string)Str::uuid())['status']===409,'confirmation must use the preview key'); $ok($confirm(substr($p['data']['preview'],0,-4).'xxxx',$key)['status']===409,'tampered preview refused'); $host->beginTransaction();try{$host->table('permission_grants')->where('user_id',$f['issuer'])->where('permission','purchasing_invoices.prepare')->delete();$ok($confirm($p['data']['preview'],$key)['status']===403,'replay revalidates current authority');}finally{$host->rollBack();} [$stale,$staleKey]=$preview('stale');[$fresh,$freshKey]=$preview('fresh'); $ok($confirm($fresh['data']['preview'],$freshKey)['status']===201,'second individually prepared document confirmed'); $ok($confirm($stale['data']['preview'],$staleKey)['status']===409,'changed source invalidates earlier preview'); [$failure,$failureKey]=$preview('failure');$beforeFailure=$native->table('invoices')->count();$inject=true; Tempo\Purchasing\Embedded\Models\AuditLog::creating(function($log)use(&$inject){if($inject&&$log->action==='sgf.invoice.command')throw new RuntimeException('Synthetic failure immediately before durable receipt');}); $failed=$confirm($failure['data']['preview'],$failureKey);$inject=false; $ok($failed['status']===500&&$native->table('invoices')->count()===$beforeFailure,'failure between writer and receipt rolls invoice back'); $ok($confirm($failure['data']['preview'],$failureKey)['status']===201,'same operation recovers after failure'); [$parallel,$parallelKey]=$preview('parallel'); file_put_contents($proofRoot.'/job.json',json_encode(['body'=>['phase'=>'confirm','order_id'=>$orderId,'preview'=>$parallel['data']['preview']],'key'=>$parallelKey],JSON_THROW_ON_ERROR)); $workers=[];foreach([0,1]as$i){$process=new Symfony\Component\Process\Process([PHP_BINARY,__FILE__,'--worker','job.json','worker-'.$i.'.json'],$repo,['SGC_INVOICE_PROOF_ROOT'=>$proofRoot],null,45);$process->start();$workers[]=$process;} foreach($workers as$process){$process->wait();if(!$process->isSuccessful())throw new RuntimeException('Worker failed; inspect private proof output');} $parallelResults=[];foreach([0,1]as$i)$parallelResults[]=json_decode(file_get_contents($proofRoot.'/worker-'.$i.'.json'),true); $statuses=array_column($parallelResults,'status');sort($statuses);$ok($statuses===[200,201],'concurrent same-key confirmations return one create and one recovery'); $ok($parallelResults[0]['data']['invoice_id']===$parallelResults[1]['data']['invoice_id'],'concurrent receipts identify one invoice'); $afterEffects=[];foreach(array_keys($effectsBefore)as$table)$afterEffects[$table]=$native->table($table)->count(); $ok($effectsBefore===$afterEffects,'no new consumption stock obligation payment or ledger effects'); $ok($native->table('invoices')->whereIn('id',$priorIds)->orderBy('id')->get()->toJson()===$existingInvoices,'previous accepted invoice rows preserved'); $ok($snapshot($host)===$hostBefore,'host rows permissions budget and sequences unchanged'); $protectedAfter=[$snapshot(DB::connection('sgc_protected_host')),$snapshot(DB::connection('sgc_protected_native'))]; $ok($protectedBefore===$protectedAfter,'8085 host/native all rows and sequences unchanged'); $cleared=false;try{app(App\Shared\Context\CompanyContext::class)->identifiers();}catch(Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException){$cleared=true;} $ok($cleared,'context cleaned after request'); file_put_contents($proofRoot.'/result.json',json_encode(['passed'=>true,'checks'=>$checks,'new_pending_invoices'=>$native->table('invoices')->count()-count($priorIds), 'concurrent_statuses'=>$statuses,'protected_8085_fingerprints'=>$protectedAfter,'database_reused'=>'receipt-composition-da77bb730751','new_database'=>false,'new_role'=>false,'new_server'=>false, 'kernel_http'=>true,'tcp_http'=>false,'sql_source_fixture'=>$fixture,'activation'=>false,'publication'=>false],JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR)); echo json_encode(['passed'=>true,'checks'=>count($checks)]).PHP_EOL;