Skip to content

Reference Eligibility

Reference Eligibility defines which records may be assigned to one scalar Reference field. For example, a Work Item’s Type can constrain which Work Items may be selected as Parent, or a candidate’s Code can be required to match Current.Scope.Code.

The released feature supports Entity Definition authoring, activation through the Configuration API, strict Entity YAML v8 portability, and Form and Table filter runtime consumers. An active rule pre-filters candidate controls and remains authoritative when an Entity Instance mutation is saved.

The dependent field must be one direct, single-value Reference in the primary table of a normal Data Entity Definition. It must use the Restrict delete behavior. Part, Association, Projection, collection, child-table, and non-Reference targets are rejected.

This is a rule-authoring restriction. A Reference without an active eligibility rule can still point to a Projection Entity Definition; its saved Form picker lists Projection rows and keeps the source-create action available.

Each dependent field owns at most one rule. Its expression uses the Current and Candidate profile:

(Current.Type = 'Bug' AND Candidate.Type IN ('Feature', 'Task'))
OR (Current.Type = 'Feature' AND Candidate.Type IN ('Epic', 'Feature'))

The Reference field action in Entity Definition opens the same shared DSL editor used by other rule expressions. It provides Current and Candidate path completion, a simple compatible-path condition builder, syntax help, server validation, impact preview, localized failure text, activation, replacement, disable, and delete. Parenthesized AND/OR expressions remain ordinary editable DSL; the UI does not replace them with a separate group builder.

Resolve all /api/workspace/... routes against the installation’s WORKSPACE_API_BASE_URL, never against moltaro.com.

POST /api/workspace/admin/entity-fields/{fieldId}/reference-eligibility/validate
Content-Type: application/json
{
"Expression": "Current.Type = 'Bug' AND Candidate.Type IN ('Feature', 'Task')"
}

A valid response includes the server-detected dependencies. Validation does not write metadata. Next, preview persisted assignments and saved UI surfaces:

POST /api/workspace/admin/entity-fields/{fieldId}/reference-eligibility/impact
Content-Type: application/json
{
"Expression": "Current.Type = 'Bug' AND Candidate.Type IN ('Feature', 'Task')"
}

Impact checks active and archived source records. Owner and Admin callers may receive an exact count and at most five source ids. Other authorized definition managers receive only whether violations exist. The same no-disclosure rule applies to incompatible saved Form and Table-filter surfaces.

Save a draft with Enabled: false, or activate an impact-clean rule with Enabled: true:

PUT /api/workspace/admin/entity-fields/{fieldId}/reference-eligibility
Content-Type: application/json
{
"Expression": "Current.Type = 'Bug' AND Candidate.Type IN ('Feature', 'Task')",
"Message": "Choose an eligible parent.",
"MessageTranslations": {
"de": "Wählen Sie ein zulässiges übergeordnetes Element."
},
"Enabled": true,
"RowVersion": null
}

Use the returned RowVersion for the next replacement, disable, or DELETE request. A stale value returns HTTP 409 with moltaro.referenceEligibility.conflict. Impact preview is advisory: enable and active replacement recompile and rerun data and saved-surface impact inside the authoritative transaction.

An active rule is attached to the saved dependent Form item by the server. The existing List, LongList, Lookup, Tree Lookup, and generic picker controls all query the same eligibility plan:

  • every dependent input sends only the draft or locked context allowed for its current create/edit operation. The server decides whether that context is ready; a non-ready response contains no candidates and cannot be selected;
  • changing a driver to a semantically different value clears every direct and transitive dependent value before a new query begins;
  • assigning the same normalized value does not clear dependants, and initial form hydration is not treated as a user change;
  • create and edit forms send only declared writable draft context. An effect/default/calculation-owned create driver is omitted from that client overlay and resolved from the authoritative create draft, including when its current field behavior is non-editable. The final create writer preserves the automation-owned value, while a caller-supplied value for that non-editable field is still rejected. Being the possible target of a mutation effect does not make a field permanently server-owned: when the effect is inapplicable and the field is editable, a user-selected value is valid. On edit, the server reads eligible non-writable driver values from the source record;
  • a declared input list is an allow-list, not a list of values that must all be present. Alternative related-create roots can be declared together while the current host sends only the root it owns;
  • a newly created or directly selected candidate is re-resolved by the server before the picker may close;
  • late responses from an older context cannot restore options or a value after a driver change;
  • server-evaluated Reference patches arrive with their permitted display and field-behavior projection values in the same response. The Form applies the canonical ID and display payload atomically, so several copied References do not flash raw IDs or start one display request per field;
  • a Tree Lookup may show accessible ancestors for navigation, but an ancestor-only or ineligible row has CanConfirm = false;
  • the final create/update API rechecks the rule. The Form pre-filter is a user experience aid, not the source of truth.

If a Form displays the same bound field more than once, all occurrences share one draft value. At least one usable occurrence of every driver must exist, and the server rejects Form Surface updates or clones that would leave an editable dependent without its required editable driver.

Field behavior can also read target values through a Reference, for example FacilityType.Number, FacilityType.KoboName, or FacilityType.Budget.Amount, without creating a Reference Eligibility rule. The direct system members Number, DisplayName, and Subtitle are returned on the Reference value. Dynamic scalar fields are returned under ProjectedValues; Money values keep their Amount and CurrencyCode members. The Form declares this projection requirement separately from eligibility. Candidate and restored-selection responses return the declared projection, while edit materialization may include the actor-readable target values needed to evaluate the saved Form safely. Runtime requests send CurrentValues as raw field values, including a Reference record ID rather than a display-value object. A false behavior condition is a normal ConditionNotMet result; it is not reported as missing access or an incomplete eligibility driver.

