← Developers

Manifest Specification

Spec riferita a layout-manifest v1 · kernel ≥ 1.0.0

Il manifest è il “bytecode” dichiarativo del verticale. Viene validato a caricamento contro uno JSON Schema, verificato nella firma crittografica e vincolato a kernel_min_version. Tutto ciò che il kernel esegue deriva da questo documento — non esiste logica di dominio nel motore.

Struttura

{
  "$schema": "https://arqen.dev/schema/v1/layout-manifest.json",
  "meta": {
    "tenant_id": "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11",
    "vertical_code": "RETAIL_POS_STANDARD",
    "version": 1,
    "kernel_min_version": "1.0.0"
  },
  "engines": {
    "formula_engine":   { "...": "espressioni" },
    "action_engine":    { "states": [], "transitions": [] },
    "data_link_engine": { "queries": {} },
    "rbac_engine":      { "roles": [], "permissions": {}, "ui_visibility": {} },
    "constraint_engine":{ "pre_commit_rules": [] }
  }
}

meta.version è monotona: il Control Plane accetta solo versioni strettamente maggiori di quella attiva, e gli edge rifiutano versioni vecchie o manomesse.

Sintassi delle espressioni

Namespace riservati risolti dal kernel a runtime:

Prefisso Sorgente
$payload.* dati della richiesta corrente
$context.* contesto di esecuzione (tenantId, operatorId, deviceId…)
$state.* stato corrente dell’aggregato (letto da sys_fsm_state, mai dal client)
$db.* lookup lettura su DB (solo SELECT whitelisted)
$formula.* output delle espressioni del formula_engine

Operatori consentiti: aritmetici (+ - * / %), confronto (== != < <= > >=), logici (&& || !), ternario ? :. Mai eval() o new Function(): le espressioni passano da un parser sicuro dedicato.

Motori

formula_engine

Record<nome, espressione> — es. "tax": "subtotal * (tax_rate / 100)". I risultati sono esposti come $formula.nome a regole e mapping. Le formule girano nella pipeline dell’azione, prima del passo Data-Link.

action_engine

{
  "states": ["INITIAL", "PROCESSING", "COMPLETED", "CANCELLED"],
  "transitions": [
    { "from": "INITIAL",    "to": "PROCESSING", "trigger": "CREATE_ORDER" },
    { "from": "PROCESSING", "to": "COMPLETED",  "trigger": "FINALIZE_PAYMENT" },
    { "from": "PROCESSING", "to": "CANCELLED",  "trigger": "CANCEL_ORDER" }
  ]
}

Lo stato corrente è persistito in sys_fsm_state (chiave: aggregate_id). Il client non può dichiarare uno stato arbitrario — ogni transizione è un passo imposto dal kernel.

"queries": {
  "CREATE_ORDER": {
    "type": "INSERT",
    "table": "biz_orders",
    "mapping": { "total": "$formula.grand_total", "operator_id": "$context.operatorId" }
  }
}

Regole di sicurezza:

Optimistic locking (version_column): su UPDATE/DELETE dichiara la colonna versione della tabella. Il kernel aggiunge AND <col> = $payload.<col> al filtro e, su UPDATE, incrementa <col> = <col> + 1 in automatico — il manifest dichiara l’intento, il kernel garantisce la meccanica:

"COMPLETE": {
  "type": "UPDATE",
  "table": "fs_work_orders",
  "mapping": { "status": "COMPLETATO" },
  "filter": "id = $payload.workOrderId",
  "version_column": "version"
}

rbac_engine

{
  "roles": ["WAITER", "CASHIER", "STORE_MANAGER"],
  "permissions": { "CASHIER": ["CREATE_ORDER", "FINALIZE_PAYMENT"], "STORE_MANAGER": ["*"] },
  "ui_visibility": { "btn_apply_discount": ["CASHIER", "STORE_MANAGER"], "btn_void_order": ["STORE_MANAGER"] }
}

ui_visibility guida il rendering del client; permissions è enforcement lato kernel — il client non è trusted.

constraint_engine

"pre_commit_rules": [
  { "id": "chk_stock", "expression": "$payload.qty <= $db.product_stock",
    "error_message": "Quantità superiore alla disponibilità.",
    "triggers": ["CREATE_ORDER"] }
]

Fail-closed: una sola false → abort prima di qualsiasi write. triggers è opzionale: se presente la regola si applica solo a quei trigger, altrimenti è globale.

Insidie pratiche (scoperte sul secondo verticale)

Regole empiriche per chi scrive manifest, emerse implementando un verticale field-service sullo stesso kernel: