dc934fb04a
3 piani: 20-01 migration BLOCKING, 20-02 schema+actions, 20-03 UI. KB-01 e KB-02 coperti. Wave 1→2→3 sequenziali per vincolo migration-prima-del-codice. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
233 lines
10 KiB
Markdown
233 lines
10 KiB
Markdown
---
|
|
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>
|