52 lines
8.2 KiB
PHP
52 lines
8.2 KiB
PHP
<?php
|
|
use Illuminate\Contracts\Console\Kernel;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Str;
|
|
use App\Models\User;
|
|
use Tempo\Purchasing\SgfSourcing\Schema;
|
|
use Tempo\Purchasing\Delivery\CodeDeployment;
|
|
$host=getenv('SOURCING_CLONE');$root=getenv('SOURCING_REPO');$work=getenv('SOURCING_WORK');
|
|
require $host.'/vendor/autoload.php';$app=require $host.'/bootstrap/app.php';$app->make(Kernel::class)->bootstrap();
|
|
try{
|
|
$db=DB::connection();$name=$db->selectOne('select current_database() as db')->db;
|
|
if(!preg_match('/^sgf_sourcing_test_[a-f0-9]{12}$/D',$name)||$name!==getenv('SOURCING_DB')||!str_contains(str_replace('\\','/',$host),'/private/sourcing-')||str_replace('\\','/',$app->basePath())!==$host)throw new RuntimeException('Not an isolated sourcing copy');
|
|
$candidate=json_decode(file_get_contents($root.'/docs/proofs/sgf-sourcing-candidate.json'),true,32,JSON_THROW_ON_ERROR);
|
|
if(!class_exists(Schema::class))require_once $candidate['directory'].'/integrations/sgf/sourcing/Schema.php';require_once $root.'/integrations/sgf/operational/CodeDeployment.php';
|
|
$deploy=new CodeDeployment($host);$checks=[];$check=function($ok,$label)use(&$checks){if(!$ok)throw new RuntimeException($label);$checks[]=$label;};
|
|
$fingerprint=function(string $table)use($db){return hash('sha256',json_encode($db->table($table)->orderBy('id')->get(),JSON_THROW_ON_ERROR));};
|
|
if(($argv[1]??'')==='setup'){
|
|
$baseline=['request'=>$fingerprint('ir_requests'),'budget'=>$fingerprint('cb_budget_entries'),'consumption'=>$fingerprint('cb_budget_consumptions'),'grants_count'=>$db->table('permission_grants')->count()];file_put_contents($work.'/origin.json',json_encode($baseline));
|
|
$armed=true;$db->listen(function($query)use(&$armed){if($armed&&str_contains(strtolower($query->sql),'create table "ss_events"')){$armed=false;throw new RuntimeException('Injected migration failure');}});
|
|
try{Schema::migrate($db,hash_file('sha256',$work.'/before.dump'),'Isolated failure proof');throw new RuntimeException('Expected failure');}catch(RuntimeException $e){$check($e->getMessage()==='Injected migration failure','Injected migration failure surfaced');}
|
|
$check(!$db->getSchemaBuilder()->hasTable('ss_suppliers')&&!$db->table('sgc_installations')->where('module','sourcing')->exists(),'Migration failure rolls back tables and catalogue');
|
|
$migration=Schema::migrate($db,hash_file('sha256',$work.'/before.dump'),'Isolated sourcing proof');$check($migration['prepared'],'Additive migration accepted');$check(Schema::migrate($db,hash_file('sha256',$work.'/before.dump'),'Isolated replay')['replayed'],'Migration replay is stable');
|
|
$old=json_decode(file_get_contents($host.'/packages/internal-requisitions/manifest.json'),true);$next=json_decode(file_get_contents($candidate['directory'].'/manifest.json'),true);
|
|
$current=json_decode(file_get_contents($host.'/storage/app/private/module-deployments/current.json'),true);
|
|
try{$deploy->plan($candidate['directory'],$candidate['archive']['manifest_sha256']);throw new RuntimeException('Unprepared plan accepted');}catch(RuntimeException $e){$check(str_contains($e->getMessage(),'migração própria'),'Schema-changing update refuses missing migration receipt');}
|
|
$receipt=['from_manifest_sha256'=>$current['manifest_sha256'],'to_manifest_sha256'=>$candidate['archive']['manifest_sha256'],'host'=>$host,'schema_verified'=>Schema::compatible($db),'changes'=>[]];
|
|
foreach($next['support_files'] as $file=>$hash)if(($old['support_files'][$file]??null)!==$hash)$receipt['changes'][$file]=['before'=>$old['support_files'][$file]??null,'after'=>$hash];
|
|
file_put_contents($host.'/storage/framework/down','{}');
|
|
$cli=new Symfony\Component\Process\Process([PHP_BINARY,$candidate['directory'].'/scripts/sgf-sourcing-install.php','migrate',$host,Tempo\Purchasing\Delivery\Installation::target($db),$work.'/before.dump',hash_file('sha256',$work.'/before.dump'),'Isolated migration CLI',$candidate['archive']['manifest_sha256']],$host,['PG_RESTORE_BINARY'=>'C:/Program Files/PostgreSQL/17/bin/pg_restore.exe']);$cli->mustRun();
|
|
$cliResult=json_decode($cli->getOutput(),true,32,JSON_THROW_ON_ERROR);$check($cliResult['prepared']&&!$cliResult['activated']&&!$cliResult['rights_granted'],'Packaged migration CLI produces exact receipt without activation or grants');
|
|
$plan=$deploy->plan($candidate['directory'],$candidate['archive']['manifest_sha256']);$applied=$deploy->apply($candidate['directory'],$candidate['archive']['manifest_sha256'],$plan['target_sha256'],'Isolated sourcing update',true);unlink($host.'/storage/framework/down');file_put_contents($work.'/applied.json',json_encode($applied));
|
|
$target=Tempo\Purchasing\Delivery\Installation::target($db);file_put_contents($host.'/config/purchasing-operational.php','<?php return '.var_export(['enabled'=>true,'target'=>$target,'scopes'=>['1:1:1']],true).';');file_put_contents($host.'/config/purchasing-sourcing.php','<?php return ["enabled"=>true];');
|
|
$private=json_decode(file_get_contents('C:/laragon/data/sgf-tempo/run/modular-regression-2576427d1939/private/development.json'),true);
|
|
$user=User::forceCreate(['name'=>'Synthetic isolated evaluator','email'=>'fixture-'.Str::uuid().'@example.invalid','password'=>$private['password'],'auth_version'=>1]);
|
|
$db->table('company_memberships')->insert(['user_id'=>$user->id,'tenant_id'=>1,'company_id'=>1]);
|
|
$departmentPolicy=$db->table('ir_policies')->where(['tenant_id'=>1,'company_id'=>1,'unit_id'=>1])->first();$departmentPayload=json_decode($departmentPolicy->payload,true);$departmentPayload['members'][]=['user_id'=>$user->id,'department_ref'=>'OPER'];$db->table('ir_policies')->where('id',$departmentPolicy->id)->update(['payload'=>json_encode($departmentPayload)]); // Disposable configuration fixture only; submitted request snapshot is unchanged.
|
|
$grants=[1=>['purchasing_sourcing.prepare'],2=>['purchasing_sourcing.evaluate','purchasing_sourcing.propose'],$user->id=>['company.view','unit.view','purchasing_requisitions.view','purchasing_sourcing.evaluate','purchasing_sourcing.confirm']];
|
|
foreach($grants as $id=>$rights)foreach($rights as $right)$db->table('permission_grants')->insert(['user_id'=>$id,'tenant_id'=>1,'company_id'=>1,'unit_id'=>$right==='company.view'?null:1,'permission'=>$right,'created_at'=>now(),'updated_at'=>now()]);
|
|
foreach([1,2,3] as $n)$db->table('ss_suppliers')->insert(['tenant_id'=>1,'company_id'=>1,'unit_id'=>1,'reference'=>'SYNTHETIC-'.$n,'payload'=>json_encode(['name'=>'Fornecedor sintético '.$n,'active'=>true,'blocked'=>false,'review_until'=>'2027-12-31','version'=>1,'synthetic_only'=>true]),'created_at'=>now()]);
|
|
file_put_contents($work.'/fixture.json',json_encode(['user_id'=>$user->id,'email'=>$user->email,'password'=>$private['password']]));
|
|
echo json_encode(['passed'=>true,'checks'=>$checks,'fixture_user_only_in_disposable_copy'=>true,'deployment'=>$applied]);
|
|
}elseif(($argv[1]??'')==='verify'){
|
|
$baseline=json_decode(file_get_contents($work.'/origin.json'),true);foreach(['request'=>'ir_requests','budget'=>'cb_budget_entries','consumption'=>'cb_budget_consumptions'] as $key=>$table)$check($baseline[$key]===$fingerprint($table),'Origin '.$key.' unchanged');
|
|
$check($db->table('ss_processes')->count()===1,'Exactly one sourcing process');$check($db->table('ss_commands')->count()===$db->table('ss_events')->count(),'One event per committed command');
|
|
$saved=$db->table('ss_processes')->first();$state=json_decode($saved->payload,true);$check($state['confirmed']!==null,'Independent award confirmed in isolated copy');
|
|
$before=$fingerprint('ss_events');$failed=false;try{$db->transaction(fn()=>$db->table('ss_events')->where('id',1)->update(['action'=>'tampered']));}catch(Throwable){$failed=true;}$check($failed&&$before===$fingerprint('ss_events'),'Database rejects event tampering');
|
|
$applied=json_decode(file_get_contents($work.'/applied.json'),true);file_put_contents($host.'/storage/framework/down','{}');$rolled=$deploy->rollback($applied['deployment_id'],'Isolated sourcing code recovery',true);
|
|
$check($state===json_decode($db->table('ss_processes')->first()->payload,true),'Code rollback preserves award data');
|
|
echo json_encode(['passed'=>true,'checks'=>$checks,'code_recovery'=>$rolled,'old_code_requires_schema_recovery'=>true]);
|
|
}else throw new RuntimeException('Use setup/verify');
|
|
}catch(Throwable $e){fwrite(STDERR,get_class($e).': '.$e->getMessage()."\n");exit(1);}
|