---
phase: 20-knowledge-base-cliente
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - src/db/migrations/0009_client_transcripts.sql
autonomous: false
requirements:
  - KB-01
must_haves:
  truths:
    - "Il file 0009_client_transcripts.sql esiste con DDL completo e idempotente"
    - "La tabella client_transcripts è presente nel database di produzione"
    - "Il codice schema-dipendente dei piani successivi non viene pushato prima che la migration sia applicata"
  artifacts:
    - path: "src/db/migrations/0009_client_transcripts.sql"
      provides: "DDL CREATE TABLE IF NOT EXISTS client_transcripts con tutti i campi D-01/D-02"
      contains: "CREATE TABLE IF NOT EXISTS client_transcripts"
  key_links:
    - from: "src/db/migrations/0009_client_transcripts.sql"
      to: "database produzione"
      via: "SSH tunnel + psql/docker exec"
      pattern: "CREATE TABLE IF NOT EXISTS client_transcripts"
---

<objective>
Scrivere il file SQL della migration manuale per la tabella `client_transcripts` e applicarlo al database di produzione prima di pushare qualsiasi codice schema-dipendente.

Purpose: Il progetto ha `drizzle-kit generate` rotto (meta-snapshot fuori sync). Ogni schema change richiede SQL a mano applicato a prod via SSH PRIMA del codice dipendente — invariante bloccante LOCKED in CLAUDE.md.

Output: `src/db/migrations/0009_client_transcripts.sql` applicato a prod. Il checkpoint umano sblocca i piani 20-02 e 20-03.
</objective>

<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/ROADMAP.md
@.planning/phases/20-knowledge-base-cliente/20-CONTEXT.md

<interfaces>
<!-- Pattern migration a mano — da src/db/migrations/0005 e 0008 -->
<!-- Convenzioni: CREATE TABLE IF NOT EXISTS, tipi Postgres espliciti, FK con ON DELETE CASCADE -->
<!-- nanoid PK: text NOT NULL — il valore viene inserito dall'applicazione, non da DEFAULT -->
<!-- Struttura 0008: header commento, istruzioni additive, CREATE INDEX IF NOT EXISTS -->

Da 0005_phase_10_crm_leads_activities_reminders.sql:
```sql
CREATE TABLE IF NOT EXISTS "activities" (
  "id" text PRIMARY KEY NOT NULL,
  "lead_id" text NOT NULL,
  ...
  FOREIGN KEY ("lead_id") REFERENCES "leads"("id") ON DELETE cascade
);
CREATE INDEX IF NOT EXISTS "activities_lead_id" ON "activities"("lead_id");
```

Da 0008_offer_tier_schema.sql (header):
```sql
-- Phase 12: Offer Editor — additive schema ...
-- All statements additive/idempotent. No drops/truncates (Data Safety LOCKED).
```
</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: Scrivere src/db/migrations/0009_client_transcripts.sql</name>
  <files>src/db/migrations/0009_client_transcripts.sql</files>
  <read_first>
    - src/db/migrations/0008_offer_tier_schema.sql (pattern header + CREATE TABLE IF NOT EXISTS)
    - src/db/migrations/0005_phase_10_crm_leads_activities_reminders.sql (pattern FK + index per tabelle CRM)
    - .planning/phases/20-knowledge-base-cliente/20-CONTEXT.md (D-01 e D-02 — campi esatti)
  </read_first>
  <action>
Creare il file `src/db/migrations/0009_client_transcripts.sql` con il seguente contenuto esatto:

