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";