Boards runtime walkthrough
This walkthrough operates the Support triage board built in the Boards configuration walkthrough: discover the board, create a ticket directly onto it, and move the item through the process. Everything uses the public integration API, so it works with any authenticated caller the board permissions allow — including a service-account key.
All examples were executed against a real Moltaro installation; response bodies are real, trimmed for length. The complete contract is under the Boards runtime tag of the API reference.
The workspace owner supplies the guide, URL, key, and permissions through the Portal or administrative handoff. An agent does not create or elevate its own account. Apply the row-version, retry, and verification rules in Reliable API automation.
Runtime authorization
Section titled “Runtime authorization”Board access has two independent gates. Boards:ACCESS admits the caller to
the module and Board shell. Every returned Board Item must also satisfy that
Board’s BoardItem.View Security Permission. Moltaro applies the View predicate
before count, sorting, cursor or offset, and page limit, so totals and cursors
describe only items the caller may read. Status columns remain visible process
structure; BoardStatus.View is not an additional Board Item predicate.
An ordinary item mutation requires pre-change BoardItem.View plus its exact
Permission. A move also requires MoveItem; when it uses a configured
transition it additionally requires UseTransition:<transition-key>. Board
Data updates require EditBoardData and Field:Write for every changed field.
Adding an item evaluates AddItem against the proposed status, target, Board
Data and due date, applies Initial Assignment Rules, and requires final
BoardItem.View before commit.
Target values are not disclosed by a successful Board Item predicate alone.
Projection requires BoardItem.View, Board Target:View, the target resource’s
own View, and that resource’s field-safe projection. Typed target(...)
paths inside a Security Statement are predicate facts and do not themselves
grant target access.
Calling Boards from workspace C# automation
Section titled “Calling Boards from workspace C# automation”For an Action, TriggerHandler, Command, Job, or HttpEndpoint in the Net
Operation Project, prefer IBoardAutomationCommandService and
IBoardAutomationQueryService from
Moltaro.Package.NET.Boards.Automation. They are application-level facades:
AddItemAsync creates Board Data internally and performs target/repeat,
Security and transition rules, audit/business events, resource events/outbox,
and attention projections. The caller supplies a
BoardRuntimeTargetRef and a caller-owned UUID OperationKey, not
BoardRuntimeActor or DataRecordId.
Ordinary calls authorize and audit the original actor captured by the host.
Only a runtime-confirmed trusted system invocation may use
moltaro-system-automation; naming that actor in package code does not create
trusted provenance. Both actor forms evaluate the same Board Security
Statements and exact Permissions, and the system actor receives no implicit
access. Calls execute in a separate application scope/transaction, and a
successful return means that main transaction committed. Ordinary facade
mutations deliberately have the same add/repeat semantics as the Runtime API
and do not create an automation receipt. After an unknown ordinary add outcome,
query the open item before deciding whether to start a new logical admission.
Retry the exact same add with its original OperationKey; an exact replay
returns the original item without a duplicate, while changed-payload reuse
returns an idempotency conflict. The replay scope includes the captured actor,
board, and target definition and does not bypass authorization.
When a failed facade result should fail the containing function, call
result.ThrowIfFailed(). This raises a bounded SDK exception that preserves
the first platform error code and its safe message in Function Operations;
wrapping Errors in a generic exception discards that reason code.
Clients built from the earlier add-item contract must regenerate from the
current OpenAPI document and replace MutationIdempotencyKey with the required
UUID OperationKey. Arbitrary string keys are no longer accepted. Package
authors must also rebuild against the current Boards Package SDK and pass the
UUID as the first AddBoardAutomationItemRequest constructor argument.
Call a Board command before staging or saving Entity or owned-table changes in
the same Net Operation Project transaction. Otherwise the facade fails before
opening its separate scope with
moltaroBoards.runtime.entityMutationBeforeBoardCommandUnsupported; start a
new transaction rather than retrying from the Entity-first transaction.
Enabled Board event Constraints are evaluated inside that transaction. A
rejection preserves every ordered error’s code, workspace-authored message,
target, and safe metadata; it commits no partial Board Item, Board Data,
Target, history, audit, resource event, notification, or outbox effect.
Historical import and reconciliation are different: they do not replay
transition, entry, or exit Constraints against today’s actor or clock. They do
enforce the resulting status invariant before committing the imported or
reconciled state.
The complete lifecycle-to-trigger matrix, including same-status reorder,
remove, terminal reopen, new pass, administrative relocation, and Board
activation behavior, is documented in
Board constraints: Which bindings run for each lifecycle operation.
Use the returned RowVersion for the next mutation, and poll any returned function
job id at GET /api/workspace/functions/jobs/{jobId}. Validation and
BeforeSaveMutation phases reject the facade with
moltaro.automation.executionPhase.unsupported.
Tag ownership and feature flags
Section titled “Tag ownership and feature flags”EntityDefinition.TagsEnabled and Board.TagsEnabled are independent.
The first controls tags whose owner is an EntityInstance; the second controls
tags whose owner is a BoardItem. An Entity-backed Board does not inherit the
Entity Definition setting, and enabling one does not enable the other.
Ordinary tag mutation accepts an authorized open BoardItem. A terminal item
returns a correlated safe Problem Details response with code
moltaro.tags.boardItem.terminalRequiresHistoricalOperation and metadata that
names ReconcileHistoricalItemTagsAsync. Disabled Entity and Board owners use
moltaro.tags.entity.disabled and moltaro.tags.board.disabled; permission
and unknown-owner failures use moltaro.tags.access.denied and
moltaro.tags.resource.notFound. Consult the installation-local OpenAPI for
the exact HTTP schema.
Historical Board import
Section titled “Historical Board import”Use ImportHistoricalItemsAsync for an initial or corrective import whose
source provides a stable per-item identity. One call accepts 1–500 ordered
items; each item commits in its own short transaction, so the batch can return
both successes and stable item errors. SourceKey is case-sensitive, at most
128 characters, and cannot contain whitespace or control characters. Moltaro
stores one durable identity per Board and source key. Ordinary historical
import does not adopt a Board item that already exists without that identity.
The item’s DataRecordId is fixed when the item is created; reconciliation
updates fields in that same Board Data record and rejects attempts to replace
the record identity.
Create requests may start directly in a terminal status. IsOpen comes only
from the configured status type; a terminal item defaults CompletedAtUtc to
its effective entered time and rejects completion before that time, while a
non-terminal item rejects a completed timestamp. Reconciliation applies the
same chronology rule after preserving omitted timestamps. Null content and due
values mean “absent” on create and “preserve” on reconcile. A JSON null inside
a supplied Fields patch clears that one field.
An exact replay returns Unchanged without requiring a row version and writes no new
Board Data, item, history, audit, or resource event. A changed identity
requires its current ExpectedRowVersion; missing or stale tokens return a
conflict. After cancellation or an unknown response, call
GetHistoricalImportAsync(boardIdOrKey, sourceKey) or safely replay the whole
batch. Keep source keys stable across retries and inspect every ordered item
result, including FollowUpOperationIds.
Reconcile Board Data on terminal historical items
Section titled “Reconcile Board Data on terminal historical items”Use ReconcileHistoricalItemDataAsync when an already imported historical
item is terminal and only its Board-owned data is wrong. This is a trusted Net
Operation Project migration operation; it does not relax UpdateDataAsync or
the Runtime API rule that ordinary callers cannot edit a closed item.
One request accepts 1-500 ordered patches and commits each item independently.
Each patch must provide exactly one locator: either BoardItemId or the
case-sensitive historical SourceKey. The item must still belong to that
Board, have a durable BoardHistoricalImportIdentity, be non-removed, and be
closed in a terminal status. ExpectedRowVersion is mandatory for every
patch. A changed patch with a stale token is rolled back; an exact replay is
recognized before the token check, returns Unchanged with the current row
version, and remains write-free.
Omit Subject or Description to preserve it. Supply
new BoardHistoricalTextPatch(null) to clear it, or wrap a string to replace
it. Fields accepts only fields from the Board-owned Part definition; JSON
null clears one supplied Part field. The normal contained-Entity path still
enforces field schema, rules, audit, presentation/cache maintenance, and the
Board UpdateData business invariant.
A successful change keeps the same Board item and Board Data record and
changes only Board Data plus the Board item’s RowVersion/ordinary modified
stamp. Status, open/closed state, rank, run, entered/completed/due timestamps,
target, responsibilities, move history, transition history, and Board
lifecycle audit are not rewritten. The Board Data audit diff records
moltaro-system-automation and preserves the original function run, function
version, original user, and correlation metadata. Inspect every ordered item
result; after an unknown outcome, safely replay the same desired patch.
Reconcile tags on terminal historical items
Section titled “Reconcile tags on terminal historical items”Use ReconcileHistoricalItemTagsAsync only from trusted Net Operation Project
automation when a terminal BoardItem itself must own provenance or
classification tags. It does not relax the ordinary Tags API. One request
accepts 1-500 ordered items and commits each owner independently; inspect
SucceededCount, FailedCount, and every item error because there is no
cross-owner transaction.
Each item provides exactly one BoardItemId or case-sensitive historical
SourceKey, plus ExpectedRowVersion. Add creates only missing names and
leaves an existing same-name tag unchanged. ReplaceManagedSet requires the
complete ManagedTagNames allow-list owned by that synchronization and can
update or remove only those names; an empty desired Tags list removes all
currently stored names in that managed set. All tags outside the managed set are
preserved, including manual and unrelated automation tags. Exact replay
returns Unchanged without writes even after the supplied token became stale;
a real change with a stale token fails.
The operation requires an active, tag-enabled Board and a non-removed item in
a terminal status. It writes tags through the governed tag service as
moltaro-system-automation; tag events and Board audit preserve function,
run, version, original-user, and correlation metadata. It never reopens or
moves the item and does not change Board Data, status, CompletedAtUtc, rank,
run, history, or Board item RowVersion. Read tags back with ITagService or
the ordinary resource-list Tags API.
Recover identity for a pre-existing Board item
Section titled “Recover identity for a pre-existing Board item”Use the separate recovery contract only for an Entity Definition target that
was already tracked before its historical SourceKey was available:
- Call
GetHistoricalRecoveryStateAsyncwith the exact Board, case-sensitive source key, and target. - Use exactly the returned recommendation:
AdoptExistingItemfor one non-removed unbound pass, orCreateAfterRemovedItemfor one removed unbound pass. - Pass the returned
BoardItemIdandRowVersionunchanged toRecoverHistoricalItemAsync, together with the desiredImportHistoricalBoardItemRequest. - Handle
Adopted,Created,Updated, orUnchanged. - After an unknown result, retry the same guarded request or call
GetHistoricalImportAsyncfor the source key.
Adoption preserves the existing Board item and Board Data ids, comments,
attachments, responsibilities, links, history, audit, and origin metadata.
Recovery after removal leaves the removed pass untouched and creates
RunNumber = removed.RunNumber + 1 with PreviousBoardItemId = null.
Multiple prior passes are ambiguous. Self-contained and Entitlement targets
are not supported by this recovery contract.
The older IBoardRuntimeCommandService and
IBoardRuntimeDefinitionProvider are advanced TrustedDirectDb interfaces;
they do not promise the complete application/WebApp orchestration above. See
the exact C# Boards recipes
and inspect the installation’s developer-surface response before injecting
a service. A direct package host that enables event Board Constraints must
register an IBoardConstraintRuntimeEvaluator through
AddMoltaroBoardsConstraintRuntimeEvaluator<TEvaluator>(); the package default
fails closed when an enabled Binding applies and no production evaluator is
available. That public custom-evaluator contract receives Board event plans;
proposed-state status-invariant evaluation is supplied by the Moltaro
application host to generated Package and Net Operation Project contexts. A
standalone package-only host fails closed for applicable status invariants.
Authoritative mutations invoke EvaluateAuthoritativeBatchAsync after taking
their governance locks. A cache-backed custom evaluator must override that
method and reload its applicable configuration and compiled plans inside the
call; delegating to EvaluateBatchAsync is safe only for uncached evaluation
sources.
Step 1 — Discover boards
Section titled “Step 1 — Discover boards”GET /api/workspace/boards
(operation)
returns the active boards the caller may see:
curl -s https://ops.example.com/api/workspace/boards \ -H "Authorization: Bearer <token>"{ "Data": [ { "Id": "zuq5JQx4kLF1", "Key": "support_triage", "DisplayName": "Support triage", "Description": "Incoming ticket triage and resolution.", "TagsEnabled": false, "DueDateEnabled": false } ], "Success": true}Item endpoints accept either the board Id or the Key as
{boardIdOrKey}.
Step 2 — Read the create context
Section titled “Step 2 — Read the create context”GET /api/workspace/boards/{boardIdOrKey}/items/create-context
(operation)
is the discovery step before creating anything: it lists the board’s target
definitions (with the full create-form model per target), the statuses, and
what the caller may do:
curl -s https://ops.example.com/api/workspace/boards/support_triage/items/create-context \ -H "Authorization: Bearer <token>"{ "Data": { "Board": { "Id": "zuq5JQx4kLF1", "Key": "support_triage", "DisplayName": "Support triage" }, "Statuses": [ { "Id": "2FEzzRX4vMNg", "DisplayName": "Triage", "MetaType": 0, "SortOrder": 1 }, { "Id": "x2lpSYpWQ3q6", "DisplayName": "In progress", "MetaType": 1, "SortOrder": 2 }, { "Id": "gc3NgI61wO4D", "DisplayName": "Resolved", "MetaType": 2, "SortOrder": 3 } ], "TargetOptions": [ { "TargetDefinition": { "Id": "ojmxCpYFwfCY", "TargetKind": 0, "TargetModelId": "pYZqCLZWb0K1", "DisplayNameSingular": "Support ticket", "CanAdd": true, "CanMove": true } } ] }, "Success": true}An agent should treat this response the way it treats an entity schema: read it first, then act on what it declares.
Step 3 — Create an item
Section titled “Step 3 — Create an item”POST /api/workspace/boards/{boardIdOrKey}/items/create
(operation)
creates the record and its board item in one atomic call: TargetCreate
carries the record fields (same write shapes as
record creation),
BoardData carries the process context:
curl -s -X POST https://ops.example.com/api/workspace/boards/support_triage/items/create \ -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \ -d '{ "BoardTargetDefinitionId": "ojmxCpYFwfCY", "TargetCreate": { "Fields": { "Title": "VPN drops every hour", "Priority": "high" } }, "BoardData": { "Subject": "VPN drops every hour" } }'{ "Data": { "Id": "1WtfVd0cpu9x", "BoardId": "zuq5JQx4kLF1", "Target": { "BoardTargetDefinitionId": "ojmxCpYFwfCY", "TargetKind": 0, "TargetModelId": "pYZqCLZWb0K1", "TargetObjectId": "89aed6c628e54e2ab4a782ae30fb156d" }, "BoardStatusId": "2FEzzRX4vMNg", "IsOpen": true, "RowVersion": "6f2acd60-ac51-4adb-ac19-49784f243436", "Actions": { "CanMove": true, "CanReadHistory": true, "CanEditBoardData": true, "CanViewAuditTrail": true }, "Card": { "Number": 1, "Subject": "VPN drops every hour", "BoardStatusDisplayName": "Triage" } }, "Success": true}The new record exists as a normal SupportTicket
(Target.TargetObjectId), and the item landed in the board’s initial
status. To put an existing record on the board instead, use
add-context
to preview admission and
POST /items
to add it.
Position a new item safely
Section titled “Position a new item safely”Position is optional on POST /api/workspace/boards/{boardIdOrKey}/items.
When it is omitted, Moltaro appends the new item to the destination status.
Explicit boundaries describe the current order in that same board, status,
and cycle:
PreviousBoardItemIdalone inserts after the current last item;NextBoardItemIdalone inserts before the current first item;- both ids insert between two items that are currently adjacent.
Read the current board order immediately before constructing explicit
boundaries. A removed item, an item from another board/status/cycle, a stale
edge, or a non-adjacent pair returns HTTP 400 with the stable code
moltaroBoards.runtime.validation.positionInvalid; re-read the board and
decide the position again. Do not retry the same stale payload unchanged.
{ "TargetKind": 0, "TargetModelId": "pYZqCLZWb0K1", "TargetObjectId": "89aed6c628e54e2ab4a782ae30fb156d", "RepeatMode": 1, "Position": { "PreviousBoardItemId": "item-before", "NextBoardItemId": "item-after" }}Step 4 — Read move options and validate
Section titled “Step 4 — Read move options and validate”The server computes what moves are allowed — the transition graph, permission checks, and process rules all apply. Never hardcode status flows; ask:
curl -s https://ops.example.com/api/workspace/boards/support_triage/items/1WtfVd0cpu9x/move-options \ -H "Authorization: Bearer <token>"{ "Data": { "BoardItemId": "1WtfVd0cpu9x", "FromBoardStatusId": "2FEzzRX4vMNg", "Options": [ { "BoardStatusId": "2FEzzRX4vMNg", "DisplayName": "Triage", "Allowed": true, "BoardTransitionId": null, "Errors": [] }, { "BoardStatusId": "x2lpSYpWQ3q6", "DisplayName": "In progress", "Allowed": true, "BoardTransitionId": "qRO10Ii6gAbn", "Errors": [] }, { "BoardStatusId": "gc3NgI61wO4D", "DisplayName": "Resolved", "Allowed": false, "BoardTransitionId": null, "Errors": [ { "Code": "moltaroBoards.runtime.transitionNotFound", "Message": "This card cannot be moved to that status." } ] } ] }, "Success": true}Resolved is blocked because the board defines no Triage → Resolved
transition. POST .../move/validate
(operation)
runs the same checks for one concrete move as a dry run — no state change:
curl -s -X POST https://ops.example.com/api/workspace/boards/support_triage/items/1WtfVd0cpu9x/move/validate \ -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \ -d '{ "ToBoardStatusId": "gc3NgI61wO4D" }'{ "Data": { "Allowed": false, "BoardItemId": "1WtfVd0cpu9x", "FromBoardStatusId": "2FEzzRX4vMNg", "ToBoardStatusId": "gc3NgI61wO4D", "BoardTransitionId": null, "Errors": [ { "Code": "moltaroBoards.runtime.transitionNotFound", "Message": "This card cannot be moved to that status." } ] }, "Success": true}Note the envelope: Success: true because the validation itself succeeded —
the verdict lives in Data.Allowed and Data.Errors.
The validation result also returns CapturedAtUtc and ordered
ConstraintDecisions when Board Constraints were evaluated. Execute promptly
with the same RowVersion. The move command rebuilds the authoritative plan
under its lock; preview is not a reservation. For the same row versions and
proposed state, preview and execute use the same Business Invariant and Board
Constraint preparation path and return the same ordered failures.
The move-options endpoint evaluates all destination candidates as one request-local batch. Board definition lookups, Constraint configuration, compiled plans, and bounded entity dependencies are shared across candidates; the runtime does not repeat full validation once per status.
When a transition has required item links, its move option and validation
result include LinkPrerequisites. Each row reports the configured scope,
CurrentCount, MissingCount, and Satisfied. Only active links to
non-removed items in the exact configured Board target definition count.
Missing links return moltaroBoards.runtime.moveLinkPrerequisiteMissing.
The move request can satisfy missing rows with LinkPrerequisiteSelections.
Select an existing readable item with OtherBoardItemId, or atomically create
a target Board item with CreateItem; supply exactly one per selection:
{ "ToBoardStatusId": "x2lpSYpWQ3q6", "RowVersion": "6f2acd60-ac51-4adb-ac19-49784f243436", "LinkPrerequisiteSelections": [ { "BoardTransitionLinkPrerequisiteId": "required-parent", "OtherBoardItemId": "parent-board-item-id" } ]}Moltaro derives the link kind and direction from configuration; clients cannot
override them. The selected item must match the configured Board and target
definition, normal read/link permissions and link invariants still apply, and
link creation plus movement commit in one transaction. With CreateItem, the
new target, its Board Data, the link, and the move all commit or roll back
together.
Step 5 — Move the item
Section titled “Step 5 — Move the item”POST /api/workspace/boards/{boardIdOrKey}/items/{boardItemId}/move
(operation)
executes the move; send the RowVersion you last saw, exactly like a record
update:
curl -s -X POST https://ops.example.com/api/workspace/boards/support_triage/items/1WtfVd0cpu9x/move \ -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \ -d '{ "ToBoardStatusId": "x2lpSYpWQ3q6", "RowVersion": "6f2acd60-ac51-4adb-ac19-49784f243436" }'{ "Data": { "Id": "1WtfVd0cpu9x", "BoardStatusId": "x2lpSYpWQ3q6", "IsOpen": true, "RowVersion": "738941bd-5d82-4013-b64d-0063012b83c9", "Card": { "Number": 1, "Subject": "VPN drops every hour", "BoardStatusDisplayName": "In progress" } }, "Success": true}A stale RowVersion fails the move instead of silently overriding a
concurrent change — keep the value returned by each mutation.
Search board items through live Entity search targets
Section titled “Search board items through live Entity search targets”POST /api/workspace/boards/{boardIdOrKey}/items/search combines SearchText
with status, cycle, target, Board Data, Target Data, responsibility, tag, due
date, and link filters. The filters narrow the Board candidate set; SearchText
then matches Board-owned text and identity or the live target projection.
For an Entity target, Moltaro uses that definition’s current configured
SearchTargets, including supported direct and related fields, table paths,
number, comments, and attachments. The same access-aware Entity search SQL used
by record lists is applied in bulk to target ids already present on the Board.
There is no Board-side copy of Entity content, synchronization job, item reopen,
or Board rebuild. Updating SearchTargets changes the next Board search after
the normal definition snapshot refresh.
Entity content contributes a match only when the caller can normally read the
root record and every protected linked path involved in that configured target.
Restricted text cannot change the returned rows or TotalCount. Each Board
target definition is evaluated separately, so equal target ids from different
Entity definitions cannot cross-match. If SearchTargets is empty, configured
Entity content does not match; stable target identity, readable target
presentation, and Board-owned text remain available as compatibility fallbacks.
Cursor paging and the other Board filters are unchanged.
Boards does not copy Entity text into Board Data and does not own a separate
search index, reconciliation task, or reindex/rebuild operation. Each request
reads the current Entity PostgreSQL storage using the same parameterized
search/index contract as Data Explorer. Candidate ids are processed in bounded
keyset batches; each match batch is written to request-local indexed PostgreSQL
temporary tables and released from application memory. Count and page queries
join those tables, while Board Data uses keyset batches without repeated count
or OFFSET queries. The complete search, including Count and page materialization,
has a 30-second deadline. If a target provider fails or any stage times out,
search fails closed with
moltaroBoards.runtime.searchProviderUnavailable; Moltaro does not return a
partial page or partial TotalCount, and diagnostics never include SearchText.
Search link candidates within an explicit scope
Section titled “Search link candidates within an explicit scope”POST /api/workspace/boards/{boardIdOrKey}/items/{boardItemId}/link-candidates
returns non-removed items that the caller may read and may link from both
endpoints. Use BoardTargetDefinitionIds whenever the workflow already knows
which target types are valid:
curl -s -X POST https://ops.example.com/api/workspace/boards/support_triage/items/1WtfVd0cpu9x/link-candidates \ -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \ -d '{ "SearchText": "VPN", "BoardTargetDefinitionIds": ["ojmxCpYFwfCY"], "LinkType": 1, "Direction": 0, "Limit": 10 }'BoardTargetDefinitionIds scopes the complete search pipeline, not only the
last result filter. Moltaro invokes target search only for the supplied active
definitions, searches Board Data only for their owning active Boards, and
queries final candidates only from those definitions. An unknown or inactive
id fails with the stable
moltaroBoards.runtime.targetDefinitionNotFound error. Read access,
ManageItemLinks, Business Invariants, duplicate-link rules, and Child or
Related cycle rules still apply to every returned candidate; inaccessible
items do not disclose target metadata.
Omitting BoardTargetDefinitionIds intentionally keeps workspace-wide search
across active Boards and target definitions. Each provider and Board Data
presearch is bounded before final candidate materialization. This bounded
workspace-wide search returns a limited page; clients must not expect every
match to be loaded.
The endpoint also supports free text and exact #RunNumber lookup. Caller
cancellation and the HTTP request deadline propagate through provider and
database work; after an unknown or cancelled response, issue a new bounded
search instead of assuming that a background search continues.
Where to go next
Section titled “Where to go next”- Process collaboration — per-item comments, attachments, and the process activity feed.
- History and audit — per-item history and audit trail; see also Record history and audit.
- Item links, responsibilities, due dates, and cycles — the rest of the Boards runtime tag.