Skip to content

Declarative logic

Declarative logic is behavior attached to an entity definition as rules instead of code, enforced by the server on every write path — generated forms, the public API, and import. All expressions use the Moltaro expression language. Prefer a rule whenever the behavior can be stated as an expression over the record and its first-level references; move to C# business logic when it needs orchestration, external data, or computation beyond the surface’s expression profile. The decision table compares all options.

Each field carries three conditions — VisibleWhen, RequiredWhen, and EditableWhen — each paired with an operation mode (Create, Update, or CreateAndUpdate) that controls when it is evaluated. Outside its mode a condition is false, which makes EditableWhen = TRUE with mode Create a create-only field. Requiredness and editability are enforced by the server as well as the form. Prefer conditions over C# whenever visibility, requiredness, or editability follows from the record’s own values; visibility is not access control, and field permissions still apply independently.

Reference: field behavior conditions. Conditions are part of the field schema, managed through the field create and field update operations and validated without saving through the rule-expression validate operation with ContextType FieldBehavior.

A validation rule is one boolean expression that must return TRUE for the record to save on create, update, and import. Rules run in ascending sort order, before any C# business logic, and the first failing rule’s localized message is returned. Use a field’s RequiredWhen when the only requirement is that one field has a value; use a validation rule when the check describes the record as a whole, compares multiple values, checks a reference, or needs a tailored failure message. Reserve a C# validation function (see entity-scoped logic) for checks outside the rule expression profile.

Reference: validation rule expressions. Rules are managed through the validation-rule operations; expressions are checked first with the validate-expression operation, and reordering takes the full active rule set with row versions.

Reference Eligibility is a field-owned condition over Current and Candidate that describes which record may be assigned to one scalar Reference. It is distinct from a general validation rule because the compiled dependencies also drive candidate pre-filtering and saved Form/Table-filter compatibility. The released workflow supports Configuration API validation, impact preflight, activation, Form and Table-filter candidate pre-filtering, and authoritative API mutation validation.

Reference: Reference Eligibility expressions and the configuration workflow. Reference Eligibility is Entity Definition metadata. It is not defined from a public Net Operation Project field builder; workspace C# can consume normal generated References but does not own schema authoring.

A calculated field is a stored Integer, Decimal, or Money field whose value is produced by an expression; runtime writes ignore user-supplied values for calculated targets. The server infers the kind from the expression: local formulas compute over same-table fields (Qty * Price), while aggregate formulas compute over child records through an inverse-reference field (SUM(Positions, TotalPrice)). Prefer a calculation over C# whenever the derived number follows from a formula — the runtime keeps it consistent on every write and recalculates affected parents automatically.

Reference: calculated field expressions. Expressions are checked with the validate-calculation operation and set through the update-calculation operation.

A statement is a named boolean fact stored on the entity definition — overdue, blocked, eligible — evaluated on the server so filters, screens, and logic share one definition instead of repeating slightly different conditions. Statements power server-side record filters; with ExposeInClient enabled, runtime record payloads also carry the statement’s boolean result for cards, highlights, and client behavior. Exposing publishes only the result, but the result can still reveal a derived business fact, so enable it deliberately.

Reference: statements. Statements are managed through the statement operations and validated through the rule-expression validate operation with ContextType Statement.

Presentation rules compute a record’s display name or subtitle. Each rule pairs a condition (when it applies) with a text template using { … } placeholders for field paths, literals, and text functions such as {upper(Code)} or {coalesce(Nickname, Name)}. Naming is presentation, not logic — keep it declarative rather than computing display strings in code.

Reference: presentation rules. The rule set is persisted through the display-settings operation; conditions and templates are validated through the same rule-expression validate operation with ContextType Presentation and the matching ExpressionType.

