This commit is contained in:
2026-11-02 20:27:11 +02:00
parent b44378b899
commit badaada043
300 changed files with 17458 additions and 1056 deletions
+191
View File
@@ -0,0 +1,191 @@
<?php
use Tempo\Purchasing\Delivery\CodeDeployment;
require dirname(__DIR__).'/integrations/sgf/operational/CodeDeployment.php';
require dirname(__DIR__).'/tests/Support/sgf-update-fixture.php';
$root = dirname(__DIR__);
$host = realpath($root.'/../sgf-tempo');
$pointer = file_get_contents($host.'/packages/purchasing/current.json');
$old = json_decode(file_get_contents($root.'/docs/proofs/sgf-code-deploy-delivery.json'), true, 32, JSON_THROW_ON_ERROR)['candidate'];
$rows = [];
exec(escapeshellarg(PHP_BINARY).' '.escapeshellarg($root.'/scripts/prepare-sgf-operational.php').' '.escapeshellarg($host), $rows, $exit);
if ($exit !== 0) {
throw new RuntimeException('Preparation failed');
}
$prepared = json_decode(implode("\n", $rows), true, 16, JSON_THROW_ON_ERROR);
$new = $prepared['directory'];
$oldHash = hash_file('sha256', $old.'/manifest.json');
$newHash = hash_file('sha256', $new.'/manifest.json');
$manifest = json_decode(file_get_contents($old.'/manifest.json'), true, 32, JSON_THROW_ON_ERROR);
$copyBundle = function (string $source, string $target) {
$manifest = json_decode(file_get_contents($source.'/manifest.json'), true, 32, JSON_THROW_ON_ERROR);
foreach ([...array_keys($manifest['support_files']), 'manifest.json'] as $file) {
if (! is_dir(dirname($target.'/'.$file))) {
mkdir(dirname($target.'/'.$file), 0700, true);
}copy($source.'/'.$file, $target.'/'.$file);
}
};
$checks = [];
$check = function ($ok, $name) use (&$checks) {
if (! $ok) {
throw new RuntimeException($name);
}$checks[] = $name;
};
$check(hash_file('sha256', $prepared['archive']['file']) === $prepared['archive']['sha256'] && $prepared['archive']['manifest_sha256'] === $newHash, 'portable review ZIP matches approved bundle and manifest');
$deny = function (callable $call, string $text) use ($check) {
try {
$call();
throw new LogicException('Unexpected success');
} catch (RuntimeException $error) {
$check(str_contains($error->getMessage(), $text), $text);
}
};
$apply = function (CodeDeployment $deployment, string $bundle, string $hash, ?callable $checkpoint = null) {
$plan = $deployment->plan($bundle, $hash);
return $deployment->apply($bundle, $hash, $plan['target_sha256'], 'Synthetic version operator', true, $checkpoint);
};
$copy = updateFixture($host, $manifest);
$d = new CodeDeployment($copy);
$v1 = $apply($d, $old, $oldHash);
$unowned = updateFixture($host, $manifest);
$copyBundle($old, $unowned.'/packages/internal-requisitions');
$deny(fn () => (new CodeDeployment($unowned))->plan($new, $newHash), 'sem proveniência');
$mutant = sys_get_temp_dir().'/sgf-update-schema-'.bin2hex(random_bytes(6));
$copyBundle($new, $mutant);
$changed = json_decode(file_get_contents($mutant.'/manifest.json'), true, 32, JSON_THROW_ON_ERROR);
$driver = 'integrations/sgf/operational/Installation.php';
file_put_contents($mutant.'/'.$driver, file_get_contents($mutant.'/'.$driver)."\n// Synthetic schema-driver revision\n");
$changed['support_files'][$driver] = hash_file('sha256', $mutant.'/'.$driver);
file_put_contents($mutant.'/manifest.json', json_encode($changed, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
$deny(fn () => $d->plan($mutant, hash_file('sha256', $mutant.'/manifest.json')), 'Transição de schema');
$plan = $d->plan($new, $newHash);
$check($plan['previous']['deployment_id'] === $v1['deployment_id'], 'plan binds update to active installation');
$v2 = $apply($d, $new, $newHash);
$check(hash_file('sha256', $copy.'/packages/internal-requisitions/manifest.json') === $newHash, 'new version applied without source connection');
$check(is_dir($copy.'/storage/app/private/module-deployments/'.$v2['deployment_id'].'/previous-bundle'), 'previous version preserved locally');
$deny(fn () => $d->rollback($v1['deployment_id'], 'Synthetic out-of-order', true), 'fora de ordem');
$deny(fn () => $d->apply($old, $oldHash, $plan['target_sha256'], 'Synthetic stale replay', true), 'versão activa');
$file = array_key_first($manifest['host_overlays']);
$content = file_get_contents($copy.'/'.$file);
file_put_contents($copy.'/'.$file, $content."\n// local work\n");
$deny(fn () => $d->plan($new, $newHash), 'Código aplicado alterado');
file_put_contents($copy.'/'.$file, $content);
file_put_contents($copy.'/packages/internal-requisitions/local-extension.php', '<?php // synthetic unrecorded extension');
$deny(fn () => $d->plan($new, $newHash), 'Conteúdo extra');
unlink($copy.'/packages/internal-requisitions/local-extension.php');
$d->rollback($v2['deployment_id'], 'Synthetic version rollback', true);
$check(hash_file('sha256', $copy.'/packages/internal-requisitions/manifest.json') === $oldHash, 'rollback restores previous bundle exactly');
$check(json_decode(file_get_contents($copy.'/storage/app/private/module-deployments/current.json'), true)['deployment_id'] === $v1['deployment_id'], 'rollback restores active provenance');
$retryPlan = $d->plan($new, $newHash);
$check($retryPlan['deployment_id'] !== $v2['deployment_id'], 'new attempt preserves rolled-back journal');
$deny(fn () => $apply($d, $new, $newHash, function ($step) {
if ($step === 'previous-bundle-preserved') {
throw new RuntimeException('Injected update switch failure');
}
}), 'Injected update switch failure');
$d->rollback($retryPlan['deployment_id'], 'Synthetic interrupted transition', true);
$check(hash_file('sha256', $copy.'/packages/internal-requisitions/manifest.json') === $oldHash, 'interrupted transition recovers previous version');
$again = $apply($d, $new, $newHash);
$check($again['deployment_id'] !== $retryPlan['deployment_id'], 'reapply creates a separate audited attempt');
$d->rollback($again['deployment_id'], 'Synthetic clean transition rollback', true);
$d->rollback($v1['deployment_id'], 'Synthetic first installation rollback', true);
$check(! is_dir($copy.'/packages/internal-requisitions'), 'complete rollback restores original host composition');
$native = updateFixture($host, $manifest, true);
$nativeDeploy = new CodeDeployment($native);
$environment = ['APP_ENV' => 'production', 'APP_DEBUG' => 'false', 'APP_BASE_PATH' => $native, 'APP_KEY' => 'base64:'.base64_encode(random_bytes(32)), 'DB_CONNECTION' => 'sqlite', 'DB_DATABASE' => ':memory:', 'DB_URL' => '', 'CACHE_STORE' => 'array', 'SESSION_DRIVER' => 'file', 'QUEUE_CONNECTION' => 'sync'];
$previousEnv = [];
foreach ($environment as $key => $value) {
$previousEnv[$key] = getenv($key);
putenv($key.'='.$value);
}
$server = null;
$start = function () use ($native) {
$socket = stream_socket_server('tcp://127.0.0.1:0');
$name = stream_socket_get_name($socket, false);
fclose($socket);
$port = (int) substr(strrchr($name, ':'), 1);
$process = proc_open([PHP_BINARY, '-S', '127.0.0.1:'.$port, '-t', $native.'/public'], [0 => ['pipe', 'r'], 1 => ['file', $native.'/storage/logs/server.log', 'a'], 2 => ['file', $native.'/storage/logs/server.log', 'a']], $pipes, $native);
fclose($pipes[0]);
for ($i = 0; $i < 100; $i++) {
$probe = @fsockopen('127.0.0.1', $port);
if ($probe) {
fclose($probe);
break;
}usleep(50000);
}
return [$process, $port];
};
$stop = function ($process) {
proc_terminate($process);
proc_close($process);
};
$status = function ($port, $path) {
$context = stream_context_create(['http' => ['ignore_errors' => true, 'header' => "Accept: application/json\r\n", 'timeout' => 10]]);
file_get_contents('http://127.0.0.1:'.$port.$path, false, $context);
preg_match('/\s([0-9]{3})\s/', $http_response_header[0], $match);
return (int) $match[1];
};
try {
$a = $apply($nativeDeploy, $old, $oldHash);
$nativeDeploy->rebuildCaches($a['deployment_id'], 'Synthetic native cache V1', true);
$check(is_file($native.'/bootstrap/cache/config.php') && is_file($native.'/bootstrap/cache/routes-v7.php') && is_file($native.'/bootstrap/cache/events.php'), 'native Laravel config route and event caches built');
$b = $apply($nativeDeploy, $new, $newHash);
$check(! is_file($native.'/bootstrap/cache/config.php') && ! is_file($native.'/bootstrap/cache/routes-v7.php'), 'version update invalidates previous native caches');
$deny(fn () => $nativeDeploy->rebuildCaches($b['deployment_id'], 'Synthetic cache compilation failure', true, function ($command) {
if ($command === 'config:cache') {
throw new RuntimeException('Injected cache compilation interruption');
}
}), 'Injected cache compilation interruption');
$cacheJournal = json_decode(file_get_contents($native.'/storage/app/private/module-deployments/'.$b['deployment_id'].'/journal.json'), true, 32, JSON_THROW_ON_ERROR);
$check($cacheJournal['cache_state'] === 'failed' && is_file($native.'/storage/framework/down'), 'partial native cache build is journalled under maintenance');
file_put_contents($native.'/bootstrap/cache/config.php', '<?php invalid_partial_cache(');
$cliRows = [];
$cliArgs = [PHP_BINARY, $new.'/scripts/sgf-code-deploy.php', 'cache', $native, $b['deployment_id'], 'Synthetic native cache V2 CLI', '--offline'];
exec(implode(' ', array_map('escapeshellarg', $cliArgs)), $cliRows, $cliExit);
$check($cliExit === 0 && json_decode(implode("\n", $cliRows), true, 16, JSON_THROW_ON_ERROR)['cache_state'] === 'built', 'cache CLI recovers interrupted native compilation');
$check(! str_contains(file_get_contents($native.'/bootstrap/cache/config.php'), 'invalid_partial_cache'), 'invalid partial config cache cannot block native recovery');
[$server,$port] = $start();
$check($status($port, '/purchasing/internal-requisitions') === 503, 'cached deployment remains in native maintenance');
$stop($server);
$server = null;
unlink($native.'/storage/framework/down');
[$server,$port] = $start();
$check($status($port, '/purchasing/internal-requisitions') === 401, 'cached module route survives restart and denies anonymous access');
$stop($server);
$server = null;
file_put_contents($native.'/storage/framework/down', '{"status":503}');
$compiled = file_get_contents($native.'/bootstrap/cache/config.php');
file_put_contents($native.'/bootstrap/cache/config.php', $compiled."\n// subsequent local cache change\n");
$deny(fn () => $nativeDeploy->rollback($b['deployment_id'], 'Synthetic conflicting cache rollback', true), 'Cache mudou após compilação');
file_put_contents($native.'/bootstrap/cache/config.php', $compiled);
$nativeDeploy->rollback($b['deployment_id'], 'Synthetic cached version rollback', true);
$check(! is_file($native.'/bootstrap/cache/config.php') && ! is_file($native.'/bootstrap/cache/routes-v7.php'), 'rollback invalidates registered rebuilt caches');
$nativeDeploy->rebuildCaches($a['deployment_id'], 'Synthetic native cache restored V1', true);
$previousCacheHash = hash_file('sha256', $native.'/bootstrap/cache/config.php');
$replayedRollback = $nativeDeploy->rollback($b['deployment_id'], 'Synthetic repeated version rollback', true);
$check($replayedRollback['replayed'] && hash_file('sha256', $native.'/bootstrap/cache/config.php') === $previousCacheHash, 'repeated rollback preserves previous-version rebuilt caches');
[$server,$port] = $start();
$check($status($port, '/purchasing/internal-requisitions') === 503, 'previous version restarts with native caches and maintenance');
$stop($server);
$server = null;
$nativeDeploy->rollback($a['deployment_id'], 'Synthetic native baseline rollback', true);
unlink($native.'/storage/framework/down');
[$server,$port] = $start();
$check($status($port, '/purchasing/internal-requisitions') === 404, 'restarted baseline exposes no removed module route');
$check($status($port, '/up') === 200, 'baseline starts after cached deployment rollback');
} finally {
if (is_resource($server)) {
$stop($server);
}foreach ($previousEnv as $key => $value) {
putenv($value === false ? $key : $key.'='.$value);
}
}
$check(file_get_contents($host.'/packages/purchasing/current.json') === $pointer, 'destination remains untouched on 0.1.1');
$proof = ['passed' => true, 'checks' => $checks, 'old_candidate' => $old, 'new_candidate' => $new, 'archive' => $prepared['archive'], 'copies' => [$copy, $native, $unowned, $mutant], 'checked_at' => gmdate('c'), 'destination_version' => '0.1.1', 'destination_applied' => false, 'prior_suites_repeated' => false, 'actual_destination_services_touched' => false, 'native_caches_built' => true, 'isolated_server_restarts_verified' => true];
file_put_contents($root.'/docs/proofs/sgf-update-cache-delivery.json', json_encode($proof, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
echo json_encode($proof, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR)."\n";