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.
Field behavior
Section titled “Field behavior”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.
Validation rules
Section titled “Validation rules”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
Section titled “Reference Eligibility”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.
Calculated fields
Section titled “Calculated fields”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.
Statements
Section titled “Statements”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
Section titled “Presentation rules”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.
Before-save changes
Section titled “Before-save changes”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: trueImport 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.