A before-save change (mutation effect) copies a source value path into a target field when one of its trigger fields changes during an application/API save. ApplyWhen optionally gates the effect with a typed condition. Blank or null means always; FALSE removes the effect from this evaluation without disabling its metadata. The condition accepts stored, non-generated primary scalar fields, Operation.Mode, and one direct Reference hop to scalar/system fields. It does not accept child tables, aggregates, classifier predicates, runtime variables, calculated/line-number fields, or deeper Reference paths. Each effect sets an apply-on mode (create, update, or both), an assignment mode — SetIfEmpty writes only when the target is currently empty, Overwrite always replaces — and a sort order among the entity’s effects. Effects run before local calculated fields, which enables chains such as copying Item.Price into a position and then computing Qty * Price (see evaluation order). Prefer an effect over C# for plain value copies and defaults; use a C# mutation function (see entity-scoped logic) when the new value must be computed rather than copied.

Effects keep one global sort order across the entity. SetIfEmpty candidates for the same target are reevaluated together when any candidate trigger changes: the first non-empty candidate wins, while an empty candidate cannot erase a value selected earlier in the run. If an earlier effect changes a trigger only after higher-priority SetIfEmpty candidates were visited, those deferred candidates are evaluated together when the target group activates. Overwrite remains independently controlled by its own trigger fields and writes at its position in the global order. If it leaves the target empty, a later SetIfEmpty candidate may fill the target from the remaining candidate chain. Equal sort orders use the effect ID as a stable tie-breaker.

The explicit trigger list remains part of the contract. Primary root fields read by ApplyWhen are additional implicit triggers. Referenced-record and metadata changes do not fan out a retroactive backfill; the condition is authoritatively reevaluated during Form evaluation and every target record create/update. Preview and final save consume the same compiled condition plan, and save never trusts a client preview.

This lets one target inherit from alternative parent references without a procedural coalesce function. For SetIfEmpty, a non-empty caller value remains authoritative; Overwrite intentionally replaces it when that effect is triggered. Effect ownership is stored with the record, so a later update after save and reload can recalculate the same ordered candidates. The target is cleared only when every applicable source is empty and the current value is still owned by effects. A referenced record that is missing, archived, or outside the actor’s read scope stops the applicable SetIfEmpty chain without a fallback, warning, label, or patch-shape difference between those unobservable states. An unavailable independent Overwrite is skipped without suppressing a later SetIfEmpty candidate that is otherwise applicable. This rule also applies when the source path ends at a Reference field: the copied ID is produced only when the actor can read the terminal referenced record. An unavailable terminal record never appears in the mutation patch or its Reference hydration payload.

ApplyWhen is checked before resolving the source. If a denied direct or referenced field, missing record, archived record, or restricted record makes the condition unsafe to evaluate, target reconciliation is cancelled: the previous value and ownership remain and no lower candidate is selected. The response does not distinguish these hidden states. Invalid persisted condition metadata fails the complete preview/save without partial effect writes.

VisibleWhen remains a Form presentation condition only. It neither activates an effect nor clears persisted data.

Form evaluation continues to return canonical Reference IDs in its mutation patch. It accompanies patched References with actor-shaped display and required field-behavior projection values, allowing the Form to publish the derived ID, label, origins, and dependent clearing as one state change without per-field display lookups.

Tracked Package SDK direct-database writes are caller-authoritative: changing a field releases any stored effect ownership for that field, so a later application save cannot mistake package code’s value for an effect-produced value. Direct-database saves do not execute configured mutation effects; use an application/API save when configured mutation effects are required.

Effects are managed through the mutation-effect operations: GET/POST /api/workspace/admin/entity-definitions/{entityDefinitionId}/mutation-effects and GET/PUT/DELETE /api/workspace/admin/entity-mutation-effects/{mutationEffectId}.

The Configuration API request/response property is nullable ApplyWhen. Updates require the effect RowVersion; missing, null, or blank condition text normalizes to the always-true contract. Definition YAML and Package EntityDefinition resources round-trip the same optional property:

MutationEffects:
- Id: effect:primary-price
TargetFieldId: field:unit-price
SourcePath: Product.Price
TriggerFieldIds: [field:product]
ApplyWhen: UsePrimary = TRUE AND Product.Price > 0
ApplyOnMode: CreateAndUpdate
AssignmentMode: SetIfEmpty
SortOrder: 10
IsEnabled: true

Import planning compares the normalized condition and validates the complete selected effect graph. Invalid paths, complexity limits, or direct/transitive target cycles fail before any selected metadata is applied.