[FEAT] Vocea whistleblowing — scaffold integrazione (dormiente) + handoff AgileHub
Scaffold per sostituire il modulo whistleblowing interno (plaintext) con Vocea (nexus-whistleblowing-ms), prodotto zero-knowledge della suite. Modalita C (integrazione verticale, nessuna crypto re-implementata). DORMIENTE: VOCEA_ENABLED =false di default -> zero impatto sul modulo legacy. - docs/sql/063_vocea_channel.sql + application/cli/migrate_063_vocea_channel.php (idempotente): organizations += vocea_tenant_slug/channel_status/enrolled_at. NON ancora applicata al DB. - application/services/VoceaService.php (nuovo): client API integratori (X-API-Key su api.vocea.cloud) per submit/status/reports/keyholders + portalUrl (embed wb-link.js). Guard enabled()/isEnabledForOrg(): se OFF/non provisioned ritorna DISABLED/NOT_PROVISIONED senza chiamate di rete. - config.php: costanti VOCEA_ENABLED/BASE_URL/API_KEY/PORTAL_URL (default off). - WhistleblowingController: branch dormiente in list() (delega a Vocea se attivo) + endpoint GET channelStatus + helper voceaOrg/listViaVocea. Modulo legacy invariato. - public/index.php: route GET:channelStatus. - docs handoff: richiesta a VIGILE (OUTGOING) + risposta VIGILE (INCOMING). Attivazione a MS live: applicare migrate_063 + VOCEA_ENABLED=true + VOCEA_API_KEY (vault tier1__nis2-agile__vocea__*) + slug canale su organizations. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
ef9a7282c5
commit
4e2e56ec0b
@@ -19,6 +19,7 @@
|
||||
|
||||
require_once __DIR__ . '/BaseController.php';
|
||||
require_once APP_PATH . '/services/WebhookService.php';
|
||||
require_once APP_PATH . '/services/VoceaService.php';
|
||||
|
||||
class WhistleblowingController extends BaseController
|
||||
{
|
||||
@@ -126,6 +127,17 @@ class WhistleblowingController extends BaseController
|
||||
{
|
||||
$this->requireOrgRole(['org_admin', 'compliance_manager']);
|
||||
|
||||
// ── Delega a Vocea quando il canale esterno è attivo per l'org (dormiente di
|
||||
// default: VOCEA_ENABLED=false → si salta del tutto e resta il modulo legacy).
|
||||
if (VoceaService::enabled()) {
|
||||
$org = $this->voceaOrg();
|
||||
$svc = new VoceaService();
|
||||
if ($org && $svc->isEnabledForOrg($org)) {
|
||||
$this->listViaVocea($svc, $org);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$conditions = ['wr.organization_id = ?'];
|
||||
$params = [$this->getCurrentOrgId()];
|
||||
|
||||
@@ -383,4 +395,69 @@ class WhistleblowingController extends BaseController
|
||||
'timeline' => $timeline,
|
||||
]);
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════════════════
|
||||
// VOCEA (integrazione whistleblowing zero-knowledge) — scaffold dormiente
|
||||
// ══════════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* GET /api/whistleblowing/channelStatus
|
||||
* Indica alla UI se mostrare il canale Vocea (embed wb-link.js) o il modulo legacy.
|
||||
* Non espone segreti (mai l'API key).
|
||||
*/
|
||||
public function channelStatus(): void
|
||||
{
|
||||
$this->requireOrgRole(['org_admin', 'compliance_manager', 'board_member']);
|
||||
|
||||
$enabled = VoceaService::enabled();
|
||||
$org = $enabled ? $this->voceaOrg() : null; // query solo se attivo (colonne vocea_* presenti)
|
||||
$slug = $org['vocea_tenant_slug'] ?? null;
|
||||
$status = $org['vocea_channel_status'] ?? 'none';
|
||||
|
||||
$this->jsonSuccess([
|
||||
'integration_enabled' => $enabled,
|
||||
'channel_status' => $status,
|
||||
'provisioned' => $enabled && !empty($slug) && $status === 'active',
|
||||
'portal_url' => !empty($slug) ? VoceaService::portalUrl($slug) : null,
|
||||
]);
|
||||
}
|
||||
|
||||
/** Carica l'org corrente con le colonne vocea_* (chiamata solo quando VOCEA_ENABLED). */
|
||||
private function voceaOrg(): ?array
|
||||
{
|
||||
return Database::fetchOne(
|
||||
'SELECT * FROM organizations WHERE id = ?',
|
||||
[$this->getCurrentOrgId()]
|
||||
) ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lista segnalazioni dal canale Vocea (metadati + ciphertext, zero-knowledge).
|
||||
* Il plaintext NON transita mai: la decifratura avviene client-side dal gestore.
|
||||
* NB: mapping campi da riconfermare allo smoke a MS live (shape risposta del MS).
|
||||
*/
|
||||
private function listViaVocea(VoceaService $svc, array $org): void
|
||||
{
|
||||
$query = [];
|
||||
foreach (['status', 'category', 'page', 'per_page'] as $f) {
|
||||
if ($this->hasParam($f)) $query[$f] = $this->getParam($f);
|
||||
}
|
||||
|
||||
$res = $svc->listReports($org['vocea_tenant_slug'], $query);
|
||||
if (!($res['ok'] ?? false)) {
|
||||
$this->jsonError(
|
||||
'Canale Vocea non raggiungibile: ' . ($res['error'] ?? 'errore'),
|
||||
502,
|
||||
'VOCEA_UPSTREAM'
|
||||
);
|
||||
}
|
||||
|
||||
$data = $res['data'] ?? [];
|
||||
$reports = $data['items'] ?? ($data['reports'] ?? (is_array($data) ? $data : []));
|
||||
$this->jsonSuccess([
|
||||
'reports' => $reports,
|
||||
'total' => $data['total'] ?? count($reports),
|
||||
'source' => 'vocea',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user