← Developers

04.1 — Playbook: aggiungere un nuovo verticale

Spec riferita a SPEC-DX-01 §5

L’Invariante

Un nuovo verticale non modifica mai un motore esistente. Entra nella piattaforma come hanno fatto tutti i motori: un bounded context, fatti in ingresso via kernel, proiezioni in uscita dallo stream eventi. Se il tuo verticale richiede di cambiare un motore esistente, il design è sbagliato — correggi il contratto, non l’accoppiamento.

Il Contratto

Cosa deve produrre/consumare un verticale per essere “nativo”:

interface VerticalContract {
  manifest: {                        // registrato in ax_manifests
    triggers: Trigger[];             // azioni INSERT/UPDATE + letture LIST_*
    roles: Record<string, string[]>; // ruolo → permessi trigger
    migrations: string[];            // SQL solo per le proprie tabelle biz_*
  };
  factsIn:  'POST /api/v1/actions/:trigger';   // sempre — mai SQL diretto
  factsOut: 'cp_events | biz_* LIST_*';        // leggi lo stream, o letture locali
}

Regole dure: le nuove tabelle sono biz_<verticale>_* e private del runtime; gli eventi seguono il Contratto Zero.

Il Flusso

1. DEFINISCI IL CONTRATTO
   Schema eventi secondo il Contratto Zero: append-only, immutabili,
   niente update-in-place. Decidi i confini dell'aggregato
   (cos'è "il lotto" del tuo verticale?).

2. REGISTRA IL MANIFEST
   trigger + ruoli + migrazioni → ax_manifests → publish → pull → verify.
   Il runtime fa hot-swap; nessun redeploy di alcun motore.

3. COLLEGA PRODUCER / CONSUMER
   Producer: chiama /api/v1/actions/:trigger (edge) — dashboard,
             integrazioni macchina, plugin usano lo stesso endpoint.
   Consumer: legge le proiezioni cp_events — mai richiama il producer.

4. VALIDA CON LA TEST SUITE
   La suite node:test del repo (path trigger/FSM/RBAC) + test di contratto
   sui payload. Suite verde ⇒ il verticale è nativamente compatibile con
   dashboard, catena di audit e canale sync — non serve altro.

Esempio di Payload

// Un nuovo verticale: "energia". Un nuovo tipo di fatto, zero modifiche ai motori.
// trigger manifest RECORD_ENERGY → biz_energy_samples (tabella propria!)
POST /api/v1/actions/RECORD_ENERGY
{ "payload": { "sample_seq": 9081, "kwh": 1.42, "power_factor": 0.93,
               "lot_id": "<lotto-aperto-legato>" } }
// → audit + outbox + cp_events gratis; portale e admin lo vedono già.

I Fallimenti Noti

Errore Conseguenza → fix
Il verticale scrive la tabella di un altro motore Isolamento rotto → sposta la scrittura dietro un trigger azione tuo
SQL diretto “temporaneo” per velocità I fatti bypassano audit/outbox → violazione del contratto, rifiuta la PR
Righe di “stato” mutabili nella tua tabella Redesign: lo stato = l’ultimo evento; archivia eventi, leggi l’ultimo
Il verticale serve dato di un altro motore Leggi cp_events/proiezioni — se manca, pubblica un fatto più ricco a monte