Standard Agile Technology · v1.0 · 2026

Sistema Segnalazioni &
Risoluzione Bug AI

Pattern completo: dall'utente che segnala → AI che classifica → Claude Code che corregge il codice in autonomia → notifica broadcast a tutti gli utenti.

PHP 8 / Slim 4 Claude Haiku (classificazione) Claude Code CLI (bug-fix autonomo) Vanilla JS MySQL 8 Multi-tenant ready Zero downtime deploy
Implementato in produzione su TaxAi (alltax.it) — 51 bug risolti autonomamente
1 Panoramica e risultati
Perché questo sistema è diventato uno standard
51
Bug risolti autonomamente da Claude Code in TaxAi
< 30'
Tempo medio risoluzione (cron ogni 30 min)
$2
Budget max per segnalazione (configurabile)
0
Interventi manuali sui bug classificati "alta" da AI
100%
Notifica broadcast agli utenti a risoluzione avvenuta
Filosofia del sistema L'utente segnala un problema → Claude Haiku lo classifica istantaneamente e risponde all'utente in linguaggio semplice → l'admin decide se gestire subito o delegare al worker automatico → Claude Code CLI legge il codice del progetto, applica il fix, verifica e chiude il ticket via API. Tutto senza intervento umano nel ciclo fix-deploy-verify.
🧠 AI a due livelli

Claude Haiku (veloce, economico) classifica ogni segnalazione in tempo reale e genera una risposta comprensibile all'utente.

Claude Code CLI (Sonnet/Opus) entra nel codebase, modifica i file, fa deploy e verifica — in piena autonomia.

🔒 Sicurezza integrata

Password gate per la chiusura manuale dei ticket da parte degli utenti. Isolamento per tenant_id su ogni query. Lock file per prevenire esecuzioni parallele del worker. Budget cap per limitare i costi AI per ticket.


2 Flusso completo end-to-end
Dalla segnalazione alla risoluzione — zero intervento umano opzionale
👤
Utente segnala
FAB arancione → modal → descrive il problema
🤖
Claude Haiku
Classifica tipo, priorità, risponde all'utente
👨‍💼
Admin decide
Imposta "In lavorazione" per delegare al worker
⚙️
Worker (cron)
Ogni 30 min legge i ticket in_lavorazione
🧑‍💻
Claude Code CLI
Legge codice, fixa, deploya, verifica
Risolto + Broadcast
Ticket chiuso + notifica a tutti gli utenti attivi
Dettaglio fasi interne del Worker
FaseAzioneEsito se fallisce
#1 Auth Login admin via /auth/login → JWT Exit 1, log ERRORE CRITICO
#2 Fetch GET /feedback?status=in_lavorazione Exit 0, nessuna azione
#3 Prompt Costruisce prompt contestuale, salva su /tmp/taxai-prompt-{id}.txt
#4 Claude docker exec -u developer devenv → claude --dangerously-skip-permissions Timeout → rimette aperto + nota
#5 Deploy Esegue /tmp/deploy-{id}.sh (creato da Claude) → docker cp Log warning, prosegue
#6 Resolve POST /feedback/{id}/resolve con resolution_password Aggiunge nota manuale
#7 Broadcast INSERT notifications per tutti gli utenti attivi del tenant Log silenzioso (non blocca)

