L’Invariante
La realtà di produzione si registra come fatti immutabili, append-only. Un lotto verificato, un ordine accettato, un verdetto di collaudo — ognuno si scrive una volta e non si muta mai. Ogni superficie di lettura (dashboard, export, verifica) è calcolata in lettura dalle righe committate e dallo stream, mai fidata da contatori in cache.
La regola da non violare mai: lo stream è la verità, tutto il resto è una vista derivata. Se un numero non si può ricostruire rileggendo gli eventi, non appartiene al sistema.
È ciò che rende l’architettura replicabile, auditabile e debuggabile — lo stesso pattern dei ledger finanziari, applicato alla manifattura.
Il Contratto
Envelope canonico dell’evento — il contratto sul filo tra edge e Control Plane:
interface ProductionEvent {
id: string; // uuid — chiave di dedup GLOBALE (cp_event_dedup)
aggregate_type: string; // la tabella biz_* che l'ha prodotto (es. BIZ_LOTTI)
aggregate_id: string; // uuid/id della riga proprietaria
event_type: string; // INSERT_* | UPDATE_* — l'op data-link che l'ha scritto
payload: Record<string, unknown>; // il fatto stesso — mai mutato dopo la scrittura
created_at?: string; // ISO-8601 UTC, orologio lato edge
}
Il Flusso
produttore (dashboard operatore / integrazione macchina / plugin)
→ POST /api/v1/actions/:trigger (edge, sessione autenticata)
→ la pipeline valida: RBAC + constraint + FSM + data-link [una transazione]
→ riga in biz_* + riga audit + entry ax_cdc_outbox
→ drain outbox → POST /sync/ingest (cloud, edge api_key, firmata)
→ cp_events (dedup su id) → i read model calcolano in lettura
Le scritture sono local-first: l’edge lavora anche a cloud spento; l’outbox si svuota quando torna la connettività. La consegna duplicata è un path di retry progettato, non un errore.
Esempio di Payload
# Un fatto di produzione come lo sottomette il daemon edge
curl -X POST https://cp.axiom-trace.eu/api/v1/sync/ingest \
-H "Authorization: Bearer $EDGE_API_KEY" \
-H 'content-type: application/json' \
-d '{"events":[{
"id":"9f2c…","aggregate_type":"BIZ_COLLAUDI",
"aggregate_id":"<row-uuid>","event_type":"INSERT_BIZ_COLLAUDI",
"payload":{"pezzo":"PZ-001","misura":0.018,"esito":"CONFORME"},
"created_at":"2026-09-26T14:01:04Z"}]}'
# → 201 { processed_count: 1, duplicate_count: 0 }
# Rimanda lo stesso batch → 201 { processed_count: 0, duplicate_count: 1 }
I Fallimenti Noti
| Violazione | Esito |
|---|---|
Stesso id evento rimandato |
Deduplicato in silenzio — cresce duplicate_count, nessun errore |
created_at in ora locale |
Corruzione dell’audit — i timestamp sono UTC |
| Mutare un evento dopo la scrittura | Nessun code path esiste — emetti un fatto correttivo |
| >1000 eventi per batch ingest | 413 BATCH_TOO_LARGE — spezza il batch |
Testo normativo: spec/ nel repository Axiom-Trace. Se questa pagina e la spec divergono, vince la spec.