← Developers

Manifest Specification

Spec matching layout-manifest v1 · kernel ≥ 1.0.0

The manifest is the declarative “bytecode” of your vertical. It is validated at load time against a JSON Schema, checked for cryptographic signature, and gated on kernel_min_version. Everything the kernel executes comes from this document — there is no domain logic in the engine itself.

Structure

{
  "$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":   { "...": "expressions" },
    "action_engine":    { "states": [], "transitions": [] },
    "data_link_engine": { "queries": {} },
    "rbac_engine":      { "roles": [], "permissions": {}, "ui_visibility": {} },
    "constraint_engine":{ "pre_commit_rules": [] }
  }
}

meta.version is monotonic: the Control Plane only accepts versions strictly greater than the active one, and edges reject stale or rolled-back versions.

Expression syntax

Reserved namespaces resolved by the kernel at runtime:

Prefix Source
$payload.* data of the current request
$context.* execution context (tenantId, operatorId, deviceId…)
$state.* current aggregate state (read from sys_fsm_state, never from the client)
$db.* read lookup on the DB (whitelisted SELECTs only)
$formula.* outputs of formula_engine expressions

Allowed operators: arithmetic (+ - * / %), comparison (== != < <= > >=), logical (&& || !), ternary ? :. There is no eval() or new Function() anywhere: expressions go through a dedicated safe parser.

Engines

formula_engine

Record<name, expression> — e.g. "tax": "subtotal * (tax_rate / 100)". Results are exposed as $formula.name to rules and mappings. Formulas run inside the action pipeline, before the Data-Link step.

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" }
  ]
}

The current state is persisted in sys_fsm_state (keyed by aggregate_id). The client cannot declare an arbitrary state — every transition is a kernel-enforced move.

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

Security rules:

Optimistic locking (version_column): on UPDATE/DELETE declare the table’s version column. The kernel appends AND <col> = $payload.<col> to the filter and auto-increments <col> = <col> + 1 on UPDATE — the manifest declares intent, the kernel guarantees mechanics:

"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 drives client rendering; permissions is kernel-side enforcement — the client is not trusted.

constraint_engine

"pre_commit_rules": [
  { "id": "chk_stock", "expression": "$payload.qty <= $db.product_stock",
    "error_message": "Quantity exceeds availability.",
    "triggers": ["CREATE_ORDER"] }
]

Fail-closed: a single false aborts before any write. triggers is optional: when present the rule applies only to those triggers, otherwise it is global.

Practical pitfalls (learned on the second vertical)

Rules of thumb for manifest authors, discovered while implementing a field-service vertical on the same kernel: