Skip to main content

Instance Runtime

Instance Lifecycle

submit → Running → approve/reject → Approved/Rejected
→ withdraw → Withdrawn
→ rollback → Returned
→ terminate → Terminated
Withdrawn/Returned → resubmit → Running (again)

The runtime state machine declares only these valid instance transitions:

FromTo
runningapproved
runningrejected
runningwithdrawn
runningterminated
runningreturned
returnedrunning
returnedterminated
returnedwithdrawn
withdrawnrunning
withdrawnterminated

Instance Statuses

StatusConstantWire valueFinal?
RunningInstanceRunningrunningNo
ApprovedInstanceApprovedapprovedYes
RejectedInstanceRejectedrejectedYes
WithdrawnInstanceWithdrawnwithdrawnNo
ReturnedInstanceReturnedreturnedNo
TerminatedInstanceTerminatedterminatedYes

The enum type is InstanceStatus.

Task Statuses

StatusConstantWire valueFinal?
WaitingTaskWaitingwaitingNo
PendingTaskPendingpendingNo
ApprovedTaskApprovedapprovedYes
RejectedTaskRejectedrejectedYes
HandledTaskHandledhandledYes
TransferredTaskTransferredtransferredYes
Rolled BackTaskRolledBackrolled_backYes
CanceledTaskCanceledcanceledYes
RemovedTaskRemovedremovedYes
SkippedTaskSkippedskippedYes

The runtime state machine declares only these valid task transitions:

FromTo
waitingpending
waitingcanceled
waitingskipped
waitingremoved
pendingapproved
pendinghandled
pendingrejected
pendingtransferred
pendingrolled_back
pendingcanceled
pendingwaiting
pendingremoved

Actions

ActionConstantWire valueDescription
SubmitActionSubmitsubmitStart a new instance
ApproveActionApproveapproveApprove a task
HandleActionHandlehandleComplete a handle task
RejectActionRejectrejectReject a task
TransferActionTransfertransferTransfer to another user
WithdrawActionWithdrawwithdrawApplicant withdraws
CancelActionCancelcancelCancel a task
RollbackActionRollbackrollbackRoll back to a previous node
Add AssigneeActionAddAssigneeadd_assigneeDynamically add an assignee
Remove AssigneeActionRemoveAssigneeremove_assigneeRemove an assignee
Add CCActionAddCCadd_ccDynamically add CC recipients
ExecuteActionExecuteexecuteInternal execution action for automatic node handling
ResubmitActionResubmitresubmitResubmit a returned or withdrawn instance
ReassignActionReassignreassignAdmin reassigns a task
TerminateActionTerminateterminateAdmin force-terminates

Rollback Configuration

PropertyOptions
RollbackTypeRollbackNone (none), RollbackPrevious (previous), RollbackStart (start), RollbackAny (any), RollbackSpecified (specified)
RollbackDataStrategyRollbackDataClear (clear, reset form), RollbackDataKeep (keep, preserve data)

Same-applicant handling uses SameApplicantAction with SameApplicantSelfApprove (self_approve), SameApplicantAutoPass (auto_pass), and SameApplicantTransferSuperior (transfer_superior). Consecutive-approver handling uses ConsecutiveApproverAction with ConsecutiveApproverNone (none) and ConsecutiveApproverAutoPass (auto_pass).

Empty Assignee Handling

When no assignee is found for a node:

ActionConstantWire value
Auto-passEmptyAssigneeAutoPassauto_pass
Transfer to adminEmptyAssigneeTransferAdmintransfer_admin
Transfer to superiorEmptyAssigneeTransferSuperiortransfer_superior
Transfer to applicantEmptyAssigneeTransferApplicanttransfer_applicant
Transfer to specifiedEmptyAssigneeTransferSpecifiedtransfer_specified

The enum type is EmptyAssigneeAction.

Timeout Handling

ActionConstantWire valueBehavior
NoneTimeoutActionNonenoneMark timeout only
Auto PassTimeoutActionAutoPassauto_passAutomatically approve
Auto RejectTimeoutActionAutoRejectauto_rejectAutomatically reject
NotifyTimeoutActionNotifynotifySend notification only
Transfer AdminTimeoutActionTransferAdmintransfer_adminTransfer to node admin

