Confirm active opportunity classifications with read-only audit
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user