← Developers

02.1 — Engine 1: Execution (FSM + atomic pipeline)

Spec matching SPEC-DX-01 / actions v1

The Invariant

A business mutation either commits atomically — FSM transition, data-link write, audit row, CDC outbox entry — or it does not exist. There is no half-applied state: a failed constraint rolls back the transaction and leaves only the audit evidence. The same Idempotency-Key always returns the same outcome.

The Contract

POST /api/v1/actions/:trigger on the edge is the single mutation surface. Every request crosses the same fail-closed pipeline, in order:

auth → idempotency → RBAC → constraint → FSM → data-link → audit → commit

A rejection at any stage aborts the request — nothing downstream runs.

The Flow

  1. auth — operator PIN or device key resolves tenant + roles from the authenticated session, never from payload fields.
  2. idempotency — the Idempotency-Key header is deduplicated; a replay returns the original response.
  3. RBAC — the manifest rbac_engine permissions map roles → allowed triggers; OPERATORE cannot fire QC_PASS.
  4. constraint — pre_commit_rules bound to the trigger are evaluated; a violation returns 422 CONSTRAINT_VIOLATED.
  5. FSM — the trigger must match a declared from → to transition for the aggregate’s current state.
  6. data-link — the manifest query for the trigger executes inside the same transaction.
  7. audit + commit — audit row and ax_cdc_outbox entry are written with the commit; sync drains later.

Payload Example

# Quality operator closes an inspection — the constraint engine checks the measure
curl -X POST http://edge:8291/api/v1/actions/QC_PASS \
  -H "Authorization: Bearer $EDGE_TOKEN" \
  -H "Idempotency-Key: qc-L0043-001" \
  -H 'content-type: application/json' \
  -d '{
        "payload": {
          "piece_id": "PZ-9044",
          "misura": 0.018
        }
      }'
# → 200 { committed: true, to_state: "CONFORME" }
#
# The same call with "misura": 0.026 violates `tolleranza_collaudo`
# → 422 { error: "CONSTRAINT_VIOLATED", rule: "tolleranza_collaudo" }
# → no commit, no business row — the audit chain keeps the attempt

Known Failures

Symptom Cause
401 Missing/expired session — login again via /api/v1/auth/login
403 Role lacks the trigger in rbac_engine.permissions — it is a denial, not a bug
409 FSM transition not declared from the current state (e.g. QC_PASS before START_MEASURE)
422 CONSTRAINT_VIOLATED A pre_commit_rule failed — the payload is outside the declared envelope
Retry loop succeeds silently Same Idempotency-Key returned the cached original response — by design