require $file)($plan['source'].'/tools/portal-stability-runtime.php'); $mode = $argv[1] ?? 'preview'; throw_unless(in_array($mode, ['preview', 'apply', 'replay'], true), RuntimeException::class, 'Unsupported QA action.'); $target = DB::selectOne('select current_database() as database,inet_server_port() as port'); throw_unless($target->database === 'stability_consumers_20261011_084534_test' && (int) $target->port === 55441 && app()->environment('testing'), RuntimeException::class, 'Exact isolated QA required.'); $actor = User::findOrFail(17); throw_unless($actor->can('manage-portal'), RuntimeException::class, 'Existing authorized QA library manager required.'); throw_unless(hash_file('sha256', $source['retained']) === $source['sha256'], RuntimeException::class, 'Original source bytes changed.'); $staging = storage_path('app/private/document-import-staging/segmentation-20261011'); if (! is_dir($staging)) { mkdir($staging, 0700, true); } $original = $staging.'/'.$source['sha256'].'.docx'; if (! is_file($original)) { copy($source['retained'], $original); } throw_unless(hash_file('sha256', $original) === $source['sha256'], RuntimeException::class, 'Staged copy differs.'); $externalId = 'investmoz-segmentation-source-'.$source['sha256']; $identityStatement = $source['exact_requested_exemplar_confirmed'] ? 'Identidade binária com segmentar processos v1(3).docx confirmada pelo responsável em 11/10/2026; v1.docx é nome local do mesmo exemplar.' : 'O nome pedido v1(3) ainda não foi confirmado; não declarar identidade entre exemplares.'; $manifest = ['documents' => [[ 'external_id' => $externalId, 'relative_path' => 'segmentation-20261011/'.$source['sha256'].'.docx', 'file_name' => $source['observed_filename'], 'title' => 'Portal do Investidor — InvesteMoz: segmentar processos v1', 'issuer' => (string) $source['version_properties']['creator'], 'category' => 'portal_specifications', 'topic' => 'segmentation_reconciliation', 'legislation' => false, 'document_type' => 'guide', 'language' => 'pt', 'version_label' => 'v1; propriedade OOXML revision '.$source['version_properties']['revision'].'; SHA256 '.substr($source['sha256'], 0, 12), 'source_reference' => 'Exemplar observado: '.$source['source'].'; SHA256 '.$source['sha256'].'; propriedades OOXML: criação '.$source['version_properties']['created'].', alteração '.$source['version_properties']['modified'].'. Autoria/datas são propriedades do original; data de emissão não identificada. '.$identityStatement, 'description' => 'Especificação de programação incorporada privadamente em QA. Referências por secção/OOXML na auditoria existente. Não é diploma oficial, não ativa regra ou cálculo e não publica conteúdo. Preservar versões anteriores, cinco fases/19 etapas e decisões pendentes.', ]]]; file_put_contents($staging.'/manifest.json', json_encode($manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR)); $counts = static function (): array { $result = []; foreach (['documents', 'portal_resources', 'portal_resource_revisions', 'document_malware_scans', 'audit_events', 'opportunities', 'organizations', 'memberships', 'users'] as $table) { $result[$table] = DB::table($table)->count(); } return $result; }; $before = $counts(); $args = ['manifest' => 'segmentation-20261011/manifest.json']; $previewExit = Artisan::call('documents:import-batch', $args); $preview = Artisan::output(); file_put_contents($directory.'/library-preview-'.$mode.'.txt', $preview); throw_unless($previewExit === 0 && ! str_contains($preview, 'review required') && ! str_contains($preview, 'not imported'), RuntimeException::class, 'Preview failed or identity/version conflict.'); throw_unless($counts() === $before, RuntimeException::class, 'Preview mutated database.'); $health = app(MalwareScanner::class)->health(); if ($mode !== 'preview') { throw_unless($health['available'] ?? false, RuntimeException::class, 'Real ClamD not healthy.'); config(['queue.default' => 'sync']); $exit = Artisan::call('documents:import-batch', $args + ['--apply' => true, '--actor' => '17']); $output = Artisan::output(); file_put_contents($directory.'/library-apply-'.$mode.'.txt', $output); throw_unless($exit === 0 && ! str_contains($output, 'review required') && ! str_contains($output, 'not imported'), RuntimeException::class, 'QA import failed.'); } $documents = DB::table('documents')->where('sha256', $source['sha256'])->whereNull('deleted_at')->get(); $retrieval = []; foreach ($documents as $document) { $resource = DB::table('portal_resources')->where('document_id', $document->id)->first(); $bytes = Storage::disk('local')->get($document->storage_key); if ($document->storage_encryption === 'laravel') { $bytes = Crypt::decryptString($bytes); } $retrieval[] = ['document_id' => $document->id, 'resource_id' => $resource?->id, 'sha256' => trim($document->sha256), 'original_bytes_recovered' => hash_equals($source['sha256'], hash('sha256', $bytes)), 'clean_scan_for_same_hash' => $document->malware_scan_status === 'clean' && hash_equals($source['sha256'], (string) $document->malware_scanned_sha256), 'private' => $document->data_classification === 'internal' && ! $resource?->published, 'resource_metadata_version' => $resource?->metadata_version, 'file_name' => $document->file_name, 'audit_count' => DB::table('audit_events')->where('entity_type', 'document')->where('entity_id', $document->id)->where('action', 'private_document_batch_imported')->count(), 'historical_revisions' => DB::table('portal_resource_revisions')->where('resource_id', $resource?->id)->count(), ]; unset($bytes); } $after = $counts(); if ($mode !== 'preview') { throw_unless(count($retrieval) === 1 && $retrieval[0]['original_bytes_recovered'] && $retrieval[0]['clean_scan_for_same_hash'] && $retrieval[0]['private'] && $retrieval[0]['audit_count'] === 1, RuntimeException::class, 'Retrieval/scan/private/audit incomplete.'); } if ($mode === 'replay') { throw_unless($before === $after, RuntimeException::class, 'Repeat created duplicates.'); } foreach (['opportunities', 'organizations', 'memberships', 'users'] as $table) { throw_unless($before[$table] === $after[$table], RuntimeException::class, 'Unrelated identities or opportunity data changed.'); } $proof = ['result' => 'passed', 'mode' => $mode, 'at' => gmdate('c'), 'target' => $target, 'requested_exemplar_identity_pending' => ! $source['exact_requested_exemplar_confirmed'], 'source_sha256' => $source['sha256'], 'manifest_sha256' => hash_file('sha256', $staging.'/manifest.json'), 'preview_read_only' => true, 'real_scanner' => get_class(app(MalwareScanner::class)), 'health' => $health, 'before' => $before, 'after' => $after, 'retrieval' => $retrieval, 'publication_separate' => true, 'rule_activation' => false, 'active_writes' => 0, ]; file_put_contents($directory.'/library-'.$mode.'-qa.json', json_encode($proof, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR)); echo json_encode(['result' => 'passed', 'mode' => $mode, 'target' => $target, 'source_sha256' => $source['sha256'], 'retrieval' => $retrieval, 'active_writes' => 0], JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR).PHP_EOL;