3 Schema database
Tabella feedback_reports — da creare nel DB di notifiche del microservizio
schema.sql
CREATE TABLE feedback_reports (
    id              INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    tenant_id       INT UNSIGNED NOT NULL,

    -- Mittente
    user_id         INT UNSIGNED,
    user_email      VARCHAR(255),
    user_role       VARCHAR(50),
    page_url        VARCHAR(1000),   -- URL/hash della pagina al momento della segnalazione

    -- Classificazione utente
    tipo            ENUM('bug','ux','funzionalita','domanda','altro') NOT NULL DEFAULT 'bug',
    priorita        ENUM('alta','media','bassa') NOT NULL DEFAULT 'media',
    descrizione     TEXT NOT NULL,
    attachment      LONGTEXT,        -- base64 immagine o NULL

    -- Stato workflow
    status          ENUM('aperto','in_lavorazione','risolto','chiuso')
                    NOT NULL DEFAULT 'aperto',
    nota_admin      TEXT,            -- nota interna dell'admin

    -- Output AI (Claude Haiku)
    ai_categoria    VARCHAR(100),    -- es: "Calcolo IMU", "Login", "Dashboard"
    ai_priorita     ENUM('alta','media','bassa'),
    ai_suggerimento TEXT,            -- analisi tecnica per admin
    ai_risposta     TEXT,            -- risposta comprensibile per l'utente
    ai_processed    TINYINT(1) NOT NULL DEFAULT 0,

    created_at      DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at      DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
                    ON UPDATE CURRENT_TIMESTAMP,

    INDEX idx_tenant_status (tenant_id, status),
    INDEX idx_tenant_user   (tenant_id, user_id),
    INDEX idx_ai_priorita   (tenant_id, ai_priorita),
    INDEX idx_created       (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
ℹ️
Multi-tenant Ogni query deve sempre filtrare su tenant_id. In sistemi non multi-tenant si può fissare tenant_id = 1 per tutta l'applicazione.

4 Backend — FeedbackService.php
Classe PHP completa da copiare nel microservizio di notifiche
createReport()

Valida tipo e priorità, inserisce il record, chiama classifyWithAI() in modo sincrono ma con timeout 10s (non blocca la response).

classifyWithAI()

Chiama Claude Haiku via REST API Anthropic. Estrae JSON: categoria, priorità, suggerimento tecnico, risposta utente. Fallback se API key assente.

updateReport()

Aggiorna status/nota. Se lo status diventa 'risolto' dalla prima volta, chiama automaticamente broadcastResolution().

resolveReport()

Password gate per chiusura manuale lato utente. Confronta con RESOLUTION_PASSWORD env. Se ok → update + broadcast.

broadcastResolution()

Legge tutti gli utenti attivi (escluso public) dal DB auth. INSERT massivo nella tabella notifications. Try/catch silenzioso.

getStats()

KPI: totale, aperti, in_lavorazione, risolti, alta_priorità, da_analizzare. Query unica con SUM condizionali.

services/notification-ms/src/Services/FeedbackService.php — classifyWithAI() (estratto chiave)
private function classifyWithAI(int $id, string $descrizione, string $tipo, string $prioritaUtente): void
{
    if (!$this->claudeApiKey) {
        // Fallback senza AI: aggiorna con dati base
        $this->pdo->prepare("UPDATE feedback_reports SET
            ai_categoria = :cat, ai_priorita = :pri,
            ai_suggerimento = :sug, ai_risposta = :risp, ai_processed = 1
            WHERE id = :id")->execute([
            ':cat'  => ucfirst($tipo),
            ':pri'  => $prioritaUtente,
            ':sug'  => 'Analisi AI non disponibile. Revisionare manualmente.',
            ':risp' => 'Grazie per la segnalazione. Il team la verificherà al più presto.',
            ':id'   => $id,
        ]);
        return;
    }

    $prompt = <<<PROMPT
Sei un assistente tecnico per {APP_NAME}.
Analizza questa segnalazione e rispondi SOLO con JSON valido:
{
  "categoria": "stringa breve (es: Login, Dashboard, PDF, Pagamenti)",
  "priorita": "alta|media|bassa",
  "suggerimento": "analisi tecnica per admin (max 3 frasi)",
  "risposta_utente": "risposta rassicurante all'utente in linguaggio semplice (max 3 frasi)"
}

Tipo dichiarato: {$tipo}
Segnalazione: "{$descrizione}"
PROMPT;

    $payload = [
        'model'      => 'claude-haiku-4-5-20251001',   // veloce ed economico
        'max_tokens' => 300,
        'messages'   => [['role' => 'user', 'content' => $prompt]],
    ];

    $ch = curl_init('https://api.anthropic.com/v1/messages');
    curl_setopt_array($ch, [
        CURLOPT_POST           => true,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 10,        // non bloccare la response più di 10s
        CURLOPT_HTTPHEADER     => [
            'Content-Type: application/json',
            'x-api-key: ' . $this->claudeApiKey,
            'anthropic-version: 2023-06-01',
        ],
        CURLOPT_POSTFIELDS => json_encode($payload),
    ]);

    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    // Parse e salva
    if ($httpCode === 200 && $response) {
        $decoded = json_decode($response, true);
        $text    = $decoded['content'][0]['text'] ?? '';
        if (preg_match('/\{[\s\S]*\}/', $text, $m)) {
            $ai = json_decode($m[0], true);
            // aggiorna DB con dati AI ...
        }
    }
}
⚠️
Importante: chiamata sincrona ma timeout breve La chiamata AI è sincrona nella stessa request dell'utente ma con timeout 10s. Per produzione ad alto traffico, valutare di passarla su una job queue (RabbitMQ/Redis).

5 API Endpoints
Da registrare nel microservizio di notifiche (o in un microservizio dedicato)
MetodoPathAuth richiestaDescrizione
POST /feedback API-Key + JWT (tutti i ruoli) Crea nuova segnalazione. Risponde con dati + risposta AI in tempo reale.
GET /feedback/mine API-Key + JWT (tutti i ruoli) Le ultime 20 segnalazioni dell'utente autenticato. Per il tab "Le mie".
GET /feedback/stats API-Key + JWT (admin only) KPI: totale, aperti, in lavorazione, risolti, alta priorità, da analizzare.
GET /feedback API-Key + JWT (admin only) Lista segnalazioni con filtri: ?status=aperto&tipo=bug&ai_priorita=alta
GET /feedback/{id} API-Key + JWT (admin o autore) Singola segnalazione con tutti i campi AI.
PUT /feedback/{id} API-Key + JWT (admin only) Aggiorna status, nota_admin, priorita. Trigger broadcast se status → risolto.
POST /feedback/{id}/resolve API-Key + JWT + resolution_password Chiusura con password gate. Usato dal worker e dal modal utente (fase 2).
Esempio payload POST /feedback
// Request
POST /feedback
X-API-Key: your-api-key
Authorization: Bearer <JWT>
Content-Type: application/json

{
  "tipo": "bug",              // bug | ux | funzionalita | domanda | altro
  "priorita": "alta",        // alta | media | bassa
  "descrizione": "Il calcolo IMU restituisce un importo errato per i terreni agricoli",
  "page_url": "https://app.esempio.it/#view-imu",
  "attachment": "data:image/png;base64,..."   // opzionale
}

// Response (con risposta AI già pronta)
{
  "success": true,
  "data": {
    "id": 42,
    "tipo": "bug",
    "status": "aperto",
    "ai_categoria": "Calcolo IMU",
    "ai_priorita": "alta",
    "ai_risposta": "Abbiamo ricevuto la tua segnalazione riguardo al calcolo IMU per terreni agricoli.
                    Il team tecnico la verificherà nelle prossime ore e provvederà a correggerla.
                    Nel frattempo puoi usare il calcolo manuale come riferimento.",
    "ai_processed": 1,
    "created_at": "2026-03-10 14:23:11"
  }
}

6 Worker autonomo — Claude Code CLI
Il cuore del sistema: un daemon PHP che invoca Claude Code per fixare i bug in autonomia
🚀
Architettura ibrida host + container Il worker PHP gira sull'host (accesso a Docker e alle porte locali). Claude Code gira nel container devenv (bind mount sul codebase). I file condivisi via bind mount permettono a Claude di modificare il codice che il worker poi deploya sull'host.
scripts/segnalazioni-worker.php — Configurazione
define('LOCK_FILE',       '/tmp/app-segnalazioni-worker.lock');
define('LOG_FILE',        '/var/log/app/segnalazioni-worker.log');
define('API_KEY',         'your-api-key');
define('RESOLUTION_PW',   'your-resolution-password');   // env: RESOLUTION_PASSWORD
define('AUTH_API',        'http://localhost:4111');       // porta auth microservice
define('NOTIF_API',       'http://localhost:4108');       // porta notification microservice
define('CLAUDE_BIN',       '/usr/bin/claude');            // path al binario Claude Code
define('PROJECT_DIR',     '/projects/your-app');          // path nel container devenv
define('PROJECT_DIR_HOST','/var/www/your-app');           // path sull'host (bind mount)
define('MAX_BUDGET_USD',  '2.00');                        // limite costo AI per segnalazione
define('CLAUDE_TIMEOUT',  1500);                          // 25 min (cron ogni 30 min)
Invocazione Claude Code (parte centrale del worker)
// 1. Costruisci il prompt contestuale e salvalo su file condiviso
$prompt     = buildPrompt($report);           // vedi sezione "Prompt Engineering"
$promptFile = PROJECT_DIR_HOST . '/tmp/taxai-prompt-' . $id . '.txt';
file_put_contents($promptFile, $prompt);

// 2. Invoca Claude Code nel container devenv come utente non-root
//    --dangerously-skip-permissions: evita conferme interattive (obbligatorio per autonomia)
//    --output-format stream-json: output incrementale, catturabile anche con timeout
//    --max-turns 30: previene loop infiniti di tool-use
$containerPromptFile = PROJECT_DIR . '/tmp/taxai-prompt-' . $id . '.txt';
$innerCmd = sprintf(
    'cd %s && CLAUDE_PROMPT=$(cat %s) && timeout %d %s --dangerously-skip-permissions --output-format stream-json --max-turns 30 -p "$CLAUDE_PROMPT"',
    escapeshellarg(PROJECT_DIR),
    escapeshellarg($containerPromptFile),
    CLAUDE_TIMEOUT,
    CLAUDE_BIN
);
$cmd = sprintf(
    'docker exec -u developer your-devenv-container bash -c %s 2>&1',
    escapeshellarg($innerCmd)
);

exec($cmd, $output, $exitCode);

// 3. Parsa l'output stream-JSON per estrarre il testo
$result = parseStreamJson($output);

// 4. Se Claude ha creato un deploy script, eseguilo sull'host
$deployScript = PROJECT_DIR_HOST . '/tmp/deploy-' . $id . '.sh';
if (file_exists($deployScript)) {
    exec('bash ' . escapeshellarg($deployScript));
    unlink($deployScript);
}

// 5. Chiudi il ticket se Claude ha completato
if ($exitCode === 0) {
    resolveReport($id, $token);   // POST /feedback/{id}/resolve
}
📝 Prompt Engineering — Struttura del prompt inviato a Claude

Il prompt deve fornire tutto il contesto necessario e istruzioni precise. Claude non può chiedere chiarimenti in modalità autonoma.

Sei Claude Code in modalità AUTONOMA nel progetto {APP_NAME}.
Working directory: {PROJECT_DIR_CONTAINER}
Ambiente: container {DEVENV_CONTAINER} su rete Docker {DOCKER_NETWORK}
IMPORTANTE: NON usare SSH o docker — non disponibili nel container.

SEGNALAZIONE DA FIXARE:
  ID:          {id}
  Tipo:        {tipo}
  Priorità:    {priorita}
  Descrizione: {descrizione}
  Vista/URL:   {view} ({page_url})
  Suggerimento AI precedente: {ai_suggerimento}

ISTRUZIONI (esegui in sequenza, senza chiedere conferma):

1. Leggi docs/CONTEXT_LAST_SESSION.md per capire lo stato del progetto.
2. Analizza il bug con Grep e Read per trovare il codice rilevante.
3. Identifica la root cause. Sii preciso.
4. Applica il fix con Edit. Modifica solo ciò che serve.
5. Deploy:
   - File JS/HTML: bind mount, NESSUN deploy necessario.
   - File PHP: scrivi deploy script in /tmp/deploy-{id}.sh (il worker lo eseguirà).
6. Verifica con curl interno verso i microservizi.
7. Chiudi il ticket via API (OBBLIGATORIO se fix riuscito):
   TOKEN=$(curl -s -X POST http://{auth-ms}/auth/login ... | grep -o '"token":"[^"]*"' | cut -d'"' -f4)
   curl -s -X POST http://{notif-ms}/feedback/{id}/resolve -H "Authorization: Bearer $TOKEN" \
     -d '{"resolution_password":"{RESOLUTION_PW}"}'
8. Se NON riesci, aggiungi nota e lascia aperto (non inventare soluzioni).

Procedi in autonomia. Non chiedere conferme. Sii efficiente.
/etc/cron.d/app-segnalazioni — Crontab (sull'host)
# Processa segnalazioni ogni 30 minuti (worker ha timeout 25min + lock per sicurezza)
*/30 * * * * root /usr/bin/php8.4 /var/www/your-app/scripts/segnalazioni-worker.php >> /var/log/app/cron.log 2>&1
parseStreamJson() — Parser output Claude Code CLI
/**
 * Estrae testo dall'output --output-format stream-json di Claude Code CLI.
 * Ogni riga è un oggetto JSON con type: result | assistant | tool_use | ...
 * Se trova type=result usa il campo result finale; altrimenti concatena i testi parziali.
 */
function parseStreamJson(array $lines): string {
    $texts = [];
    foreach ($lines as $line) {
        $line = trim($line);
        if ($line === '') continue;
        $obj = json_decode($line, true);
        if (!is_array($obj)) continue;
        if ($obj['type'] === 'result' && isset($obj['result'])) {
            return (string) $obj['result'];   // riga finale: usa questa
        }
        if ($obj['type'] === 'assistant' && isset($obj['message']['content'])) {
            foreach ($obj['message']['content'] as $block) {
                if (($block['type'] ?? '') === 'text' && isset($block['text'])) {
                    $texts[] = $block['text'];
                }
            }
        }
    }
    return implode("\n\n", $texts);   // fallback: concatena testi parziali
}

7 Frontend — FAB + Modal
Vanilla JS: floating action button arancione + modal a due fasi
Fase 1 — Invio segnalazione
  • FAB arancione fisso bottom-right (visibile solo utenti autenticati)
  • Tab "Nuova segnalazione" + tab "Le mie"
  • Campo tipo (bug/ux/funzionalità/domanda/altro)
  • Campo priorità (alta/media/bassa)
  • Textarea descrizione + drag&drop screenshot
  • Cattura automatica URL/hash corrente
  • Invia → mostra risposta AI (Fase 2)
Fase 2 — Risposta AI + chiusura
  • Mostra ai_risposta da Claude Haiku
  • Pulsanti "Sì, risolto" / "No, problema persiste"
  • "Sì" → password gate → POST /feedback/{id}/resolve
  • "No" → conferma ricezione, ticket rimane aperto
  • Tab "Le mie": lista con status badge
  • Pulsante "Segna all'admin" / "Risolvi" per ogni ticket
HTML — FAB e overlay (da aggiungere in index.html)
<!-- Floating Action Button Segnalazioni -->
<button id="feedback-fab" title="Segnala un problema"
        style="position:fixed;bottom:1.5rem;right:1.5rem;z-index:9000;
               width:3.2rem;height:3.2rem;border-radius:50%;border:none;
               background:#f97316;color:white;font-size:1.3rem;cursor:pointer;
               box-shadow:0 4px 16px rgba(0,0,0,0.25);display:none">
  <i class="fas fa-exclamation-circle"></i>
</button>

<!-- Modal overlay -->
<div id="feedback-modal-overlay"
     style="display:none;position:fixed;inset:0;z-index:9100;
            background:rgba(0,0,0,0.5);align-items:center;justify-content:center">
  <div style="background:white;border-radius:1rem;width:min(520px,95vw);
              max-height:85vh;overflow-y:auto;padding:1.5rem;position:relative">

    <!-- Tab bar -->
    <div style="display:flex;gap:0.5rem;margin-bottom:1rem">
      <button id="feedback-tab-nuova" class="tab-btn tab-active">Nuova segnalazione</button>
      <button id="feedback-tab-mie"   class="tab-btn">Le mie</button>
    </div>

    <!-- Fase 1: form -->
    <div id="feedback-panel-nuova">
      <div id="feedback-phase1">
        <p id="feedback-page-label" style="font-size:0.8rem;color:#64748b;margin-bottom:0.8rem"></p>
        <select id="feedback-tipo" style="width:100%;margin-bottom:0.6rem;padding:0.5rem">
          <option value="bug">🐛 Bug / Errore</option>
          <option value="ux">🎨 Problema UX</option>
          <option value="funzionalita">✨ Richiesta funzionalità</option>
          <option value="domanda">❓ Domanda</option>
          <option value="altro">📋 Altro</option>
        </select>
        <select id="feedback-priorita" style="width:100%;margin-bottom:0.6rem;padding:0.5rem">
          <option value="media">Priorità media</option>
          <option value="alta">🔴 Priorità alta</option>
          <option value="bassa">Priorità bassa</option>
        </select>
        <textarea id="feedback-descrizione" rows="4" placeholder="Descrivi il problema..."
                  style="width:100%;padding:0.6rem;margin-bottom:0.8rem;resize:vertical"></textarea>
        <!-- drag&drop screenshot -->
        <div id="feedback-drop-zone" style="border:2px dashed #cbd5e1;border-radius:0.5rem;
             padding:1rem;text-align:center;cursor:pointer;font-size:0.85rem;color:#64748b">
          📎 Trascina uno screenshot qui oppure <u>clicca per allegare</u>
        </div>
        <input id="feedback-file-input" type="file" accept="image/*" style="display:none">
        <div id="feedback-attachment-preview" style="display:none;margin-top:0.5rem">
          <img id="feedback-attachment-img" style="max-width:100%;border-radius:0.4rem">
          <button id="feedback-attachment-remove">✕ Rimuovi</button>
        </div>
        <p id="feedback-error-msg" style="color:#dc2626;font-size:0.85rem;margin-top:0.5rem"></p>
        <button id="feedback-submit-btn" style="width:100%;margin-top:0.8rem;padding:0.7rem;
                background:#f97316;color:white;border:none;border-radius:0.5rem;cursor:pointer;font-weight:600">
          <i class="fas fa-paper-plane"></i> Invia segnalazione
        </button>
      </div>

      <!-- Fase 2: risposta AI -->
      <div id="feedback-phase2" style="display:none">
        <h3 style="margin-bottom:0.8rem">🤖 Risposta del sistema</h3>
        <div id="feedback-ai-risposta" style="background:#f0f9ff;border-radius:0.5rem;padding:1rem;
             border-left:4px solid #0ea5e9;font-size:0.9rem"></div>
        <p style="margin-top:1rem;font-weight:600">Questo ha risposto alla tua domanda?</p>
        <div style="display:flex;gap:0.6rem;margin-top:0.6rem">
          <button id="feedback-yes-btn">✅ Sì, risolto</button>
          <button id="feedback-no-btn">❌ No, problema persiste</button>
        </div>
        <!-- Password gate per chiusura manuale -->
        <div id="feedback-password-gate" style="display:none;margin-top:1rem">
          <input id="feedback-resolve-password" type="password" placeholder="Password di conferma">
          <button id="feedback-confirm-resolve-btn">Conferma risoluzione</button>
          <p id="feedback-password-error" style="color:#dc2626;font-size:0.8rem"></p>
        </div>
        <div id="feedback-phase2-success" style="display:none;color:#16a34a;margin-top:0.8rem">
          ✅ Ottimo! Il ticket è stato chiuso. Grazie per il feedback.
        </div>
      </div>
    </div>

    <!-- Tab: Le mie segnalazioni -->
    <div id="feedback-panel-mie" style="display:none">
      <div id="feedback-mie-loading">Caricamento...</div>
      <div id="feedback-mie-empty" style="display:none">Nessuna segnalazione inviata.</div>
      <div id="feedback-mie-list"></div>
    </div>

  </div>
</div>
JS — Submit segnalazione (estratto core)
submitBtn.addEventListener('click', async function() {
    var descrizione = descEl.value.trim();
    if (descrizione.length < 10) {
        errorMsg.textContent = 'Descrivi il problema in almeno 10 caratteri.';
        return;
    }

    submitBtn.disabled = true;
    submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Invio in corso...';

    var payload = {
        tipo:        document.getElementById('feedback-tipo').value,
        priorita:    document.getElementById('feedback-priorita').value,
        descrizione: descrizione,
        page_url:    window.location.href,                  // cattura URL corrente
        attachment:  currentAttachmentB64 || null,           // base64 screenshot
    };

    try {
        var res = await AppAPI.submitFeedback(payload);    // POST /feedback
        var aiRisposta = res.data?.ai_risposta || 'Segnalazione ricevuta.';
        var reportId   = res.data?.id;

        // Mostra Fase 2 con risposta AI
        phase1.style.display = 'none';
        phase2.style.display = 'block';
        aiRispEl.textContent = aiRisposta;

        // Fase 2: gestione "Sì risolto" con password gate
        yesBtn.onclick = function() {
            pwGate.style.display = 'block';
        };
        pwConfBtn.onclick = async function() {
            var pw = pwInput.value.trim();
            if (!pw) return;
            var r = await AppAPI.resolveFeedback(reportId, pw);   // POST /feedback/{id}/resolve
            if (r.success) {
                p2success.style.display = 'block';
                pwGate.style.display = 'none';
            } else {
                pwErrEl.textContent = r.error || 'Password non corretta.';
            }
        };
    } catch(err) {
        errorMsg.textContent = 'Errore: ' + (err.message || 'Impossibile inviare.');
        submitBtn.disabled = false;
        submitBtn.innerHTML = '<i class="fas fa-paper-plane"></i> Invia segnalazione';
    }
});

8 Guida installazione in un nuovo progetto
Checklist step-by-step per adottare il sistema
  1. Crea la tabella feedback_reports nel DB del microservizio notifiche (schema §3).
  2. Copia FeedbackService.php in services/notification-ms/src/Services/. Adatta il namespace e la stringa DSN del DB auth.
  3. Registra gli endpoint in services/notification-ms/public/index.php (vedi §5). Assicurati che il middleware JWT sia già attivo nel servizio.
  4. Aggiungi le variabili d'ambiente:
    ANTHROPIC_API_KEY=sk-ant-...      # per Claude Haiku
    RESOLUTION_PASSWORD=your-secret   # password gate chiusura manuale
  5. Aggiungi nginx route:
    location /api/feedback {
        proxy_pass http://notification_ms;
    }
    Poi: docker restart your-gateway
  6. Aggiungi HTML (FAB + modal overlay) in app/index.html (vedi §7).
  7. Aggiungi JS: funzione initFeedbackFab() chiamata dopo login in app.js. Implementa AppAPI.submitFeedback(), AppAPI.resolveFeedback(), AppAPI.getMyFeedback().
  8. Copia il worker scripts/segnalazioni-worker.php nel progetto. Adatta le costanti (api key, password, porte, container name, project dir).
  9. Installa il crontab sull'host:
    echo "*/30 * * * * root php8.4 /var/www/your-app/scripts/segnalazioni-worker.php" \
      > /etc/cron.d/your-app-segnalazioni
  10. Assicurati che il container devenv abbia accesso alla rete Docker del progetto:
    docker network connect your-app-network your-devenv-container
  11. Aggiungi la view admin #view-segnalazioni con KPI, filtri, tabella segnalazioni e pulsanti azioni (in_lavorazione / risolto / chiudi).
  12. Testa: apri l'app, clicca il FAB, invia una segnalazione di test. Verifica che la risposta AI arrivi. Imposta status "in_lavorazione" dall'admin. Attendi il prossimo cron (o esegui il worker manualmente) e verifica il fix.

9 Variabili di configurazione
Tutte le variabili da configurare per adottare il sistema
VariabileDoveValoreNote
ANTHROPIC_API_KEY .env / docker-compose sk-ant-api03-... Per Claude Haiku (classificazione). Se assente → fallback senza AI.
RESOLUTION_PASSWORD .env / docker-compose Stringa segreta Password gate per chiusura manuale ticket. Uguale in FeedbackService e worker.
CLAUDE_BIN Worker PHP (define) /usr/bin/claude Path al binario Claude Code CLI nel container devenv.
PROJECT_DIR Worker PHP (define) /projects/your-app Path del progetto NEL container devenv.
PROJECT_DIR_HOST Worker PHP (define) /var/www/your-app Path del progetto SULL'HOST (bind mount condiviso).
CLAUDE_TIMEOUT Worker PHP (define) 1500 (25 min) Timeout in secondi per ogni fix Claude. Deve essere < intervallo cron.
MAX_BUDGET_USD Worker PHP (define) 2.00 Budget massimo per segnalazione. Non ancora enforced da Claude Code CLI — da implementare.
DEVENV_CONTAINER Worker PHP (docker exec) your-devenv-container Nome del container devenv dove gira Claude Code CLI.
DEVENV_USER Worker PHP (docker exec -u) developer User non-root nel container. Root bloccato da --dangerously-skip-permissions.

10 Note di sicurezza & best practice
🔒 Password Gate

Il campo RESOLUTION_PASSWORD deve essere in variabile d'ambiente, mai hardcoded nel codice committato. Usare una stringa lunga e casuale. Il gate esiste per prevenire chiusure accidentali da parte degli utenti.

🔑 Claude Code CLI

Il flag --dangerously-skip-permissions è necessario per l'autonomia. Limitare il suo uso al solo container devenv, mai sull'host direttamente. Assicurarsi che il container devenv non abbia accesso a dati sensibili di produzione.

🔐 Multi-tenant

Ogni query API deve filtrare per tenant_id estratto dal JWT. Un utente non deve poter vedere o modificare segnalazioni di altri tenant. Verificare nella route PUT e GET /{id} che il tenant_id combaci.

⚙️ Lock file Worker

Il worker usa un lock file (/tmp/app-worker.lock) per prevenire esecuzioni parallele. Verificare periodicamente che il lock non rimanga "zombie" se il server viene riavviato senza pulizia del /tmp.

📊 Attachment

Gli screenshot vengono salvati come base64 in LONGTEXT. Per volumi alti, considerare di salvarli su S3/MinIO e memorizzare solo l'URL. Limitare la dimensione lato frontend prima dell'encoding (max 1-2MB consigliato).

🐇 Queue async (opzionale)

La chiamata Claude Haiku è sincrona nella request dell'utente (timeout 10s). Per produzione ad alto traffico, pubblicare un messaggio su RabbitMQ/Redis e processare in background. La response all'utente includerà dati base, con AI arrivata in un secondo momento via polling.

⚠️
Nota sull'output di Claude Code CLI in modalità autonoma Con --output-format stream-json, Claude produce output solo al completamento di ogni tool call. Con timeout aggressivi (es. 124 = SIGTERM), l'output potrebbe essere vuoto anche se Claude ha fatto progressi. Il parser parseStreamJson() gestisce questo caso usando i testi parziali come fallback. Impostare CLAUDE_TIMEOUT a un valore ragionevole (almeno 10 minuti per bug semplici).