← Developers

01.1 — Meta-kernel e isolamento tenant

Spec riferita a SPEC-DX-01

L’Invariante

Il runtime edge è l’unico scrittore dei dati business. Nessun route handler, script o plugin scrive le tabelle biz_* direttamente — ogni mutazione passa da un trigger dichiarato nel manifest, che impacchetta RBAC, constraint, enforcement FSM, mapping data-link, audit ed emissione outbox in un’unica transazione.

Seconda invariante: i tenant non si vedono tra loro. La Row-Level Security di PostgreSQL limita ogni riga biz_*/ax_* al tenant_id della sessione — l’isolamento lo impone il database, non il codice che si ricorda una WHERE.

Il Contratto

Il manifest dichiara i motori — forma da manifests/LayoutManifest.production-line.example.json:

{
  "action_engine": {
    "transitions": [{ "from": "VERIFICATO", "to": "ACCETTATO", "trigger": "ACCEPT_LOT" }]
  },
  "data_link_engine": {
    "queries": {
      "ACCEPT_LOT": { "type": "INSERT", "table": "biz_lotti",
                      "mapping": { "lotto": "$payload.lot_id",
                                   "operator_id": "$context.operatorId" } } }
  },
  "rbac_engine": { "permissions": { "MAGAZZINIERE": ["ACCEPT_LOT", "…"] } },
  "constraint_engine": { "pre_commit_rules": [{ "id": "peso_coerente",
      "expression": "$formula.weight_delta_pct <= 0.5 && $formula.weight_delta_pct >= -0.5",
      "triggers": ["ACCEPT_LOT"] }] }
}

Contesto di sessione iniettato dal runtime — il payload non può sovrascriverlo:

interface EdgeContext {
  tenantId: string;     // dalla sessione, imposto dalla RLS
  deviceCode: string;   // $context.deviceCode — identità del device
  userRole: string;     // MAGAZZINIERE | OPERATORE | QUALITA | ADMIN | …
  operatorId: string;   // chi ha toccato davvero la macchina
}

Il Flusso

POST /api/v1/actions/:trigger
  → auth di sessione (ax_sessions, scadenza enforced)
  → check RBAC (role ∈ permissions)              → 403 ACTION_FAILED
  → check constraint (pre_commit_rules)          → 400 ACTION_FAILED
  → check FSM (from → to dichiarata)             → 409 ACTION_FAILED
  → scrittura data-link + riga audit + outbox    [stessa transazione]
  → 200 { committed, to_state, … }

Esempio di Payload

# Accetta un lotto — il constraint sul peso deve reggere o niente commit
curl -X POST http://edge:8291/api/v1/actions/ACCEPT_LOT \
  -H "Authorization: Bearer $TOKEN" -H "X-Idempotency-Key: $(uuidgen)" \
  -d '{"payload":{"lot_id":"LOT-001","peso_dichiarato":100.0,
        "peso_misurato":100.3}}'

I Fallimenti Noti

Sintomo Causa → fix
401 AUTH_REQUIRED TTL sessione scaduto o sessione revocata → rifai login
409 ACTION_FAILED Transizione FSM non dichiarata dallo stato corrente → ispeziona le transitions del manifest
403 ACTION_FAILED Il ruolo non ha il trigger → i permessi del manifest sono legge
400 ACTION_FAILED Una pre_commit_rule è fallita → il messaggio d’errore nomina la regola
LIST_* vuota che “dovrebbe avere righe” RLS: stai guardando un altro tenant/device di quanto credi
Scrittura ok ma il cloud non la vede L’outbox drena in asincrono — guarda 02-cdc-outbox, non la tabella biz