Skip to content

Form and Card Related Table API and automation

This guide is the public contract for developers and AI agents that configure or consume a Related Table on an Entity Form or Card. The item binds one persisted leaf-surface item to one direct inverse relationship. Moltaro owns the runtime relation filter and locked parent value; clients choose neither.

GET ${WORKSPACE_API_BASE_URL}/openapi/moltaro-config-v1.json
GET ${WORKSPACE_API_BASE_URL}/openapi/moltaro-public-v1.json

Public JSON properties use PascalCase. Enums are numeric in JSON. Relevant values are EntityUiItemType.RelatedTable = 3, EntityUiSurfaceSelectorMode.Default = 0, and CustomKey = 1. Discover exact schemas and enum metadata from OpenAPI instead of copying a request shape from a different installation version.

V1 accepts exactly one direct, explicitly paired InverseReference from a standalone parent Data entity to a standalone child Data entity. The paired child field must be one direct scalar Reference back to the parent.

It rejects Part, Association, generated Association inverse links, multi-hop paths, owned Table fields, and relations whose endpoints or pairing are not current. There is no client-defined runtime filter, binding, or Unlink operation.

Read options for the target Form or Card before authoring:

GET /api/workspace/admin/entity-definitions/{entityDefinitionId}/ui/form-surfaces/configuration/options
GET /api/workspace/admin/entity-definitions/{entityDefinitionId}/ui/card-surfaces/configuration/options

Data.RelatedTableTargets is the server-authoritative list. Each entry contains:

  • Key and Label for the Designer;
  • Target, the exact inverse field target to write back;
  • ChildEntityDefinitionId and child display names;
  • ChildReferenceFieldId, ChildReferenceFieldKey, and display name;
  • TableSurfaces, the child tables valid for CustomKey selection.

An empty list is a supported result: the parent currently has no eligible relationship. Do not construct a target from field names when it is absent.

Example abbreviated option:

{
"Key": "field-parent-work-items",
"Label": "Work items",
"Target": {
"TargetKind": 0,
"EntityFieldDefinitionId": "field-parent-work-items",
"SystemField": null,
"ResponsibilityKey": null,
"DisplayFieldId": null,
"ObjectContextFactId": null
},
"ChildEntityDefinitionId": "entity-work-item",
"ChildEntityDisplayNameSingular": "Work item",
"ChildEntityDisplayNamePlural": "Work items",
"ChildReferenceFieldId": "field-work-item-parent",
"ChildReferenceFieldKey": "Parent",
"ChildReferenceFieldDisplayName": "Parent",
"TableSurfaces": [
{ "Key": "related", "Name": "Related work items", "IsDefault": false }
]
}

Form and Card configuration are complete replacements, not item patches:

GET /api/workspace/admin/entity-definitions/{entityDefinitionId}/ui/form-surfaces/{surfaceKey}/configuration
PUT /api/workspace/admin/entity-definitions/{entityDefinitionId}/ui/form-surfaces/{surfaceKey}/configuration
GET /api/workspace/admin/entity-definitions/{entityDefinitionId}/ui/form-surfaces/{surfaceKey}/configuration
GET /api/workspace/admin/entity-definitions/{entityDefinitionId}/ui/card-surfaces/{surfaceKey}/configuration
PUT /api/workspace/admin/entity-definitions/{entityDefinitionId}/ui/card-surfaces/{surfaceKey}/configuration
GET /api/workspace/admin/entity-definitions/{entityDefinitionId}/ui/card-surfaces/{surfaceKey}/configuration

Preserve MaxWidth, ColumnCount, and every existing Items entry. Add or change only the intended item, send the complete document, then read it back. The following fragment illustrates the Related Table item; it is not a complete PUT request:

