Gate OTP live su hub.iamcavalli.net. Verificato: gate senza cookie con zero dati di progetto nell'HTML, no-enumeration, codice sbagliato/corretto, cookie Secure+HttpOnly+SameSite 90 giorni, rientro col cookie, isolamento fra clienti. Dati di test rimossi, tabelle protette invariate. Resta da popolare la whitelist dei 3 clienti reali. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
14 KiB
gsd_state_version, milestone, milestone_name, status, stopped_at, last_updated, last_activity, progress
| gsd_state_version | milestone | milestone_name | status | stopped_at | last_updated | last_activity | progress | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1.0 | v2.3 | Email & Accesso | executing | v2.3 deployata in produzione; resta da popolare la whitelist dei 3 clienti reali | 2026-07-29T21:20:00.000Z | 2026-07-29 -- dominio Resend verificato, gate OTP deployato in produzione |
|
Project State
Project Reference
See: .planning/PROJECT.md (updated 2026-06-21)
Core value: Il cliente apre il link e vede esattamente a che punto è il suo progetto, cosa deve ancora succedere e cosa ha già approvato — senza dover scrivere email per chiedere aggiornamenti.
Current focus: Milestone v2.3 "Email & Accesso" — consegnata. Il portale cliente è protetto da gate email OTP. L'invio del preventivo via email (SEND-01/02) è stato rinviato a v2.4 — si manda a mano.
Current Position
Phase: 23/24/25 completate e deployate
Plan: ~/.claude/plans/si-ma-abbiamo-un-jaunty-micali.md
Status: in produzione. Resta da popolare la whitelist dei 3 clienti reali.
Last activity: 2026-07-29 — dominio Resend verificato, deploy su hub.iamcavalli.net
✅ Prerequisiti email risolti (2026-07-29)
- Dominio
iamcavalli.netverificato su Resend. L'utente ha ricreato la registrazione del dominio (nuovo idf81202f1-3bba-47c5-8c0f-84101440b960, la precedentea2a80798-…non esiste più) e messo i DNS. Tutti e tre i recordverified: DKIM TXT suresend._domainkey, SPF TXT + MX susend. Invio dano-reply@iamcavalli.netverso un indirizzo esterno confermato riuscito.- Nota: ricreare il dominio su Resend rigenera la chiave DKIM. Se in futuro il dominio torna
failed, non fidarsi di valori DKIM annotati in passato — rileggerli daGET /domainse confrontarli condig +short TXT resend._domainkey.iamcavalli.net @8.8.8.8.
- Nota: ricreare il dominio su Resend rigenera la chiave DKIM. Se in futuro il dominio torna
RESEND_API_KEY+RESEND_FROMconfigurate su Coolify (production e preview) via API — verificate presenti.- Mailer verificato:
sendEmail()col template OTP reale ha restituito{ok:true, id:…}. - Verificato che quando Resend rifiuta, la route risponde comunque col messaggio neutro e logga l'errore lato server — il no-enumeration tiene anche a provider guasto.
✅ Verificato in produzione (2026-07-29, commit 27da969)
Su hub.iamcavalli.net: gate mostrato senza cookie e zero dati di progetto nell'HTML (12.487 byte); email fuori whitelist e in whitelist danno risposta identica e solo la seconda genera un OTP; codice sbagliato rifiutato, corretto accettato; cookie ch_sess_<id> con Secure + HttpOnly + SameSite=lax + Max-Age=7776000 (90 giorni); rientro col cookie mostra la dashboard; sessione di un cliente sull'URL di un altro mostra il gate. Nessun errore d'invio nei log del container. Dati di test rimossi, tabelle protette invariate (4/5/11/10).
Da fare
Popolare la whitelist dei 3 clienti reali da /admin/clients/<id> → "Accessi al portale", e reinviare loro il link. La migration aveva seedato solo mario@test.it (cliente di test "Rossi Inc"); Protocollo Estetico, Caruso Speaker e Teckell hanno whitelist vuota e finché lo è il loro portale non è accessibile.
Cosa è stato consegnato in v2.3 (codice locale, buildato e testato)
- Resend:
resend@6.18.1,src/lib/mailer.ts(Result tipizzato, mai catch silenzioso) + template OTP in italiano. - Schema: migration
0015_otp_access.sqlgià applicata a prod —client_emails(whitelist, unique case-insensitive),otp_codes(hash del codice, mai il codice),clients.sessions_valid_from(revoca). Additiva pura: conteggi pre/post identici su clients 4 / projects 5 / payments 11 / phases 10. - Admin: sezione "Accessi al portale" in
/admin/clients/[id]— aggiungi/rimuovi email + "Revoca sessioni attive". Server actions inclients/[id]/actions.ts. Scritta a token semantici benché la pagina attorno sia ancora a palette vecchia. - Gate:
src/lib/otp.ts(codice 6 cifre CSPRNG, hash SHA-256 conNEXTAUTH_SECRET+clientId, TTL 15 min, max 5 tentativi),src/lib/client-session.ts(cookie HMAC per-clientech_sess_<id>, 90 giorni, httpOnly/secure/lax, path=/client),src/lib/client-gate.ts, route/api/client/otp/request|verify, componenteOtpGate.
⚠️ Lezione: il gate NON va nel layout
Prima implementazione: gate in client/[token]/layout.tsx che rendeva <OtpGate/> al posto di {children}. Non funziona come protezione. Nell'App Router il segmento page viene renderizzato in parallelo al layout: la dashboard spariva a schermo ma fasi, task e pagamenti restavano leggibili nel payload RSC dell'HTML (46.907 byte → 17.594 dopo il fix). Il gate è ora in cima alla page, prima di ogni query, via getClientGate(). Ogni nuova route sotto /client/[token]/ deve fare lo stesso — il layout porta un commento che lo ricorda.
Lavoro recente precedente (in prod)
- [2026-07-27/28] Audit di sicurezza: 4 vulnerabilità chiuse e deployate (secondo gate admin, hardening slug, XSS, CSP/HSTS); slug clienti deboli ruotati a 12 char CSPRNG;
INTERNAL_SECRETeADMIN_PASSWORDconfigurati su Coolify. Finding #1 (password Postgres committata) declassato CRITICO→BASSO: verificata inattiva, già ruotata. Report in.planning/SECURITY-*.md. - [2026-07-28] Riorganizzazione cartella: fasi di planning consolidate, script one-off archiviati in
cestino/,CLAUDE.mdarricchito. - Design system "Quiet Luxury": dashboard, liste (Clienti/Offerte/Catalogo/Preventivi/Progetti), Conversazioni, Impostazioni, Pipeline+Kanban, dettaglio Lead e portale cliente base sono a token e dual-theme. Ancora a design vecchio (funzionanti, solo estetica):
/admin/offers/[id]/edit,/admin/projects/[id](il cluster peggiore, ~140 occorrenze fra i suoi tab),/admin/projects/new,/admin/clients/[id],/admin/clients/[id]/edit,/admin/login, badge in/admin/preventivi/[id], chat portale cliente — più, non censiti prima: tutto/quote/[token](~40 occorrenze, pagina rivolta al cliente) eui/dialog.tsx, che propaga la palette vecchia a ogni modale. - Tassonomie: gestione centralizzata categorie/tag in Impostazioni (
src/lib/taxonomy.ts). - Lead → Cliente (A+B):
clients.email/phone+leads.archived(migration 0011);convertLeadToClient.
Fasi completate (v2.2, storico)
Phase 18 (cleanup), Phase 19 (Kanban CRM), Phase 20 (transcript KB), Phase 21 (AI agent), Phase 22 (deck pubblico) — consegnate e in prod 2026-06-20.
Performance Metrics
Velocity:
- Total plans completed: 7 (v2.1) + 9 (v2.2) = 16 totali
- Average duration: —
- Total execution time: —
By Phase:
| Phase | Plans | Total | Avg/Plan |
|---|---|---|---|
| Phase 11 P01 | 25min | 2 tasks | 6 files |
| 11 | 4 | - | - |
| 14 | 3 | - | - |
Recent Trend:
- Last 5 plans: —
- Trend: —
Updated after each plan completion | Phase 11 P02 | 12min | 2 tasks | 2 files | | Phase 11 P03 | 9min | 2 tasks | 2 files | | Phase 11 P04 | 12min | 2 tasks | 4 files |
Accumulated Context
Decisions
Decisions are logged in PROJECT.md Key Decisions table. Recent decisions affecting current work:
- [v2.3 2026-06-21] Resend come provider email unico — PUB-03 (invio preventivo) e AUTH-OTP-01 (OTP gate) condividono la stessa integrazione Resend. Phase 23 configura SDK + env vars, Phase 25 li riusa.
- [v2.3 2026-06-21] OTP gate è strato aggiuntivo, non rimpiazzo del token middleware — proxy.ts e
/api/internal/validate-tokenrimangono invariati. Il gate OTP interviene dopo la validazione del token, nel rendering della route/client/[token]/*. - [v2.3 2026-06-21] Migration Phase 24 è additiva pura —
client_emailseotp_codessono nuove tabelle. Nessun drop/truncate. SQL a mano (drizzle-kit generate rotto). Applicare via SSH prima del codice dipendente. - [RESET 2026-06-19] Milestone v2.2 "Sales Loop" sostituisce le fasi residue v2.1. Decisioni bloccate: (1) URL preventivo =
/preventivo/[slug]pubblico; (2) tagliare Forecast + quote builder manuale + Phase 15, fondere/admin/analyticsnella dashboard; (3) portale post-vendita resta core, non si tocca (Phase 13 congelata); (4) agente AI = "io scelgo l'offerta, l'AI personalizza" leggendo i transcript, provider Claude. Piano:.claude/plans/glittery-sprouting-pudding.md - [SUPERSEDED dal reset] v2.1 roadmap: Offer Studio (Phases 11-15) sequenced before Proposal AI (Phases 16-17) — clean/fast data UX before the AI builder
- Phase 11 bundles catalog database-view UX (OFFER-07..10) with legacy consolidation (OFFER-13) since the new UX should be built on a single unified
servicestable, not on top of legacyservice_catalog/offer_services - Phase 13 (Workspace — Servizi Attivi) is independent of Phases 11/12 — can execute in parallel order if useful, but numbered after for narrative flow
- Phase 15 (Dashboard Revenue Stats / DASH-11) is isolated and BLOCKED on user-provided mockup; no other phase depends on it — can be deferred/skipped without blocking Phase 16/17
- Phase 16/17 split: schema/automation (payment link field + auto-provisioning) first, then AI builder + public page redesign + email — keeps the AI-dependent work last
- [Phase 11]: Phase 11: hand-write Drizzle migration SQL (0006_add_tags_table.sql) following the project's established convention since drizzle-kit generate is non-functional (meta snapshots out of sync since migration 0001, pre-existing since Phase 8) — Avoids architectural snapshot-reconciliation work (Rule 4, out of scope) while matching exact precedent from migrations 0003-0005
- [Phase 11]: Phase 11 Plan 02: onConflictDoNothing() without explicit target compiles cleanly for tags table (single unique index tags_entity_name_unique) — used as written in plan, no fallback needed — Avoids unnecessary deviation; Drizzle's no-target ON CONFLICT DO NOTHING is correct given the single unique constraint from Plan 01
- [Phase 11]: Phase 11 Plan 03: removed the plan's prescribed value-sync useEffect (and a follow-up render-time ref-read attempt) from EditableCell — both violate this project's react-hooks lint rules (set-state-in-effect, refs-during-render / React Compiler). tempValue is now only (re)initialized in startEdit()/cancel(), and the toggle display branch reads
valuedirectly instead oftempValue— Rule 1 lint fix, no behavioral change to the 8 spec'd test behaviors - [Phase 11]: Phase 11 Plan 04: left
createService/serviceSchemainsrc/app/admin/catalog/actions.tsas unused dead code after deletingServiceForm.tsx(its only consumer) —actions.tswas outside this plan'sfiles_modifiedscope andupdateServicestill depends onserviceSchema; logged to deferred-items.md for future cleanup - [Phase 18-02]: fmtEur unified to number version (analytics/page.tsx variant); KPI card callers using DB string values wrapped with parseFloat() — cleaner than maintaining two named variants
- [Phase 18-02]: /admin/analytics route deleted; YearSelector now routes to /admin?year=Y — single admin entry point for statistics (CLEAN-03)
Pending Todos
[From .planning/todos/pending/ — ideas captured during sessions]
None yet.
Blockers/Concerns
Record DKIM / dominio Resend— risolto 2026-07-29: dominio ricreato everified, invio dal dominio reale confermato.— fatto 2026-07-29, production e preview.RESEND_API_KEY+RESEND_FROMsu Coolify- Whitelist vuota per 3 clienti su 4 (non bloccante: l'utente li re-invita) — da popolare da
/admin/clients/<id>→ "Accessi al portale". - Coolify API: credenziali in
~/.coolify.env(export COOLIFY_URL/COOLIFY_TOKEN, va sorgentato conset -a; . ~/.coolify.env). App ClientHub uuidxsksow44g4kcoo8wocsgkscc. Il POST su/api/v1/applications/<uuid>/envsnon accetta il campois_build_time(422): mandare solokey,value,is_preview. I token Hetzner/Cloudflare nel file sono vuoti → il DNS non è modificabile via API. - Migrations (sempre valido): ogni fase con schema DEVE avere la migration applicata a prod PRIMA di pushare il codice dipendente.
drizzle-kit generaterotto → SQL a mano. La 0015 è già applicata. Due strade:cat migration.sql | ssh root@178.104.27.55 "docker exec -i xwkk0040w0kk0gsgcgog8owk psql -U clienthub -d clienthub -v ON_ERROR_STOP=1 --single-transaction"(autoritativa, nessun tunnel), oppure tunnelssh -f -N -L 54321:localhost:54321 root@178.104.27.55conDATABASE_URLriscritto a127.0.0.1:54321se serve puntarci il tooling locale. .env.localpunta al DB di PRODUZIONE (178.104.27.55:54321, richiede il tunnel). Non esiste un DB di sviluppo separato: qualsiasi test in locale scrive su dati reali. Verificare sempre i conteggi delle tabelle protette prima e dopo.- Debito tecnico (non bloccante): tabelle legacy
service_catalog/offer_services/offer_micro_servicesrestano come deadweight;createService/serviceSchemadead code incatalog/actions.ts.
Deferred Items
Items acknowledged and carried forward from previous milestone close:
| Category | Item | Status | Deferred At |
|---|---|---|---|
| v2.4 | PROP-03 — Stripe Payment Link su deck pubblico | Backlog | v2.3 kickoff |
| v2.4 | PROP-04 — Auto-provisioning cliente/progetto/fasi al "Vinto" | Backlog | v2.3 kickoff |
| v2+ | Phase 13 — Servizi attivi/ricorrenti post-vendita | Congelata | v2.1 kickoff |
| v2 | OFFER-14 — Sezioni analitiche stile Notion | Backlog | v2.1 kickoff |
| v2 | ARCH-01 — Split modulo "compartimento stagno" in deploy separato | Backlog (only if module grows) | v2.1 kickoff |
| v2.4 | SEND-01/02 — Invio link preventivo via email dall'admin | Backlog (mailer già pronto) | 2026-07-28 |
| Design | 11 pagine ancora a palette vecchia — vedi elenco in "Lavoro recente" | Backlog | 2026-07-28 |
Session Continuity
Last session: 2026-07-29T21:20:00.000Z
Stopped at: v2.3 deployata in produzione. Dominio Resend verificato, Coolify configurato, gate OTP live su hub.iamcavalli.net.
Next: popolare la whitelist dei 3 clienti reali da /admin/clients/<id> → "Accessi al portale", e reinviare loro il link.
Resume file: .planning/STATE.md