The enum type is TimeoutAction.

Form Data Storage

ModeConstantWire valueLocation
JSONStorageJSONjsonapv_instance.form_data (JSONB column)
TableStorageTabletablea generated physical table per published version, with apv_instance.form_data still populated as the canonical JSON snapshot

StorageMode.IsValid() accepts both exported modes. Table mode records generated DDL metadata through FormTable and FormTableColumn.

Table Storage Metadata

When a published version uses StorageTable, the framework exposes two public metadata models:

ModelPurposeKey JSON fields
FormTableone generated physical table (main projection or detail-table child)flowId, versionId, physicalTableName, sourceFieldKey
FormTableColumnone generated column per form field or built-in columnformTableId, columnName, columnType, isNullable, sourceFieldKey, sortOrder

FormTable is the single source of truth for the DDL the framework generated: the engine consults it for idempotency before creating a table, and operators can map a version to its projection tables through it. FormTable.SourceFieldKey is "" for the version's main projection table, or the owning table field's key for a detail-table child projection; (versionId, sourceFieldKey) is unique. ColumnDataType is the logical field-to-column vocabulary used by form definitions before the storage layer maps it to dialect-specific SQL types.

Generated Table Layout

Publishing a table-mode version provisions one main projection table plus one child table per detail-table (table kind) field (v0.36):

TablePhysical nameBuilt-in columnsField columns
main projectionapv_form_<code>_<versionId> (sanitized flow code, truncated to the 63-char identifier cap; falls back to apv_form_<versionId>)id (PK), instance_id (UNIQUE), created_at lastone column per scalar field, in declared order
detail-table childapv_form_<versionId>__<fieldKey suffix>id (PK), instance_id (indexed, NOT unique), row_index, created_at lastone column per table-field column, in declared order

Every physical table and column name is validated as a safe SQL identifier before it reaches a DDL/DML string, field keys must not collide with the built-in column names, and every form value is bound as a ? argument — never interpolated. CREATE TABLE IF NOT EXISTS runs outside the publish transaction (DDL implicitly commits the in-flight transaction on MySQL) and is idempotent; the FormTable / FormTableColumn metadata is recorded inside the publish transaction so it commits or rolls back with the version's published state.

At write time the projection is replace-never-append: the main table holds exactly one row per instance (backed by the instance_id UNIQUE constraint), a child table holds one row per detail line ordered by row_index, and each projection (at start and at every resubmit) deletes the instance's existing rows before inserting fresh ones — the tables reflect current form data, never an accumulating history.

Instance Progress Projections

Admin and user instance-detail responses expose two read-only projections beside the instance snapshot:

ProjectionPublic typesPurpose
timelineTimelineEntryKind, TimelineEntry, NodeVisitStatus, NodeParticipant, Activity, ActivityUrge, CCRecipientchronological account of the path the instance actually took
flow graphNodeProgressStatus, InstanceFlowGraph, FlowGraphNode, FlowGraphNodeData, FlowGraphEdgeReact Flow-compatible graph annotated with runtime progress

FlowGraphNode.ID is the React Flow design-time node id. FlowGraphNode.NodeID is the persistent flow-node id used by action logs and rollback targets.

Progress and timeline enums are exported explicitly:

EnumConstants
TimelineEntryKindTimelineEntryStart, TimelineEntryApproval, TimelineEntryHandle, TimelineEntryCC, TimelineEntryWithdraw, TimelineEntryTerminate
NodeVisitStatusNodeVisitActive, NodeVisitPassed, NodeVisitRejected, NodeVisitReturned, NodeVisitCanceled
NodeProgressStatusNodeProgressPending, NodeProgressActive, NodeProgressPassed, NodeProgressRejected, NodeProgressReturned, NodeProgressCanceled

The persisted visit model behind these projections is NodeVisit.

Instance Number Generation

Implement the InstanceNoGenerator interface to customize instance numbering:

type InstanceNoGenerator interface {
Generate(ctx context.Context, flowCode string) (string, error)
}

Next: Events & Integration for reacting to lifecycle transitions from host code.