> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mattone.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Reconciliation: regole per chi tocca il codice

> Repo canonico, database canonico, guardrail nel codice, procedura di deploy e le cose da non fare mai. Da leggere prima di qualunque modifica al tool.

Questa pagina è per chi **modifica o deploya** il tool: team Mattone, chi fa manutenzione, qualunque agente. Uno studente non ha bisogno di leggerla.

## Le tre fonti di verità

| Cosa | Dov'è | Regola |
| - | - | - |
| **Codice** | repo GitHub privato `mattone-reconciliation`, branch `main` | `main` **è** la produzione. Niente codice che vive solo sul server |
| **Database** | Postgres su Supabase, progetto dedicato al tool, utente dell'app `reconciliation_app` | è l'**unico** database. L'host canonico è scritto nel codice (`backend/src/common/db-guard.ts`) e il backend si rifiuta di partire su un altro |
| **Server** | un droplet DigitalOcean con Docker (backend NestJS + frontend Vite dietro nginx) | si tocca solo per deployare da `main`, mai per scrivere codice |

Le credenziali (database, JWT, segreto HMAC con la platform) stanno nel `.env` del server e nel Vault. **Mai nel repo, mai in questa documentazione.**

## L'architettura non si sposta

Il tool è diviso in tre pezzi che vivono in posti diversi: **backend e frontend sul droplet DigitalOcean**, **database su Supabase**, più un progetto Vercel che builda il frontend ma **non serve il sito pubblico**. Questa divisione fa venire la tentazione di «rifare il backend da un'altra parte» (su Vercel, su Supabase Edge, su un nuovo server) o di attaccare un secondo backend allo stesso database.

**Non si fa.** Gli studenti e Cosmica usano il tool ogni mese: un secondo backend, o un database parallelo, li rompe in silenzio (dati scritti in due posti, tenant che divergono, riconciliazioni che spariscono). Finché una decisione esplicita di Mattone non dice altro:

* il backend è **uno**, gira sul droplet, si aggiorna solo da `main` di questo repo;
* il database è **uno**, quello canonico su Supabase, e nessun altro servizio ci scrive sopra;
* il frontend pubblico è quello servito dal droplet: il build Vercel serve a verificare che compili, non a servire gli studenti;
* chi vuole cambiare questa architettura apre una discussione prima, con piano di migrazione e rollback, non un branch.

> [!WARNING]
>
> Se trovi un backend, un cron o uno script che scrive nel database del tool da un posto diverso dal droplet, è un errore da segnalare subito, non una feature.

## Perché esistono queste regole

Fino ad agosto 2026 la produzione **non corrispondeva a `main`**: c'era codice che viveva solo sul server, e un `git reset --hard` avrebbe cancellato roba viva. Sullo stesso server girava anche un servizio che col tool non c'entrava niente. Il tentativo di aprile di spostare il database su Supabase era fallito perché le tabelle avevano la protezione per riga accesa e l'utente dell'app non le vedeva: il tool sembrava vuoto ed è stato riportato indietro. Ogni regola qui sotto chiude uno di questi buchi. Il resoconto degli incidenti sta nel repo interno.

## I guardrail nel codice

* **`db-guard.ts`**: all'avvio confronta l'host del database con quello canonico. Se è diverso, o è un host DigitalOcean, il processo **esce**. Non ci sono eccezioni self-service: se un caso lo richiedesse davvero, si concorda prima con il team e la procedura è nel repo interno.
* **`scripts/preflight-deploy.sh`**: prima del deploy verifica albero git pulito, `HEAD` presente su `origin`, nessun certificato DigitalOcean, `tsc --noEmit` a zero errori. Se fallisce, non si deploya.
* **`/api/health`**: risponde con `db.canonical`, un'impronta dell'host e `schemaGuard`. Se nella risposta **manca** la chiave `db`, in produzione gira codice vecchio.
* **`assertTenantId`**: ogni query dei repository lancia un errore se il tenant manca. Motivo: TypeORM ignora `tenantId: undefined` e restituirebbe i dati di **tutti** i tenant.
* **Script da riga di comando**: richiedono `--tenant=<id>` o `TENANT_ID`, nessun default. Un audit esiste: `node backend/scripts/audit-tenant-scope.js`.
* **Migration all'avvio spente**: le migration **non** partono all'avvio; si applicano a mano via SQL dopo averle lette.

