diff --git a/docs/investmoz/v2/backlog-mestre.md b/docs/investmoz/v2/backlog-mestre.md index 75754d3a..abe3ced4 100644 --- a/docs/investmoz/v2/backlog-mestre.md +++ b/docs/investmoz/v2/backlog-mestre.md @@ -197,3 +197,13 @@ Plano condicional de exemplos: classificação explícita pela auditoria existen [Relatório e procedimento](revisao-origem-oportunidades.md). Página `/management/opportunity-origin`: seis itens, decisões real/exemplo/indeterminado, fundamento obrigatório, autor/data e pré-visualização antes de confirmação individual. Nenhuma classificação inferida por ausência de prova ou coincidência com seeder; associações a projetos/clusters permanecem separadas. **QA:** 58 testes/917 asserções passaram; percurso no navegador com separação/reversão, API/mapa e consulta autorizada. Backup/restauro de 288 tabelas/cinco ficheiros; comparação de dez tabelas após ensaio demonstrou preservação. **Ativo:** leitura com zero decisões de classificação; sem escritas nesta entrega. Prova preparada válida até 10/10/2026 02:38:49 locais, aplicação exclusivamente autenticada após pré-visualização. Cobertura documental, questões jurídicas, ClamD e primeiro pedido real mantêm estados próprios. + +### Confirmação ativa das classificações — 9 de outubro + +Leitura `READ ONLY` após a ação autenticada: **IDs 1–6 classificados como `real`**, pela conta **13**, entre **02:48:05 e 02:51:00 locais**. Seis eventos `origin_classification_decided` registam estado anterior indeterminado, fundamento na decisão do Conselho de Ministros, autor, data e `relationships_changed=false`. Trata-se da decisão do responsável registada no portal; não de classificação inferida pelo seeder. + +Todos mantêm estado editorial `published` e inclusão no filtro público do catálogo/API/mapa; nenhum foi separado como exemplo. A leitura confirma elegibilidade pelo filtro implementado, não a existência de coordenadas ou de um ponto no mapa para cada item. + +**Preservação:** hashes das dez tabelas comparadas com o backup coincidem, incluindo IDs/dados das seis oportunidades, pedidos, recibos, mensagens e relações. Os **118 eventos históricos anteriores** permanecem presentes e inalterados; foram acrescentados seis eventos de classificação. Os dois eventos do ensaio QA são excluídos da comparação histórica, pois não pertencem à base ativa. + +**Associações separadas:** zero relações oportunidade–projeto e oportunidade–cluster continuam presentes, tal como antes. As **28 localizações de projetos e 13 relações cluster–projeto** permanecem iguais ao backup. Classificar origem não aplica as correspondências ambíguas. Evidência privada: `opportunity-origin-active-confirmation.json`; leitor `tools/opportunity-origin-active-confirmation.php`. Nenhuma escrita ativa nesta confirmação. As restantes pendências independentes mantêm-se. diff --git a/docs/investmoz/v2/revisao-origem-oportunidades.md b/docs/investmoz/v2/revisao-origem-oportunidades.md index 9fe3e136..6fbed14a 100644 --- a/docs/investmoz/v2/revisao-origem-oportunidades.md +++ b/docs/investmoz/v2/revisao-origem-oportunidades.md @@ -35,3 +35,9 @@ Provas privadas: `opportunity-origin-confirmation.json`, `opportunity-origin-bac Na leitura protegida desta entrega há **zero decisões de classificação** na base ativa. Nenhum dos seis foi classificado, retirado do catálogo ou associado automaticamente. A página está preparada para revisão individual pelo responsável, começando pela pré-visualização. Não foi enviado contacto externo. Continuam separadas: decisões sobre realidade dos investimentos e correspondências ambíguas; diferença de cobertura do inventário; questões jurídicas específicas; ClamD real; fontes institucionais pendentes específicas; primeiro pedido real no Balcão. Estes testes não concluem nenhuma dessas pendências. + +## Confirmação posterior por leitura + +Em 9 de outubro, após ação autenticada da conta 13: os seis IDs estão classificados como `real`, com seis eventos entre 02:48:05 e 02:51:00 locais e fundamento registado na decisão do Conselho de Ministros. Todos continuam publicados e incluídos no filtro público; não foram criados exemplos. O estado anterior de zero decisões acima descreve a entrega anterior à ação do utilizador. + +Comparação protegida: dez tabelas iguais ao backup e 118 eventos históricos preservados, sem alterações nem ausências; seis eventos novos de classificação. Eventos exclusivamente QA excluídos da comparação. IDs, relações e interações preservados. As relações oportunidade–projeto/cluster continuam vazias; 28 localizações de projetos e 13 relações cluster–projeto permanecem preservadas. Evidência privada `opportunity-origin-active-confirmation.json`. Nenhuma escrita ativa efetuada na confirmação. diff --git a/tools/opportunity-origin-active-confirmation.php b/tools/opportunity-origin-active-confirmation.php index d804b11d..c8d90a07 100644 --- a/tools/opportunity-origin-active-confirmation.php +++ b/tools/opportunity-origin-active-confirmation.php @@ -13,6 +13,23 @@ foreach ($records as &$record) { } unset($record); $checks = []; +$restored = $backup['restored_target']; +if (! str_ends_with($restored['database'], '_test') || (int) $restored['port'] !== 55441) { + throw new RuntimeException('Invalid isolated comparison target'); +} +$baseline = new PDO('pgsql:host=127.0.0.1;port=55441;dbname='.$restored['database'], $c['username'], $c['password'], [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]); +$baseline->exec('BEGIN TRANSACTION READ ONLY'); +$oldEvents = $baseline->query("SELECT id,row_to_json(t)::text AS row FROM audit_events t WHERE source_system IS DISTINCT FROM 'opportunity-origin-review' ORDER BY id")->fetchAll(PDO::FETCH_KEY_PAIR); +$currentEvents = $pdo->query('SELECT id,row_to_json(t)::text AS row FROM audit_events t ORDER BY id')->fetchAll(PDO::FETCH_KEY_PAIR); +$missing = $changed = 0; +foreach ($oldEvents as $id => $row) { + if (! isset($currentEvents[$id])) { + $missing++; + } elseif ($currentEvents[$id] !== $row) { + $changed++; + } +} +$baseline->exec('ROLLBACK'); foreach (['opportunities','opportunity_projects','opportunity_clusters','opportunity_favourites','interest_requests','interest_submission_receipts','investment_messages','tasks','project_locations','cluster_projects'] as $table) { $key = '"public"."'.$table.'"'; $rows = $pdo->query('SELECT row_to_json(t)::text AS row FROM '.$key.' t ORDER BY row_to_json(t)::text')->fetchAll(PDO::FETCH_COLUMN); @@ -20,6 +37,6 @@ foreach (['opportunities','opportunity_projects','opportunity_clusters','opportu $checks[$table] = ['matches_backup' => $hash === ($backup['table_hashes'][$key] ?? null), 'hash' => $hash, 'rows' => count($rows)]; } $pdo->exec('ROLLBACK'); -$proof = ['captured_at' => gmdate('c'), 'identity' => $identity, 'records' => $records, 'audit' => $events, 'preservation' => $checks, 'active_writes' => 0]; +$proof = ['captured_at' => gmdate('c'), 'identity' => $identity, 'records' => $records, 'audit' => $events, 'preservation' => $checks, 'historical_audit' => ['baseline_events' => count($oldEvents), 'missing' => $missing, 'changed' => $changed, 'additional_events' => count($currentEvents) - count($oldEvents)], 'active_writes' => 0]; file_put_contents($directory.'/opportunity-origin-active-confirmation.json', json_encode($proof, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR)); echo json_encode($proof, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR).PHP_EOL;