AgileTTS — Novità
Una voce per data: cosa è cambiato, a chi serve e come si adotta. Le voci «consegnato, non in linea» diventano «in linea» con la data del «vai» della regia. Il contratto completo sta in API.
30/9/2026 — L'indirizzo pubblico serve il servizio, non la sua stanza dei bottoni consegnato, non in linea
Stiamo per aprire un indirizzo pubblico di AgileTTS, perché un prodotto già in produzione possa usare la nostra voce al posto del fornitore di prima. Il pezzo davanti al servizio, però, faceva passare su quell'indirizzo tutto quel che il servizio sa fare: non solo la sintesi, anche il governo — la console con cui un'azienda gestisce le sue chiavi e le sue persone, le licenze, e le richieste di licenza arrivate, che sono di chi governa. Prima di dire «pronto» l'abbiamo contato una rotta per volta, leggendo l'elenco dal codice del servizio e non da una lista scritta a mano — una lista a mano dimentica sempre la cosa nata ieri. Il conto: delle venti vie di governo, zero erano chiuse. Ora venti su venti, e il prodotto continua a passare per intero, dieci su dieci, modulo della pagina pubblica compreso. La chiusura è per famiglia e non per elenco: quel che nascerà domani sotto il governo nasce chiuso, invece di nascere aperto e aspettare che qualcuno se ne accorga. E il servizio, dalla sua, ascolta solo se stesso: misurato sul computer che lo ospita, non risponde nemmeno dalla rete privata del gruppo, quindi chi arriva dall'indirizzo pubblico passa per forza dal nostro filtro. Quel dettaglio veniva da un valore implicito, e un valore implicito si capovolge in silenzio: adesso è scritto dove si avvia il servizio.
A chi serve: al prodotto che sta per adottarci, che ottiene la voce senza ottenere insieme la nostra stanza dei bottoni; a chi apre l'indirizzo, che ora sa quali vie escono e con quale conto; e a chi amministra, che sa da dove si entra e sa che glielo abbiamo misurato invece di dedurlo.
Come si adotta: nulla da cambiare per chi usa il servizio con la propria chiave — sull'indirizzo pubblico la sintesi è identica. Chi amministra (console, licenze, richieste arrivate) entra da dentro il computer che ospita il servizio: sull'indirizzo pubblico quelle vie rispondono «non esiste», e non è un guasto, è il perimetro. Un dettaglio che la misura di oggi ha corretto: siccome il servizio ascolta solo se stesso, oggi la console non si raggiunge nemmeno dalla rete privata del gruppo — solo da dentro. Come debba raggiungerla è una scelta di chi governa, e l'abbiamo posta. L'indirizzo pubblico non è ancora attivo: quel che è chiuso qui è la configurazione, e la verifica dal posto di chi lo userà si farà quando il nome sarà davvero in linea.
30/9/2026 — Una licenza è di un'azienda, e una chiave anche consegnato, non in linea
Una licenza appartiene a un'azienda; una chiave appartiene a un'azienda. Finché le due combaciano, la licenza sopra la chiave è un contratto. Se non combaciano, la licenza della vittima paga per un estraneo: il suo tetto al minuto, la sua quota del mese e la riga che finirà in fattura. La domanda ci è arrivata dal servizio gemello e l'abbiamo misurata sul nostro invece di darla per buona: nessuno dei due posti che scrivono il legame chiave→licenza confrontava le due aziende. Su un archivio usa e getta: mille caratteri su mille della quota della vittima consumabili da chi non è suo cliente, la chiave dell'estraneo elencata nel dettaglio della licenza altrui, e il posto al minuto della vittima occupato da lui. Ora zero. Abbiamo chiuso due porte, non una: spostare in un'altra azienda una chiave emessa sotto una licenza è rifiutato, e il rifiuto nomina la licenza; e a ogni richiesta una chiave la cui licenza è di un'altra azienda riceve un no che dice quale licenza, servito prima dei conti del tetto al minuto — altrimenti il posto della vittima sarebbe già occupato quando il no arriva. Le chiavi senza licenza, quelle di prima, si legano come sempre: chiudere anche quel caso sarebbe stato peggio del difetto.
A chi serve: a ogni azienda che ha una licenza, perché il suo tetto, la sua quota e la sua fattura non possono più essere consumati da una chiave che non è sua; e a chi amministra, che se prova a spostare una chiave sotto licenza legge un no col nome della licenza, invece di scoprirlo da una fattura sbagliata.
Come si adotta: nulla da cambiare per chi usa il servizio con la propria chiave. Se una chiave deve passare a un'altra azienda, la si revoca sotto la sua licenza e se ne emette una nuova sotto quella giusta: spostare la riga non è più possibile, ed è esattamente il punto. Chi riceve questo no non deve cambiare chiave: è una riga d'archivio da sistemare, e si segnala a chi amministra.
30/9/2026 — Il modulo della pagina pubblica, dal browser, chiedeva il permesso a nessuno consegnato, non in linea
La pagina pubblica sta su un indirizzo e il servizio su un altro: per un browser sono due mondi diversi, e prima di mandare un modulo dall'uno all'altro il browser chiede il permesso. Il servizio a quella domanda non rispondeva affatto, e nemmeno alle risposte vere metteva la riga che autorizza il browser a leggerle: il modulo «richiedi una licenza» e lo stato del servizio dal vivo — due dei sei elementi della pagina consegnati stamattina — dal browser non funzionavano, pur rispondendo benissimo agli strumenti da riga di comando. Provato dal posto del browser: due elementi su otto, ora otto su otto. Il permesso si dà per nome, mai a chiunque, e solo sulle tre cose che la pagina chiede davvero: le rotte con la chiave restano chiuse al browser, perché una chiave sta sul server di chi la possiede e non dentro una pagina. Un indirizzo fuori elenco riceve un rifiuto che lo nomina, invece dell'errore muto che il browser mostra di solito. Nello stesso punto è corretto il tetto del modulo: contava l'indirizzo del collegamento, ma davanti al servizio c'è un proxy sullo stesso computer, quindi quell'indirizzo è sempre lo stesso e il tetto sarebbe stato uno per tutta Internet — tre richieste al minuto in tutto, e il primo robot avrebbe chiuso il modulo in faccia a ogni cliente vero. Ora conta il cliente, e crede a chi lo dichiara solo quando la richiesta arriva dal nostro proxy.
A chi serve: a chiunque apra la pagina pubblica, perché il modulo e lo stato del servizio ora funzionano davvero dal browser e non solo dagli strumenti tecnici; e a chi riceve le richieste di licenza, che altrimenti ne avrebbe viste arrivare zero da una pagina apparentemente in ordine.
Come si adotta: nulla da cambiare per chi usa il servizio con la propria chiave. Chi serve la pagina da un altro nome deve dichiararlo nella configurazione del servizio, altrimenti dal browser il modulo tace pur rispondendo bene agli strumenti da riga di comando — ed è proprio il caso in cui il rifiuto dice quale nome manca. Chi mette il servizio dietro un proxy diverso dal nostro deve accertarsi che il proxy dichiari l'indirizzo del cliente, o il tetto del modulo tornerà a contare il proxy.
30/9/2026 — Le licenze: il diritto di usare AgileTTS ha un nome, uno stato e un conto consegnato, non in linea
Fino a oggi chi usava AgileTTS aveva una chiave, e la chiave era tutto: il permesso, il tetto e il contratto insieme. Mancava quel che sta in mezzo fra un'azienda e le sue chiavi: la licenza. L'abbiamo misurato prima di scrivere una riga, chiedendo al servizio vero le dieci rotte che lo standard di tutti i servizi Agile prevede: ne rispondevano zero. Da oggi una licenza dice a chi appartiene, quale piano è, quali permessi copre, quanto si può dire in un mese, quante richieste al minuto, da quando e fino a quando. Le chiavi le stanno sotto: si sospende la licenza e tutte le sue chiavi tacciono insieme, si riattiva e riprendono — senza che nessun prodotto debba ricevere una chiave nuova. Lo stato che conta è quello vero: una licenza con la scadenza di ieri è scaduta anche se in archivio c'è scritto «attiva», e la sua chiave riceve un rifiuto che dice quale licenza e perché, non un generico «chiave non valida». Una scaduta si rinnova; una revocata non torna: se ne rilascia un'altra, e si vede nell'elenco che è un'altra. Il consumo si conta per licenza ed è la riga che finirà in fattura: dentro restano anche le chiavi spente, perché quel che è stato detto è stato detto. E i numeri di partenza dei piani non sono scelti a occhio: vengono dalla capacità misurata del servizio (quanti caratteri al mese regge una scheda, con margine per il picco), e la prova è l'uno per cento di quella capacità. I prezzi restano su richiesta.
A chi serve: a ogni azienda che ci integra, perché «da quando a quando, per quanto, con quali permessi» adesso è scritto in un posto solo e si legge dalla console, invece di stare nella memoria di chi ha emesso la chiave; a chi governa il servizio, che sospende o riattiva un'azienda intera senza toccare le sue chiavi una per una; e a chi fattura, che avrà una riga per licenza invece di una somma da rifare a mano.
Come si adotta: nulla da cambiare per chi usa il servizio con la chiave che ha oggi — le chiavi nate prima delle licenze continuano a funzionare identiche, e spegnerle perché «non hanno licenza» sarebbe un guasto travestito da regola. Chi amministra trova le licenze nella console, con la sua credenziale di amministratore: creare, sospendere, rinnovare e revocare spetta a chi governa il servizio, mentre l'amministratore d'azienda legge le proprie e ci emette le chiavi dentro i limiti della licenza.
30/9/2026 — La pagina pubblica, con i piani e il modulo per chiedere una licenza consegnato, non in linea
Il sito di AgileTTS era una documentazione: come si chiama l'API, quali rotte ci sono, quali errori. Per chi deve decidere se usarlo non c'era niente: né a chi serve, né quali piani esistono, né come si chiede una licenza, né se il servizio in questo momento è in piedi. Dei sette elementi che una pagina pubblica deve avere, la nostra ne aveva due; ora ne ha sei, in italiano e in inglese: per chi è, i tre piani con quel che comprendono e il prezzo su richiesta (zero cifre in pagina: i prezzi non li decidiamo noi), il link alla documentazione, lo stato del servizio dal vivo — la pagina lo chiede al servizio e lo dice, e se non risponde scrive «stato non raggiungibile» invece di restare bianca — e il modulo «richiedi una licenza», che non manda una email e non inghiotte: registra la richiesta con un numero e te lo dice, e un giro la porta a chi la deve leggere segnandola avvisata solo se l'avviso è partito davvero. Il settimo elemento — il confronto col leader coi numeri veri — manca, e la pagina lo dichiara: i nostri sei numeri ci sono, ognuno con la sua fonte (compreso quello scomodo), ma dei numeri del leader non ne abbiamo nessuno misurato, perché la chiave per provarlo è bloccata dal 21/9. Un numero inventato in una pagina pubblica è peggio di un numero che manca.
A chi serve: a chi valuta AgileTTS — un prodotto interno, un cliente, chi lo propone — e fino a oggi doveva chiedere tutto a voce; a chi ci integra, che dalla stessa pagina vede se il servizio è in piedi; e a chi riceve le richieste, che ora arrivano con un numero invece che per passaparola.
Come si adotta: nulla da cambiare per chi usa l'API. I nomi pubblici della pagina e dell'API li crea chi governa la rete e non sono ancora attivi: finché non lo sono, la pagina si guarda dagli artefatti. Chi la serve da un altro indirizzo deve cambiare la riga del modulo, che chiama il servizio per registrare la richiesta.
30/9/2026 — Chiedere l'azienda di un altro è un no, non la propria senza dirlo consegnato, non in linea
Nella console si può dire su quale azienda si sta lavorando, perché chi amministra il servizio lavora su tutte. Per un'azienda normale quel nome veniva ignorato: chi chiedeva le chiavi o le persone di un'altra azienda riceveva le proprie, senza che nessuno glielo dicesse — misurato, 2 casi su 2. Chi poi copia quell'elenco in un rapporto convinto che sia dell'azienda che aveva chiesto non se ne accorge. Da oggi, se l'azienda non è la tua, la risposta è un rifiuto; se è la tua, passa come prima. E il rifiuto lascia la sua riga nel registro di chi ha chiesto, non in quello dell'azienda nominata: altrimenti un estraneo potrebbe scrivere righe nel registro di un'azienda che non è sua.
A chi serve: a ogni azienda che ci integra, perché un elenco letto per sbaglio non è più indistinguibile da uno letto per diritto; e a chi scrive l'integrazione, perché un parametro ignorato in silenzio è un errore che non si vede finché non fa danno.
Come si adotta: nulla da cambiare per chi usa il servizio per far parlare i propri prodotti. Chi amministra e indicava la propria azienda continua identico; chi ne indicava un'altra aspettandosi le proprie cose ora riceve un no.
30/9/2026 — Il registro di chi ha fatto che cosa si legge dall'azienda consegnato, non in linea
Dal 29/9 un'azienda si gestisce da sola le chiavi e le persone, e ogni azione lascia la sua riga in un registro: chi ha emesso una chiave e quando, chi ha invitato chi, chi ha sospeso chi, e anche i tentativi rifiutati. Quel registro però lo leggevamo solo noi, dal nostro box. Misurato: una giornata di lavoro scrive 12 righe e l'amministratore dell'azienda ne leggeva zero — le cinque domande che ci si fa il giorno dopo avevano risposta 0 su 5 dalla console e 5 su 5 da noi. Da oggi quelle righe le legge l'azienda, dalla più recente, filtrando per persona, per azione o per periodo: 12 su 12 e 5 domande su 5. Le righe di un'altra azienda non ci sono mai, e a chi le chiede si risponde di no invece di dargli le proprie senza dirlo; il nome di chi ha agito compare solo se è una persona di quell'azienda; e guardare quel registro è a sua volta un accesso ai dati dell'azienda, quindi lascia la sua riga.
A chi serve: a ogni azienda che deve rispondere «chi ha fatto questo, e quando» — a una verifica interna, a un audit di sicurezza, a un collega — senza chiedere niente a noi; e a noi, che smettiamo di stare in mezzo a ogni domanda.
Come si adotta: nulla da cambiare per chi usa il servizio per far parlare i propri prodotti: non cambia nessuna chiamata e non sparisce nessun campo. Chi amministra trova il registro nella console, con la sua credenziale di amministratore; un utente semplice non lo vede.
30/9/2026 — Un ridispiego non butta più via chi sta parlando consegnato, non in linea
Quando aggiorniamo AgileTTS lo riavviamo, e riavviare vuol dire dire al programma «fermati». Fino a oggi il programma si fermava nell'istante: chi stava parlando in quel momento perdeva la frase a metà, e non riceveva nemmeno una spiegazione — solo un errore di rete, che dentro un prodotto è il ramo che ripiega su un fornitore americano. Cioè: un ripiego silenzioso, provocato da noi, a ogni aggiornamento. L'abbiamo misurato prima di toccare il codice, col servizio vero, la coda piena come in produzione e il «fermati» a metà frase: 0 telefonate su 3 ricevevano l'audio, 3 su 3 restavano senza audio e senza spiegazione, e nel registro non restava nessuna riga — quindi nemmeno noi potevamo accorgercene. Da oggi il servizio che si ferma fa tre cose: finisce di dire quello che aveva cominciato (una frase dura pochi secondi, e la scheda l'aveva già fatta); a chi arriva in quel momento risponde «mi sto fermando, riprova fra pochi secondi» con un codice e un numero, e non gli mette quell'errore nel conto, perché non è colpa sua; e lo dichiara nella sua pagina di stato, così chi smista il traffico ci toglie dal giro prima che l'indirizzo taccia e la vigilanza non suona l'allarme per un aggiornamento voluto. Stessa misura dopo: 3 su 3 ricevono l'audio, nessuno resta senza spiegazione, il registro porta tutte le righe e l'indirizzo resta muto un quarto di secondo. Due cose le ha trovate il banco e non il ragionamento: a servizio fermo l'uscita costava mezzo secondo di troppo, regalato a ogni riavvio; e il tempo che si aspetta non è un numero scelto a occhio, è quante frasi la scheda può avere in bocca per la durata di una frase, perché la scheda ne dice una alla volta. Per questo, quando quel tempo non basta, il servizio lo scrive nel giornale col numero delle frasi tagliate: non è un cliente a doverlo scoprire.
A chi serve: al telefono dell'081 per primo, che ci chiama mentre qualcuno è in linea; a chiunque ci integri, perché un fermo voluto ora ha un nome e un numero invece di sembrare un guasto; e a noi, perché è la condizione per aggiornare il servizio in orario di lavoro invece di aspettare la notte.
Come si adotta: nulla da cambiare. C'è un codice nuovo per il rifiuto «mi sto fermando», e chi già tratta i rifiuti temporanei con il «riprova fra» non deve scrivere una riga. Chi installa deve prendersi due righe nuove della configurazione di sistema, perché senza quelle il sistema operativo ucciderebbe proprio chi stiamo aspettando — e una prova diventa rossa se i due numeri non si parlano. Una cosa la diciamo perché resta aperta: un testo molto lungo (il massimo che accettiamo) richiede più di nove minuti di scheda, e nessuna finestra di attesa lo coprirà mai: mandato nell'istante di un aggiornamento verrà tagliato, e il giornale lo dirà.
30/9/2026 — Il rifiuto che si può riprovare dice quando, col numero vero consegnato, non in linea
Ci sono rifiuti che non sono guasti: il tetto di telefonate al minuto, la quota del mese finita, la macchina piena, la macchina giù. Per tutti la domanda di chi chiama è la stessa — quando posso riprovare? — e se rispondiamo male a pagare è il cliente educato, quello che ci ascolta. Misurato prima di toccare il codice, con AgileTTS vero e telefonate vere: dicevamo 60 secondi dove il minuto riapriva fra 35 (venticinque secondi di attesa regalata), dicevamo 60 secondi a chi aveva finito la quota del mese, che riapriva fra 85534 — mille volte tanto, e un cliente educato ribussava una volta ogni minuto per settimane — e dicevamo 2 secondi davanti a richieste che serviamo in 6,5, con dodici clienti rifiutati insieme che tornavano tutti nello stesso secondo. Da oggi il numero è quello vero: per i tetti a finestra è l'istante in cui la finestra riapre, per la macchina piena è quel che resta alla richiesta in corso, misurato su quello che AgileTTS ha appena servito. Dopo: una bussata invece di tre e servito dopo 6,6 secondi, cioè quando il posto si è liberato davvero; e dei dodici rifiutati insieme, al massimo cinque tornano nello stesso secondo invece di nove.
A chi serve: a ogni prodotto che ci chiama e non deve inventarsi una politica di attesa; e a noi, perché ogni bussata a vuoto è lavoro che la scheda fa per niente.
Come si adotta: nulla da cambiare, nessun campo sparisce. Il numero lo leggono da sole le librerie HTTP; chi vuole l'istante esatto al secondo lo trova nel corpo del rifiuto. Il numero detto sulla quota del mese è tagliato a un'ora di proposito: un piano si può alzare in ogni momento, e dire «torna domani» terrebbe fuori un prodotto anche dopo che gli abbiamo allargato la quota.
29/9/2026 — Il tetto è dell'azienda, non della singola chiave consegnato, non in linea
Con un'azienda si concorda un piano — quante telefonate al minuto, quanto testo al mese — e da oggi le diamo una console per farsi le chiavi da sé. Le due cose insieme avevano un buco, e nessuno l'aveva mai misurato: il limite al minuto e la quota stavano sulla singola chiave, e dalla console un amministratore si fa quante chiavi vuole — una per prodotto, che è esattamente quel che gli chiediamo noi. L'abbiamo misurato prima di scrivere una riga di codice, col servizio vero, la console vera e telefonate vere: col piano «sei richieste al minuto, sei testi al mese» e tre chiavi fatte in buona fede ne passavano diciotto al minuto e tre volte il testo del mese; una chiave «senza limite» veniva accettata sotto quel piano; e il piano non si poteva nemmeno scrivere da nessuna parte — era concordato a voce, e il servizio non lo sapeva. Da oggi il piano è dell'azienda e morde in due punti: quando si emette una chiave che da sola prometterebbe più del piano (rifiutata, «senza limite» compreso), e a ogni richiesta, sulla somma di tutte le chiavi dell'azienda. Stessa misura dopo: uno a uno, il piano rispettato. Quattro scelte meritano una riga. (1) Il piano lo scriviamo solo noi: se se lo potesse alzare l'amministratore dell'azienda sarebbe un tetto per modo di dire. Lui lo legge, col consumo del mese, e quel numero è lo stesso su cui il servizio rifiuta — o console e servizio direbbero due cose diverse sulla stessa azienda. (2) All'emissione si guarda la chiave singola, a ogni richiesta la somma: rifiutare subito sulla somma vieterebbe la seconda chiave a chi ha due prodotti, che è l'uso per cui la console esiste. (3) La rotazione di una chiave non si blocca mai sul piano: copia i limiti di una chiave che c'è già e non promette niente di nuovo, e bloccarla impedirebbe una rotazione di sicurezza a un'azienda a cui il piano è stato abbassato dopo. (4) Il rifiuto dice quando riprovare, col numero esatto: il tetto al minuto ha un istante preciso in cui riapre e il servizio lo sa, quindi dice i secondi che mancano invece di un minuto tondo. Un difetto l'ha trovato la misura e non il ragionamento: con i due controlli in fila, una richiesta fermata dal tetto dell'azienda aveva già consumato un posto del ritmo della chiave, e subito dopo il cliente si sentiva dire «troppe richieste su questa chiave» — il limite sbagliato, quello che un amministratore alzerebbe senza risolvere niente. Ora o si occupa un posto in tutti e due i tetti, o in nessuno.
A chi serve: alle aziende clienti, che vedono finalmente scritto il proprio piano e sanno da dove arriva un rifiuto; e a noi, perché senza questo la console che consegniamo oggi regalava il modo di moltiplicare il piano concordato per il numero di chiavi, senza che nessuno mentisse. Per chi usa AgileTTS per parlare (core/081, adp, nis2 voce, vocea, dfm) non cambia niente: le loro chiavi le emettiamo noi dal box, non appartengono a nessuna azienda della console e nessun piano può riguardarle — e questo l'abbiamo misurato, non dedotto.
Come si adotta: nulla da cambiare, nessun campo sparisce, nessuna chiave cambia di un byte. Un'azienda senza piano scritto si comporta esattamente come prima: il piano si scrive quando si concorda, non c'è un valore predefinito. Campi nuovi: la pagina dei consumi della chiave porta ora anche il piano dell'azienda e quanto ne è stato consumato nel mese, così un prodotto che riceve un rifiuto vede da dove arriva senza una credenziale di console che non ha. Una cosa la diciamo perché resta aperta: a cavallo dello scoccare del minuto passa ancora il doppio delle richieste in pochi secondi, perché la finestra è fissa — vale per la chiave come per l'azienda, ed è un cambio a sé che faremo dichiarandolo.
29/9/2026 — Due macchine di velocità diversa: la telefonata va a quella che risponde prima consegnato, non in linea
Con due macchine dietro AgileTTS, la telefonata andava a quella con meno lavoro in corso, e a parità di lavoro a turno. Ma le telefonate arrivano una alla volta: le macchine sono quasi sempre scariche, «quella con meno lavoro» è un pareggio a zero, e a decidere restava il turno. L'abbiamo misurato prima di scrivere una riga di codice, col servizio vero, due macchine di velocità diversa e clienti veri: alla macchina lenta andava metà delle telefonate e il cliente aspettava 0,65 secondi di media contro 0,15 della sola macchina veloce — quattro volte il meglio ottenibile, pagato con la macchina veloce accesa e ferma. Il numero per evitarlo l'avevamo già: il servizio misura la velocità di ogni macchina su quello che ha appena detto (lo usa dal 28/9 per dire se il tetto delle conversazioni è ancora onesto). Da oggi la usa anche per scegliere: le macchine si ordinano per quanto costerebbe la telefonata su ognuna — il lavoro che hanno davanti, moltiplicato per quanto sono veloci — e si prende la prima che ha posto. Stessa misura dopo: alla lenta lo zero per cento, e il cliente aspetta quanto aspetterebbe con la sola macchina veloce. Tre scelte meritano una riga. (1) La velocità si impara servendo, non si dichiara: nessuno scrive «questa è la lenta», perché un numero scritto a mano invecchia col primo cambio di modello e nessuno se ne accorge; nella misura sono bastate dieci telefonate. (2) Una macchina appena aggiunta, che non abbiamo ancora misurato, conta come la più veloce e non come la peggiore: darle un peso prudente vorrebbe dire non mandarle telefonate, quindi non misurarla mai, quindi lasciarla per sempre col peso di chi non si conosce. (3) È una preferenza, non un divieto: quando la macchina preferita è occupata la telefonata va sull'altra ed è servita — con quattro clienti insieme entrano quattro su quattro come prima, la capienza non cambia di un posto — e la voce comanda sempre: una voce che sta solo sulla macchina lenta va alla macchina lenta.
A chi serve: a chiunque domani metta accanto alla scheda di oggi una seconda scheda diversa, che è il caso normale — le schede si comprano quando si trovano, non uguali a quella di prima. Senza questo, metà dei clienti finiva sulla macchina più lenta e pagava la differenza in attesa mentre la veloce era ferma: si pagava una scheda per peggiorare il servizio a metà delle telefonate. Per chi usa AgileTTS per parlare (core/081, adp, nis2 voce, vocea, dfm) non cambia nulla: oggi la seconda macchina non ce l'ha nessuno, e con una sola non c'è niente da smistare.
Come si adotta: nulla da cambiare, nessun campo sparisce e nessuna impostazione nuova è obbligatoria. Chi ha più di una macchina non deve dichiarare quale sia la più veloce: il servizio la misura da sé. La pagina di stato dice se la scelta guarda la velocità e, per ogni macchina, quante volte costa più della più veloce. Chi non la vuole può tornare con un'impostazione allo smistamento di prima, che guarda solo il lavoro in corso.
29/9/2026 — La seconda macchina aggiunge posti, non solo un indirizzo consegnato, non in linea
Da stamattina AgileTTS sa stare davanti a più motori. Quel che non avevamo misurato è la ragione per cui un motore si aggiunge: la capienza. L'abbiamo misurata prima di scrivere una riga di codice, col servizio vero e clienti veri: con il tetto delle conversazioni a due e quattro clienti insieme, con una macchina ne entravano due; con due macchine ne entravano ancora due — la seconda aggiungeva zero posti. E il cliente di una voce che sta solo sulla seconda macchina riceveva un «riprova fra poco» mentre quella macchina era ferma a zero richieste: pagava la fila di una scheda che non lo stava servendo. Il motivo: il tetto era del servizio, cioè del processo che rilancia i byte, mentre a riempirsi è la scheda. Da oggi il tetto è di una macchina: due macchine sono due file, e sopra resta una rete del servizio che si può fissare a parte. Stessa misura dopo: quattro clienti su quattro, e il cliente della seconda macchina servito. Tre scelte meritano una riga. (1) La macchina si sceglie e il posto si occupa nello stesso istante: contarli in due momenti diversi vuol dire che due richieste vedono lo stesso posto libero e lo prendono tutte e due. (2) Lo smistamento guarda il carico e non solo il turno: il giro a rotazione distribuisce i turni, non il lavoro, e mandava sulla macchina piena mentre l'altra era ferma; a parità di carico resta il giro di prima, quindi con le macchine scariche — il caso normale — non cambia niente. (3) Il rifiuto ora dice quale dei due tetti ha parlato e nomina la macchina piena, perché i due rimedi sono diversi: aggiungere una macchina, o allargare il servizio. La ragione che avevamo scritto per tenere il tetto unico — «non si sa ancora quale macchina servirà la richiesta» — il codice la smentiva: la macchina si sceglieva già prima.
A chi serve: a noi e al titolare, perché «aggiungere un motore non cambia nulla ai clienti» vale solo se aggiungerlo aggiunge capienza; e a chiunque domani metta una seconda scheda accanto a quella di oggi, che altrimenti la pagherebbe per lasciarla ferma. Per chi usa AgileTTS per parlare (core/081, adp, nis2 voce, vocea, dfm) non cambia nulla: oggi la seconda macchina non ce l'ha nessuno, e con una sola il tetto è esattamente il numero di prima.
Come si adotta: nulla da cambiare, e nessun campo sparisce. Chi ha più di una macchina non deve toccare il numero che aveva già scritto: cambia solo il suo significato, da «tutto il servizio» a «una macchina», che è quello che ha sempre voluto dire. Chi vuole tenere il servizio più stretto delle macchine può fissare una rete a parte. La pagina di stato dice, per ogni macchina, il suo nome, quanti posti ha occupati e quanti ne ha.
29/9/2026 — Le persone di un'azienda si amministrano dalla console, e nessuna azienda resta senza amministratore consegnato, non in linea
Da stamattina l'archivio del fronte sa chi sono le persone di un'azienda, ma nessuno ci arrivava da fuori: per aggiungere un collega al pannello, togliergli l'accesso il giorno che se ne va o nominare un secondo amministratore bisognava ancora scrivere a noi. Prima di scrivere una riga di codice abbiamo misurato quanto un'azienda potesse fare da sé: delle sei operazioni sulle proprie persone (vederle, invitarne una, leggerla, sospenderla, riattivarla, cambiarle ruolo) zero su sei erano possibili — la chiamata non esisteva proprio, che è una cosa diversa dal «risponde vuoto». Da oggi sono sei su sei, e ognuna resta scritta con chi l'ha fatta e quando. Tre cose meritano una riga. (1) Nessuna azienda resta senza amministratore. La stessa misura ha trovato due porte aperte che non c'entravano con le chiamate nuove: l'ultimo amministratore di un'azienda poteva sospendere sé stesso e poteva declassare sé stesso a utente semplice, e in tutti e due i casi l'azienda restava senza nessuno che potesse amministrarla, nemmeno per riaprirsi la porta. Ora sono due rifiuti dichiarati, e il controllo sta sui dati e non solo nella chiamata: chiudere la sola sospensione avrebbe lasciato aperta l'altra porta. Dove un secondo amministratore c'è, invece, il declassamento passa: un controllo che blocca tutto non serve a nessuno. (2) Il ruolo di amministratore di tutto il servizio non si assegna e non si invita da qui, nemmeno su nostra richiesta; e invitare qualcuno già con un ruolo passa dallo stesso controllo di quando glielo si assegna — sarebbe la porta di servizio dello stesso permesso. (3) Le persone di un'altra azienda rispondono esattamente come una persona che non esiste: stessa risposta, stesso testo, così non si può scoprire chi lavora altrove provando gli identificativi uno per uno. Due difetti li ha trovati la prova e non il ragionamento: invitare due volte la stessa persona sembrava un guasto del servizio (ora è un rifiuto chiaro), e chi amministra tutte le aziende, se non diceva quale, si ritrovava in silenzio l'elenco di una di esse (ora gli si chiede di dirlo).
A chi serve: alle aziende clienti, che per gestire le proprie persone non devono più aspettare noi; e a noi, che restiamo solo dove serve davvero — creare un'azienda e nominarne il primo amministratore. Per chi usa AgileTTS per parlare (core/081, adp, nis2 voce, vocea, dfm) non cambia nulla: nessuna chiamata nuova sulle vie di sintesi, nessun campo nuovo, nessuna chiave toccata.
Come si adotta: per parlare con AgileTTS non cambia nulla, e chi non ha una credenziale di console non vede niente di nuovo. Per amministrare le proprie persone servono la credenziale dell'amministratore, che oggi emettiamo noi, e sei chiamate: l'elenco (che dice anche quanti amministratori attivi ha l'azienda), l'invito, la lettura, la sospensione, la riattivazione e il cambio di ruolo. Nessun aggiornamento da fare sugli archivi: sono quelli di stamattina. La console non è ancora in linea: si accende col prossimo dispiegamento, e la sua pagina web è il passo dopo.
29/9/2026 — Le chiavi di un'azienda si emettono dalla console, non da root consegnato, non in linea
Per una chiave nuova, o per ruotarne una, finora bisognava scrivere a noi: l'emissione si faceva solo dalla riga di comando, sulla macchina del servizio. Prima di scrivere una riga di codice abbiamo misurato quanto un'azienda potesse fare da sé: delle quattro operazioni sulle proprie chiavi (vederle, crearne una, ruotarla, revocarla) zero su quattro erano possibili, e zero su quattro lasciavano scritto chi le avesse fatte. Da oggi sono quattro su quattro, e ognuna resta scritta. Tre scelte meritano una riga. La console non si apre con la chiave del prodotto: serve una credenziale diversa, perché una chiave rubata da un telefono non deve valere come una password d'amministratore — e la credenziale della console, al contrario, non può far parlare il servizio. La chiave di un'altra azienda risponde esattamente come una chiave che non esiste: stessa risposta, stesso testo, così da fuori non si può scoprire quali chiavi abbiano gli altri provandone gli identificativi uno per uno. E il controllo dei permessi avviene prima di toccare l'archivio: anche i rifiuti restano scritti, con chi ha provato e quando.
A chi serve: a chi oggi deve aspettare noi per una chiave nuova o una rotazione, e a noi, che restiamo solo dove serve davvero — creare un'azienda e nominarne il primo amministratore.
Come si adotta: per parlare con AgileTTS non cambia nulla — le chiamate di sempre sono intatte, gli archivi si aggiornano da soli e nessuna chiave esistente cambia. Per usare la console serve una credenziale, che oggi emettiamo noi per l'amministratore dell'azienda. La console non è ancora in linea: si accende col prossimo dispiegamento, e la sua pagina web è il passo dopo.
29/9/2026 — Chi ha invitato chi, e quando: le fondamenta della console per azienda consegnato, non in linea
L'archivio del fronte sapeva, per ogni chiave, un proprietario e un'azienda — due campi di testo libero. Non sapeva niente delle persone: chi ha ruotato una chiave, chi ha invitato chi, chi è stato sospeso e quando, non si poteva nemmeno chiedere. Misurato prima di scrivere codice, sulle 8 domande che una console multi-azienda deve saper girare all'archivio (quali aziende esistono, quali utenti ha un'azienda, che ruolo ha una persona, chi è sospeso, chi ha invitato chi e quando, chi ha cambiato un ruolo, a quale azienda appartiene una chiave per identità, tutte le operazioni di un'azienda in ordine di tempo): 0 su 8 si potevano perfino formulare — non «rispondono vuoto», la domanda non esisteva — e 4 operazioni su 4 che cambiavano l'archivio erano mute. Da oggi ci sono le aziende, gli utenti con ruolo e stato, e il registro delle operazioni; e una chiave si lega a un'azienda per identità invece che per il testo libero di prima. Dopo la stessa misura: 8 domande su 8 con risposta e 12 operazioni di gestione su 12 con la loro riga di registro. Tre scelte che vanno oltre «fare le tabelle». (1) La riga di registro sta nella stessa transazione del cambiamento: se la traccia non si può scrivere, il cambiamento non resta — un archivio non deve poter finire in uno stato che nessuno sa spiegare, e ricordarsi di chiamare il registratore non basta. (2) Il registro non porta mai il testo in chiaro di una chiave: un dettaglio che lo contiene viene rifiutato, non ripulito in silenzio — ripulirlo lascerebbe ripetere l'errore altrove. (3) Il ruolo di superadmin non si assegna da nessuna funzione, nemmeno da un amministratore d'azienda: la guardia sta sui dati e non solo nella rotta che ci arriverà sopra. La migrazione è additiva e idempotente: su un archivio già in uso tutto si aggiunge da sé, nessuna chiave esistente cambia di un byte e le chiavi vecchie continuano a valere. Come si adotta: nulla nei client e nulla da fare per chi amministra — la migrazione gira da sé alla prima connessione, come già quella delle colonne della rotazione. La console self-service non c'è ancora: queste sono le fondamenta, le rotte sono il passo dopo.
A chi serve: il titolare e la regia, perché è il punto A del mandato «servizio completo» (un'azienda ha i suoi utenti con ruoli, e i dati di un'azienda non sono mai visibili a un'altra) e la base su cui poggeranno le rotte self-service; e ogni azienda cliente che oggi, per farsi emettere o ruotare una chiave, deve chiedere a un umano con accesso di amministrazione al box del fronte. Per i prodotti già integrati (core/081, adp, nis2 voce, vocea, dfm) non cambia niente: nessuna rotta nuova, nessun campo nuovo nelle risposte, nessuna chiave toccata.
29/9/2026 — Un fronte solo davanti a N motori: lo smistamento per voce e le sonde in parallelo consegnato, non in linea
Fino a oggi il fronte puntava a un motore: aggiungere capienza voleva dire un secondo fronte, cioè un secondo contratto, un secondo archivio di chiavi e una seconda misura d'uso. Da oggi, con AGILETTS_MOTORI popolata (N indirizzi separati da virgola), un fronte solo sta davanti a N motori e smista ogni richiesta in round-robin fra quelli sani. Tre scelte, tutte misurate. (1) Lo smistamento guarda la voce: un motore riceve solo le voci che dichiara. Senza quel filtro due motori con cataloghi diversi darebbero 404 UNKNOWN_VOICE a intermittenza sulla stessa voce — la richiesta riesce o fallisce a seconda di dove cade il round-robin, che è il modo peggiore di rompersi. (2) La via calda non sonda mai: le sonde girano in parallelo e stanno in una cache di 5 secondi. Il modo ovvio — chiedere a ogni motore, in fila, al momento della richiesta — ha un costo che non si vede finché tutti rispondono, e si paga tutto insieme quando un motore diventa muto (processo vivo ma bloccato: costa il timeout intero, mentre un processo morto rifiuta subito). Misura con motori muti veri: sonde in fila 2,06 s con un muto e 8,01 s con quattro, in parallelo 2,01 s in tutti e due i casi; /health del fronte resta piatto a 2,01 s da 1 a 4 motori; sulla via calda 6 sintesi costano 0,36 s con la cache contro 12,39 s sondando a ogni richiesta (p95 per richiesta 72 ms contro 2071 ms) — davanti a un ttfb p95 del prodotto di 0,88 s, sondare ogni volta avrebbe triplicato il tempo di risposta per stare dietro a motori che nessuno stava usando. (3) Un motore che muore si segna giù subito: una richiesta cade sul motore che muore, non tutte, e la sonda periodica lo rimette in servizio da sé quando torna sano — nessuna lista nera che poi nessuno sa più togliere. Il fronte non ritenta la richiesta caduta su un altro motore: sarebbe una seconda sintesi non chiesta, ed è una decisione sul contratto che il box non prende da sé. Una voce che sta su un motore ora giù dà 503 ENGINE_DOWN (tornerà), una voce che nessun motore dichiara dà 404 UNKNOWN_VOICE: sono due cose diverse e il cliente deve poterle distinguere. GET /v1/voices è l'unione dei cataloghi dei motori sani; /health porta il blocco motori al posto di motore. Il tetto della coda resta aggregato sul fronte, e il rtf si misura per motore — una finestra di campioni per ogni indirizzo, esposta in motori.dettaglio[].rtf_motore e in coda_onesta.rtf_per_motore — col verdetto sul peggiore p95 fra i motori sani (è il motore più lento a decidere se accettare N conversazioni è ancora onesto); finché almeno un motore sano non ha abbastanza campioni il fronte dice «non lo so» e non dichiara il verdetto.
A chi serve: la regia e il titolare, perché è il punto 5 del mandato «servizio completo» ed è quel che serve per mettere una seconda scheda accanto alla prima senza dare ai prodotti un secondo indirizzo; e ogni prodotto già integrato (core/081, adp, nis2 voce, vocea, dfm), per cui aggiungere capienza dietro al fronte non cambia niente: stesso indirizzo, stessa chiave, stesso contratto. Come si adotta: nulla nei client, e nulla da fare per chi non popola AGILETTS_MOTORI — senza quella variabile non c'è una sonda né un filo in più, ed è la prima cosa che la prova verifica. Chi vuole due motori dietro un fronte: AGILETTS_MOTORI=http://127.0.0.1:8795,http://secondo-motore:8795 nell'unità del fronte, poi GET /health per vedere motori.sani. Provato da fronte/prova_multi_motore.py (51 controlli, 6 mutazioni col raggio dichiarato) e misurato da banco/tts/capienza_motori.py.
29/9/2026 — Le patch del motore che aspettano il «vai» ora si applicano al motore vero, e i nomi delle variabili del GEX44 erano documentati male consegnato, non in linea
La notte del 29/9, dentro la finestra del «vai», la patch guardia non si è applicata sul motore in produzione — «ancoraggi assenti» — e il lanciatore ha fatto rollback con un riavvio inutile del motore alle 02:00:42; coda e tetto non sono state nemmeno tentate. Le tre patch erano verdi da giorni, ma erano provate sul seme del repository, e il seme non è il motore: 615 righe contro 717, 326 righe di differenza. Il difetto, misurato sulla copia del file vivo, è di due pezzi e nessuno dei due riguarda il comportamento delle patch. (1) Il prefisso: il seme è ribattezzato e legge AGILE_TTS_*, il motore vivo legge AGILETTS_*, quindi un ancoraggio come os.environ.get("AGILE_TTS_MAX_IDENT_RETRY" sul vivo non esiste — cadeva il primo pezzo di tutte e tre. (2) Una riga del solo seme: il pezzo di coda che aggiunge il blocco a /health entrava su una riga che il ribattezzo crea e che il motore vero non ha, e coda restava rossa anche una volta risolto il prefisso. Ora ogni ancoraggio si cerca in tutte e due le forme e vale quella che compare esattamente una volta, e l'ancoraggio di /health è stato spostato sull'ultima riga del blocco, che esiste in tutte e due. Si traducono solo gli ancoraggi, mai le manopole nuove: quelle hanno un nome pubblico, e tradurle significherebbe cambiare il contratto di nascosto, di notte, per un difetto di ancoraggio. Misura sulla copia del file vivo: le tre patch si applicano, la pila compila, e le tre prove funzionali sono verdi sul vivo (13 + 17 + 9 controlli), col morso che le vuole rosse sul vivo non patchato. Le 30 manopole che il motore già leggeva restano AGILETTS_*, le stesse trenta; le 7 nuove restano AGILE_TTS_* come sono pubblicate. Presidio nuovo: bin/gex44-notte/prova_seme_vivo.py, con 5 mutazioni che pretendono l'insieme esatto dei controlli caduti.
A chi serve: il titolare e la regia in primo luogo, perché la notte del «vai» è la loro — tre patch su quattro non sarebbero partite, e il costo non era zero: un riavvio del motore in produzione per una patch che non aveva cambiato un byte. Poi chi configura il motore sul GEX44: la tabella delle variabili d'ambiente di API portava i nomi del seme (AGILE_TTS_MODEL, AGILE_TTS_WARM, …) come se fossero quelli del motore vero, mentre il motore in produzione legge AGILETTS_* — una variabile AGILE_TTS_* sul GEX44 non dà errore, resta senza effetto, che è il modo più silenzioso di credere di aver configurato qualcosa. Come si adotta: nulla nei client, il contratto HTTP non si muove e i nomi delle manopole pubblicate dalle patch non cambiano; chi configura il motore sul GEX44 usi AGILETTS_* per le variabili del motore base. Le patch restano consegnate e non applicate: aspettano il «vai» del titolare.
29/9/2026 — Chiave passe-partout di prova, propria e condivisa fra i servizi sovrani consegnato, non in linea
Chi integra AgileTTS per la prima volta doveva finora aspettare l'emissione di una chiave di prodotto per fare un primo smoke test. Ora agiletts-keys.py issue-test emette una chiave PASSE-PARTOUT di ambito test (status: "prova"): stessa forma di una chiave vera, scope pieno, ma con limiti bassi (20/min, 20000 caratteri/mese di default) e una scadenza obbligatoria dichiarata all'emissione. Prima della scadenza si comporta come una chiave attiva in tutto; scaduta risponde 403 KEY_EXPIRED — mai KEY_REVOKED, perché non è stata revocata, è solo finito il tempo dichiarato. In più, per il mandato della regia del 29/9 («ogni servizio sovrano pronto e completo», con una chiave passe-partout di prova accettata da ognuno), il fronte accetta anche una seconda chiave, condivisa fra tutti i servizi sovrani di Agile Software, dal caveau (agile__passepartout_test/api_key, «si ruota il 29/10»): se la env var AGILETTS_FRONTE_PASSEPARTOUT è popolata al boot (mai in git, mai cablata), il fronte la installa da sé con lo stesso trattamento. Env assente = nessuna chiave condivisa installata, comportamento invariato. Provato da fronte/prova_keys.py (26/26) e fronte/prova_auth_fronte.py (due mutazioni di controllo dedicate).
A chi serve: chi integra AgileTTS per la prima volta e vuole un curl vero prima di chiedere una chiave di prodotto (core/081, adp, nis2 voce, vocea, dfm); e la regia, per cui la chiave condivisa è un requisito del mandato «servizio completo» comune a tutti i servizi sovrani. Come si adotta: nulla per chi ha già una chiave di prodotto; per uno smoke test, agiletts-keys.py issue-test sul box del fronte o la chiave passe-partout condivisa (si richiede al centralino interno).
29/9/2026 — «I testi non vengono mai registrati» ora è misurato su tutto il disco del fronte consegnato, non in linea
La garanzia sta nel contratto dal 15/9 e il 28/9 è stata rafforzata sulla via di servizio (err = il solo tipo dell'eccezione). Era però misurata per campi: una prova legge le righe del registro e controlla che nessun campo porti il testo. È la guardia giusta sul registro, ma il registro non è tutto il disco: restavano fuori dalla misura lo stdout e lo stderr del processo — che in produzione sono il giornale di systemd, cioè il posto dove un testo resterebbe per giorni senza che nessuno l'abbia deciso — il DB, la pagina d'uso, il corpo di /health e i file temporanei. Da oggi la misura è sul disco intero, in due clausole. A · nessuna traccia del testo: il fronte gira come processo vero (non importato: stdout e stderr sono descrittori di file, come sotto systemd) e riceve un testo con una sentinella su 26 vie — blocco wav/pcm16k/opus, streaming, testo lungo spezzato, le vie storiche /tts e /tts-stream di 081 e dell'avatar, e ogni via di guasto e di rifiuto; poi tutta la cartella di stato viene rastrellata byte per byte con 12 aghi: la sentinella, le chiavi in chiaro e l'impronta sha256 del testo, perché un'impronta permette di confermare un testo indovinato ed è ritenzione anche quella. La sentinella viaggia anche in una query string, perché una riga di richiesta finita nel giornale porterebbe via il testo insieme al percorso. Misura: zero occorrenze su 9 file, con 27 righe di registro a dire che il fronte aveva davvero servito (una prova in cui non passa testo non prova niente). B · nessun audio abbandonato: l'audio è il testo detto e per l'art. 9 vale quanto il testo, quindi il giro gira con TMPDIR dentro la cartella di prova e pretende che a fine giro sia vuota — un temporaneo della trascodifica che sopravvive è audio del cliente lasciato su disco, e la clausola A non lo vedrebbe perché dentro c'è audio, non il token. Provato da fronte/prova_ritenzione.py (tre mutazioni sulle superfici nuove: un print del testo, 97 occorrenze su stdout; log_message non più zittito, una sola occorrenza su stderr ed è proprio il punto; il temporaneo non più cancellato).
A chi serve: vocea in primo luogo, che il 28/9 ha copiato questa garanzia fra le condizioni di una adozione futura con la regola giusta — «la verifichiamo in uno smoke invece di assumerla, perché una promessa è una promessa finché non la si misura» — e per cui il contenuto di una segnalazione sarebbe dato art. 9 GDPR in un prodotto zero-knowledge; poi ogni prodotto che manda al fronte testi che non devono lasciare traccia (nis2 voce, core/081, adp, dfm) e chiunque debba firmare un DPA come sub-responsabile art. 28. Come si adotta: nulla nei client, è una garanzia che vale da sé; chi vuole rifare la misura sul proprio impianto lancia fronte/prova_ritenzione.py (sola libreria standard, nessuna GPU, 4,1 s), con --mutazioni per vederla mordere.
29/9/2026 — Il fronte non cambia l'audio: parità col motore misurata byte per byte consegnato, non in linea
Il fronte sta fra i prodotti e il motore per aggiungere chiave, quota, registro, coda onesta e contratto OpenAI. Tutto quello che aggiunge è contorno: l'audio che esce deve essere quello del motore, e fino a oggi nessuno lo provava. La differenza fra i due bracci del banco — il motore nudo e il fronte — si poteva quindi leggere in due modi opposti: «il fronte costa N millisecondi» oppure «dal fronte la voce suona diversa», senza che nessun numero dicesse quale dei due. Misurato il 29/9: a parità di richiesta il fronte consegna gli stessi byte del motore, sul blocco (POST /tts, formati pcm16k e wav) e sullo stream (POST /tts-stream, pcm16k), con gli stessi secondi di audio. Il sovrappiù del fronte è quindi puro sovrappiù e si misura: p50 10,7 ms sul blocco e 3,2 ms sullo stream, su 6 coppie a una richiesta per volta. L'unica differenza ammessa è il testo lungo, e il fronte la dichiara: oltre il tetto del motore spezza a fine frase e mette 600 ms di silenzio fra i pezzi — provato byte per byte (audio = pezzo + silenzio + pezzo, i pezzi chiesti al motore nudo; 19 200 byte esatti di silenzio in pcm16k, non uno di più) e sempre accompagnato da X-AgileTTS-Pezzi. Una differenza dichiarata è un contratto; la stessa differenza non dichiarata è l'audio cambiato di nascosto. Provato da banco/tts/prova_parita_fronte_motore.py (36/36, quattro mutazioni che mordono la sezione giusta e nessun'altra).
A chi serve: ogni prodotto che si sposta dal motore al fronte con la patch additiva — core/081 e adp in primo luogo, poi nis2 voce, vocea, dfm — perché adesso il costo del passaggio è un numero di millisecondi e non un dubbio sulla voce; e il titolare, perché l'ascolto cieco vale solo se quello che esce dal fronte è la stessa voce che il banco misura sul motore. Come si adotta: nulla nei client, è una garanzia che vale da sé; chi vuole rifare la misura sul proprio impianto lancia banco/tts/prova_parita_fronte_motore.py (sola libreria standard, nessuna GPU), che stampa il sovrappiù p50 e p95 in millisecondi per il blocco e per lo stream.
29/9/2026 — Uno stream che muore a metà non è più invisibile al servizio consegnato, non in linea
In streaming le intestazioni 200 partono prima dell'audio: da quel momento un guasto non può più diventare un 5xx. Misurato il 29/9 con un motore che si pianta dopo le intestazioni: il fronte lasciava risalire l'eccezione e il gestore scriveva un HTTP/1.1 500 dentro il corpo chunked già aperto — rumore nell'audio di chi stava ascoltando — mentre nel registro restava una riga http: 500 INTERNAL senza audio_ms e senza niente che dicesse che il cliente era rimasto a metà frase. Ora il fronte tiene il 200, non scrive più una seconda risposta dentro lo stream, lascia il corpo chunked senza chunk finale (il solo segnale onesto che resti dopo le intestazioni) e registra troncato: <tipo dell'eccezione>, mai il testo. La richiesta conta come errore nella misura d'uso e non produce campioni di rtf: misurare il motore su mezza frase sarebbe mentire. pagina_uso.py ha la colonna «stream troncati» e un allarme senza soglia: uno solo basta. Non sono troncamenti — e si chiudono puliti — il cliente che se ne va (client_disconnect), il pezzo fallito (pezzo_fallito) e il tetto anti-runaway del motore. Provato da fronte/prova_stream_troncato.py (19/19, tre mutazioni).
A chi serve: chi ascolta AgileTTS mentre parla e non a file finito — core/081 e adp/adp_brain in primo luogo (una frase troncata al telefono o sull'avatar l'utente la sente subito), poi nis2 voce, vocea, dfm — e la regia, che vede quante frasi sono morte a metà e per quale eccezione. Come si adotta: nulla nei client, ma vale la pena metterlo nel proprio codice: un corpo chunked che non si chiude col chunk finale è uno stream troncato e va trattato come errore, non come frase finita (IncompleteRead con urllib/requests; a mano, il 0\r\n\r\n finale).
28/9/2026 — Il fronte misura la velocità del motore e dice se il tetto della coda onesta è ancora onesto consegnato, non in linea
La coda onesta rifiuta con 503 BUSY + Retry-After oltre max_coda richieste in volo (3). Misurandola il 28/9 si è visto che quel tetto è un numero fisso davanti a un motore che cambia velocità: è onesto solo finché max_coda × rtf < 1, e col motore misurato il 21/9 (rtf 1,41 già a una sintesi per volta) il fronte accetterebbe tre conversazioni servendole tutte e tre peggio del tempo reale. Ora il fronte misura il rtf del motore su ogni richiesta servita (dal synth_s del meta, o dal tempo di parete solo se la richiesta era sola: sotto carico si misurerebbe l'attesa delle altre) e lo dice in GET /health → coda_onesta {max_coda, in_volo, rtf_motore {p50, p95, campioni, minimo_campioni, da_synth, da_parete}, prodotto, onesto, regola}, con onesto vuoto finché i campioni non bastano. Il registro porta il campione (rtf_motore, rtf_fonte) e pagina_uso.py alza l'allarme quando rtf p95 ≥ 1 o max_coda × rtf p95 ≥ 1. Il tetto non si muove da sé: cambiarlo è una decisione della regia. Provato da banco/tts/prova_tetto_onesto.py (36/36).
A chi serve: chi manda conversazioni e non file — core/081 e adp in primo luogo (una telefonata servita a rtf > 1 accumula ritardo a ogni battuta), poi nis2 voce, vocea, dfm — e la regia, che vede il tetto diventare disonesto prima che lo senta un utente. Come si adotta: nulla nei client; chi vigila legge coda_onesta.onesto dal /health o lancia pagina_uso.py --json uso.json --riga --health …, che esce 2 se c'è un allarme.
28/9/2026 — Il registro per richiesta non porta il testo nemmeno quando un errore interno lo cita consegnato, non in linea
Sul 500 INTERNAL il registro per richiesta teneva err = la rappresentazione dell'eccezione, repr(e). Molte eccezioni di libreria si portano dietro nel messaggio la stringa su cui sono inciampate (UnicodeEncodeError è l'esempio classico): per quella via di servizio il testo del cliente poteva finire su disco. Misurato il 28/9 con un'eccezione che cita il testo: la riga conteneva la frase per intero. Ora err porta il solo tipo ("ValueError", "UnicodeEncodeError", …); la rappresentazione intera resta nella risposta 500 al chiamante, che il proprio testo ce l'ha già. Provato da fronte/prova_registro.py.
A chi serve: ogni prodotto che manda testi che non devono lasciare traccia — vocea in primo luogo (zero-knowledge, dato art. 9 GDPR), poi nis2 voce, core/081, adp, dfm. Come si adotta: nulla da fare; il campo è documentato in docs/REGISTRO.md e la riga di contratto in docs/API.md §3.
27/9/2026 — Estensione del lessico: date per esteso, decenni e secoli, sigle puntate, frazioni e numeri negativi disponibile lato cliente
normalizza_it.py ora legge anche le date per esteso («il 25 dicembre», «25 dicembre 2026»), i decenni e i secoli in cifre («gli anni '90» → «gli anni novanta», «nel '68» → «nel sessantotto», «il 1400» → «il quattrocento»), le sigle puntate per esteso («U.S.A.» → «u esse a», «R.S.A.» → «erre esse a», «D.O.C.» → «di o ci»), le frazioni con una cifra per lato («1/2» → «un mezzo», «2/3» → «due terzi», «3/4» → «tre quarti») e il meno davanti a un numero («-5 °C» → «meno cinque gradi»). I casi in casi.json salgono a 241 (217 → 241, L218–L241; questo turno 231 → 241, L232–L241), verifica_lessico.py 241/241.
A chi serve: core/081 e adp (leggono date, decenni, secoli e sigle senza indovinare), nis2 voce, vocea, dfm. Come si adotta: come per il normalizzatore — copiare normalizza_it.py e chiamare normalizza(testo), oppure sul fronte AGILETTS_FRONTE_NORMALIZZA=1.
27/9/2026 — Client a riga di comando per il fronte (ob. 6) consegnato, non in linea
client/agiletts-cli.py: client a riga di comando in pura libreria standard (argparse/json/urllib) che avvolge client/agiletts_client.py; speech per la sintesi a blocco o in streaming su file (-o, con --voice/--model/--format/--speed/--stream), models/model/voices/usage per leggere modelli, voci e uso; la chiave si dà con --api-key o AGILETTS_API_KEY (o AGILETTS_KEY), --base-url punta al fronte; gli errori del fronte escono su stderr con esito non-zero. Prova 11/11.
A chi serve: chi vuole provare o automatizzare la sintesi senza scrivere codice Python (regia, collaudatori, script di vigilanza) e ogni prodotto che integra AgileTTS da uno script o da una pipeline. Come si adotta: nulla finché il fronte non è in linea; poi python3 client/agiletts-cli.py speech "testo" --voice serena -o saluto.wav (chiave con --api-key o AGILETTS_API_KEY).
27/9/2026 — Guida di adozione per i prodotti consegnato, non in linea
docs/ADOZIONE.md (e docs/ADOZIONE.en.md): la guida passo passo per spostare un prodotto sul fronte OpenAI-compatibile uno alla volta, col «vai» del titolare e il rollback pronto — URL (https://api.agiletts.agile.software/v1), chiave (Authorization: Bearer <chiave> o X-Api-Key), formato (response_format/format), timeout (8 s riferimento nexus, ≥ 10 s finché non è WARM), 503 BUSY + Retry-After e ripiego dichiarato (mai silenzioso), sequenza «vai»/rollback e tabella dei 5 prodotti (nexus-voice-ms, adp_brain, nis2 voce, vocea, dfm).
A chi serve: gli integratori e i clienti che dovranno passare da ElevenLabs (o dal motore) al fronte. Come si adotta: è documentazione, si legge docs/ADOZIONE.md (o docs/ADOZIONE.en.md); all'iniezione ogni prodotto segue la sezione 6 («vai» e rollback) uno per volta.
27/9/2026 — Catalogo voci con consenso tracciato e ref cifrati a riposo (ob. 7) consegnato, non in linea
Il catalogo diventa «voci con consenso»: provenienza.schema.json (tipo persona/sintetica/blend, consenso con protocollo/data/scadenza/usi/revoca, ambito, riferimento con sha256/durata/cifrato, politica di ripiego); audit_catalogo.py accende violazioni su persona senza consenso, consenso scaduto o revocato, ambito fuori, persona nel catalogo pubblico, ref non cifrato, sha256 diverso, audio tracciato in git; cifra_ref.py cifra i ref a riposo (AES-256 + HMAC, chiave su file, mai stampata). Prova 16/16.
A chi serve: regia e chi tiene il catalogo sul GEX44. Come si adotta: nulla per i clienti; sul motore audit_catalogo.py --voices … --radice … --repo … e i ref si cifrano con cifra_ref.py cifra ref.wav chiave.file.
27/9/2026 — Documentazione e sito in italiano e inglese (consegna bilingue) consegnato, non in linea
Contratto e aiuto del fronte in due lingue: docs/API.en.md, docs/NOVITA.en.md, client/ESEMPI.en.md e le pagine del sito in inglese col selettore it/en; prova_docs.py e prova_sito.py verificano la coerenza. Nomi propri, marchi e percorsi restano identici in inglese.
A chi serve: integratori e clienti in inglese (nis2 voce, vocea, dfm). Come si adotta: nulla; si apre la pagina col selettore EN. I sorgenti italiani restano la verità.
26/9/2026 — Client di esempio in libreria standard (ob. 6) consegnato, non in linea
client/agiletts_client.py: client Python in pura libreria standard (speech a blocco e streaming, modelli, voci, uso; gli errori del fronte diventano eccezioni Errore con http/code/message/retry_after/extra); client/ESEMPI.md con esempi Python, curl e fetch. Prova 27/27.
A chi serve: nis2 voce, vocea, dfm e ogni prodotto Python senza SDK OpenAI. Come si adotta: nulla finché il fronte non è in linea; poi from agiletts_client import AgileTTS o gli esempi da ESEMPI.md.
22/9/2026 — Pagina d'uso in json con gli allarmi e la riga di stato (fronte v0.5, ob. 10) consegnato, non in linea
pagina_uso.py --json uso.json --riga scrive le stesse misure della pagina in json con la lista allarmi e stampa una riga di stato pronta per dico serverino "AGILETTS: …"; esce 2 se c'è almeno un allarme. Allarmi assoluti: ripieghi esterni (mai silenziosi), 503 BUSY, pezzi falliti, 403 KEY_ROTATED per chiave, grazia finita con richieste ancora sulla vecchia, chiave ferma, /health del fronte muto. Allarmi con soglia: errori ≥ ×2 e primo byte p95 ≥ ×1,5 rispetto alle ore prima (con almeno 10 richieste per finestra).
A chi serve: regia e vigilanza. Come si adotta: nulla per i clienti; è la riga serale del box.
22/9/2026 — Pagina d'uso: confronto con le ore precedenti (fronte v0.5, ob. 10) consegnato, non in linea
La sezione «Rispetto alle N ore precedenti» affianca la stessa finestra spostata indietro con richieste, ok, errori, primo byte e totale p50/p95, audio, ripieghi, rigenerazioni e 503 BUSY, col Δ colorato (rosso peggiora, verde migliora). Sotto, chiavi nuove e chiavi sparite; nella tabella per chiave, Δ richieste e Δ errori.
A chi serve: regia e vigilanza. Come si adotta: nulla; è la pagina generata dal box.
22/9/2026 — Pagina d'uso con le rotazioni delle chiavi e i testi spezzati (fronte v0.5, ob. 6 e 10) consegnato, non in linea
Sezione «Rotazioni delle chiavi»: chiavi in grazia (scadenza e chiave nuova), grazia finita, richieste sulla chiave vecchia e 403 KEY_ROTATED per chiave; stato in chiaro e «usato questo mese (famiglia)» per ogni chiave. Il registro ora annota i rifiuti di una chiave riconosciuta con l'id, anche sulle GET.
A chi serve: chi gestisce le chiavi durante una rotazione. Come si adotta: nulla per i clienti.
22/9/2026 — Testi lunghi fino a 4096 caratteri, spezzati a fine frase e riuniti (fronte v0.5, ob. 6) consegnato, non in linea
Il fronte accetta fino a 4096 caratteri (oltre 413); sopra i 2000 spezza il testo a fine frase, chiede i pezzi al motore uno dopo l'altro e li riunisce con 600 ms di silenzio. Vale in blocco e in streaming; risposta con X-AgileTTS-Pezzi e meta unito. Un pezzo in errore = errore di tutta la richiesta. Il motore non cambia.
A chi serve: chi manda testi lunghi (nis2 voce, vocea, dfm). Come si adotta: mandare il testo intero; sotto i 2000 caratteri nulla cambia.
21/9/2026 — Rotazione delle chiavi con periodo di grazia (fronte v0.4, ob. 6) consegnato, non in linea
rotate <key_id> [grazia_ore=24] emette la chiave nuova e mette la vecchia in grazia: continua a funzionare, ma ogni risposta porta X-AgileTTS-Chiave-Scade e X-AgileTTS-Chiave-Nuova; finita la grazia risponde 403 KEY_ROTATED con rotated_to. La quota mensile segue la famiglia (ruotare non regala caratteri).
A chi serve: regia e clienti del fronte — la chiave si cambia senza fermo. Come si adotta: alla rotazione, spostare il segreto nella cassetta del prodotto entro la grazia.
21/9/2026 — Politica di ripiego per voce, dichiarata dal catalogo (fronte v0.3, ob. 8) consegnato, non in linea
Ogni voce può avere in provenienza.json una politica sovrana (default) o dichiarato con verso. Su ogni errore 5xx il fronte risponde con error.ripiego e X-AgileTTS-Ripiego e scrive ripiego nel registro. Il fronte non chiama mai un fornitore esterno: la decisione è del titolare per voce, l'esecuzione resta al cliente.
A chi serve: core/081 e adp (oggi ripiegano in silenzio), titolare. Come si adotta: sui 5xx leggere X-AgileTTS-Ripiego e ripiegare solo se consentito.
21/9/2026 — Fronte v0.3: contratto OpenAI collaudato senza l'SDK (ob. 6) consegnato, non in linea
Un client scritto per OpenAI funziona senza modifiche: model: tts-1 | tts-1-hd | gpt-4o-mini-tts sono alias di agiletts; senza response_format con un alias esce mp3; GET /v1/models/{id}; formati nuovi opus, flac, aac fatti dal fronte con ffmpeg; stream_format: sse → 400 onesto.
A chi serve: nis2 voce, vocea, dfm. Come si adotta: al «vai» cambiano base_url e chiave, non il codice.
21/9/2026 — Pagina d'uso con la sezione «motore» (ob. 10) consegnato, non in linea
pagina_uso.py legge dal registro anche attesa in coda, 503 BUSY, rigenerazioni, frasi corte, somiglianza p50, stream col tetto; tabella per chiave delle ultime ore. Mai testi né chiavi in chiaro.
A chi serve: regia, poi ogni prodotto per la propria chiave. Come si adotta: pagina_uso.py --health … --out uso.html e pubblica dal serverino.
21/9/2026 — Fronte v0.2 allineato alle novità del motore consegnato, non in linea
/v1/models dice che in produzione gira il 0.6B; /health.motore riporta coda, stream_tetto, guardia_identita; sul blocco passa il meta intero, sullo stream le intestazioni della coda e del tetto; un 503 busy del motore diventa 503 BUSY col Retry-After del motore. Interruttore del normalizzatore AGILETTS_FRONTE_NORMALIZZA=1 (default 0).
A chi serve: tutti all'iniezione e la regia. Come si adotta: nulla oggi.
21/9/2026 — Normalizzatore del testo italiano disponibile lato cliente
normalizza_it.py (solo libreria standard, una funzione normalizza(testo)) trasforma in parole ciò che il modello oggi indovina: codici POD/PDR/IBAN/codice fiscale, importi, percentuali, date, ore, telefoni, unità, ordinali, numeri romani, sigle, accenti sugli omografi. Misura: 200/200 sui casi di banco/lessico/casi.json.
A chi serve: core/081 e adp subito (leggono POD, IBAN, importi, date). Come si adotta: copiare normalizza_it.py e chiamare normalizza(testo) prima della sintesi; nel motore entrerà dopo l'A/B con le orecchie.
21/9/2026 — Tetto anti-runaway sullo stream consegnato, non applicato
Ogni stream ha un tetto = min(max_s della voce, max(3 s, 1,0 s × parole + 3 s)): oltre, il motore chiude lo stream, scrive STREAM_RUNAWAY e conta in /health.stream_tetto; l'intestazione X-AgileTTS-StreamTetto dice il tetto in anticipo. Limite: uno stream chiuso dal tetto è audio troncato, non un errore HTTP.
A chi serve: core/081 e adp (niente biascichi da 18 s per 4 parole), regia. Come si adotta: nulla lato cliente.
21/9/2026 — Coda onesta del motore consegnata, non applicata
Ogni richiesta misura la sua attesa (queue_wait_s, queue_ahead, intestazione X-AgileTTS-Queue, blocco coda in /health). Solo se la regia accende AGILE_TTS_MAX_CODA=N (default 0), oltre N richieste la nuova riceve subito 503 busy + Retry-After invece di aspettare in silenzio.
A chi serve: core/081 e adp (sapere se il primo byte lento è la GPU o la fila), regia. Come si adotta: nulla finché MAX_CODA resta 0.
21/9/2026 — Guardia d'identità con durata minima consegnata, non applicata
Su /tts la guardia misura sempre ma non rigenera sulle frasi corte (meno di 3 parole o audio sotto 1,2 s), dove il metro non distingue la persona; da 3 parole in su tutto identico. Nuovo campo identity_short nel meta e blocco guardia_identita in /health. Patrizia resta un caso a sé (fallisce anche sulle frasi lunghe).
A chi serve: core/081 (i «Sì.»/«Pronto?» scendono da ~3 s a ~0,7-1 s), adp. Come si adotta: nulla lato cliente.
18/9/2026 — Patch «sempre caldo» estesa: si scalda anche la via di /tts consegnata, non applicata
La sintesi muta dopo ogni load passa da entrambe le vie (stream con grafo CUDA e blocco con decode eager, che non condividono il compile). Riscaldo periodico spento per default (AGILETTS_PRECALDO_OGNI_S) per misurare il «freddo dopo un'ora». /health.caldo guadagna riscaldo_vie, riscaldi_periodici, ultimo_riscaldo.
A chi serve: core/081 (primo audio dopo un riavvio), adp. Come si adotta: nulla lato cliente.
15/9/2026 — Fronte OpenAI-compatibile v0.1 consegnato, non in linea
POST /v1/audio/speech, /v1/models, /v1/voices, /v1/usage con chiavi ats_ per prodotto e tenant, scope, quota, misura d'uso, registro senza testi, X-AgileTTS-Via, coda onesta 503 + Retry-After; passaggio storico /tts e /tts-stream con chiave.
A chi serve: tutti i clienti futuri e quelli di oggi all'iniezione. Come si adotta: chiave dalla cassetta e base_url col client OpenAI.
15/9/2026 — Patch «sempre caldo» consegnata, non applicata
Dopo ogni load il motore costruisce i prompt delle voci e fa una sintesi muta: sparisce il primo /tts-stream a 8,3 s dopo un riavvio e i 2 s alla prima richiesta di ogni voce.
A chi serve: adp (avatar), core/081. Come si adotta: nulla lato cliente; il riavvio dura 60-90 s in più.
14/9/2026 sera — In produzione gira il modello 0.6B (regia, reversibile) in linea
AGILETTS_MODEL → Qwen3-TTS 0.6B: 2 GB in meno sulla scheda condivisa; le soglie della guardia d'identità restano tarate sul 1.7B, quindi aspettarsi più rigenerazioni finché il banco d'identità non le ritara.
A chi serve: tutti i clienti di oggi. Come si adotta: nulla; chi confronta la voce sappia che il modello è cambiato.
14/9/2026 21:15 — Cancello d'attacco in linea
Tolto il soffio semi-vocalico che «serena» emetteva in testa a ogni frase, per struttura e testo, su /tts e /tts-stream. Nuovo campo attacco in X-AgileTTS-Meta (taglio_ms, scarti, motivo), trim_attacco nel corpo per spegnerlo e in /health.
A chi serve: core/081 e adp. Come si adotta: core deve rendere inerte il suo _agiletts_trim_head a valle (ora taglia due volte); se una frase perde la prima sillaba, segnalare testo e voce.
14/9/2026 14:35 — Modello residente (regia) in linea
AGILETTS_WARM=1, AGILETTS_IDLE_UNLOAD_S=0: niente scarico dopo 180 s; primo byte 333 ms invece di 1655 ms dopo lo scarico.
A chi serve: tutti (soprattutto core/081: spariscono i primi audio da 60-70 s). Come si adotta: nulla; i timeout di 8 s lato core possono restare.