Governance, integrata nel nucleo
In MindooDB la governance è una proprietà dello strato dei dati stesso, non un'aggiunta montata sopra un server o uno strato applicativo di cui fidarsi. Policy di accesso, identità e un audit trail dimostrabile crittograficamente e riproducibile a un istante preciso sono archiviati e applicati nel nucleo del database. Il vantaggio: puoi dimostrare non solo che cosa è cambiato, ma anche chi era autorizzato a cambiarlo, nel momento in cui la modifica è davvero entrata nel tenant.
Ha due metà: un lato scrittura che stabilisce chi può creare, modificare o eliminare i documenti — applicato anche quando gli utenti sono offline — e un lato lettura che stabilisce chi può vedere i dati, combinando un gate di accesso a livello di sincronizzazione con una distribuzione delle chiavi cieca per l'admin. Entrambi si appoggiano alla cifratura end-to-end di MindooDB.
La governance è una proprietà dello strato dei dati
La maggior parte degli stack spinge la governance verso l'alto, nel server o nell'applicazione: il database salva le righe e qualcos'altro decide chi può toccarle e tiene il log. MindooDB rovescia l'impostazione. Policy, identità, cronologia completa e audit trail vivono negli stessi store cifrati end-to-end e append-only, quindi le garanzie viaggiano con i dati attraverso server, peer e dispositivi offline, e sopravvivono anche a un server compromesso del tutto.
Policy e regole sono documenti firmati dall'admin nel database directory e sono a loro volta append-only. Il momento esatto in cui la governance è stata attivata — o disattivata temporaneamente — fa parte del registro permanente, non è una modifica di configurazione che non lascia traccia.
Ogni voce porta con sé un tempo attendibile e la directory conserva una catena time-travel della propria cronologia. Così wasAllowedAt(op, user, dbid, at) può riprodurre quali erano le regole e chi era autorizzato in qualunque momento passato: un verdetto deterministico su cui ogni replica onesta concorda.
Ogni modifica è firmata con Ed25519, quindi non ripudiabile, e attestata contro un orologio attendibile. Le violazioni non vengono scartate in silenzio: finiscono in un log di quarantena e audit per ogni tenant, che Haven mostra. Così resta tracciabile anche un tentativo rifiutato.
Le concessioni contengono array di chiavi per ogni dispositivo; revocare significa semplicemente rimuovere chiavi, e un'esplicita cancellazione remota rimuove l'intero tenant da un dispositivo rubato o dismesso alla connessione successiva: il ciclo di vita dell'identità è governato nella stessa directory firmata.
Il modello a due tier
Ogni regola ricade in uno dei due tier, in base a ciò che il server di sincronizzazione può vedere. Il server tratta soltanto testo cifrato più un piccolo insieme di metadati in chiaro: il corpo dei documenti non può leggerlo mai. Questa sola distinzione è tutta l'architettura: permette a MindooDB di fare una promessa onesta su cosa è garantito crittograficamente e cosa invece è applicato dalle policy tra client che collaborano.
| Tier | Cosa verifica | Applicata da | Forza |
|---|---|---|---|
| Tier 1 - Identità | Identità dell'autore, database di destinazione, tipo di operazione | Server e client | Crittografica - il server si rifiuta di attestare una voce che viola la regola, che non può quindi propagarsi |
| Tier 2 - Contenuto | Il contenuto effettivo del documento (withfields) | Solo i client | Policy - regola i client onesti e modella l'esperienza d'uso. Un client manomesso può aggirarla solo in locale; ogni replica onesta ricontrolla withfields alla ricezione e mette in quarantena la modifica che viola la regola, che non diventa quindi visibile a nessun altro |
Ricevute del testimone: un orologio attendibile senza doversi fidare del client
La domanda più difficile in un sistema local-first è: "a quale orologio crediamo?". Chi lavora offline potrebbe retrodatare le proprie voci per far passare una modifica aggirando una policy. MindooDB risponde con una ricevuta del testimone: un'attestazione, firmata da un testimone fidato (il tuo server di sincronizzazione), che una voce è stata accettata a un'ora precisa. Ogni punto di applicazione usa così esattamente un orologio ben definito.
L'SDK valuta Tier 1 + Tier 2 contro lo stato locale della directory dell'utente, all'ora locale corrente. Se l'operazione è consentita, la voce viene salvata in locale senza campi del testimone ed è subito visibile su questo dispositivo. Sulla vista solo locale decide l'orologio dell'utente, e va bene: la modifica non è ancora entrata nel tenant condiviso.
Il server valuta il Tier 1 contro il proprio stato, all'ora del server. Se l'operazione è consentita, marca receivedAt, registra la chiave del testimone, firma la ricevuta e restituisce i campi del testimone, così che tornino al mittente e proseguano. Se la nega, restituisce un AccessDenied strutturato e la voce resta in locale: non può propagarsi.
Chi riceve verifica la firma della ricevuta contro il proprio elenco di testimoni fidati. Una firma valida significa che il Tier 1 era soddisfatto all'ora receivedAt, quindi per impostazione predefinita non viene rivalutato. Il ricevente controlla poi il Tier 2 in locale e materializza la modifica oppure la indirizza a un log locale di quarantena e audit.
Come si configurano le decisioni
Tutto lo stato del controllo degli accessi vive nel database directory, accessibile solo agli admin, e si sincronizza con tutti i partecipanti. Tutto ciò che al server serve per il Tier 1 è cifrato con la chiave $publicinfos, quindi è leggibile senza possedere la chiave predefinita del tenant; il contenuto di withfields il server non può leggerlo mai. Ogni chiamata che modifica qualcosa è firmata dall'admin.
Una policy predefinita del tenant e policy opzionali per singolo database definiscono quali operazioni sono negate se nessuna regola allow corrisponde:
- Creazione, modifica, eliminazione e ripristino - consentite per impostazione predefinita, finché non le neghi
- Snapshot & purge - per impostazione predefinita solo per gli admin
- Lettura - la baseline del gate di lettura/sincronizzazione a livello di database, affinata dalle regole di lettura
- ID dei database consentiti - l'elenco dei database che possono esistere nel tenant; il server si rifiuta di creare o sincronizzare qualsiasi database fuori da quell'elenco
- Chiave di crittografia predefinita - quale chiave usa un nuovo documento quando non ne viene indicata una; una comodità, non una misura di sicurezza
- Interruttore generale - un flag che disattiva in un colpo solo ogni controllo di accesso
Un tenant appena creato non ha alcun documento di policy, quindi tutto è consentito finché un admin non ne scrive uno. Ogni revisione viene aggiunta alla cronologia, così la finestra esatta di attivazione e disattivazione resta verificabile. Soprattutto: ogni modifica è giudicata in base alle policy attive al suo tempo attendibile (receivedAt). Le modifiche entrate nel tenant dopo l'attivazione ne sono coperte, mentre le precedenti si risolvono contro l'implicito «tutto consentito»: i dati preesistenti sono quindi salvaguardati automaticamente, senza alcun passaggio di migrazione.
Ogni regola riguarda un tipo di operazione e un database ("*" = tutti) ed elenca gli utenti o i gruppi a cui si applica. La valutazione è insiemistica e indipendente dall'ordine:
- una regola deny che corrisponde → negato
- altrimenti una regola allow che corrisponde → consentito
- altrimenti decide la policy di baseline
L'indipendenza dall'ordine conta perché i documenti delle regole si fondono tra le repliche tramite CRDT: non c'è alcun ordine di regole su cui poter essere in disaccordo.
Un server può arrivare solo a un verdetto di Tier 1. Se tra consentire e negare c'è soltanto una regola di contenuto di Tier 2, tratta la voce come consentita al Tier 1 e lascia ai client il controllo di withfields. Ogni decisione restituisce un risultato strutturato — allowed, un reason leggibile, il matchedRuleId e il tier — che l'SDK usa per disattivare le azioni che un utente non può compiere.
Una clausola withfields verifica un percorso con punti all'interno del documento, con un insieme chiuso di operatori (equals, contains, gt, …) e segnaposto come ${user.usernames}. Ogni clausola viene valutata contro uno stato scelto del documento:
- before - il documento esistente (predefinito per modifica ed eliminazione): "devi essere già un editor". Valutare lo stato after permetterebbe di aggiungersi da soli e autorizzare la propria modifica.
- after - il documento con la modifica applicata (predefinito per la creazione): "chi crea deve aggiungersi a
myeditors".
Le regole vengono confrontate con l'hash del nome utente, con gli hash dei suoi gruppi (compresi quelli annidati) e con pseudo-token riservati:
$everyone- tutti gli utenti registrati$admin- solo gli admin$author- chi ha creato originariamente il documento (Tier 1, modello di proprietà)
La revoca si ottiene rimuovendo le chiavi dal documento di concessione firmato dall'admin: non esistono documenti di revoca separati. Un admin può inoltre emettere un'esplicita cancellazione remota del dispositivo, da attivare consapevolmente, firmandola per chiave: il tenant viene rimosso da un dispositivo rubato o dismesso alla connessione successiva.
Un CRM con editor per singolo record
Qui una policy realistica viene percorsa dall'inizio alla fine, così i pezzi si incastrano. Il tenant ha un database crm e vogliamo quattro regole: tutti possono creare contatti ma devono inserirsi in myeditors; solo chi è già elencato come editor può modificare un contatto; solo chi l'ha creato può eliminarlo; e il gruppo HR può modificare qualsiasi cosa, come via di escalation.
La baseline è deny. La regola 1 corrisponde via $everyone e la sua clausola withfields passa, perché nello stato after myeditors contiene Alice → consentito. Il server conferma solo il Tier 1, poi attesta la voce.
La regola 2 corrisponde via $everyone, ma la sua clausola withfields fallisce: nello stato before myeditors non contiene Bob → negato in locale, anche se la sua modifica prova ad aggiungerlo. Un client manomesso potrebbe inviarla, ma ogni ricevente onesto la mette in quarantena al momento della materializzazione.
La regola 3 corrisponde via $author: la chiave di chi ha creato il documento e quella di chi firma l'eliminazione si risolvono allo stesso utente → consentito e attestato (Tier 1, resiste a un client malevolo). La regola 4 permette a qualsiasi membro del gruppo HR di modificare qualunque contatto, indipendentemente da myeditors.
Si vede così la divisione dei compiti: le regole di Tier 1 ($author, gruppo hr) sono applicate dal server e resistono a un client malevolo; le regole di Tier 2 (i controlli sul contenuto di myeditors) sono applicate da ogni client onesto e, se violate, finiscono in quarantena alla ricezione.
Controllo degli accessi in lettura
Le regole di scrittura decidono chi può modificare i dati; l'accesso in lettura decide chi può vederli, e MindooDB lo applica su due livelli, entrambi firmati dall'admin e cifrati con $publicinfos, così il server zero-trust può lavorare sui metadati che gli servono senza mai possedere la chiave del tenant.
1. Un gate di lettura/sincronizzazione a livello di database. Una baseline denyDocRead più le regole doc_read (la stessa valutazione deny-overrides-allow, rivolta a utenti, gruppi, $everyone o $admin) decidono chi può del tutto aprire e sincronizzare un database. È intrecciato direttamente nel sistema di sincronizzazione: chi perde l'accesso in lettura smette di ricevere aggiornamenti e non riesce più nemmeno ad aprire la copia locale già sincronizzata. Poiché il gate sta davanti a ogni operazione di sincronizzazione, la lettura è il gate principale per ogni database e regola anche le scritture: chi non può leggere un database non può nemmeno creare dati al suo interno (e altrimenti scriverebbe documenti che non potrebbe mai vedere).
2. La riservatezza dei documenti resta crittografica. Un documento può leggerlo solo chi ha nel proprio KeyBag (l'archivio di chiavi personale, protetto da password) una chiave che lo decifra: qui il server non prende alcuna decisione sulla lettura. La distribuzione delle chiavi è il modo in cui quelle chiavi arrivano in sicurezza alle persone giuste: una policy firmata dall'admin indica quali utenti e gruppi devono detenere ciascuna chiave, e ogni client mantiene automaticamente il proprio KeyBag allineato. È opt-in: con la chiave predefinita condivisa e senza gate di lettura, tutti possono leggere tutto, esattamente come prima.
Nel punto di strozzatura sul server, lo stesso hook che valuta l'elenco degli ID di database consentiti valuta anche doc_read per il principal autenticato, contro l'ora attendibile del server, e si rifiuta di servire o di accettare invii per un database negato: gli aggiornamenti non permessi semplicemente non vengono mai consegnati. In parallelo, il percorso di apertura sul client si rifiuta di aprire un database a cui l'utente ha perso l'accesso, così un utente revocato non riesce a leggere nemmeno la propria copia sincronizzata in locale. Il database directory non passa mai dal gate (contiene proprio le policy da cui il gate dipende); l'admin è esente.
Quando una chiave viene inviata a un utente, è cifrata con la chiave pubblica (RSA) personale di quell'utente, così solo lui può estrarla: nessun altro utente, e nemmeno l'admin, può leggere la chiave. Per questo la distribuzione è cieca per l'admin: chi già detiene la chiave la incapsula per i destinatari e l'admin firma e pubblica solo il risultato. Anche un utente normale può preparare una distribuzione e passarla a un admin perché la firmi, pure a un admin che non possiede la chiave.
Ogni client mantiene il proprio KeyBag allineato alla policy all'avvio e dopo ogni sincronizzazione. Invia una chiave a un utente e la sua sincronizzazione successiva porta con sé i documenti che quella chiave apre: compaiono semplicemente nel database, ora leggibili. Ritira la chiave e quei documenti scompaiono: le copie locali che non si possono più decifrare vengono rimosse dal database, dalle cache e dalle viste. Promozioni, rotazioni e cambi di reparto passano automaticamente da qui.
Come tutela della banda, il server smette anche di consegnare dati cifrati con una chiave che un utente ha perso. Ma chi è stato rimosso e ha conservato una copia vecchia può ancora leggere quello che aveva già: il vero taglio è quindi la rotazione, cioè emettere una nuova versione della chiave solo per i destinatari rimanenti, così che ogni modifica futura usi una versione che l'utente rimosso non ha mai ricevuto.
Distribuzione di app basata su policy
Lo stesso meccanismo firmato dall'admin che distribuisce le chiavi distribuisce anche le applicazioni Haven. Un admin pubblica una policy che indica quali app offre il tenant e chi deve riceverle, e ogni client Haven riconcilia il proprio elenco locale di applicazioni con quella policy dopo la sincronizzazione della directory: così l'onboarding di un utente in un tenant installa automaticamente le app che quel tenant pubblica, e revocare l'accesso le rimuove di nuovo.
A differenza di una chiave, un'app non porta alcun segreto, quindi la distribuzione riguarda semplicemente chi riceve cosa. La definizione dell'app e i suoi metadati sono cifrati con la chiave del tenant, quindi il server zero-trust vede solo testo cifrato e instrada comunque la policy ai destinatari giusti.
Una policy si rivolge sia a singoli utenti sia a interi gruppi. Un elenco di invio dice chi deve ricevere un'app; un elenco di ritiro la riprende. L'appartenenza ai gruppi si risolve al momento della distribuzione, quindi aggiungere o togliere qualcuno da un gruppo cambia chi riceve l'app senza toccare la policy, e in caso di sovrapposizione il ritiro ha sempre la precedenza sull'invio.
Un utente normale può preparare una nuova distribuzione — o una modifica a una esistente — e consegnarla a un admin come richiesta pronta da firmare. L'admin la esamina e firma il documento di policy; solo quella firma la rende autorevole. È identico alla distribuzione delle chiavi, quindi chi già gestisce le chiavi gestisce anche le app.
Dopo ogni sincronizzazione della directory, ogni client confronta la policy con quello che ha in locale: installa le app che ora gli spettano, aggiorna quelle di cui cambiano versione pubblicata o dettagli e rimuove ogni app a cui non ha più diritto, senza che l'utente debba muovere un dito.
Un'app distribuita è contrassegnata come gestita dal tenant e mostra da quale tenant arriva. Le app gestite non possono essere modificate né rimosse dall'utente, ma solo duplicate in una copia privata per sviluppare e provare in locale, così la versione pubblicata dal tenant resta quella di riferimento.
Verificabile per progetto, onesto sui suoi limiti
Poiché ogni voce porta un tempo attendibile (receivedAt, con ripiego su createdAt per le voci solo locali) e la directory conserva una catena time-travel della propria cronologia, si può rispondere alla domanda: "l'utente X era autorizzato a modificare questo documento quando la modifica è entrata davvero nel tenant?" — e ricostruire esattamente come è cambiato nel tempo il suo accesso. La query wasAllowedAt(op, user, dbid, at) lo rende un'API di prima classe. Le violazioni di Tier 2 non svaniscono in silenzio: vengono registrate in un log di quarantena per ogni tenant, che Haven mostra nella sua vista di audit.
Il livello governa sia le scritture sia le letture (a livello di documento e di chiave). Non può impedire a un client manomesso di comporre una voce in locale, ma impedisce che quella voce venga accettata nel tenant, e il gate di lettura del server impedisce ai dati non dovuti di arrivare a un client. La v1 punta sulla sincronizzazione mediata dal server come testimone. La sincronizzazione da dispositivo a dispositivo su Iroh esiste ormai, ma un peer non emette alcuna ricevuta di testimone: il dispositivo che riceve valuta da sé le regole di identità, e il server giudica di nuovo la voce quando arriva. Le ricevute di testimone emesse da un peer e un vero controllo della lettura a livello di campo (chiavi per campo) restano lavori futuri. La cronologia non viene mai ricifrata sul posto, perché le firme degli autori coprono il testo cifrato.
MindooDB Haven porta la stessa cifratura end-to-end, la cronologia firmata e la governance integrata in un'area di lavoro tranquilla e multipiattaforma, compresa la vista di audit che mostra le modifiche in quarantena. Prova la beta gratuita o leggi gli approfondimenti.