Record explicit workshop acceptance and preserve subsequent synthetic work without accepting receivables. Keep 080000/070000 unapplied, real workshop grants pending and operational issuance blocks intact. Exclude local credentials and real-data preservation reports.
46 lines
6.6 KiB
PHP
46 lines
6.6 KiB
PHP
<?php
|
|
use App\Modules\Workshop\WorkshopWorkflow;
|
|
use App\Shared\Context\CompanyContext;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Str;
|
|
use Symfony\Component\Process\Process;
|
|
require __DIR__.'/../vendor/autoload.php';
|
|
$app=require __DIR__.'/../bootstrap/app.php';$app->make(\Illuminate\Contracts\Console\Kernel::class)->bootstrap();WorkshopWorkflow::guard();
|
|
$f=json_decode(file_get_contents('C:/laragon/data/sgf-tempo/secrets/browser-fixture.json'),true,32,JSON_THROW_ON_ERROR);$fx=$f['workshop'];$w=app(WorkshopWorkflow::class);
|
|
$base=['tenant'=>$f['tenant'],'company'=>$f['company'],'unit'=>$f['unit']];$proof=[];
|
|
$select=fn($actor)=>app(CompanyContext::class)->selectFor($actor,$f['tenant'],$f['company'],$f['unit']);
|
|
$key=fn()=>(string)Str::uuid();$rev=fn($id)=>(int)DB::selectOne('SELECT sgf_wo_revision(?) AS v',[$id])->v;
|
|
$pair=function($one,$two)use($base){
|
|
$a=new Process([PHP_BINARY,'scripts/workshop-worker.php','--env=testing'],base_path(),null,json_encode($one+$base+['pause'=>true]));
|
|
$b=new Process([PHP_BINARY,'scripts/workshop-worker.php','--env=testing'],base_path(),null,json_encode($two+$base));
|
|
$a->start();if(!$a->waitUntil(fn($t,$v)=>str_contains($v,'LOCK_HELD')))throw new RuntimeException('No overlapping lock.');$b->start();$a->wait();$b->wait();$out=[];
|
|
foreach([$a,$b] as $p){if(!$p->isSuccessful())throw new RuntimeException('Synthetic worker failed.');$lines=array_values(array_filter(explode("\n",trim($p->getOutput())),fn($v)=>str_starts_with($v,'{')));$out[]=json_decode(end($lines),true,32,JSON_THROW_ON_ERROR);}return $out;
|
|
};
|
|
$select($f['manager_id']);
|
|
$opening=['method'=>'open','actor'=>$f['manager_id'],'key'=>$key(),'input'=>['reference'=>'OT-REPLAY-'.Str::uuid(),'client_id'=>$fx['client'],'equipment_id'=>DB::table('wo_orders')->find($fx['order'])->equipment_id,'request'=>'Abertura concorrente.','location'=>'Local de teste','evidence'=>'Pedido sintético.']];
|
|
$out=$pair($opening,$opening);if($out[0]['status']!==200||$out[1]['status']!==200||!$out[1]['result']['replayed']||$out[0]['result']['id']!==$out[1]['result']['id'])throw new RuntimeException('Concurrent opening replay failed.');$proof['same_opening_one_order_one_replay']=true;
|
|
$o=$w->open($f['manager_id'],['reference'=>'OT-CONCURRENT-'.Str::uuid(),'client_id'=>$fx['client'],'equipment_id'=>DB::table('wo_orders')->find($fx['order'])->equipment_id,'request'=>'Prova de concorrência independente.','location'=>'Local de teste','evidence'=>'Somente ensaio isolado.'],$key())['id'];
|
|
$w->record($o,$f['manager_id'],['kind'=>'assignment','origin'=>'CONCURRENT-ASSIGN','version'=>1,'revision'=>0,'technician_id'=>$fx['technician_id']],$key());$select($fx['technician_id']);
|
|
$d=['kind'=>'task','origin'=>'CONCURRENT-TASK','version'=>1,'revision'=>$rev($o),'description'=>'Tarefa sintética concorrente.'];
|
|
$input=['method'=>'record','id'=>$o,'actor'=>$fx['technician_id'],'key'=>$key(),'input'=>$d];$out=$pair($input,$input);
|
|
if($out[0]['status']!==200||$out[1]['status']!==200||!$out[1]['result']['replayed']||$out[0]['result']['id']!==$out[1]['result']['id'])throw new RuntimeException('Record replay race failed.');
|
|
$task=$out[0]['result']['id'];$proof['same_record_one_insert_one_replay']=true;
|
|
$d=['kind'=>'time','task_id'=>$task,'origin'=>'CONCURRENT-TIME-A','version'=>1,'revision'=>$rev($o),'starts_at'=>$fx['day'].'T14:00:00+02:00','ends_at'=>$fx['day'].'T15:00:00+02:00','mode'=>'reported','time_type'=>'work','evidence'=>'Relato retrospectivo sintético.'];
|
|
$a=['method'=>'record','id'=>$o,'actor'=>$fx['technician_id'],'key'=>$key(),'input'=>$d];$b=$a;$b['key']=$key();$b['input']['origin']='CONCURRENT-TIME-B';$out=$pair($a,$b);
|
|
if(array_column($out,'status')!==[200,409])throw new RuntimeException('Overlap race failed.');$time=$out[0]['result']['id'];$proof['concurrent_same_technician_not_double_counted']=true;
|
|
$a=['method'=>'verify','id'=>$time,'actor'=>$fx['reviewer_id'],'key'=>$key(),'input'=>['version'=>1,'revision'=>$rev($o),'decision'=>'acknowledged','reason'=>'Somente auditoria; não autoriza execução.']];$out=$pair($a,$a);
|
|
if($out[0]['status']!==200||$out[1]['status']!==200||!$out[1]['result']['replayed'])throw new RuntimeException('Verification replay race failed.');$proof['verification_one_decision_one_replay']=true;
|
|
// A new version and a decision against its old version cannot both win.
|
|
$original=DB::table('wo_entries')->find($time);$a=['method'=>'record','id'=>$o,'actor'=>$fx['technician_id'],'key'=>$key(),'input'=>array_replace($d,['origin'=>$original->origin,'version'=>2,'previous_id'=>$time,'revision'=>$rev($o),'correction_reason'=>'Correcção concorrente explícita.','ends_at'=>$fx['day'].'T14:30:00+02:00'])];
|
|
$b=['method'=>'verify','id'=>$time,'actor'=>$fx['reviewer_id'],'key'=>$key(),'input'=>['version'=>1,'revision'=>$rev($o),'decision'=>'refused','reason'=>'Revisão antiga concorrente.']];$out=$pair($a,$b);
|
|
if(array_column($out,'status')!==[200,409])throw new RuntimeException('Version versus review race failed.');$proof['new_version_invalidates_old_review']=true;
|
|
$row=(array)DB::table('wo_entries')->find($out[0]['result']['id']);unset($row['id']);$row['origin']='SQL-OVERLAP-A';$row['version']=1;$row['previous_id']=null;$row['starts_at']=$fx['day'].'T16:00:00+02:00';$row['ends_at']=$fx['day'].'T17:00:00+02:00';$row['minutes']=60;$row['revision']=$rev($o);
|
|
$select($f['manager_id']);
|
|
$o2=$w->open($f['manager_id'],['reference'=>'OT-CROSS-'.Str::uuid(),'client_id'=>$fx['client'],'equipment_id'=>DB::table('wo_orders')->find($fx['order'])->equipment_id,'request'=>'Segunda ordem para disputa de tempo.','location'=>'Local de teste','evidence'=>'Somente ensaio de concorrência.'],$key())['id'];
|
|
$w->record($o2,$f['manager_id'],['kind'=>'assignment','origin'=>'CROSS-ASSIGN','version'=>1,'revision'=>0,'technician_id'=>$fx['technician_id']],$key());$select($fx['technician_id']);
|
|
$t2=$w->record($o2,$fx['technician_id'],['kind'=>'task','origin'=>'CROSS-TASK','version'=>1,'revision'=>1,'description'=>'Tarefa de outra ordem.'],$key())['id'];
|
|
$a=['method'=>'sql','actor'=>$fx['technician_id'],'sql'=>$row];$b=$a;$b['sql']['origin']='SQL-OVERLAP-B';$b['sql']['order_id']=$o2;$b['sql']['task_id']=$t2;$b['sql']['revision']=$rev($o2);$out=$pair($a,$b);
|
|
if(array_column($out,'status')!==[200,409]||!($out[1]['database_guard']??false))throw new RuntimeException('SQL concurrency guard failed.');$proof['raw_sql_revision_overlap_guard']=true;
|
|
file_put_contents(base_path('docs/proofs/workshop-concurrency.json'),json_encode(['database'=>'sgf_tempo_test','overlapping_processes_per_case'=>2,'checks'=>$proof,'synthetic_only'=>true],JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR));
|
|
echo "Six overlapping-process scenarios passed only in sgf_tempo_test; no duplicate order, record, decision or effective time.\n";
|