{
"Id": "work-items",
"ItemType": 3,
"SortOrder": 40,
"Column": 1,
"ColumnSpan": 2,
"Label": "Work items",
"Hint": "Changes to work items are saved immediately.",
"Visible": true,
"ShowRelatedTableSaveNotice": true,
"Binding": {
"RefKind": 0,
"FieldId": "field-parent-work-items",
"FieldKey": "WorkItems",
"FieldType": 14,
"ReferenceToEntityDefinitionId": "entity-work-item",
"PairedReferenceFieldId": "field-work-item-parent"
},
"RelatedTableSelectors": [
{
"Id": "work-items-editors",
"Priority": 20,
"Mode": 1,
"SurfaceKey": "related",
"RoleIds": ["role-editor"]
},
{
"Id": "work-items-default",
"Priority": 0,
"Mode": 0,
"SurfaceKey": null,
"RoleIds": []
}
]
}

This fragment assumes a two-column Form. The server normalizes Column to 1 and ColumnSpan to the Form’s current ColumnCount, so readback is always a full Form row. Clients should not attach field editor, reference renderer, hierarchy, lookup selector, text, alert, margin, or padding behavior to it. The Designer omits Column and Column span controls even though the shared read model can return normalized layout values.

For Card, preserve every Tabs, Items, and LabelRules entry and the LabelPlacement and HideEmptyFields values. The item may set TabId; the server always reads it back at structural ColumnSpan = 3. Clients and package code cannot author a narrower Card Related Table.

Selector resolution is role-aware: higher Priority wins; at equal priority a role-specific match precedes a global match. An empty list or Default uses the child’s default Table Surface. CustomKey must be one of that child’s Table Surfaces. It cannot select a table from another entity.

ShowRelatedTableSaveNotice is optional and defaults to true on Form and Card Related Table items. It controls the independent-save notice on Form only; Card hosts do not display this notice. Set it to false to hide the Form notice. Clone, configuration readback, runtime bootstrap and YAML preserve this value; child operations still save independently.

Entity Definition portability uses YAML version 10. The inverse target is encoded by the existing field reference members, and selectors use the new RelatedTableSelectors collection:

Version: 10
UiSurfaces:
- Surface: Form
Key: edit
ColumnCount: 2
Items:
- Id: work-items
ItemType: RelatedTable
SortOrder: 40
Label: Work items
Hint: Changes to work items are saved immediately.
ShowRelatedTableSaveNotice: true
FieldRefKind: EntityField
EntityFieldDefinitionId: field-parent-work-items
RelatedTableSelectors:
- Id: work-items-editors
Priority: 20
Mode: CustomKey
SurfaceKey: related
RoleIds:
- role-editor
- Id: work-items-default
Priority: 0
Mode: Default
RoleIds: []
IsVisible: true

If RelatedTableSelectors is absent or empty, runtime uses the child’s default Table Surface. Export after import and compare the item id, inverse field id, selector modes, keys, priorities, and roles. Do not raise the YAML version or translate the item into a field or hosted-layout related section.

Load a table only for an existing parent:

POST /api/workspace/entity/{parentEntityIdOrKey}/ui/form/{formSurfaceId}/items/{formItemId}/related-table?instanceId={parentInstanceId}
POST /api/workspace/entity/{parentEntityIdOrKey}/ui/card/{cardSurfaceId}/items/{cardItemId}/related-table?instanceId={parentInstanceId}
{
"Page": 1,
"ArchiveMode": 0
}

The request deliberately has no target, binding, filter, child entity, or Table Surface property. The server re-resolves the parent Form/Card and item, inverse pair, current actor roles, child Table Surface, schema, security policy, and parent instance on every request.

EntityFormRelatedTableBootstrap or EntityCardRelatedTableBootstrap returns the actor-shaped Table, either Page or TreePage, CreateAction, runtime sorting, missing-surface state, error state, child identity, effective ArchiveMode, explicit AvailableArchiveModes, and opaque ContextRevision. Flat pages always use ten rows. The child parent-Reference column is omitted. The locked equality predicate is combined with Unified Security and runs in SQL before count and pagination.

Abbreviated archive capability fragment:

{
"ArchiveMode": 0,
"AvailableArchiveModes": [0, 1]
}

Send ArchiveMode = 0 for active children or ArchiveMode = 1 for archived children. Treat the returned ArchiveMode as the effective scope and replace local state with it after every response. AvailableArchiveModes is the only source for the scope control: 0 requires child View; 1 requires child View plus ReadArchive. Do not infer archive browsing from Archive, Restore, or Delete row actions. The WebApp never requests All. Create is active-scope only. Changing scope starts again at page 1 and obtains a fresh ContextRevision.

For a Tree View table, use only revisions from the current bootstrap:

POST .../related-table/tree/roots?instanceId={parentInstanceId}
POST .../related-table/tree/children?instanceId={parentInstanceId}

Roots and children require ExpectedContextRevision, ExpectedTableSurfaceId, ExpectedTableSurfaceRowVersion, page, archive mode, and—only for children—ParentInstanceId. A context or surface change returns HTTP 409; discard the old tree state and bootstrap again.

Related Table persists sort only. It does not expose column-layout editing, saved views, filters, or Manual Order:

GET .../related-table/grid-settings?instanceId={parentInstanceId}&expectedContextRevision={contextRevision}
PATCH .../related-table/grid-settings?instanceId={parentInstanceId}&expectedContextRevision={contextRevision}

Example sort patch:

{
"ExpectedRowVersion": "00000000-0000-0000-0000-000000000000",
"ExpectedTableSurfaceId": "table-surface-related",
"Fields": [1],
"ColumnLayout": null,
"Sort": [
{
"Field": null,
"Target": {
"Path": [
{
"FieldId": "field-work-item-title",
"FieldKey": null,
"Special": null,
"StatementId": null,
"StatementKey": null,
"ObjectContextFactId": null,
"ObjectContextFactFilterKey": null
}
]
},
"Direction": 0
}
]
}

Use the row version returned by GET. On HTTP 409 with moltaro.ui.gridSettings.concurrency.conflict, read settings again, reconcile the user’s intended sort, and retry once with the new version. On moltaro.ui.surface.conflict, bootstrap the Related Table again because its leaf item, relation, roles, schema, parent, or selected table is stale.

CreateAction is server-shaped. If CanCreate is true, use its child entity, Form selector, and LockedFields; do not let the caller edit or omit the locked parent Reference. Edit uses the actor-shaped Form selected by the child Table action. Open details uses the selected child Details page in a new tab. Archive/Delete use ordinary Entity Instance mutation routes with their current row version, operation key, confirmation, and audit reason.

Delete is a two-step lifecycle operation: the row must already have ArchivedAt, and the grid must not expose Delete for an active row. After Archive, the WebApp automatically switches to ArchiveMode = 1, bootstraps the archived row, then submits Delete with that row’s current row version. An API client must perform the same second bootstrap explicitly. Do not reuse a row version or ContextRevision captured before Archive.

Every child operation is a separate committed mutation. It must refresh only this table and must not clear or submit the parent draft. Cancelling the parent cannot undo a completed child mutation.

Unified Security is authoritative for parent and child independently:

  • parent View is required to resolve the parent instance;
  • child View is applied SQL-before-pagination;
  • denied child fields are omitted from columns and values;
  • Create, Update, Archive, and Delete require their respective child action;
  • Admin/Owner status is not a record-access shortcut;
  • unreadable and unrelated child records must not affect count or page data.

Stable failures include moltaro.instances.access.denied, moltaro.ui.surface.notFound, moltaro.ui.surface.conflict, moltaro.ui.binding.field.invalid, and moltaro.ui.gridSettings.sort.invalid. Branch on Errors[].Code, not a localized message. See Errors and responses.

Save children with the parent (Form submission)

Section titled “Save children with the parent (Form submission)”

A Form Related Table item with RelatedSaveMode = 1 (WithParent) does not write children on its own. The configuration members of the item are:

MemberValuesMeaning
RelatedSaveMode0 Independent (default), 1 WithParentForm only; Card is always Independent
RelatedPresentation0 Grid (default), 1 InlineInline needs a unique constraint on the child Reference proving at most one child
RelatedVisibleWhen, RelatedRequiredWhenField Behavior condition text or nullOver the parent record; decided again on the saved parent
RelatedHiddenPolicy0 Keep, 1 Block, 2 ArchiveWhat a save does with the children of a hidden section
RelatedFormSelectorssurface selectors, slot RelatedForm = 10The child Form; default is the child’s default Form

A configuration PUT sends RelatedSaveMode on every Related Table item, also its default 0 (it is required in OpenAPI). An item without it is the item of a client that does not know these members: when its stored settings are not the defaults, the PUT is refused with HTTP 409 moltaro.ui.configuration.clientOutdated (Field Items[{itemId}].RelatedSaveMode) and changes nothing, because an older client would otherwise reset the section to independent saves. With RelatedSaveMode present, the other five members may be left out the way a serializer that omits nulls or defaults leaves them out: RelatedVisibleWhen and RelatedRequiredWhen then read as null, RelatedFormSelectors as [], and RelatedPresentation and RelatedHiddenPolicy as 0. YAML import and packages always carry the members.

Several Related Table items may bind the same relation (for example a grid and an inline view). They must agree on RelatedSaveMode, RelatedVisibleWhen, RelatedRequiredWhen and RelatedHiddenPolicy; RelatedPresentation and RelatedFormSelectors may differ. A later item that disagrees is refused with moltaro.ui.relatedSaveMode.invalid at its first differing member.

The runtime Form model then carries, per WithParent item, RelatedChild (child definition, cardinality, potential capabilities, the child Form, ParentReferenceFieldKey, SectionItemId and PreauthorizationParentFieldKeys, see Child requests before the parent is saved) and the actor-shaped plans RelatedVisibleWhenPlan / RelatedRequiredWhenPlan. SectionItemId is the item that owns the relation’s one child draft, the first WithParent item of the relation in Form order; every item of the relation names it. Stage each child change once under it. Every stored Form carries Submission { Revision, MaximumRequestBytes, MaximumSteps }. A Form with a WithParent item saves through the route below, and so does a related-create of any create Form (it sends Origin; the WebApp’s related-create wizard always does):

POST /api/workspace/entity/{entityIdOrKey}/ui/form/{formSurfaceId}/submissions
{
"OperationKey": "8a2e7d4c-1f0b-4c3e-9a77-2c1f6b9d5e10",
"ExpectedRevision": "r1.<from Submission.Revision>",
"Root": { "EntityInstanceId": null, "RowVersion": null, "Fields": { "Title": "Case 1" },
"Resources": { "FileUploadCreateKey": "<uuid of the root's direct uploads>" } },
"Sections": [
{ "ItemId": "<RelatedChild.SectionItemId>",
"Children": [
{ "LocalId": "c1", "Create": { "Fields": { "Title": "Detail 1" } } },
{ "LocalId": "c2", "Archive": { "EntityInstanceId": "<id>", "RowVersion": "<seen>", "Reason": "Closed" } }
] }
]
}
  • Each section names a draft owner (RelatedChild.SectionItemId), at most once. Another item of the same relation, or a second section of it, is refused with moltaro.request.invalid at Sections[i].ItemId. Conditions, the hidden policy and the Archive set are decided once per relation, and Metadata.ItemId of an error names the owner. The hidden-policy preview and ParentContext.ItemId accept any WithParent item of the relation.
  • Child actions are Create, Update (changed fields only), Archive and Delete, each with a LocalId (^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$, not root, not policy-…). The server sets every relation: a child never sends its Reference to the parent (moltaro.ui.form.submission.fieldServerOwned).
  • An edit with an empty Root.Fields guards the parent (it must still be current and visible) without writing it; it names no Resources. A request that would write no record at all is refused with moltaro.ui.form.submission.nothingToSave.
  • A runtime table model may offer the Edit row action (Action = 5) to a reader without Update on the record: it keeps the action when the Form of its FormSelector has a WithParent item whose child records the reader may potentially create, update, archive or delete (a children-only edit). Offer it per row from the row’s own rights (readable, active, no Update), open that Form with GET /api/workspace/entity/{entityIdOrKey}/ui/form?surfaceKey={FormSelector.SurfaceKey} (the table-action Form route …/ui/table/actions/{actionId}/form still requires Update), show the record read-only and save with an empty Root.Fields (a Guard root) and the child changes.
  • A related-create sends Origin { SourceEntityDefinitionId, SourceEntityInstanceId, SourceInverseFieldId } (a Create root only), taken from the CreateAction.Origin of the related table or block; the server checks that the actor can read the source record and that the InverseReference pairs with the root’s Reference, then sets that Reference itself. An association record has an Origin too: its endpoint Reference pairs with the InverseReference the association generated on the source endpoint. CreateAction does not say how to save; the client decides from the create Form it loads.
  • A hidden section with the Archive policy must carry HiddenPolicy { AffectedSetToken, Reason } from POST …/items/{itemId}/hidden-policy/preview?instanceId={parent}.
  • The whole request is one transaction within the host policy (default 100 written records, 1 MiB body, 15 s budget); every participant passes the same rights, validations and C# save functions as a single-record save.

A success answers Kind: "EntityFormSubmission" with Root and Children (LocalId, ItemId, Action 0 Create, 1 Update, 2 Unchanged, 3 Archive, 4 Delete, and Result RecordIdentity / RecordReceipt / RecordRemoved). A refusal places each error at its request path in Field (for example Sections[0].Children[1].Create.Fields.Title) with Metadata.LocalId and Metadata.ItemId. A cell of an owned Table names its row when the server knows it (Root.Fields.Lines[1].Item, the index of the row in the sent array, also in Metadata.RowIndex); an error of a whole column, such as a unique constraint of the table, names the column only (Root.Fields.Lines.Item). The record and composite routes keep Field = Lines.Item and carry the same Metadata.RowIndex (and Metadata.RowId when the row has an id); the installed error catalog lists the codes and reactions under the EntityFormSubmissionRest surface; a code it does not list leaves the outcome unknown (UnlistedAnswer), as on the record and composite surfaces. The field checks it lists, such as moltaro.instances.validation.requiredFields with one moltaro.instances.field.required per empty field or Table cell, or moltaro.instances.field.invalid, are NotApplied: nothing was saved, so the person corrects the named field and saves again. A cascade that would archive or delete another participant is refused with moltaro.instances.composite.duplicateTarget. The OpenAPI operation ids are entity-form-submissions-execute and entity-form-submissions-preview-hidden-policy. The same OperationKey with the same body replays the stored result (also after the Form changed); another body under that key is refused (moltaro.security.idempotency.keyReuse). Confirm an unknown outcome with GET /api/workspace/entity-mutation-outcomes?Operation=5&OperationKey=… (Composite handle; steps Create, Update, Archive, Delete, a Delete step has no RowVersion).

Child-scoped requests made while the parent is a Form draft may send ParentContext { EntityDefinitionId, FormSurfaceId, ExpectedRevision, ItemId, ParentInstanceId, ParentFields }. The server evaluates the parent draft (ParentInstanceId null for a new parent, else the stored parent with the draft over it) and lets every one-hop read through the child’s parent Reference (Current.<Parent>.<Field>) read it:

