L’Invariante
Una scrittura business committata non deve mai perdersi tra il commit locale e la consegna al cloud. Il kernel scrive riga business, riga di audit ed entry outbox nella stessa transazione — se la riga esiste, l’evento raggiungerà il cloud. Il canale di sync è asincrono per design: prima l’autonomia edge, la convergenza cloud arriva dopo.
Il Contratto
interface OutboxEntry { // ax_cdc_outbox (lato edge)
id: string; // uuid — diventa la chiave di dedup globale
aggregate_type: string; // BIZ_*
aggregate_id: string;
event_type: string; // INSERT_* | UPDATE_*
payload: object;
created_at: string; // UTC
}
// Lato cloud: cp_events lo conserva verbatim; cp_event_dedup(id) è la
// frontiera dell'exactly-once — gli id processati non si riprocessano.
Limiti del canale (contratto duro): batch ingest ≤ 1000 eventi (413 BATCH_TOO_LARGE), NDJSON edge-audit ≤ 5000 righe. 503 code: DRAINING significa che il cloud si sta spegnendo — backoff, non allarme.
Il Flusso
txn: riga biz + audit + outbox ──commit──► il daemon legge ax_cdc_outbox
│ batch (≤1000), ordinato per seq
▼
POST /sync/ingest (Bearer edge api_key)
│ dedup su id
▼
cp_events ──► proiezioni in lettura
│
ack → l'edge marca le entry consegnate
cloud giù? le entry restano in coda, l'edge produce — offline-first
Ogni chiamata di sync autenticata tocca anche last_poll_at — l’heartbeat di flotta è un side-effect gratuito della consegna.
Esempio di Payload
# Il drain del daemon — esattamente ciò che manda, niente di più
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":"…","aggregate_type":"BIZ_CYCLES",
"aggregate_id":"…","event_type":"INSERT_BIZ_CYCLES",
"payload":{"cycle_seq":1042,"duration_ms":812},
"created_at":"2026-09-26T14:01:03Z"}]}'
I Fallimenti Noti
| Sintomo | Causa → fix |
|---|---|
401 su ingest |
API key ruotata al re-claim; la vecchia è invalida by design → riprovisiona |
403 EDGE_CAP_EXCEEDED |
Tenant oltre il numero di edge licenziati → l’admin alza max_edges |
413 BATCH_TOO_LARGE |
>1000 eventi → spezza; mai ritentare lo stesso batch sovradimensionato |
429 |
Bucket ingest per-tenant → rispetta Retry-After, backoff con jitter |
503 DRAINING |
Restart del cloud → ritenta dopo; allarmare su questo è un falso positivo |
| Eventi fermi in outbox | Controlla i log del daemon e la rete — le righe sono al sicuro, la consegna è solo differita |