commit14
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
require dirname(__DIR__).'/integrations/sgf/operational/CodeDeployment.php';
|
||||
require dirname(__DIR__).'/integrations/sgf/access/AccessOverlay.php';
|
||||
require dirname(__DIR__).'/integrations/sgf/operational/BudgetOverlay.php';
|
||||
require dirname(__DIR__).'/integrations/sgf/operational/BootOverlay.php';
|
||||
|
||||
use Tempo\Purchasing\Delivery\CodeDeployment;
|
||||
|
||||
$root = dirname(__DIR__);
|
||||
$host = realpath($root.'/../sgf-tempo');
|
||||
$pointer = file_get_contents($host.'/packages/purchasing/current.json');
|
||||
$checks = [];
|
||||
$check = function (bool $ok, string $name) use (&$checks) {
|
||||
if (! $ok) {
|
||||
throw new RuntimeException($name);
|
||||
}$checks[] = $name;
|
||||
};
|
||||
$deny = function (callable $work, string $text) use ($check) {
|
||||
try {
|
||||
$work();
|
||||
throw new LogicException('Unexpected success');
|
||||
} catch (RuntimeException $error) {
|
||||
$check(str_contains($error->getMessage(), $text), $text);
|
||||
}
|
||||
};
|
||||
$command = escapeshellarg(PHP_BINARY).' '.escapeshellarg($root.'/scripts/prepare-sgf-operational.php').' '.escapeshellarg($host);
|
||||
exec($command, $output, $exit);
|
||||
if ($exit !== 0) {
|
||||
throw new RuntimeException('Prepare failed');
|
||||
}
|
||||
$bundle = json_decode(implode("\n", $output), true, 16, JSON_THROW_ON_ERROR)['directory'];
|
||||
$manifest = json_decode(file_get_contents($bundle.'/manifest.json'), true, 32, JSON_THROW_ON_ERROR);
|
||||
$hash = hash_file('sha256', $bundle.'/manifest.json');
|
||||
$make = function () use ($host, $manifest) {
|
||||
$copy = sys_get_temp_dir().'/sgf-code-deploy-'.bin2hex(random_bytes(6));
|
||||
mkdir($copy, 0700);
|
||||
foreach ($manifest['host_overlays'] as $file => $values) {
|
||||
if (! is_dir(dirname($copy.'/'.$file))) {
|
||||
mkdir(dirname($copy.'/'.$file), 0700, true);
|
||||
}copy($host.'/'.$file, $copy.'/'.$file);
|
||||
}
|
||||
mkdir($copy.'/packages/purchasing', 0700, true);
|
||||
copy($host.'/packages/purchasing/current.json', $copy.'/packages/purchasing/current.json');
|
||||
mkdir($copy.'/bootstrap/cache', 0700, true);
|
||||
mkdir($copy.'/storage/framework', 0700, true);
|
||||
file_put_contents($copy.'/bootstrap/cache/config.php', "<?php return ['synthetic'=>true];");
|
||||
file_put_contents($copy.'/bootstrap/cache/routes-v7.php', '<?php return [];');
|
||||
file_put_contents($copy.'/bootstrap/cache/unrelated.php', '<?php // preserve unrelated cache support');
|
||||
|
||||
return $copy;
|
||||
};
|
||||
$copy = $make();
|
||||
$deploy = new CodeDeployment($copy);
|
||||
$deny(fn () => new CodeDeployment(''), 'Host explícito obrigatório');
|
||||
$deny(fn () => $deploy->plan('', $hash), 'Bundle explícito obrigatório');
|
||||
$plan = $deploy->plan($bundle, $hash);
|
||||
$check(! $plan['writes'] && ! is_dir($copy.'/storage/app'), 'plan is read-only');
|
||||
$deny(fn () => $deploy->plan($bundle, str_repeat('0', 64)), 'Manifesto divergente');
|
||||
file_put_contents($copy.'/.env', 'APP_CONFIG_CACHE=storage/framework/custom.php');
|
||||
$deny(fn () => $deploy->plan($bundle, $hash), 'Cache Laravel personalizado');
|
||||
unlink($copy.'/.env');
|
||||
$deny(fn () => $deploy->apply($bundle, $hash, $plan['target_sha256'], 'Synthetic operator', true), 'manutenção activa');
|
||||
file_put_contents($copy.'/storage/framework/down', '{"status":503}');
|
||||
$deny(fn () => $deploy->apply($bundle, $hash, $plan['target_sha256'], 'Synthetic operator', false), 'serviços de escrita parados');
|
||||
$deny(fn () => $deploy->apply($bundle, $hash, str_repeat('0', 64), 'Synthetic operator', true), 'Destino de código divergente');
|
||||
$beforeCache = file_get_contents($copy.'/bootstrap/cache/config.php');
|
||||
$result = $deploy->apply($bundle, $hash, $plan['target_sha256'], 'Synthetic operator', true);
|
||||
$id = $result['deployment_id'];
|
||||
$check($result['state'] === 'applied' && ! $result['activation_performed'] && is_file($copy.'/storage/framework/down'), 'applied offline without activation');
|
||||
$check(! is_file($copy.'/bootstrap/cache/config.php') && ! is_file($copy.'/bootstrap/cache/routes-v7.php') && is_file($copy.'/bootstrap/cache/unrelated.php'), 'invalidates known Laravel caches without touching unrelated files');
|
||||
foreach ($manifest['host_overlays'] as $file => $values) {
|
||||
$check(hash_file('sha256', $copy.'/'.$file) === $values['after_sha256'], 'applied verified overlay '.$file);
|
||||
}
|
||||
$check($deploy->apply($bundle, $hash, $plan['target_sha256'], 'Synthetic operator', true)['replayed'], 'apply retry does not duplicate installation');
|
||||
$file = array_key_first($manifest['host_overlays']);
|
||||
$applied = file_get_contents($copy.'/'.$file);
|
||||
file_put_contents($copy.'/'.$file, $applied."\n// local change\n");
|
||||
$deny(fn () => $deploy->rollback($id, 'Synthetic operator', true), 'Código local alterado');
|
||||
$check(is_dir($copy.'/packages/internal-requisitions'), 'refused rollback preserves bundle and local code');
|
||||
file_put_contents($copy.'/'.$file, $applied);
|
||||
file_put_contents($copy.'/bootstrap/cache/config.php', '<?php return ["new_configuration"=>true];');
|
||||
$deny(fn () => $deploy->rollback($id, 'Synthetic operator', true), 'Cache recriado');
|
||||
unlink($copy.'/bootstrap/cache/config.php');
|
||||
$rollback = $deploy->rollback($id, 'Synthetic operator', true);
|
||||
$check($rollback['state'] === 'rolled-back' && ! $rollback['data_schema_changed'] && is_file($copy.'/storage/framework/down'), 'rollback preserves maintenance and never touches data schema');
|
||||
$check(file_get_contents($copy.'/bootstrap/cache/config.php') === $beforeCache, 'rollback restores exact previous cache');
|
||||
foreach ($manifest['host_overlays'] as $file => $values) {
|
||||
$check(hash_file('sha256', $copy.'/'.$file) === $values['before_sha256'], 'restored original overlay '.$file);
|
||||
}
|
||||
$check(! is_dir($copy.'/packages/internal-requisitions') && is_dir($copy.'/storage/app/private/module-deployments/'.$id.'/withdrawn'), 'withdrawn bundle preserved for diagnosis');
|
||||
$check(file_get_contents($copy.'/packages/purchasing/current.json') === $pointer, 'clone remains on original package pointer');
|
||||
$failed = $make();
|
||||
file_put_contents($failed.'/storage/framework/down', '{"status":503}');
|
||||
$recover = new CodeDeployment($failed);
|
||||
$plan2 = $recover->plan($bundle, $hash);
|
||||
$deny(fn () => $recover->apply($bundle, $hash, $plan2['target_sha256'], 'Synthetic failure', true, function ($step) {
|
||||
if (str_starts_with($step, 'file:')) {
|
||||
throw new RuntimeException('Injected deployment interruption');
|
||||
}
|
||||
}), 'Injected deployment interruption');
|
||||
$check(is_file($failed.'/storage/framework/down'), 'interruption retains maintenance');
|
||||
$recover->rollback($id, 'Synthetic recovery', true);
|
||||
foreach ($manifest['host_overlays'] as $file => $values) {
|
||||
$check(hash_file('sha256', $failed.'/'.$file) === $values['before_sha256'], 'interrupted deployment recovers '.$file);
|
||||
}
|
||||
$check(file_get_contents($host.'/packages/purchasing/current.json') === $pointer, 'destination pointer untouched');
|
||||
$switched = $make();
|
||||
file_put_contents($switched.'/storage/framework/down', '{"status":503}');
|
||||
$switchDeployment = new CodeDeployment($switched);
|
||||
$switchPlan = $switchDeployment->plan($bundle, $hash);
|
||||
$deny(fn () => $switchDeployment->apply($bundle, $hash, $switchPlan['target_sha256'], 'Synthetic commit failure', true, function ($step) {
|
||||
if ($step === 'bundle-switched') {
|
||||
throw new RuntimeException('Injected post-switch interruption');
|
||||
}
|
||||
}), 'Injected post-switch interruption');
|
||||
$switchDeployment->rollback($id, 'Synthetic post-switch recovery', true);
|
||||
$check(! is_dir($switched.'/packages/internal-requisitions') && is_file($switched.'/storage/framework/down'), 'post-switch interruption recovers without exposing partial code');
|
||||
$lockPath = $switched.'/storage/app/private/module-deployments/deployment.lock';
|
||||
$lock = fopen($lockPath, 'c+');
|
||||
flock($lock, LOCK_EX);
|
||||
$deny(fn () => $switchDeployment->rollback($id, 'Synthetic concurrent recovery', true), 'Outra implantação em curso');
|
||||
flock($lock, LOCK_UN);
|
||||
fclose($lock);
|
||||
$cliCopy = $make();
|
||||
file_put_contents($cliCopy.'/storage/framework/down', '{"status":503}');
|
||||
$cli = function (array $args) use ($bundle) {
|
||||
$command = implode(' ', array_map('escapeshellarg', [PHP_BINARY, $bundle.'/scripts/sgf-code-deploy.php', ...$args]));
|
||||
$rows = [];
|
||||
exec($command, $rows, $exit);
|
||||
if ($exit !== 0) {
|
||||
throw new RuntimeException('Code deployment CLI failed.');
|
||||
}
|
||||
|
||||
return json_decode(implode("\n", $rows), true, 32, JSON_THROW_ON_ERROR);
|
||||
};
|
||||
$cliPlan = $cli(['plan', $cliCopy, $bundle, $hash]);
|
||||
$check(! $cliPlan['writes'], 'CLI plan verifies bundle without writes');
|
||||
$cliApplied = $cli(['apply', $cliCopy, $bundle, $hash, $cliPlan['target_sha256'], 'Synthetic CLI operator', '--offline']);
|
||||
$check($cliApplied['state'] === 'applied' && $cliApplied['maintenance_preserved'], 'CLI apply leaves services and maintenance under operator control');
|
||||
$cliRolled = $cli(['rollback', $cliCopy, $cliApplied['deployment_id'], 'Synthetic CLI recovery', '--offline']);
|
||||
$check($cliRolled['state'] === 'rolled-back' && file_get_contents($cliCopy.'/packages/purchasing/current.json') === $pointer, 'CLI rollback preserves 0.1.1 pointer');
|
||||
$proof = ['passed' => true, 'checks' => $checks, 'candidate' => $bundle, 'clones' => [$copy, $failed, $switched, $cliCopy], 'checked_at' => gmdate('c'), 'destination_version' => '0.1.1', 'destination_applied' => false, 'old_suites_repeated' => false, 'database_opened' => false, 'services_restarted' => false];
|
||||
file_put_contents($root.'/docs/proofs/sgf-code-deploy-delivery.json', json_encode($proof, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
|
||||
echo json_encode($proof, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR)."\n";
|
||||
Reference in New Issue
Block a user