99 lines
5.6 KiB
PHP
99 lines
5.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
use Tempo\Distribution\Release;
|
|
|
|
// Produces review artifacts, never installs, migrates, updates a release channel or changes pointers.
|
|
$root = dirname(__DIR__);
|
|
$runtime = [];
|
|
$verification = [];
|
|
$add = function (string $relative, array &$list) use ($root): void {
|
|
$path = $root.'/'.$relative;
|
|
if (! is_file($path) || is_link($path)) {
|
|
throw new RuntimeException('Ficheiro de candidata inexistente ou link: '.$relative);
|
|
}
|
|
$list[$relative] = hash_file('sha256', $path);
|
|
};
|
|
foreach (['packages/purchasing', 'integrations/sgf/internal-requisitions'] as $directory) {
|
|
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($root.'/'.$directory, FilesystemIterator::SKIP_DOTS)) as $file) {
|
|
if ($file->isLink()) {
|
|
throw new RuntimeException('Links recusados.');
|
|
}
|
|
if ($file->isFile()) {
|
|
$add(str_replace('\\', '/', substr($file->getPathname(), strlen($root) + 1)), $runtime);
|
|
}
|
|
}
|
|
}
|
|
foreach (['app/Services/InternalRequisitionPort.php', 'app/Services/RequisitionWorkflow.php', 'app/Models/Requisition.php', 'app/Providers/AppServiceProvider.php', 'app/Http/Requests/StoreRequisitionRequest.php', 'app/Http/Controllers/RequisitionController.php', 'app/Http/Middleware/HandleInertiaRequests.php', 'resources/js/pages/requisitions/form.tsx', 'resources/js/pages/requisitions/show.tsx', 'resources/js/types/procurement.ts', 'database/migrations/2026_11_01_000046_add_requisition_command_versions.php'] as $file) {
|
|
$add($file, $runtime);
|
|
}
|
|
foreach (['scripts/internal-requisitions-proof.php', 'scripts/internal-requisitions-sgf-proof.php', 'scripts/internal-requisition-screen-check.mjs', 'tests/Support/internal-requisition-sgf-router.php', 'tests/Support/internal-requisition-sgf-worker.php', 'tests/Support/internal-requisition-sgf-restored.php', 'tests/Feature/InternalRequisitionCandidateTest.php'] as $file) {
|
|
$add($file, $verification);
|
|
}
|
|
ksort($runtime);
|
|
ksort($verification);
|
|
$hash = hash('sha256', json_encode([$runtime, $verification], JSON_THROW_ON_ERROR));
|
|
$id = 'IR-CANDIDATE-'.substr($hash, 0, 16);
|
|
$destination = $root.'/storage/app/private/candidates/'.$id;
|
|
if (! is_dir($destination)) {
|
|
mkdir($destination, 0700, true);
|
|
}
|
|
$manifest = ['format' => 'tempo.integration-candidate.v1', 'candidate_id' => $id, 'source_sha256' => $hash, 'installable_by_code_updater' => false, 'activation_authorized' => false,
|
|
'destination_version_unchanged' => '0.1.1', 'context' => 'procurement.internal', 'scope' => 'draft -> submit -> independent approval -> consultation',
|
|
'validated_scope_excludes' => ['people.third_party', 'equipment/spare-part technical workflows', 'supplier sourcing and purchase orders'],
|
|
'host_schema_changes' => ['compras' => 'baseline 45 + additive migration 46', 'sgf' => 'ir-v1 + permission column widening/private catalogue check extension; no grants'],
|
|
'rollback' => ['code' => 'separate pointer mechanism; candidate not selected', 'schema_data' => 'transactional DDL, compatible additive column and backup/restore tested separately; no automatic down/restore'],
|
|
'runtime_files' => $runtime, 'verification_files' => $verification];
|
|
$manifestText = json_encode($manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR)."\n";
|
|
foreach ($runtime + $verification as $file => $expected) {
|
|
$target = $destination.'/'.$file;
|
|
if (is_file($target)) {
|
|
if (hash_file('sha256', $target) !== $expected) {
|
|
throw new RuntimeException('Candidata imutável divergente.');
|
|
}
|
|
|
|
continue;
|
|
}
|
|
if (! is_dir(dirname($target))) {
|
|
mkdir(dirname($target), 0700, true);
|
|
}
|
|
if (! copy($root.'/'.$file, $target) || hash_file('sha256', $target) !== $expected) {
|
|
throw new RuntimeException('Origem mudou durante a preparação.');
|
|
}
|
|
}
|
|
if (is_file($destination.'/manifest.json') && file_get_contents($destination.'/manifest.json') !== $manifestText) {
|
|
throw new RuntimeException('Manifesto imutável divergente.');
|
|
}
|
|
file_put_contents($destination.'/manifest.json', $manifestText);
|
|
$archives = [];
|
|
foreach (['runtime' => $runtime, 'verification' => $verification] as $kind => $files) {
|
|
$archive = $destination.'/'.$kind.'.zip';
|
|
if (! is_file($archive)) {
|
|
$zip = new ZipArchive;
|
|
if ($zip->open($archive, ZipArchive::CREATE | ZipArchive::EXCL) !== true) {
|
|
throw new RuntimeException('Falha no arquivo da candidata.');
|
|
}
|
|
foreach ($files as $file => $expected) {
|
|
$zip->addFile($destination.'/'.$file, $file);
|
|
}
|
|
$zip->addFromString('manifest.json', $manifestText);
|
|
if (! $zip->close()) {
|
|
throw new RuntimeException('Arquivo incompleto.');
|
|
}
|
|
}
|
|
$archives[$kind] = ['file' => $archive, 'sha256' => hash_file('sha256', $archive)];
|
|
}
|
|
require_once $root.'/packages/module-updates/Release.php';
|
|
$rejected = false;
|
|
try {
|
|
Release::inspect($archives['runtime']['file'], hash('sha256', $manifestText));
|
|
} catch (RuntimeException $e) {
|
|
$rejected = true;
|
|
}
|
|
if (! $rejected) {
|
|
throw new RuntimeException('A candidata não deve ser instalável pelo updater de código.');
|
|
}
|
|
$result = ['candidate_id' => $id, 'directory' => $destination, 'manifest_sha256' => hash('sha256', $manifestText), 'archives' => $archives, 'standard_updater_refuses_candidate' => true, 'activation_performed' => false];
|
|
file_put_contents($root.'/docs/proofs/internal-requisitions-candidate.json', json_encode($result,JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR));
|
|
echo json_encode($result,JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR)."\n";
|