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.
data_link_engine
"queries": {
"CREATE_ORDER": {
"type": "INSERT",
"table": "biz_orders",
"mapping": { "total": "$formula.grand_total", "operator_id": "$context.operatorId" }
}
}
Security rules:
tablemust belong to the tenant schema whitelist;tenant_idis injected by the kernel, never by the manifest;- allowed types:
SELECT | INSERT | UPDATE | DELETE; SELECTqueries are stateless: they need no FSM transition;- every write emits a
sys_cdc_outboxevent in the same transaction; - optional
"required_plugin": "MODULE_ADV_INVOICE"gates the trigger behind licensing, before any engine runs.
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"
}
- the payload MUST carry
payload.<col>as an integer (the version the client read) — missing or non-integer → fail-closed error; - existing row with a mismatched version →
VersionConflictError→ HTTP 409 (lost update intercepted, never silently overwritten); - queries without
version_columnbehave exactly as before.
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:
||returns boolean, not coalesce. For defaults use the ternary:$db.x == null ? 0 : $db.x(equality is loose:null == undefined).NUMERICarrives from PostgreSQL as a string.+concatenates when one operand is a string → cast additive lookups::float8(*and/coerce to number,+does not).- Lookup to resolve FKs from the payload:
filteradmits subqueries — e.g.id = (SELECT technician_id FROM fs_work_orders WHERE id = $payload.workOrderId). - Self-transition (
from == to, e.g.IN_LAVORO → IN_LAVOROonADD_PART) models “within-state” actions without changing the FSM. - Same trigger from multiple
fromstates: a fan-in likeCANCELLEDfrom three states is expressed as three transitions sharing the sametrigger.