On create, omitted read-only or absent condition fields are evaluated from the authoritative create draft after schema defaults, create effects, and local calculations. Related-create actions that lock a Reference ID resolve that ID through the saved Form selection endpoint before the first behavior evaluation, so projected paths such as Parent.Code work without asking the user to reselect the locked value.

Section titled “Related-create with an effect-owned driver”

Consider this configuration:

  • Case and GroupEvent have alternative related actions that create an Assistance and lock their matching root field to the current record ID;
  • create mutation effects copy either Case.ServiceFormat or GroupEvent.ServiceFormat to Assistance.ServiceFormat;
  • Assistance.ServiceFormat has EditableWhen = Case IS NULL AND GroupEvent IS NULL, so it remains a configured editable Form occurrence for direct-create authoring but becomes authoritative and read-only as soon as related-create supplies either root;
  • LocationType owns Reference Eligibility Current.ServiceFormat.Id = Candidate.ServiceFormat.Id;
  • the Assistance Form contains visible, configured-editable items for ServiceFormat and LocationType, but it does not need a Case item.

Moltaro supports this chain. The related-create host contributes its locked root, while the server computes ServiceFormat authoritatively. Read the saved LocationType Form item’s ReferenceContext.CurrentValueInputs. CreateDraftFieldIds, UpdateDraftFieldIds, and LockedCreateFieldIds are operation-specific allow-lists, not required-all lists. Use the same current host context for candidate query and selection:

POST /api/workspace/entity/assistance/ui/form/{formId}/items/{locationTypeItemId}/reference-candidates/query
Content-Type: application/json
{
"ExpectedFormSurfaceRowVersion": "{rowVersion}",
"SourceInstanceId": null,
"CurrentValues": {
"Case": "case-id"
},
"Query": {
"Page": 1,
"PageSize": 50
}
}

The selection request uses the same ExpectedFormSurfaceRowVersion, SourceInstanceId, and CurrentValues, and adds "SelectedId": "location-type-id". Send a scalar Reference as its raw instance ID, not as { "Id": "..." }. Do not send ServiceFormat. Effect origin tells the UI to omit it, while EditableWhen = Case IS NULL AND GroupEvent IS NULL is the authoritative runtime guard that rejects a caller substitution once related-create supplies either root. The final Assistance create contains the locked Case and selected LocationType, and omits ServiceFormat; create evaluation computes it again and the writer rechecks Eligibility before commit.

In the related-create UI, ServiceFormat is populated by form evaluation, shown read-only because Case or GroupEvent is present, and LocationType becomes available as soon as the authoritative result is ready. Opening the picker, confirming a selection, and saving all use the same locked context. A Case-related host sends only Case; a GroupEvent-related host sends only GroupEvent. Form configuration shows a neutral diagnostic of possible external roots; it does not claim that every listed root is required.

The same configuration also supports ordinary create. Because neither Case nor GroupEvent is present, ServiceFormat.EditableWhen is true and the Form may send the user-selected raw ServiceFormat ID. CreateDraftFieldIds therefore includes ServiceFormat even though it is also a possible mutation effect target:

{
"ExpectedFormSurfaceRowVersion": "form-row-version",
"SourceInstanceId": null,
"CurrentValues": {
"ServiceFormat": "service-format-id"
},
"Query": {
"SearchTerm": null,
"Page": 1,
"PageSize": 25
}
}

Candidate query, selection, and final save use that user-owned driver. Before the user selects it, the correct state is ContextIncomplete. If an effect’s ApplyWhen is false and field behavior still permits editing, the effect does not claim the value and the same standalone rule applies. Conversely, sending both a locked related root and ServiceFormat is rejected when the root makes ServiceFormat non-editable.

Do not add an external root as a hidden (Visible = false) Form item. Hidden items are not accepted as client overlays. Either keep a genuinely usable visible editable item for ordinary create, or rely on the server-owned locked context of the related-create action.

An active rule is also attached to direct filter items on a compatible saved Table Surface. The filter drawer uses the persisted item identities rather than client-supplied field paths:

  • every participating dependent and driver is singleton in the filter draft;
  • adding a value-bearing dependent (Eq or Ne) adds each missing driver as one top-level Eq condition;
  • the dependent Reference picker stays disabled until every driver has one typed literal value;
  • changing a driver clears direct and transitive dependent values and cancels stale candidate requests;
  • a driver cannot be removed while a value-bearing dependent requires it;
  • IsNull and IsNotNull remain singleton but open no picker, require no driver, and release otherwise-unused driver guards;
  • duplicate, nested, OR-scoped, unknown, or wrong-operator participants are not inferred. A loaded personal or shared view in that state requires explicit repair before Apply.

The candidate and selection endpoints receive a bounded, non-executable draft tree together with the exact Table Surface row version. The server resolves the saved surface, dependent item, active rule, and driver types itself. A malformed envelope is HTTP 400; a valid but incomplete or ambiguous context is HTTP 200 with an empty result and a stable context state.

Entity Definition YAML version 8 carries ReferenceEligibility inside the owning field. A selected field owns that setting; omitting it removes a dynamic rule. Earlier Entity YAML versions are rejected after the destructive cutover. YAML v8 import stages metadata first, applies compatible UI surfaces, and only then activates through the same authoritative command used by admin PUT. Any impact or compatibility failure rolls back the complete import.