Files
SGF/scripts/obligation-replay-proof.php
T
2026-11-04 09:37:20 +02:00

16 lines
3.2 KiB
PHP

<?php
use Illuminate\Support\Facades\{DB,Auth};
$s=json_decode(file_get_contents(getenv('RECEIPT_COMPOSITION_STATE')),true);$out=$s['root'].'/obligation-replay.json';if(!($s['obligations']??false)||is_file($out)||!is_file($s['root'].'/obligation-result.json'))throw new LogicException('Completed isolated recognition required');
require $s['app'].'/vendor/autoload.php';$app=require $s['app'].'/bootstrap/app.php';$app->make(Illuminate\Contracts\Console\Kernel::class)->bootstrap();$f=json_decode(file_get_contents($s['root'].'/fixture.json'),true);$n=json_decode(file_get_contents($s['root'].'/native-fixture.json'),true);config(['database.connections.replay_verify'=>$s['nativeConnection']]);$native=DB::connection('replay_verify');$ob=$native->table('pa_obligations')->first();$role=(int)$ob->recognised_by===$n['finance_reviewer']?'reviewer':'auditor';$user=App\Models\User::findOrFail($f[$role]);Auth::setUser($user);request()->setUserResolver(fn()=>$user);app(App\Shared\Context\CompanyContext::class)->selectFor($user->id,$f['tenant'],$f['company'],$f['unit']);$checks=[];
(new App\Modules\Purchasing\ReceiptRuntime)->run('purchasing_obligations.validate',function($actor)use(&$checks){$db=DB::connection();$service=app(Tempo\Purchasing\Embedded\Services\SupplierPayables::class);$engine=$service->engine($actor);$ob=$db->table('pa_obligations')->first();$command=$db->table('pa_commands')->where('actor_id',$actor->id)->where('result->obligation_id',$ob->id)->first();$before=[];foreach(['audit_logs','pa_obligations','pa_commands','pa_events','pa_outbox']as$t)$before[$t]=$db->table($t)->orderBy($t==='pa_outbox'?'event_id':'id')->get()->all();$level=$db->transactionLevel();$db->beginTransaction();
try{
// Adversarial fixture fact only, visible to this transaction and always rolled back.
$invoice=(int)json_decode($command->result,true)['local_source_id'];$audit=(array)$db->table('audit_logs')->where('action','invoice.validated')->where('after->invoice_id',$invoice)->orderByDesc('id')->first();unset($audit['id']);$db->table('audit_logs')->insert($audit);
$result=$engine->command('validate',(int)$ob->proposal_id,'Conferencia individual sintetica da responsabilidade.',$command->command_key);
if($result['state']!=='conflict')throw new RuntimeException('Stale replay accepted');$checks[]='recorded recognition replay rechecks changed source and returns conflict';
if($db->table('pa_obligations')->count()!==1||(int)$db->table('pa_obligations')->sum('principal_minor')!==10000)throw new RuntimeException('Duplicate principal');$checks[]='stale replay preserves one original principal';
}finally{while($db->transactionLevel()>$level)$db->rollBack();}
foreach($before as$t=>$rows)if(json_encode($rows)!==json_encode($db->table($t)->orderBy($t==='pa_outbox'?'event_id':'id')->get()->all()))throw new RuntimeException('Probe leaked facts');$checks[]='rollback removes injected validation marker and conflict evidence';
});
file_put_contents($out,json_encode(['passed'=>true,'checks'=>$checks,'probe'=>'same original native adapter and kernel; rollback-only adversarial audit marker, no original facts changed','visual_review'=>false],JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR));echo count($checks)." replay source checks passed.\n";