```sql
-- Phase 20: Knowledge Base Cliente — tabella transcript datati per lead/cliente
-- Additive only. No drops/truncates (Data Safety LOCKED).
-- Applicare a prod via SSH tunnel PRIMA di pushare il codice dipendente (D-03).

CREATE TABLE IF NOT EXISTS client_transcripts (
  id          text PRIMARY KEY NOT NULL,
  lead_id     text REFERENCES leads(id) ON DELETE CASCADE,
  client_id   text REFERENCES clients(id) ON DELETE CASCADE,
  title       text,
  content     text NOT NULL,
  call_date   date NOT NULL,
  created_at  timestamp with time zone NOT NULL DEFAULT now()
);

CREATE INDEX IF NOT EXISTS client_transcripts_lead_id_idx
  ON client_transcripts (lead_id);

CREATE INDEX IF NOT EXISTS client_transcripts_client_id_idx
  ON client_transcripts (client_id);

CREATE INDEX IF NOT EXISTS client_transcripts_call_date_idx
  ON client_transcripts (call_date DESC);
```

Note sui campi (da D-01/D-02):
- `id`: text PK NOT NULL — nanoid inserito dall'app, nessun DEFAULT SQL (pattern progetto)
- `lead_id`: nullable FK → leads(id) ON DELETE CASCADE (D-01)
- `client_id`: nullable FK → clients(id) ON DELETE CASCADE (D-01)
- `title`: text nullable (D-02) — titolo libero opzionale
- `content`: text NOT NULL (D-02) — testo grezzo illimitato
- `call_date`: date NOT NULL (D-02) — giorno della call, non timestamp
- `created_at`: timestamp with time zone NOT NULL DEFAULT now() (D-02)

Gli indici su lead_id, client_id e call_date ottimizzano le query per lead (Phase 20) e future query per client_id (Phase 21+).
  </action>
  <verify>
    <automated>grep -c "CREATE TABLE IF NOT EXISTS client_transcripts" /Users/simonecavalli/Vault/IAMCAVALLI/src/db/migrations/0009_client_transcripts.sql</automated>
  </verify>
  <acceptance_criteria>
    - File `src/db/migrations/0009_client_transcripts.sql` esiste
    - Contiene `CREATE TABLE IF NOT EXISTS client_transcripts`
    - Contiene `lead_id     text REFERENCES leads(id) ON DELETE CASCADE`
    - Contiene `client_id   text REFERENCES clients(id) ON DELETE CASCADE`
    - Contiene `content     text NOT NULL`
    - Contiene `call_date   date NOT NULL`
    - Contiene `created_at  timestamp with time zone NOT NULL DEFAULT now()`
    - Contiene 3 `CREATE INDEX IF NOT EXISTS` (lead_id_idx, client_id_idx, call_date_idx)
    - Nessun DROP o TRUNCATE nel file
  </acceptance_criteria>
  <done>File SQL completo e idempotente scritto, pronto per applicazione a prod.</done>
</task>

<task type="checkpoint:human-action" gate="blocking">
  <name>Task 2: [BLOCKING] Applicare migration 0009 al database di produzione via SSH</name>
  <read_first>
    - src/db/migrations/0009_client_transcripts.sql (verificare il contenuto prima di applicare)
  </read_first>
  <action>
La migration va applicata a prod via SSH tunnel PRIMA di eseguire i piani 20-02 e 20-03. Il database di produzione è accessibile solo tramite tunnel SSH.

**Passi:**

1. Aprire un terminale locale e avviare il tunnel SSH:
   ```bash
   ssh -L 54321:localhost:54321 root@178.104.27.55
   ```
   Lasciare questo terminale aperto per tutta la durata.

2. In un altro terminale, applicare la migration con psql (il DATABASE_URL del progetto punta a 127.0.0.1:54321):
   ```bash
   cd /Users/simonecavalli/Vault/IAMCAVALLI
   psql "$(grep DATABASE_URL .env.local | cut -d= -f2-)" -f src/db/migrations/0009_client_transcripts.sql
   ```

   Oppure, se il DATABASE_URL non funziona direttamente, applicare via docker exec sul server:
   ```bash
   # Sul server (nella sessione SSH aperta al punto 1):
   docker exec -i <nome_container_postgres> psql -U <db_user> -d <db_name> < /path/to/0009_client_transcripts.sql
   ```
   (Il nome del container e le credenziali sono in `.env.local` o nei secret Coolify.)