> [!WARNING]
>
> **Non usare i log come prova che lo SchemaGuard è andato bene.** La riga `SchemaGuard: all tables and columns OK` esce **solo quando non ha corretto nulla**: se ha applicato anche una sola modifica scrive `SchemaGuard applied N fix(es)`, e chi cerca la prima riga conclude che qualcosa è rotto quando invece ha funzionato. Entrambi i casi sono normali dopo un deploy — al redeploy del 5 settembre 2026 la riga è uscita, allo switch del database no. La verifica che vale è `schemaGuard: "ok"` in `/api/health`.

## Come si fa una modifica

**1. Branch e PR sul repo**

Lavori in locale su un branch, apri una pull request verso `main`. Niente commit diretti su `main`, niente modifiche sul server.

**2. Typecheck e test in locale**

`npm run typecheck` nel backend deve dare zero errori. Il preflight lo ricontrolla.

**3. Merge**

La PR si mergia su `main`. Il frontend viene buildato anche da Vercel (utile per vedere che compila), ma il sito pubblico è servito dal server.

**4. Deploy sul server**

Sul droplet, nella cartella del progetto: `git fetch`, `git checkout main`, `git pull --ff-only`, poi `./scripts/preflight-deploy.sh`. Solo se è verde: `docker-compose -f docker-compose.prod.yml build backend && docker-compose -f docker-compose.prod.yml up -d backend`. Per il frontend lo stesso con `frontend`.

**5. Verifica**

La prova buona è **`curl -s https://reconciliation.mattone.co/api/health`**: deve rispondere `db.canonical: true` e `schemaGuard: "ok"`. Infine login dalla platform e un conteggio noto (es. i movimenti di un mese del tenant Cosmica).

## Da non fare mai

1. **Mai modificare file sul server** e lasciarli lì. Se una cosa funziona solo sul droplet, non esiste.
2. **Mai `git reset --hard` sul server**: prima si controlla `git status`, se c'è roba non committata si porta su GitHub.
3. **Mai far partire il backend su un database diverso da quello canonico.** Se un caso lo richiede, si concorda prima con il team e si scrive nella PR.
4. **Mai `DB_MIGRATIONS_RUN=true`.** Le migration si leggono e si applicano a mano.
5. **Mai una query o uno script senza tenant.**
6. **Mai un database di sviluppo sul server di produzione**, e mai una porta database esposta su internet.
7. **Il deploy si fa solo a mano, con la procedura qui sopra.** La vecchia GitHub Action di deploy è ferma: prima di anche solo prendere in considerazione di riaccenderla va riscritta e rivista.
8. **Mai fidarsi della documentazione sullo stato del server**: la verità è `curl /api/health` e `docker exec ... printenv DB_HOST`.

## Cose note da sistemare

* L'elenco puntuale dei residui da ripulire nel codice sta nel repo interno, insieme a chi li ha in carico.
* Alcuni documenti in `docs/` del repo descrivono endpoint o regole di abbinamento diversi dal codice. Fa fede il codice; questa pagina è allineata al 6 settembre 2026.
* Nella configurazione di avvio le migration automatiche sono spente, ma alcune note nel repo dicono ancora il contrario.

> [!NOTE]
>
> **Stato al 6 settembre 2026 — passaggio completato e consolidato.** Il server gira `main` sul **database canonico su Supabase**. Verificato allo switch: preflight verde e conteggi delle tabelle principali uguali a quelli del database precedente. Il giorno dopo sono state riattivate le regole di accesso per riga su tutte le tabelle — con verifica che l'applicazione continuasse a funzionare — e corrette le date invertite rimaste dall'importazione, confrontate una per una con il backup.
>
> Il database precedente non si legge e non si scrive: **è fuori dal giro**, e la sua dismissione è una procedura programmata del team. Sia il piano di rientro sia quello di dismissione stanno nel repo interno con le loro verifiche — non sono operazioni self-service.
>
> Da qui in poi vale la regola in cima: `main` **è** la produzione, e la verità sullo stato del server è `curl /api/health`, non questa pagina.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.