RequestWhere ParentContext goesWhat reads the parent draft
Reference candidates, selection, Lookup table, tree and hierarchy requestsrequest member ParentContextEligibility rules
Form evaluation POST …/instances/form-evaluation and …/instances/{id}/form-evaluationrequest member ParentContextEffect sources and conditions (Parent.Kind), calculations over their results, and ParentReference in the answer for the child’s Form conditions
File-field upload POST /api/workspace/files/uploads of a new child (FieldTarget.CreateOperationKey)request member ParentContext, next to FieldTargetThe child’s Create and Field:CreateWrite pre-authorization
Markdown attachment scope POST /api/workspace/markdown/scope/issue of a new child (OperationKind = Create, provider core.entity)ProviderContextJson = {"ParentContext": {…}} (sized by the scope’s upload header, below; member names are read as in a request body)The child’s Create, Attachments:Create and Field:CreateWrite pre-authorization

ParentFields may hold only fields shown as field items of the parent Form. The server overlays only the fields sent: every other field of a stored parent is read as stored, and of a new parent as empty. A child reads only a parent draft its actor could make on the parent Form, as the parent Form’s own evaluation requires:

  • a new parent (ParentInstanceId null): a Create right on the parent definition that the preview does not deny, with Field:CreateWrite on every sent field;
  • a stored parent with ParentFields: View, Update and Field:Write on every sent field, so send only the fields the user changed;
  • a stored parent without ParentFields (a children-only edit): View.

Any other parent answers like one the actor cannot read or that does not exist. The server sets the child’s parent Reference: a child may send it only as null or as the stored parent, and a Form evaluation never answers it in Patch. Its ParentReference names the parent with the projected values the child’s Form conditions read; while the parent is unsaved its State is still 0 (Resolved) and Id, Number, DisplayName and Subtitle are null. The answers per request:

RequestAnother record as the child’s parent ReferenceParent not readable, missing or not draftableStale ExpectedRevision
Reference candidates, selection, Lookup table, tree and hierarchyruntime state ContextInvalid404 moltaro.instances.notFound409 moltaro.ui.form.submission.revisionChanged
Form evaluation400 moltaro.ui.form.submission.fieldServerOwned at DraftFields.<key>404 moltaro.instances.notFound409 moltaro.ui.form.submission.revisionChanged
File-field upload400 moltaro.instances.field.invalid at FieldTarget404 moltaro.instances.notFound409 moltaro.ui.form.submission.revisionChanged
Markdown scope issue404 markdown.inline.unavailable404 markdown.inline.unavailable404 markdown.inline.unavailable

The File upload and the Markdown scope of a new child keep the context, so it must stay small. Send in their ParentFields exactly the keys of RelatedChild.PreauthorizationParentFieldKeys, each with its whole value, and leave out a key the parent draft does not hold (a stored parent’s unchanged field is read as stored). The list names the parent field items whose values these pre-authorizations read: the fields the child’s Create, Attachments:Create and Field:CreateWrite rights read through the parent Reference, and the fields the parent’s own Create and Field:CreateWrite rights read for a new parent. When a right reads a parent field the parent Form computes (a calculated field or the target of a mutation effect), it names every parent field item; when no right reads the parent, it is empty. Never shorten or drop a value to fit: the rights would then be decided on another draft. The list is derived from the rights of every role, not only the actor’s, so it can name a field the actor’s own rights do not read, and it names keys only, never values or conditions.

Size limits: the File upload’s policy context (the target, its DraftFields and the ParentContext, as the server keeps it) is at most 64 KiB. The Markdown scope’s token travels in the X-Moltaro-Markdown-Scope header of every draft upload, so the server issues no scope whose header line, X-Moltaro-Markdown-Scope: <token> with its line end, would exceed 7,168 bytes. The nginx in front of the API answers its own 400 to a header line over 8 KiB; the rest is a margin for other proxies. The token protects and encodes the whole scope, so it is larger than the context: about 2,000 Ukrainian letters, 4,000 Latin ones or 350 emoji (which the token carries escaped) fit in one context. Only the server can measure the token, so a client sends the context and shows the refusal. A larger context is refused before any right is decided, so the answer depends only on the request and the caller’s own account, and nothing is uploaded or issued:

RequestAnswerFieldMetadata
File-field upload400 moltaro.ui.form.parentContext.tooLargeParentContextMaximum 65536, Length (the policy context, in characters; non-ASCII letters count once)
Markdown scope issue400 moltaro.ui.form.parentContext.tooLargeProviderContextJsonMaximum 7168, Length (the bytes of the scope’s upload header line, name and line end included; when the context’s own UTF-8 bytes already exceed the line, the line the context alone would make, a lower bound)

A proxy you put in front of Moltaro must accept request header lines of at least 8 KiB.

Show the refusal to the person, for example by asking them to save the parent first or to shorten its long values. ParentContext is a preview only: it grants nothing, the File policy and the Markdown scope keep it with the upload or draft, and the submission decides every right again on the saved records.

Editable source members use the same Form submission route. Read their runtime catalog and Submission.Revision from the configured Form. For an existing root, pass instanceId to GET /api/workspace/entity/{entity}/ui/form; the server requires View on that exact record and shapes admission for that record. Omit instanceId for a new root. Optional objectContextFactIds selects up to 100 bindings of the same Entity without changing the chosen Form Surface.

Root.ObjectContextMembers and a child Create/Update’s ObjectContextMembers contain source inputs alongside ordinary Fields. Copy the binding and schema versions from the runtime catalog. Existing source edits require the complete Handle from the current member snapshot:

{
"BindingId": "<binding id>",
"BindingVersion": "<binding row version>",
"SchemaVersion": "<provider schema version>",
"Handle": {
"Resource": {
"ModuleKey": "Boards",
"ResourceKind": "BoardItem",
"ContextResourceId": "<board id>",
"ResourceObjectId": "<item id>"
},
"Version": "<opaque item and Part version>"
},
"Admit": true,
"Members": [
{ "MemberKey": "status", "Value": "<status id>" },
{ "MemberKey": "due", "Value": { "DueDate": "2030-05-15", "DueTime": "09:30:00" } }
]
}

Treat the handle as opaque; do not reconstruct or silently refresh it on save. Admit = true participates in the source operation. With no handle it requests placement of a missing source, only when the catalog offers Creation. Use its defaults and supported Create inputs. An optional explicit placement may be declined; automatic placement cannot be suppressed. Admit = false sends no members and no handle and declines only an optional placement. A source already present uses Admit = true, its handle and edit inputs. Boards status is a status ID string. Due is null to clear it, or a typed object with DueDate and DueTime; DateTime mode requires time and DateOnly mode rejects time. Other members use their advertised field type and input binding.

For a source-only edit send root identity, current root RowVersion and empty Fields. This guards the root without changing its ordinary fields. The server checks source permissions, versions, field rules and prerequisites and commits the root, children and sources atomically. A failed check writes none of them. Workflow context members remain read-only.

An agent should finish the workflow only after it has:

  1. Read the installed Configuration and Runtime OpenAPI documents.
  2. Read RelatedTableTargets; never invent an inverse target or table key.
  3. Read the complete Form or Card configuration and preserve all unrelated items.
  4. Write one item with ItemType = 3 and only eligible selectors.
  5. Read the leaf configuration back and verify item id, inverse field, order, visibility, label/hint, selectors, priorities, keys, and roles.
  6. Export YAML v10 and verify the same values.
  7. Bootstrap with two different parents and verify each response contains only its own child rows.
  8. Test an editor, reader, and no-access actor; verify denied data does not appear in rows, counts, fields, actions, or errors.
  9. Change the leaf item or selected Table Surface and verify an old ContextRevision fails closed.
  10. Verify Delete is absent for active rows; Archive a row, read Archived scope, then Delete it with the new row version.
  11. Reload after Add/Edit/Archive/Delete and verify persisted child state while the parent draft remains independent or the containing Card stays in place.
  12. For Card, verify Details, drawer, related-card, host-block, and Boards consumers plus a narrow viewport; the table must remain full-container-width.

For UI setup, see Configure Form and Card Related tables. For the user workflow, see Related tables in forms and cards.