3. Verificare che la tabella esista:
   ```bash
   psql "$(grep DATABASE_URL .env.local | cut -d= -f2-)" -c "\d client_transcripts"
   ```
   Deve mostrare le colonne: id, lead_id, client_id, title, content, call_date, created_at.

4. Chiudere il tunnel SSH solo dopo aver verificato il punto 3.
  </action>
  <what-built>Il file SQL 0009_client_transcripts.sql è stato scritto da Claude nel Task 1. Questo task richiede solo che tu applichi la migration a prod — nessun codice da scrivere.</what-built>
  <how-to-verify>
    Eseguire in locale (con tunnel attivo):
    ```bash
    psql "$(grep DATABASE_URL .env.local | cut -d= -f2-)" -c "SELECT column_name, data_type, is_nullable FROM information_schema.columns WHERE table_name = 'client_transcripts' ORDER BY ordinal_position;"
    ```
    Risultato atteso: 7 righe con le colonne id, lead_id, client_id, title, content, call_date, created_at.
  </how-to-verify>
  <resume-signal>Digita "migration applicata" dopo aver verificato che \d client_transcripts mostra le 7 colonne attese.</resume-signal>
  <acceptance_criteria>
    - La tabella `client_transcripts` esiste nel database di produzione
    - `\d client_transcripts` mostra 7 colonne: id (text), lead_id (text, nullable), client_id (text, nullable), title (text, nullable), content (text, NOT NULL), call_date (date, NOT NULL), created_at (timestamptz, NOT NULL)
    - 3 indici presenti: client_transcripts_lead_id_idx, client_transcripts_client_id_idx, client_transcripts_call_date_idx
  </acceptance_criteria>
  <done>Database di produzione aggiornato con la tabella client_transcripts. I piani 20-02 e 20-03 sono sbloccati.</done>
</task>

</tasks>

<threat_model>
## Trust Boundaries

| Boundary | Descrizione |
|----------|-------------|
| developer → database prod | SQL applicato manualmente via SSH tunnel autenticato |

## STRIDE Threat Register

| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-20-01 | Tampering | 0009_client_transcripts.sql | mitigate | Revisione manuale del contenuto prima dell'applicazione (Task 2 step 1); file in version control |
| T-20-02 | Repudiation | Applicazione migration prod | accept | L'operazione è tracciata nel git commit di questo piano; nessun audit log applicativo necessario per DDL |
| T-20-03 | Denial of Service | DROP/TRUNCATE accidentale | mitigate | Il file usa solo CREATE TABLE IF NOT EXISTS + CREATE INDEX IF NOT EXISTS; nessuna istruzione distruttiva presente |
</threat_model>

<verification>
```bash
# Verificare che il file SQL esista e contenga i campi obbligatori
grep -E "CREATE TABLE IF NOT EXISTS client_transcripts|content.*text NOT NULL|call_date.*date NOT NULL|lead_id.*REFERENCES leads|client_id.*REFERENCES clients" \
  /Users/simonecavalli/Vault/IAMCAVALLI/src/db/migrations/0009_client_transcripts.sql

# Verificare assenza di istruzioni distruttive
grep -i "drop\|truncate" /Users/simonecavalli/Vault/IAMCAVALLI/src/db/migrations/0009_client_transcripts.sql | wc -l
# Atteso: 0
```
</verification>

<success_criteria>
- `src/db/migrations/0009_client_transcripts.sql` esiste con DDL completo e idempotente (KB-01)
- La tabella `client_transcripts` è presente nel database di produzione con le 7 colonne di D-02
- Nessuna istruzione distruttiva nel file SQL
- Il checkpoint umano è stato completato e segnalato con "migration applicata"
</success_criteria>

<output>
Dopo il completamento del Task 2, creare `.planning/phases/20-knowledge-base-cliente/20-01-SUMMARY.md` con:
- Migration file creato: `src/db/migrations/0009_client_transcripts.sql`
- Migration applicata a prod: sì/no
- Colonne verificate: lista delle 7 colonne confermate
</output>
