Un assistente AI on-premise per gli studi professionali svizzeri, costruito perché un modello linguistico compromesso non possa comunque far fare nulla al server.
Workspace AI single-tenant per studi legali, banche private, fiduciarie, gestori patrimoniali e notai del Ticino — dove i dati dei clienti sono coperti dal segreto professionale (art. 321 CP), dalla vigilanza FINMA e dalla nuova LPD, e l'AI in cloud non è un'opzione.
Unico architetto e sviluppatore
Prodotto, backend, frontend, sicurezza, infrastruttura, documentazione di conformità
~7 settimane
29 giu → 15 ago 2026, sviluppo attivo
Studi ticinesi
Legale, banca privata, fiduciario, gestione patrimoniale, notarile
Entrambi i controlli di qualità sono stati rieseguiti durante la stesura di questo caso studio: pytest → 878 superati · vite build → 1755 moduli, 234 KB gzip. Nessun test tocca la rete.
test backend superati
endpoint API
connettori dati revisionati
task AI dichiarativi in 8 settori
righe di codice backend
righe di codice frontend
righe di codice di test
tabelle database · 11 migrazioni
Gli studi professionali svizzeri custodiscono esattamente il tipo di dati che rende impraticabile l'AI in cloud: fascicoli coperti dal segreto professionale, dati bancari sotto vigilanza FINMA, dati personali sotto la nuova LPD e il GDPR. Uno studio legale di Lugano non può incollare il mandato di un cliente in ChatGPT — non perché il risultato sarebbe scadente, ma perché farlo è già una divulgazione.
Il prodotto vende prima sicurezza e discrezione, poi funzionalità. È l'inversione dell'ordine abituale, e si vede ovunque nel codice: un difetto di sicurezza o di onestà dei dati conta più di una funzione mancante. Tre vincoli hanno guidato ogni decisione:
Ogni studio ha la propria istanza. Nessun database condiviso, nessun percorso di query tra clienti diversi in cui poter sbagliare.
Il testo dei documenti caricati vive in un dizionario in memoria e non tocca mai il disco. I contenuti delle chat vengono salvati solo se un operatore attiva esplicitamente il logging con scadenza TTL.
Né su una destinazione, né su un permesso, né su un fatto. Al componente non fidato l'autorità è negata strutturalmente — dal codice, non da una policy.
La funzione dashboard esiste perché un gestore patrimoniale vuole un desk di mercato. Il design esiste perché quella funzione mette in mano a un modello linguistico la capacità di generare richieste dentro una rete privata. Quattro confini lo impediscono — ciascuno imposto dal codice:
L'unico output machine-readable del modello è una specifica di dashboard validata fail-closed. Nessun campo può contenere URL, hostname, porte, percorsi, header, credenziali o codice — e non esistono aritmetica, template string o formule. Una prompt injection perfetta produce una dashboard sbagliata ma inerte, mai una richiesta.
Una specifica nomina un connettore per id di registro e passa valori tipizzati. Host, percorso, metodo, header e credenziali sono costanti in Python revisionato. Se il modello vuole una fonte che non esiste, può solo inoltrare una richiesta in una coda amministrativa — solo una persona aggiunge una fonte dati.
Carica la specifica → valida → scarica → normalizza → renderizza. I payload scaricati non rientrano mai in un prompt, quindi una risposta upstream ostile non può diventare prompt injection. La docstring avverte esplicitamente il prossimo ingegnere di non aggiungere un "riassumi questa tile" senza rileggerla.
Proprietà e condivisione sono imposte nella query su ogni percorso — elenco, ricerca, lettura, modifica, cancellazione e contesto passato al modello. Un utente che jailbreaka completamente l'assistente non può comunque leggere il lavoro di un collega, perché non è l'assistente a decidere.
"Al modello non viene mai affidata autorità. Né una destinazione, né un permesso, né un fatto."
La reale forza di ogni controllo è scritta nel codice — compresi quelli che sono solo tripwire, e l'unico rischio residuo che resta aperto per decisione documentata.
Rilevamento regex delle prompt injection la cui docstring recita: "serve ad allertare, non a prevenire." Registra a audit il caso realistico; a proteggere il server è il confine strutturale.
Una allowlist non dice nulla su ciò che un host approvato restituisce. La struttura del payload è limitata (profondità, chiavi, lunghezze, tetto di 256 KB), i tipi dichiarati sono imposti, i campi di prosa vengono escapati mentre quelli numerici rifiutano il markup all'istante.
Condividere fuori dallo studio congela i dati dietro un token non indovinabile. Aprirlo è una SELECT — nessun fetch, nessun connettore, nessun modello — così un visitatore anonimo non può mai causare chiamate in uscita dalla rete di uno studio.
Dove l'URL è davvero fornito dall'utente (arricchimento dei prospect), ogni indirizzo risolto deve essere globalmente instradabile, i redirect vengono seguiti a mano ricontrollando ogni salto, e 46 test dedicati verificano che un bersaglio rifiutato non abbia prodotto alcuna richiesta — non solo nessun risultato.
Una tile è ok, stale (in grigio, con l'età del dato) o error. Mai uno zero, mai un vuoto, mai interpolata, mai finta — regola imposta anche su snapshot ed export PDF, perché un documento sopravvive allo schermo da cui è nato.
Qui un'allucinazione è un evento di responsabilità civile, quindi un unico contratto — una procedura, non un'esortazione — è scritto una volta e importato in ogni prompt: chiedi quando sei incerto, non inventare mai cifre o citazioni, distingui fonte, conoscenza e assunzione. "Non lo so" è una risposta corretta.
Il workspace come lo vede uno studio — catturato dalla demo live. Si noti l'etichetta di onestà su ogni cifra di mercato: indicativo, possibile ritardo, con l'ora esatta del rilevamento.
Una dashboard da desk di mercato — SMI con intervalli giornaliero e a 52 settimane, tile di quotazione UBS e Nestlé, notizie di mercato curate. L'intestazione recita "Versione 3": ogni modifica è passata da proponi → rivedi → salva, e un click esporta il desk in PDF o Word.
La chat nel settore banca privata, con i blocchi di portafoglio — ciambella di composizione e grafico dell'andamento disegnati dal renderer SVG fatto a mano, con storico scaricato e metriche calcolate lato server, mai dal modello. Sopra i task rapidi; sotto, la dicitura "i messaggi non vengono salvati".
La libreria documenti — un modello di modifica delle condizioni con {{segnaposto}} dichiarati, compilazione lato server ("i campi lasciati vuoti vengono segnalati, mai inventati") e l'anteprima a foglio A4 che va a capo dove andrà a capo la stampa.
Un workspace completo — 8 settori professionali, 42 task AI dichiarativi, dashboard di mercato in tempo reale, una libreria documenti e un funnel commerciale integrato.
Modificare un messaggio inviato crea un ramo fratello invece di sovrascrivere la cronologia — entrambe le risposte sopravvivono e l'utente passa dall'una all'altra. Ogni percorso di lettura filtra sul proprietario in SQL.
Le risposte dell'assistente contengono blocchi JSON rigorosi — tabelle, grafici, statistiche, form, ticker — resi in SVG fatto a mano nel browser, in docx/pptx sul server e in PDF. Nei documenti i grafici sono seguiti dalle loro cifre come testo reale.
"Lo SMI e USD/CHF ogni mattina" diventa una specifica validata, con ciclo di riparazione e proponi → rivedi → salva su ogni modifica. Griglia a mosaico, drag-and-drop, cronologia versioni append-only, snapshot del mattino nei giorni feriali.
Banca Nazionale Svizzera, BCE, notizie FINMA, Yahoo Finance, stampa finanziaria svizzera e italiana, GDELT — più indicatori calcolati e cifre ponderate di portafoglio, computate lato server, mai dal modello.
Gli allegati della chat vivono solo in RAM e muoiono al riavvio; la libreria conserva ciò che l'utente ha archiviato esplicitamente. L'AI propone bozze, l'utente salva — nulla viene persistito senza che qualcuno lo abbia deciso. I controlli deterministici nLPD/GDPR girano sempre.
Un'unica pipeline rende risposte di chat e dashboard in docx, pptx e PDF — con un'anteprima a foglio A4 impaginata per misura reale, così l'anteprima va a capo dove andrà a capo la stampa.
Richieste demo self-service, arricchimento del sito web con guardia SSRF, punteggio sul cliente ideale, approvazione umana, demo pubbliche per settore dietro identità guest, ed email di outreach con default dry-run e clausola di opt-out LCSl aggiunta automaticamente.
Traccia di audit JSONL append-only, logging dei contenuti opt-in con TTL, politica di retention come dato, purghe ordinate per dipendenza e offboarding degli utenti di prima classe — più un watchdog cron che manda un'email quando il servizio degrada.
Un design system piatto inchiostro-su-carta con contrasto AA misurato, ~650 stringhe UI bilingui, grafici SVG costruiti a mano senza librerie, e un tour guidato in 11 passi che porta il browser sulla pagina di ogni funzione.
Disegnata nel linguaggio grafico del prodotto stesso: raggio zero, bordi da 2 px, nessuna ombra. L'assunzione del worker singolo è esplicita — il codice dichiara in quattro punti che, se mai cambiasse, tutte le cache locali al processo migrano a Redis insieme.
TLS · HSTS · CSP · rate limiting · serve il frontend compilato · reverse proxy su /api — origine unica, volutamente nessun CORS da nessuna parte
Migrazioni Alembic all'avvio · JWT in cookie httpOnly SameSite con sessioni revocabili su database · RBAC come un'unica mappa revisionata (28 azioni × 4 ruoli) · livello LLM astratto per provider (OpenAI · mock · on-premise)
12 tabelle · solo metadati per default · cronologia versioni delle dashboard append-only
Cron esterno, ogni job eseguibile a mano — debuggabile alle 09:00 quando un desk segnala uno snapshot mancante.
Testing
878 test su SQLite in memoria con LLM simulato — nessuna rete. Copertura concentrata sui confini: 46 test solo sulla guardia SSRF.
Configurazione
47 impostazioni tipizzate con default sicuri: email in dry-run, logging dei contenuti spento, e rifiuto di avviarsi in produzione con il segreto JWT di fabbrica.
Documentazione come deliverable
Guida al deployment, runbook, incident response, DPIA su LPD svizzera/GDPR, piano di continuità operativa con RPO/RTO — ciascuno prodotto anche in italiano.
Nominati di proposito — un portfolio che elenca solo vittorie non è un portfolio, e il codice li nomina da sé.
Non "sanifichiamo gli input" — un sistema progettato in cui al componente non fidato l'autorità è negata strutturalmente, in cui la reale forza di ogni controllo è messa per iscritto, e in cui un rischio residuo accettato è documentato accanto al codice che lo porta.
Lo stesso problema di HTTP in uscita è risolto in due modi diversi nello stesso codice, perché in un caso la destinazione è una costante e nell'altro è l'input di uno sconosciuto. Riconoscere che servono controlli diversi è la competenza.
Funzioni filtrate per settore, perché una dashboard irrilevante è una prima impressione peggiore di nessuna. Un tour che naviga, perché spiegare una pagina da un'altra pagina non funziona. Un funnel, perché una demo che nessuno raggiunge non vende nulla.
Dal modello dati alle migrazioni, API, streaming, visualizzazioni fatte a mano, design system, testi bilingui, Docker, nginx, cron, backup, incident response, DPIA e continuità operativa — per un prodotto con un deployment reale e un funnel reale alle spalle.
Ottieni una piattaforma AI su misura, un deployment on-premise o un prodotto SaaS completo, ingegnerizzato con la stessa disciplina — dal modello di minaccia agli script di backup.