Semantius Logo
Docs Business Logic

Business Logic

Business logic in Semantius is data, not code. Rules live on the same entities and fields as your schema, expressed in JsonLogic, and evaluated by a PL/pgSQL engine inside the database itself. There are no hooks to deploy, no functions to host, and no out-of-band scripts to keep in sync with the model. Because enforcement happens at the storage layer, no agent, misbehaving client, or alternate API surface can bypass a rule, every write and every read passes through the same engine.

The platform exposes four extension points. Each one is a column on either an entity or a field, and each one holds a JsonLogic expression (or an array of them):

PurposeColumn
Derives values for the named fields on every insert/update, before the row is written.entities.computed_fields
Each rule must evaluate truthy or the write is rejected with the given message and code.entities.validation_rules
Per-row read filter, only rows where the rule evaluates truthy are visible to the caller.entities.select_rule
Controls field visibility in the UI, evaluated client-side against the current form values to show or hide the field.fields.input_type_rule

Why JsonLogic

JsonLogic gives Semantius a single, portable expression language that:

  • Serializes cleanly as JSON, so rules sit alongside the rest of the model in the same markdown spec, the same database row, and the same diff.
  • Runs identically in the database (for computed_fields, validation_rules, select_rule) via a PL/pgSQL interpreter and in the browser (for input_type_rule), with no compiler or runtime to ship.
  • Cannot be bypassed. Because the engine lives in the database, every insert, update, and select is evaluated against the rules regardless of which client, agent, or service issued the request.
  • Is safe to evaluate against untrusted input, since it has no side effects, no I/O, and no host access beyond the variables the platform binds for it.
  • Is readable enough that agents can author, audit, and refactor rules without touching application code.

For the language itself, see the JsonLogic overview. For the Semantius-specific variables and operators (such as $user_id, $old, set_record, has_permission, value_changed, throw_error), see JsonLogic extensions.

Where the rules run

The four extension points map onto distinct stages of the request lifecycle:

  1. Insert or update arrives at the API. The platform loads the entity’s computed_fields and runs them in declared order. Each derived value is written into the incoming row before validation, so later rules can reference it.
  2. Validation runs the entity’s validation_rules against the merged row. Any rule that evaluates falsy (or that calls throw_error / require_permission and fails) rejects the write with the rule’s code and message.
  3. Persist. The row is written.
  4. Read requests pass through the entity’s select_rule. The platform evaluates the rule per row, with the row bound as the root context, and returns only rows where it evaluates truthy. This is row-level security expressed as data.
  5. UI render. When a form is built from the model, each field’s input_type_rule is evaluated client-side against the current form values. Falsy hides the field, truthy shows it. This is purely a presentation concern and is never the authoritative gate: server-side validation always wins.

Authoring rules

Rules are stored in the model alongside the entities and fields they govern. In day-to-day work you let an agent write and edit them through the Business Analyst skill, and the Deploy skill ships them byte-for-byte to the live instance. When you do need to write one by hand, the workflow is:

  1. Decide which extension point the rule belongs to. If the rule derives a value, it goes in computed_fields. If it rejects a write, it goes in validation_rules. If it filters reads, it goes in select_rule. If it shows or hides UI, it goes in input_type_rule.
  2. Write the expression using standard JsonLogic plus the Semantius extensions. Reach for var to read fields of the current row, $old to compare against the previous state on update, and set_record to pull in a related record by id.
  3. Test it with a representative row. The Optimize skill can round-trip a model from a live instance, so you can verify that rules behave as expected in production before locking them in.

Next