approval

package
v0.31.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 17, 2026 License: Apache-2.0 Imports: 13 Imported by: 0

Documentation

Index

Constants

View Source
const (
	EventTypeInstanceCreated       = "approval.instance.created"
	EventTypeInstanceCompleted     = "approval.instance.completed"
	EventTypeInstanceWithdrawn     = "approval.instance.withdrawn"
	EventTypeInstanceRolledBack    = "approval.instance.rolled_back"
	EventTypeInstanceReturned      = "approval.instance.returned"
	EventTypeInstanceResubmitted   = "approval.instance.resubmitted"
	EventTypeInstanceBindingFailed = "approval.instance.binding_failed"

	EventTypeNodeAutoPassed = "approval.node.auto_passed"

	EventTypeTaskCreated         = "approval.task.created"
	EventTypeTaskApproved        = "approval.task.approved"
	EventTypeTaskHandled         = "approval.task.handled"
	EventTypeTaskRejected        = "approval.task.rejected"
	EventTypeTaskCanceled        = "approval.task.canceled"
	EventTypeTaskTransferred     = "approval.task.transferred"
	EventTypeTaskReassigned      = "approval.task.reassigned"
	EventTypeTaskTimedOut        = "approval.task.timed_out"
	EventTypeAssigneesAdded      = "approval.task.assignees_added"
	EventTypeAssigneesRemoved    = "approval.task.assignees_removed"
	EventTypeTaskDeadlineWarning = "approval.task.deadline_warning"
	EventTypeTaskUrged           = "approval.task.urged"

	EventTypeCCNotified = "approval.cc.notified"

	EventTypeFlowCreated   = "approval.flow.created"
	EventTypeFlowUpdated   = "approval.flow.updated"
	EventTypeFlowDeployed  = "approval.flow.deployed"
	EventTypeFlowToggled   = "approval.flow.toggled"
	EventTypeFlowPublished = "approval.flow.published"
)

Approval event type identifiers. Exposed as constants so framework callers (route inspection, subscription filters, metrics labels) can reference them by symbol rather than risking a typo on the wire string.

View Source
const (
	DefaultExecutionType             = ExecutionManual
	DefaultApprovalMethod            = ApprovalParallel
	DefaultPassRule                  = PassAll
	DefaultEmptyAssigneeAction       = EmptyAssigneeAutoPass
	DefaultSameApplicantAction       = SameApplicantSelfApprove
	DefaultConsecutiveApproverAction = ConsecutiveApproverNone
	DefaultRollbackType              = RollbackPrevious
	DefaultRollbackDataStrategy      = RollbackDataKeep
	DefaultTimeoutAction             = TimeoutActionNone
	DefaultCCTiming                  = CCTimingAlways

	// Handle nodes default to sequential execution with the PassAny rule,
	// since any handler completing the task is sufficient.
	DefaultHandleApprovalMethod = ApprovalSequential
	DefaultHandlePassRule       = PassAny

	// DefaultUrgeCooldownMinutes is the urge cooldown applied when a node
	// leaves UrgeCooldownMinutes at 0. The flow editor surfaces the same
	// value in its placeholder text; keep the two in lockstep.
	DefaultUrgeCooldownMinutes = 30
)

Designer-aligned defaults resolved by ApplyTo when a field is omitted from the node data payload. The flow editor displays exactly these values for untouched controls, so resolving them here keeps "what the designer shows" and "what the engine runs" identical by construction.

View Source
const DefaultTenantID = "default"

DefaultTenantID is the tenant identifier used when a caller does not carry an explicit tenant — the conventional single-tenant deployment value.

View Source
const SuperAdminRole = "approval:super_admin"

SuperAdminRole is the role string that grants cross-tenant access to admin queries and operations. Hosts assign this role to platform-level operators that legitimately need to act across tenants (audit teams, billing, etc.). Without it, admin endpoints reject requests that lack a tenant filter — guarding against accidental cross-tenant data exposure.

Variables

View Source
var (
	ErrUnknownNodeKind   = errors.New("unknown node kind")
	ErrNodeDataUnmarshal = errors.New("node data unmarshal failed")
)
View Source
var ErrCrossTenantAccess = errors.New("approval: cross-tenant access denied")

ErrCrossTenantAccess is returned when a non-super-admin caller attempts to act on an entity owned by a different tenant. Resource and command handlers use CallerContext.Authorize to surface it consistently.

View Source
var ErrInvalidBusinessIdentifier = errors.New("approval: invalid business identifier (must match ^[A-Za-z_][A-Za-z0-9_]{0,62}$)")

ErrInvalidBusinessIdentifier is returned by ValidateBusinessIdentifier for values that do not match a SQL-safe identifier pattern. Hosts that implement BusinessBindingHook should bubble this up (or wrap it) so admin-side flow CRUD surfaces a meaningful error to operators.

View Source
var SystemCaller = CallerContext{IsSystemInternal: true}

SystemCaller is the canonical CallerContext for callers that bypass tenant scoping by carrying IsSystemInternal. In the current tree its only consumers are test fixtures (production system paths operate on trusted, pre-scoped rows without a CallerContext); use it instead of the zero value so the bypass intent is explicit at the call site.

Functions

func AllEventTypes added in v0.29.0

func AllEventTypes() []string

AllEventTypes returns every approval domain event type identifier. It is the canonical, enumerable registry of the event surface: callers that must stay exhaustive over the event set (e.g. the start-up routing check, which asserts each transactional event resolves to a transactional transport) derive from this list so adding a new event constant cannot silently slip past them. Keep this in sync with the EventType* constants above — the only reason a new constant would be omitted is a deliberate, reviewed decision.

func IsSuperAdmin added in v0.24.0

func IsSuperAdmin(p *security.Principal) bool

IsSuperAdmin reports whether the principal carries the cross-tenant override role. Nil principal returns false.

func PayloadOccurredAt added in v0.24.0

func PayloadOccurredAt(e DomainEvent) timex.DateTime

PayloadOccurredAt extracts the OccurredTime field from a DomainEvent payload. Every in-tree approval event struct carries this field; publishers use the value to project business time onto Envelope.OccurredAt via event.WithOccurredAt. Returns the zero DateTime for payloads that lack the field (defensive — should never happen for in-tree events).

Exposed as a package-level helper rather than a method on DomainEvent so the interface can stay minimal (EventType only) while still letting transports / behaviors project business time.

func ValidateBusinessIdentifier added in v0.24.0

func ValidateBusinessIdentifier(id string) error

ValidateBusinessIdentifier reports whether id is a safe SQL identifier for use as a table or column name in business binding interpolation. Empty / whitespace-only strings pass — the caller decides whether absence is itself an error (see Flow validation paths for the policy).

Types

type ActionLog

type ActionLog struct {
	orm.BaseModel `bun:"table:apv_action_log,alias:aal"`
	orm.Model
	orm.CreationTrackedModel

	InstanceID             string           `json:"instanceId" bun:"instance_id"`
	NodeID                 *string          `json:"nodeId" bun:"node_id,nullzero"`
	TaskID                 *string          `json:"taskId" bun:"task_id,nullzero"`
	Action                 ActionType       `json:"action" bun:"action"`
	OperatorID             string           `json:"operatorId" bun:"operator_id"`
	OperatorName           string           `json:"operatorName" bun:"operator_name"`
	OperatorDepartmentID   *string          `json:"operatorDepartmentId" bun:"operator_department_id,nullzero"`
	OperatorDepartmentName *string          `json:"operatorDepartmentName" bun:"operator_department_name,nullzero"`
	IPAddress              *string          `json:"ipAddress" bun:"ip_address,nullzero"`
	UserAgent              *string          `json:"userAgent" bun:"user_agent,nullzero"`
	Opinion                *string          `json:"opinion" bun:"opinion,nullzero"`
	TransferToID           *string          `json:"transferToId" bun:"transfer_to_id,nullzero"`
	TransferToName         *string          `json:"transferToName" bun:"transfer_to_name,nullzero"`
	RollbackToNodeID       *string          `json:"rollbackToNodeId" bun:"rollback_to_node_id,nullzero"`
	AddAssigneeType        *AddAssigneeType `json:"addAssigneeType" bun:"add_assignee_type,nullzero"`
	AddedAssigneeIDs       []string         `json:"addedAssigneeIds" bun:"added_assignee_ids,type:jsonb"`
	RemovedAssigneeIDs     []string         `json:"removedAssigneeIds" bun:"removed_assignee_ids,type:jsonb"`
	CCUserIDs              []string         `json:"ccUserIds" bun:"cc_user_ids,type:jsonb"`
	Attachments            []string         `json:"attachments" bun:"attachments,type:jsonb,nullzero"`
	Meta                   map[string]any   `json:"meta" bun:"meta,type:jsonb,nullzero"`
}

ActionLog represents an action log entry.

type ActionType

type ActionType string

ActionType represents the type of action performed by an operator.

const (
	ActionSubmit         ActionType = "submit"
	ActionApprove        ActionType = "approve"
	ActionHandle         ActionType = "handle"
	ActionReject         ActionType = "reject"
	ActionTransfer       ActionType = "transfer"
	ActionWithdraw       ActionType = "withdraw"
	ActionCancel         ActionType = "cancel"
	ActionRollback       ActionType = "rollback"
	ActionAddAssignee    ActionType = "add_assignee"
	ActionRemoveAssignee ActionType = "remove_assignee"
	ActionExecute        ActionType = "execute"   // System execution action
	ActionResubmit       ActionType = "resubmit"  // Resubmit a returned instance
	ActionReassign       ActionType = "reassign"  // Admin reassigned task to a different user
	ActionTerminate      ActionType = "terminate" // Admin force-terminated an instance
	ActionAddCC          ActionType = "add_cc"    // Participant added CC recipients
)

type AddAssigneeType

type AddAssigneeType string

AddAssigneeType represents the type of dynamic assignee addition. It defines how a newly added assignee is positioned relative to the current task.

const (
	AddAssigneeBefore   AddAssigneeType = "before"   // Before: new assignee processes first, original task becomes pending after completion
	AddAssigneeAfter    AddAssigneeType = "after"    // After: new assignee processes after the original assignee completes
	AddAssigneeParallel AddAssigneeType = "parallel" // Parallel: new assignee joins the current parallel group to process together
)

func (AddAssigneeType) IsValid

func (t AddAssigneeType) IsValid() bool

IsValid checks if the AddAssigneeType is a valid value.

func (*AddAssigneeType) UnmarshalJSON

func (t *AddAssigneeType) UnmarshalJSON(data []byte) error

UnmarshalJSON validates AddAssigneeType values when decoding JSON payloads.

type ApprovalMethod

type ApprovalMethod string

ApprovalMethod represents the method of approval for a node with multiple assignees. It defines how the approval decision is made when there are multiple approvers.

const (
	ApprovalSequential ApprovalMethod = "sequential" // Sequential: approvers process one by one in order, all must approve
	ApprovalParallel   ApprovalMethod = "parallel"   // Parallel: approvers process simultaneously, decision based on consensus rules
)

func (ApprovalMethod) IsValid added in v0.29.0

func (m ApprovalMethod) IsValid() bool

IsValid reports whether the approval method is one of the defined values.

type ApprovalNodeData

type ApprovalNodeData struct {
	BaseNodeData
	TaskNodeData

	ApprovalMethod            ApprovalMethod            `json:"approvalMethod,omitempty"`
	PassRule                  PassRule                  `json:"passRule,omitempty"`
	PassRatio                 decimal.Decimal           `json:"passRatio"`
	SameApplicantAction       SameApplicantAction       `json:"sameApplicantAction,omitempty"`
	ConsecutiveApproverAction ConsecutiveApproverAction `json:"consecutiveApproverAction,omitempty"`
	RollbackType              RollbackType              `json:"rollbackType,omitempty"`
	RollbackDataStrategy      RollbackDataStrategy      `json:"rollbackDataStrategy,omitempty"`
	RollbackTargetKeys        []string                  `json:"rollbackTargetKeys,omitempty"`
	IsRollbackAllowed         *bool                     `json:"isRollbackAllowed,omitempty"`
	IsAddAssigneeAllowed      *bool                     `json:"isAddAssigneeAllowed,omitempty"`
	AddAssigneeTypes          []AddAssigneeType         `json:"addAssigneeTypes,omitempty"`
	IsRemoveAssigneeAllowed   *bool                     `json:"isRemoveAssigneeAllowed,omitempty"`
	IsManualCCAllowed         *bool                     `json:"isManualCcAllowed,omitempty"`
}

ApprovalNodeData contains data specific to approval nodes.

The Is*Allowed permission toggles are pointers because their default is true; see TaskNodeData for the rationale.

func (*ApprovalNodeData) ApplyTo

func (d *ApprovalNodeData) ApplyTo(node *FlowNode)

ApplyTo applies approval node data to a FlowNode, resolving omitted fields to the designer defaults. The target node is assumed to be freshly constructed (zero value); all fields are overwritten unconditionally as a full-snapshot deploy operation.

func (*ApprovalNodeData) Kind

func (*ApprovalNodeData) Kind() NodeKind

Kind returns the node kind.

type AssigneeDefinition

type AssigneeDefinition struct {
	Kind      AssigneeKind `json:"kind"`
	IDs       []string     `json:"ids,omitempty"`
	FormField *string      `json:"formField,omitempty"`
	SortOrder int          `json:"sortOrder"`
}

AssigneeDefinition represents an assignee configuration in the flow definition.

type AssigneeKind

type AssigneeKind string

AssigneeKind represents the kind of assignee.

const (
	AssigneeUser             AssigneeKind = "user"
	AssigneeRole             AssigneeKind = "role"
	AssigneeDepartment       AssigneeKind = "department"        // Department head
	AssigneeSelf             AssigneeKind = "self"              // Applicant themselves
	AssigneeSuperior         AssigneeKind = "superior"          // Direct superior
	AssigneeDepartmentLeader AssigneeKind = "department_leader" // Leaders of the applicant's own department (single level)
	AssigneeFormField        AssigneeKind = "form_field"        // Based on form field
)

func (AssigneeKind) IsValid added in v0.29.0

func (k AssigneeKind) IsValid() bool

IsValid reports whether the assignee kind is one of the defined values.

type AssigneeService

type AssigneeService interface {
	// GetSuperior returns the direct superior's user info for the given user.
	GetSuperior(ctx context.Context, userID string) (*UserInfo, error)
	// GetDepartmentLeaders returns the leader user info for the given department.
	GetDepartmentLeaders(ctx context.Context, departmentID string) ([]UserInfo, error)
	// GetRoleUsers returns all user info for users that have the given role.
	GetRoleUsers(ctx context.Context, roleID string) ([]UserInfo, error)
}

AssigneeService resolves approval assignees from organizational data (implemented by host app).

type AssigneesAddedEvent

type AssigneesAddedEvent struct {
	InstanceID    string            `json:"instanceId"`
	TenantID      string            `json:"tenantId"`
	NodeID        string            `json:"nodeId"`
	TaskID        string            `json:"taskId"`
	AddType       AddAssigneeType   `json:"addType"`
	AssigneeIDs   []string          `json:"assigneeIds"`
	AssigneeNames map[string]string `json:"assigneeNames"`
	OccurredTime  timex.DateTime    `json:"occurredTime"`
}

AssigneesAddedEvent fired when assignees are dynamically added.

func NewAssigneesAddedEvent

func NewAssigneesAddedEvent(instanceID, tenantID, nodeID, taskID string, addType AddAssigneeType, assigneeIDs []string, assigneeNames map[string]string) *AssigneesAddedEvent

func (*AssigneesAddedEvent) EventType added in v0.24.0

func (*AssigneesAddedEvent) EventType() string

type AssigneesRemovedEvent

type AssigneesRemovedEvent struct {
	InstanceID    string            `json:"instanceId"`
	TenantID      string            `json:"tenantId"`
	NodeID        string            `json:"nodeId"`
	TaskID        string            `json:"taskId"`
	AssigneeIDs   []string          `json:"assigneeIds"`
	AssigneeNames map[string]string `json:"assigneeNames"`
	OccurredTime  timex.DateTime    `json:"occurredTime"`
}

AssigneesRemovedEvent fired when assignees are dynamically removed.

func NewAssigneesRemovedEvent

func NewAssigneesRemovedEvent(instanceID, tenantID, nodeID, taskID string, assigneeIDs []string, assigneeNames map[string]string) *AssigneesRemovedEvent

func (*AssigneesRemovedEvent) EventType added in v0.24.0

func (*AssigneesRemovedEvent) EventType() string

type BaseNodeData

type BaseNodeData struct {
	Name        string  `json:"name,omitempty"`
	Description *string `json:"description,omitempty"`
}

BaseNodeData contains common fields shared across all node data types.

func (BaseNodeData) GetDescription

func (d BaseNodeData) GetDescription() *string

GetDescription returns the node description.

func (BaseNodeData) GetName

func (d BaseNodeData) GetName() string

GetName returns the node name.

type BindingMode

type BindingMode string

BindingMode represents the mode of binding with business data. It defines how the approval workflow stores and associates form data.

const (
	BindingStandalone BindingMode = "standalone" // Standalone: form data is stored in the approval workflow's own table
	BindingBusiness   BindingMode = "business"   // Business: links to existing business data table
)

type BusinessBindingHook added in v0.24.0

type BusinessBindingHook interface {
	// OnInstanceCreated returns the business primary key (BusinessRecordID)
	// to persist on the instance. Returning empty string indicates the host
	// has nothing to bind (engine stores nil).
	OnInstanceCreated(ctx context.Context, db orm.DB, flow *Flow, instance *Instance) (businessRecordID string, err error)
	// WriteBackStatus writes the final approval status back to the
	// business table. Called asynchronously from the binding Listener
	// after InstanceCompletedEvent fires. Implementations should be
	// idempotent — the listener may retry through the outbox.
	WriteBackStatus(ctx context.Context, db orm.DB, flow *Flow, instance *Instance, finalStatus InstanceStatus) error
}

BusinessBindingHook bridges the approval engine with the host application's business tables when Flow.BindingMode is BindingBusiness. It is narrowly scoped to the "approval row ↔ business row" plumbing; broader lifecycle extension goes through InstanceLifecycleHook instead.

Two lifecycle moments matter:

  • OnInstanceCreated runs inside the start_instance transaction so the host can resolve / create the business row and return the primary key that the engine stores in Instance.BusinessRecordID. Returning an error rolls back the entire instance creation.

  • WriteBackStatus runs asynchronously via the binding Listener (which subscribes to InstanceCompletedEvent) so the host can stamp the final approval decision onto its own business table. A non-nil error does NOT roll back the approval — the workflow has already decided. Instead the listener publishes InstanceBindingFailedEvent so the host can retry (saga / outbox compensation).

Hosts override the default implementation by binding their own BusinessBindingHook into the FX container, typically through vef.SupplyBusinessBindingHook.

type CCDefinition

type CCDefinition struct {
	Kind      CCKind   `json:"kind"`
	IDs       []string `json:"ids,omitempty"`
	FormField *string  `json:"formField,omitempty"`
	Timing    CCTiming `json:"timing,omitempty"`
}

CCDefinition represents a CC recipient in node data.

type CCKind

type CCKind string

CCKind represents the kind of CC recipient.

const (
	CCUser       CCKind = "user"
	CCRole       CCKind = "role"
	CCDepartment CCKind = "department"
	CCFormField  CCKind = "form_field"
)

func (CCKind) IsValid added in v0.29.0

func (k CCKind) IsValid() bool

IsValid reports whether the CC kind is one of the defined kinds.

type CCNodeData

type CCNodeData struct {
	BaseNodeData

	CCs                   []CCDefinition        `json:"ccs,omitempty"`
	IsReadConfirmRequired bool                  `json:"isReadConfirmRequired,omitempty"`
	FieldPermissions      map[string]Permission `json:"fieldPermissions,omitempty"`
}

CCNodeData contains data specific to CC nodes.

func (*CCNodeData) ApplyTo

func (d *CCNodeData) ApplyTo(node *FlowNode)

ApplyTo applies CC node data to a FlowNode.

func (*CCNodeData) GetCCs

func (d *CCNodeData) GetCCs() []CCDefinition

GetCCs returns the CC definitions from CCNodeData.

func (*CCNodeData) Kind

func (*CCNodeData) Kind() NodeKind

Kind returns the node kind.

type CCNotifiedEvent

type CCNotifiedEvent struct {
	InstanceID   string            `json:"instanceId"`
	TenantID     string            `json:"tenantId"`
	NodeID       string            `json:"nodeId"`
	CCUserIDs    []string          `json:"ccUserIds"`
	CCUserNames  map[string]string `json:"ccUserNames"`
	IsManual     bool              `json:"isManual"`
	OccurredTime timex.DateTime    `json:"occurredTime"`
}

CCNotifiedEvent fired when users are carbon-copied.

func NewCCNotifiedEvent

func NewCCNotifiedEvent(instanceID, tenantID, nodeID string, ccUserIDs []string, ccUserNames map[string]string, isManual bool) *CCNotifiedEvent

func (*CCNotifiedEvent) EventType added in v0.24.0

func (*CCNotifiedEvent) EventType() string

type CCRecord

type CCRecord struct {
	orm.BaseModel `bun:"table:apv_cc_record,alias:acr"`
	orm.Model
	orm.CreationTrackedModel

	InstanceID string          `json:"instanceId" bun:"instance_id"`
	NodeID     *string         `json:"nodeId" bun:"node_id,nullzero"`
	TaskID     *string         `json:"taskId" bun:"task_id,nullzero"`
	CCUserID   string          `json:"ccUserId" bun:"cc_user_id"`
	CCUserName string          `json:"ccUserName" bun:"cc_user_name"`
	IsManual   bool            `json:"isManual" bun:"is_manual"`
	ReadAt     *timex.DateTime `json:"readAt" bun:"read_at,nullzero"`
}

CCRecord represents a CC notification record.

type CCTiming

type CCTiming string

CCTiming represents the timing of CC notification.

const (
	CCTimingAlways    CCTiming = "always"     // Always: send CC regardless of result
	CCTimingOnApprove CCTiming = "on_approve" // OnApprove: send CC only when approved
	CCTimingOnReject  CCTiming = "on_reject"  // OnReject: send CC only when rejected
)

func (CCTiming) IsValid added in v0.29.0

func (t CCTiming) IsValid() bool

IsValid reports whether the CC timing is one of the defined values.

type CallerContext added in v0.24.0

type CallerContext struct {
	// TenantID is the caller's resolved tenant. Empty for super-admin or
	// for system-internal callers; must be non-empty for every authenticated
	// API request.
	TenantID string
	// IsSuperAdmin grants cross-tenant access regardless of TenantID.
	IsSuperAdmin bool
	// IsSystemInternal marks a caller without an HTTP / RPC principal whose
	// scope is established by other means, so Authorize passes
	// unconditionally. Resource paths must NEVER populate this. In the
	// current tree only test fixtures set it (the in-tree system paths —
	// timeout scanner, binding listener — act directly on already-loaded,
	// trusted rows and never construct a CallerContext); it remains the
	// intended marker for any host or future in-process system code that
	// legitimately needs to bypass tenant scoping.
	IsSystemInternal bool
}

CallerContext bundles the tenant authority of a single API call. Resource handlers resolve it from the security principal (via PrincipalTenantResolver + IsSuperAdmin) and pass it into commands / queries through their struct fields so handlers can enforce data ownership without re-parsing principal details.

Production code MUST populate exactly one of TenantID / IsSuperAdmin / IsSystemInternal. A zero-value CallerContext is treated as **unauthorized** so a forgotten resolver call surfaces as a deny rather than silently waving the request through — this is the deliberate fail- closed posture introduced after the security audit caught fail-open regressions in the default principal resolver.

func (CallerContext) Allows added in v0.24.0

func (c CallerContext) Allows(entityTenantID string) bool

Allows is the bool variant of Authorize. Query handlers use it when a failed authorization must mimic "not found" rather than surface an error — the typical multi-tenant pattern that avoids leaking entity existence across tenants.

func (CallerContext) Authorize added in v0.24.0

func (c CallerContext) Authorize(entityTenantID string) error

Authorize reports whether the caller is allowed to act on an entity owned by entityTenantID. Super-admin callers always pass; system-internal callers pass too (no HTTP principal exists, scope is enforced upstream). Every other caller must have a non-empty TenantID that matches the entity tenant exactly — zero-value contexts are rejected.

func (CallerContext) ResolveWriteTenant added in v0.29.0

func (c CallerContext) ResolveWriteTenant(clientTenant string) (string, error)

ResolveWriteTenant returns the tenant a new or mutated entity must be stamped with. Super-admin / system-internal callers may target any tenant, so the client-supplied value is honored verbatim; every other caller is pinned to their own tenant and fails closed with ErrCrossTenantAccess when they have none — preventing a non-privileged caller from writing into another tenant.

func (CallerContext) TenantScopeFilter added in v0.29.0

func (c CallerContext) TenantScopeFilter(override string) (*string, error)

TenantScopeFilter resolves the tenant filter for a LIST / metrics query, applying the same fail-closed posture as Authorize. A non-nil result scopes the query to that tenant; a nil result means unfiltered cross-tenant access and is only ever returned to super-admin / system-internal callers. An ordinary caller that resolves to an empty tenant fails closed with ErrCrossTenantAccess, so a forgotten or empty PrincipalTenantResolver result can never silently widen a list query to every tenant's rows — mirroring Authorize's rejection of a zero-value context. The override is honored only for privileged callers; ordinary callers are always pinned to their own tenant regardless of what the client sent. This is the single source of truth for list queries — the resource layer must never read params.TenantID directly.

type Condition

type Condition struct {
	Kind       ConditionKind     `json:"kind"`
	Subject    string            `json:"subject"`
	Operator   ConditionOperator `json:"operator"`
	Value      any               `json:"value"`
	Expression string            `json:"expression"`
}

Condition represents a branch condition evaluated by condition nodes.

type ConditionBranch

type ConditionBranch struct {
	ID              string           `json:"id"`
	Label           string           `json:"label"`
	ConditionGroups []ConditionGroup `json:"conditionGroups,omitempty"`
	IsDefault       bool             `json:"isDefault,omitempty"`
	Priority        int              `json:"priority"`
}

ConditionBranch represents a branch in a condition node. Each branch has its own condition groups and can be linked to an edge via its ID.

type ConditionEvaluator

type ConditionEvaluator interface {
	// Kind returns the condition kind this evaluator handles.
	Kind() ConditionKind
	// Evaluate evaluates a single condition against the given evaluation context.
	Evaluate(ctx context.Context, cond Condition, ec *EvaluationContext) (bool, error)
}

ConditionEvaluator evaluates branch conditions.

type ConditionGroup

type ConditionGroup struct {
	Conditions []Condition `json:"conditions"`
}

ConditionGroup represents a group of conditions evaluated with AND logic. Multiple groups in a branch are evaluated with OR logic.

type ConditionKind

type ConditionKind string

ConditionKind represents the kind of condition for condition branches.

const (
	ConditionField      ConditionKind = "field"      // Field-based condition
	ConditionExpression ConditionKind = "expression" // Expression-based condition
)

type ConditionNodeData

type ConditionNodeData struct {
	BaseNodeData

	Branches []ConditionBranch `json:"branches,omitempty"`
}

ConditionNodeData contains data specific to condition nodes.

func (*ConditionNodeData) ApplyTo

func (d *ConditionNodeData) ApplyTo(node *FlowNode)

ApplyTo applies condition node data to a FlowNode.

func (*ConditionNodeData) Kind

func (*ConditionNodeData) Kind() NodeKind

Kind returns the node kind.

type ConditionOperator added in v0.29.0

type ConditionOperator string

ConditionOperator enumerates the comparison operators a field condition may use. The set is the shared contract between the flow designer (which offers operators per field kind) and the engine's field-condition evaluator; both sides must stay in lockstep with this list.

const (
	OperatorEquals      ConditionOperator = "eq"
	OperatorNotEquals   ConditionOperator = "ne"
	OperatorGreater     ConditionOperator = "gt"
	OperatorGreaterOrEq ConditionOperator = "gte"
	OperatorLess        ConditionOperator = "lt"
	OperatorLessOrEq    ConditionOperator = "lte"
	OperatorIn          ConditionOperator = "in"
	OperatorNotIn       ConditionOperator = "not_in"
	OperatorContains    ConditionOperator = "contains"
	OperatorNotContains ConditionOperator = "not_contains"
	OperatorStartsWith  ConditionOperator = "starts_with"
	OperatorEndsWith    ConditionOperator = "ends_with"
	OperatorIsEmpty     ConditionOperator = "is_empty"
	OperatorIsNotEmpty  ConditionOperator = "is_not_empty"
)

func (ConditionOperator) IsValid added in v0.29.0

func (o ConditionOperator) IsValid() bool

IsValid reports whether the operator is one of the defined values.

type ConsecutiveApproverAction

type ConsecutiveApproverAction string

ConsecutiveApproverAction represents the action when the same approver appears in consecutive approval nodes and approved in the previous node.

const (
	ConsecutiveApproverNone     ConsecutiveApproverAction = "none"
	ConsecutiveApproverAutoPass ConsecutiveApproverAction = "auto_pass"
)

func (ConsecutiveApproverAction) IsValid added in v0.29.0

func (a ConsecutiveApproverAction) IsValid() bool

IsValid reports whether the consecutive-approver action is one of the defined values.

type Delegation

type Delegation struct {
	orm.BaseModel `bun:"table:apv_delegation,alias:ad"`
	orm.FullAuditedModel

	DelegatorID    string         `json:"delegatorId" bun:"delegator_id"`
	DelegateeID    string         `json:"delegateeId" bun:"delegatee_id"`
	FlowCategoryID *string        `json:"flowCategoryId" bun:"flow_category_id,nullzero"`
	FlowID         *string        `json:"flowId" bun:"flow_id,nullzero"`
	StartTime      timex.DateTime `json:"startTime" bun:"start_time"`
	EndTime        timex.DateTime `json:"endTime" bun:"end_time"`
	IsActive       bool           `json:"isActive" bun:"is_active"`
	Reason         *string        `json:"reason" bun:"reason,nullzero"`
}

Delegation represents an approval delegation.

type DomainEvent

type DomainEvent interface {
	// EventType returns the unique event identifier (e.g., "approval.instance.created").
	EventType() string
}

DomainEvent is the contract every approval domain event satisfies. EventType matches the framework's event.Event surface so domain events can be published through the event Bus without adaptation. Business time is carried as OccurredTime in the concrete payload and projected onto Envelope.OccurredAt via event.WithOccurredAt at publish time.

Tenant scope: every instance/task/node/cc-level event carries TenantID so subscribers can route on tenancy without re-querying. Flow-level events (created/updated/etc.) already include TenantID on the payload.

type EdgeDefinition

type EdgeDefinition struct {
	ID           string         `json:"id"`
	Source       string         `json:"source"`
	Target       string         `json:"target"`
	SourceHandle *string        `json:"sourceHandle,omitempty"`
	Data         map[string]any `json:"data,omitempty"`
}

EdgeDefinition represents a connection between nodes.

type EmptyAssigneeAction

type EmptyAssigneeAction string

EmptyAssigneeAction represents the action when no assignee is found.

const (
	EmptyAssigneeAutoPass          EmptyAssigneeAction = "auto_pass"
	EmptyAssigneeTransferAdmin     EmptyAssigneeAction = "transfer_admin"
	EmptyAssigneeTransferSuperior  EmptyAssigneeAction = "transfer_superior"
	EmptyAssigneeTransferApplicant EmptyAssigneeAction = "transfer_applicant"
	EmptyAssigneeTransferSpecified EmptyAssigneeAction = "transfer_specified"
)

func (EmptyAssigneeAction) IsValid added in v0.29.0

func (a EmptyAssigneeAction) IsValid() bool

IsValid reports whether the empty-assignee action is one of the defined values.

type EndNodeData

type EndNodeData struct {
	BaseNodeData
}

EndNodeData contains data specific to end nodes.

func (*EndNodeData) ApplyTo

func (d *EndNodeData) ApplyTo(node *FlowNode)

ApplyTo applies end node data to a FlowNode.

func (*EndNodeData) Kind

func (*EndNodeData) Kind() NodeKind

Kind returns the node kind.

type EvaluationContext

type EvaluationContext struct {
	FormData              FormData
	ApplicantID           string
	ApplicantDepartmentID *string
}

EvaluationContext provides context for condition evaluation.

type ExecutionType

type ExecutionType string

ExecutionType represents how a task node is executed. It determines whether the node waits for human decisions or resolves itself the moment the instance enters it.

const (
	ExecutionManual     ExecutionType = "manual"      // Manual: creates tasks and waits for assignees to act
	ExecutionAutoPass   ExecutionType = "auto_pass"   // AutoPass: the node passes immediately on entry, no tasks are created
	ExecutionAutoReject ExecutionType = "auto_reject" // AutoReject: the instance is rejected immediately on entry
)

func (ExecutionType) IsValid added in v0.29.0

func (t ExecutionType) IsValid() bool

IsValid reports whether the execution type is one of the defined values.

type FieldKind

type FieldKind string

FieldKind represents the kind of a form field.

const (
	FieldInput    FieldKind = "input"
	FieldTextarea FieldKind = "textarea"
	FieldSelect   FieldKind = "select"
	FieldNumber   FieldKind = "number"
	FieldDate     FieldKind = "date"
	FieldUpload   FieldKind = "upload"
)

func (FieldKind) IsValid added in v0.29.0

func (k FieldKind) IsValid() bool

IsValid reports whether the field kind is one of the defined values.

type FieldOption

type FieldOption struct {
	Label string `json:"label"`
	Value any    `json:"value"`
}

FieldOption represents a selectable option for select/radio/checkbox fields.

type Flow

type Flow struct {
	orm.BaseModel `bun:"table:apv_flow,alias:af"`
	orm.FullAuditedModel

	TenantID               string      `json:"tenantId" bun:"tenant_id"`
	CategoryID             string      `json:"categoryId" bun:"category_id"`
	Code                   string      `json:"code" bun:"code"`
	Name                   string      `json:"name" bun:"name"`
	Icon                   *string     `json:"icon" bun:"icon,nullzero"`
	Description            *string     `json:"description" bun:"description,nullzero"`
	BindingMode            BindingMode `json:"bindingMode" bun:"binding_mode"`
	BusinessTable          *string     `json:"businessTable" bun:"business_table,nullzero"`
	BusinessPkField        *string     `json:"businessPkField" bun:"business_pk_field,nullzero"`
	BusinessTitleField     *string     `json:"businessTitleField" bun:"business_title_field,nullzero"`
	BusinessStatusField    *string     `json:"businessStatusField" bun:"business_status_field,nullzero"`
	AdminUserIDs           []string    `json:"adminUserIds" bun:"admin_user_ids,type:jsonb"`
	IsAllInitiationAllowed bool        `json:"isAllInitiationAllowed" bun:"is_all_initiation_allowed"`
	InstanceTitleTemplate  string      `json:"instanceTitleTemplate" bun:"instance_title_template"`
	IsActive               bool        `json:"isActive" bun:"is_active"`
	CurrentVersion         int         `json:"currentVersion" bun:"current_version"`
}

Flow represents a flow definition.

type FlowCategory

type FlowCategory struct {
	orm.BaseModel `bun:"table:apv_flow_category,alias:afc"`
	orm.FullAuditedModel

	TenantID  string         `json:"tenantId" bun:"tenant_id"`
	Code      string         `json:"code" bun:"code"`
	Name      string         `json:"name" bun:"name"`
	Icon      *string        `json:"icon" bun:"icon,nullzero"`
	ParentID  *string        `json:"parentId" bun:"parent_id,nullzero"`
	SortOrder int            `json:"sortOrder" bun:"sort_order"`
	IsActive  bool           `json:"isActive" bun:"is_active"`
	Remark    *string        `json:"remark" bun:"remark,nullzero"`
	Children  []FlowCategory `json:"children,omitempty" bun:"-"`
}

FlowCategory represents a category for grouping flows.

type FlowCreatedEvent added in v0.24.0

type FlowCreatedEvent struct {
	FlowID       string         `json:"flowId"`
	TenantID     string         `json:"tenantId"`
	Code         string         `json:"code"`
	Name         string         `json:"name"`
	CategoryID   string         `json:"categoryId"`
	OccurredTime timex.DateTime `json:"occurredTime"`
}

FlowCreatedEvent fires when a new flow definition is created.

func NewFlowCreatedEvent added in v0.24.0

func NewFlowCreatedEvent(flowID, tenantID, code, name, categoryID string) *FlowCreatedEvent

func (*FlowCreatedEvent) EventType added in v0.24.0

func (*FlowCreatedEvent) EventType() string

type FlowDefinition

type FlowDefinition struct {
	Nodes []NodeDefinition `json:"nodes"`
	Edges []EdgeDefinition `json:"edges"`
}

FlowDefinition represents the structure of a flow definition JSON (React Flow compatible).

type FlowDeployedEvent added in v0.24.0

type FlowDeployedEvent struct {
	FlowID       string         `json:"flowId"`
	TenantID     string         `json:"tenantId"`
	VersionID    string         `json:"versionId"`
	Version      int            `json:"version"`
	OccurredTime timex.DateTime `json:"occurredTime"`
}

FlowDeployedEvent fires when a flow's schema is deployed as a new draft version (before it's published).

func NewFlowDeployedEvent added in v0.24.0

func NewFlowDeployedEvent(flowID, tenantID, versionID string, version int) *FlowDeployedEvent

func (*FlowDeployedEvent) EventType added in v0.24.0

func (*FlowDeployedEvent) EventType() string

type FlowEdge

type FlowEdge struct {
	orm.BaseModel `bun:"table:apv_flow_edge,alias:afe"`
	orm.Model

	FlowVersionID string  `json:"flowVersionId" bun:"flow_version_id"`
	Key           string  `json:"key" bun:"key,nullzero"`
	SourceNodeID  string  `json:"sourceNodeId" bun:"source_node_id"`
	SourceNodeKey string  `json:"sourceNodeKey" bun:"source_node_key"`
	TargetNodeID  string  `json:"targetNodeId" bun:"target_node_id"`
	TargetNodeKey string  `json:"targetNodeKey" bun:"target_node_key"`
	SourceHandle  *string `json:"sourceHandle" bun:"source_handle,nullzero"`
}

FlowEdge represents a directed edge between two flow nodes.

type FlowInitiator

type FlowInitiator struct {
	orm.BaseModel `bun:"table:apv_flow_initiator,alias:afi"`
	orm.Model

	FlowID string        `json:"flowId" bun:"flow_id"`
	Kind   InitiatorKind `json:"kind" bun:"kind"`
	IDs    []string      `json:"ids" bun:"ids,type:jsonb"`
}

FlowInitiator represents a flow initiator configuration.

type FlowNode

type FlowNode struct {
	orm.BaseModel `bun:"table:apv_flow_node,alias:afn"`
	orm.FullAuditedModel

	FlowVersionID             string                    `json:"flowVersionId" bun:"flow_version_id"`
	Key                       string                    `json:"key" bun:"key"`
	Kind                      NodeKind                  `json:"kind" bun:"kind"`
	Name                      string                    `json:"name" bun:"name"`
	Description               *string                   `json:"description" bun:"description,nullzero"`
	ExecutionType             ExecutionType             `json:"executionType" bun:"execution_type"`
	ApprovalMethod            ApprovalMethod            `json:"approvalMethod" bun:"approval_method"`
	PassRule                  PassRule                  `json:"passRule" bun:"pass_rule"`
	PassRatio                 decimal.Decimal           `json:"passRatio" bun:"pass_ratio"`
	EmptyAssigneeAction       EmptyAssigneeAction       `json:"emptyAssigneeAction" bun:"empty_assignee_action"`
	FallbackUserIDs           []string                  `json:"fallbackUserIds" bun:"fallback_user_ids,type:jsonb"`
	AdminUserIDs              []string                  `json:"adminUserIds" bun:"admin_user_ids,type:jsonb"`
	SameApplicantAction       SameApplicantAction       `json:"sameApplicantAction" bun:"same_applicant_action"`
	IsRollbackAllowed         bool                      `json:"isRollbackAllowed" bun:"is_rollback_allowed"`
	RollbackType              RollbackType              `json:"rollbackType" bun:"rollback_type"`
	RollbackDataStrategy      RollbackDataStrategy      `json:"rollbackDataStrategy" bun:"rollback_data_strategy"`
	RollbackTargetKeys        []string                  `json:"rollbackTargetKeys" bun:"rollback_target_keys,type:jsonb,nullzero"`
	IsAddAssigneeAllowed      bool                      `json:"isAddAssigneeAllowed" bun:"is_add_assignee_allowed"`
	AddAssigneeTypes          []AddAssigneeType         `json:"addAssigneeTypes" bun:"add_assignee_types,type:jsonb"`
	IsRemoveAssigneeAllowed   bool                      `json:"isRemoveAssigneeAllowed" bun:"is_remove_assignee_allowed"`
	FieldPermissions          map[string]Permission     `json:"fieldPermissions" bun:"field_permissions,type:jsonb"`
	IsManualCCAllowed         bool                      `json:"isManualCcAllowed" bun:"is_manual_cc_allowed"`
	IsTransferAllowed         bool                      `json:"isTransferAllowed" bun:"is_transfer_allowed"`
	IsOpinionRequired         bool                      `json:"isOpinionRequired" bun:"is_opinion_required"`
	TimeoutHours              int                       `json:"timeoutHours" bun:"timeout_hours"`
	TimeoutAction             TimeoutAction             `json:"timeoutAction" bun:"timeout_action"`
	TimeoutNotifyBeforeHours  int                       `json:"timeoutNotifyBeforeHours" bun:"timeout_notify_before_hours"`
	UrgeCooldownMinutes       int                       `json:"urgeCooldownMinutes" bun:"urge_cooldown_minutes"`
	ConsecutiveApproverAction ConsecutiveApproverAction `json:"consecutiveApproverAction" bun:"consecutive_approver_action"`
	IsReadConfirmRequired     bool                      `json:"isReadConfirmRequired" bun:"is_read_confirm_required"`
	Branches                  []ConditionBranch         `json:"branches" bun:"branches,type:jsonb,nullzero"`
}

FlowNode represents a node within a flow version.

type FlowNodeAssignee

type FlowNodeAssignee struct {
	orm.BaseModel `bun:"table:apv_flow_node_assignee,alias:afna"`
	orm.Model

	NodeID    string       `json:"nodeId" bun:"node_id"`
	Kind      AssigneeKind `json:"kind" bun:"kind"`
	IDs       []string     `json:"ids" bun:"ids,type:jsonb"`
	FormField *string      `json:"formField" bun:"form_field,nullzero"`
	SortOrder int          `json:"sortOrder" bun:"sort_order"`
}

FlowNodeAssignee represents a node assignee configuration.

type FlowNodeCC

type FlowNodeCC struct {
	orm.BaseModel `bun:"table:apv_flow_node_cc,alias:afnc"`
	orm.Model

	NodeID    string   `json:"nodeId" bun:"node_id"`
	Kind      CCKind   `json:"kind" bun:"kind"`
	IDs       []string `json:"ids" bun:"ids,type:jsonb"`
	FormField *string  `json:"formField" bun:"form_field,nullzero"`
	Timing    CCTiming `json:"timing" bun:"timing"`
}

FlowNodeCC represents a node CC configuration.

type FlowPublishedEvent

type FlowPublishedEvent struct {
	FlowID       string         `json:"flowId"`
	TenantID     string         `json:"tenantId"`
	VersionID    string         `json:"versionId"`
	OccurredTime timex.DateTime `json:"occurredTime"`
}

FlowPublishedEvent fired when a flow version is published.

func NewFlowPublishedEvent

func NewFlowPublishedEvent(flowID, tenantID, versionID string) *FlowPublishedEvent

func (*FlowPublishedEvent) EventType added in v0.24.0

func (*FlowPublishedEvent) EventType() string

type FlowToggledEvent added in v0.24.0

type FlowToggledEvent struct {
	FlowID       string         `json:"flowId"`
	TenantID     string         `json:"tenantId"`
	IsActive     bool           `json:"isActive"`
	OccurredTime timex.DateTime `json:"occurredTime"`
}

FlowToggledEvent fires when a flow is activated or deactivated.

func NewFlowToggledEvent added in v0.24.0

func NewFlowToggledEvent(flowID, tenantID string, isActive bool) *FlowToggledEvent

func (*FlowToggledEvent) EventType added in v0.24.0

func (*FlowToggledEvent) EventType() string

type FlowUpdatedEvent added in v0.24.0

type FlowUpdatedEvent struct {
	FlowID       string         `json:"flowId"`
	TenantID     string         `json:"tenantId"`
	OccurredTime timex.DateTime `json:"occurredTime"`
}

FlowUpdatedEvent fires when a flow's metadata (name, description, admins, initiators, etc.) is updated. Version publication has its own event.

func NewFlowUpdatedEvent added in v0.24.0

func NewFlowUpdatedEvent(flowID, tenantID string) *FlowUpdatedEvent

func (*FlowUpdatedEvent) EventType added in v0.24.0

func (*FlowUpdatedEvent) EventType() string

type FlowVersion

type FlowVersion struct {
	orm.BaseModel `bun:"table:apv_flow_version,alias:afv"`
	orm.FullAuditedModel

	FlowID      string          `json:"flowId" bun:"flow_id"`
	Version     int             `json:"version" bun:"version"`
	Status      VersionStatus   `json:"status" bun:"status"`
	Description *string         `json:"description" bun:"description,nullzero"`
	StorageMode StorageMode     `json:"storageMode" bun:"storage_mode"`
	FlowSchema  *FlowDefinition `json:"flowSchema" bun:"flow_schema,type:jsonb,nullzero"`
	FormSchema  *FormDefinition `json:"formSchema" bun:"form_schema,type:jsonb,nullzero"`
	PublishedAt *timex.DateTime `json:"publishedAt" bun:"published_at,nullzero"`
	PublishedBy *string         `json:"publishedBy" bun:"published_by,nullzero"`
}

FlowVersion represents a versioned snapshot of a flow definition.

type FormData

type FormData map[string]any

FormData wraps a map to provide helper methods for form data operations.

func NewFormData

func NewFormData(data map[string]any) FormData

func (FormData) Clone

func (f FormData) Clone() (FormData, error)

Clone creates a deep copy via JSON serialization. It returns an error if the map contains values that cannot be marshaled to JSON (e.g. channels, functions). The caller should treat an error as uncloneable data and act accordingly rather than silently losing the original content.

func (FormData) Get

func (f FormData) Get(key string) any

func (FormData) Set

func (f FormData) Set(key string, val any)

func (FormData) ToMap

func (f FormData) ToMap() map[string]any

type FormDefinition

type FormDefinition struct {
	Fields []FormFieldDefinition `json:"fields"`
}

FormDefinition represents the form schema definition for a flow version.

type FormFieldDefinition

type FormFieldDefinition struct {
	// Key is the unique identifier for this field (used in form data keys).
	Key string `json:"key"`
	// Kind is the field type (e.g., "input", "textarea", "select", "number", "date", "upload").
	Kind FieldKind `json:"kind"`
	// Label is the display label.
	Label string `json:"label"`
	// Placeholder is the input placeholder text.
	Placeholder string `json:"placeholder,omitempty"`
	// DefaultValue is the default value for this field.
	DefaultValue any `json:"defaultValue,omitempty"`
	// IsRequired indicates whether this field is required.
	IsRequired bool `json:"isRequired,omitempty"`
	// Options is the list of selectable options (for select, radio, checkbox, etc.).
	Options []FieldOption `json:"options,omitempty"`
	// Validation contains validation rules.
	Validation *ValidationRule `json:"validation,omitempty"`
	// Props contains additional component-specific properties.
	Props map[string]any `json:"props,omitempty"`
	// SortOrder controls the display order.
	SortOrder int `json:"sortOrder"`
}

FormFieldDefinition represents a single form field.

type FormSnapshot

type FormSnapshot struct {
	orm.BaseModel `bun:"table:apv_form_snapshot,alias:afs"`
	orm.Model
	orm.CreationTrackedModel

	InstanceID string         `json:"instanceId" bun:"instance_id"`
	NodeID     string         `json:"nodeId" bun:"node_id"`
	FormData   map[string]any `json:"formData" bun:"form_data,type:jsonb"`
}

FormSnapshot represents a form snapshot for rollback strategies.

type HandleNodeData

type HandleNodeData struct {
	BaseNodeData
	TaskNodeData
}

HandleNodeData contains data specific to handle nodes.

func (*HandleNodeData) ApplyTo

func (d *HandleNodeData) ApplyTo(node *FlowNode)

ApplyTo applies handle node data to a FlowNode, resolving omitted fields to the handle defaults (sequential execution, PassAny rule).

func (*HandleNodeData) Kind

func (*HandleNodeData) Kind() NodeKind

Kind returns the node kind.

type InitiatorKind

type InitiatorKind string

InitiatorKind represents the kind of initiator.

const (
	InitiatorUser       InitiatorKind = "user"
	InitiatorRole       InitiatorKind = "role"
	InitiatorDepartment InitiatorKind = "department"
)

type Instance

type Instance struct {
	orm.BaseModel `bun:"table:apv_instance,alias:ai"`
	orm.FullAuditedModel

	TenantID                string          `json:"tenantId" bun:"tenant_id"`
	FlowID                  string          `json:"flowId" bun:"flow_id"`
	FlowVersionID           string          `json:"flowVersionId" bun:"flow_version_id"`
	Title                   string          `json:"title" bun:"title"`
	InstanceNo              string          `json:"instanceNo" bun:"instance_no"`
	ApplicantID             string          `json:"applicantId" bun:"applicant_id"`
	ApplicantName           string          `json:"applicantName" bun:"applicant_name"`
	ApplicantDepartmentID   *string         `json:"applicantDepartmentId" bun:"applicant_department_id,nullzero"`
	ApplicantDepartmentName *string         `json:"applicantDepartmentName" bun:"applicant_department_name,nullzero"`
	Status                  InstanceStatus  `json:"status" bun:"status"`
	CurrentNodeID           *string         `json:"currentNodeId" bun:"current_node_id,nullzero"`
	FinishedAt              *timex.DateTime `json:"finishedAt" bun:"finished_at,nullzero"`
	BusinessRecordID        *string         `json:"businessRecordId" bun:"business_record_id,nullzero"`
	FormData                map[string]any  `json:"formData" bun:"form_data,type:jsonb,nullzero"`
}

Instance represents a flow instance.

type InstanceBindingFailedEvent added in v0.24.0

type InstanceBindingFailedEvent struct {
	InstanceID    string         `json:"instanceId"`
	TenantID      string         `json:"tenantId"`
	FlowID        string         `json:"flowId"`
	FinalStatus   InstanceStatus `json:"finalStatus"`
	BusinessTable string         `json:"businessTable"`
	ErrorMessage  string         `json:"errorMessage"`
	OccurredTime  timex.DateTime `json:"occurredTime"`
}

InstanceBindingFailedEvent fires when business binding (writing the final status back to the host's business table) fails after the approval itself has already committed. Subscribers retry asynchronously; the approval is not rolled back. Operators can grep these events for stuck bindings.

func NewInstanceBindingFailedEvent added in v0.24.0

func NewInstanceBindingFailedEvent(instanceID, tenantID, flowID string, finalStatus InstanceStatus, businessTable, errorMessage string) *InstanceBindingFailedEvent

func (*InstanceBindingFailedEvent) EventType added in v0.24.0

func (*InstanceBindingFailedEvent) EventType() string

type InstanceCompletedEvent

type InstanceCompletedEvent struct {
	InstanceID   string         `json:"instanceId"`
	TenantID     string         `json:"tenantId"`
	FinalStatus  InstanceStatus `json:"finalStatus"`
	FinishedAt   timex.DateTime `json:"finishedAt"`
	OccurredTime timex.DateTime `json:"occurredTime"`
}

InstanceCompletedEvent fired when instance reaches a final status.

func NewInstanceCompletedEvent

func NewInstanceCompletedEvent(instanceID, tenantID string, finalStatus InstanceStatus) *InstanceCompletedEvent

func (*InstanceCompletedEvent) EventType added in v0.24.0

func (*InstanceCompletedEvent) EventType() string

type InstanceCreatedEvent

type InstanceCreatedEvent struct {
	InstanceID    string         `json:"instanceId"`
	TenantID      string         `json:"tenantId"`
	FlowID        string         `json:"flowId"`
	Title         string         `json:"title"`
	ApplicantID   string         `json:"applicantId"`
	ApplicantName string         `json:"applicantName"`
	OccurredTime  timex.DateTime `json:"occurredTime"`
}

InstanceCreatedEvent fired when a new instance is created.

func NewInstanceCreatedEvent

func NewInstanceCreatedEvent(instanceID, tenantID, flowID, title, applicantID, applicantName string) *InstanceCreatedEvent

func (*InstanceCreatedEvent) EventType added in v0.24.0

func (*InstanceCreatedEvent) EventType() string

type InstanceLifecycleHook added in v0.24.0

type InstanceLifecycleHook interface {
	// OnInstanceCreated runs after the instance row is persisted and the
	// initial action log is written, but before the engine advances to
	// the first node. Returning an error rolls back start_instance.
	OnInstanceCreated(ctx context.Context, db orm.DB, instance *Instance) error
	// OnInstanceCompleted runs after the engine applies the final state
	// transition (within the same transaction). Returning an error rolls
	// back the completion. For at-most-once / fire-and-forget side effects
	// (webhooks, notifications), subscribe to InstanceCompletedEvent
	// instead so the outbox can guarantee delivery.
	OnInstanceCompleted(ctx context.Context, db orm.DB, instance *Instance, finalStatus InstanceStatus) error
}

InstanceLifecycleHook is a synchronous extension point for host applications that need to react to approval lifecycle moments inside the same transaction as the business change. Unlike event subscriptions — which are asynchronous, retryable through outbox, and fire after commit — hooks run *during* the transaction, so a hook that returns an error aborts the surrounding business operation.

Use hooks for invariants that must hold within the transaction (e.g. allocating a business row, writing a tightly-coupled record). Use event subscriptions for everything else (webhooks, notifications, analytics, async integrations).

Multiple implementations are aggregated via FX group `group:"vef:approval:lifecycle_hooks"` and invoked in registration order; any non-nil error stops further hooks and bubbles back to the caller.

type InstanceNoGenerator

type InstanceNoGenerator interface {
	// Generate creates a unique instance number for a flow identified by flowCode.
	Generate(ctx context.Context, flowCode string) (string, error)
}

InstanceNoGenerator generates unique instance numbers for flow instances.

type InstanceResubmittedEvent

type InstanceResubmittedEvent struct {
	InstanceID   string         `json:"instanceId"`
	TenantID     string         `json:"tenantId"`
	OperatorID   string         `json:"operatorId"`
	OccurredTime timex.DateTime `json:"occurredTime"`
}

InstanceResubmittedEvent fired when the initiator resubmits a returned instance.

func NewInstanceResubmittedEvent

func NewInstanceResubmittedEvent(instanceID, tenantID, operatorID string) *InstanceResubmittedEvent

func (*InstanceResubmittedEvent) EventType added in v0.24.0

func (*InstanceResubmittedEvent) EventType() string

type InstanceReturnedEvent

type InstanceReturnedEvent struct {
	InstanceID   string         `json:"instanceId"`
	TenantID     string         `json:"tenantId"`
	FromNodeID   string         `json:"fromNodeId"`
	ToNodeID     string         `json:"toNodeId"`
	OperatorID   string         `json:"operatorId"`
	OccurredTime timex.DateTime `json:"occurredTime"`
}

InstanceReturnedEvent fired when instance is returned to the initiator.

func NewInstanceReturnedEvent

func NewInstanceReturnedEvent(instanceID, tenantID, fromNodeID, toNodeID, operatorID string) *InstanceReturnedEvent

func (*InstanceReturnedEvent) EventType added in v0.24.0

func (*InstanceReturnedEvent) EventType() string

type InstanceRolledBackEvent

type InstanceRolledBackEvent struct {
	InstanceID   string         `json:"instanceId"`
	TenantID     string         `json:"tenantId"`
	FromNodeID   string         `json:"fromNodeId"`
	ToNodeID     string         `json:"toNodeId"`
	OperatorID   string         `json:"operatorId"`
	OccurredTime timex.DateTime `json:"occurredTime"`
}

InstanceRolledBackEvent fired when instance is rolled back.

func NewInstanceRolledBackEvent

func NewInstanceRolledBackEvent(instanceID, tenantID, fromNodeID, toNodeID, operatorID string) *InstanceRolledBackEvent

func (*InstanceRolledBackEvent) EventType added in v0.24.0

func (*InstanceRolledBackEvent) EventType() string

type InstanceStatus

type InstanceStatus string

InstanceStatus represents the status of a flow instance.

const (
	InstanceRunning    InstanceStatus = "running"
	InstanceApproved   InstanceStatus = "approved"
	InstanceRejected   InstanceStatus = "rejected"
	InstanceWithdrawn  InstanceStatus = "withdrawn"
	InstanceReturned   InstanceStatus = "returned"
	InstanceTerminated InstanceStatus = "terminated"
)

func (InstanceStatus) IsFinal

func (s InstanceStatus) IsFinal() bool

func (InstanceStatus) String

func (s InstanceStatus) String() string

type InstanceWithdrawnEvent

type InstanceWithdrawnEvent struct {
	InstanceID   string         `json:"instanceId"`
	TenantID     string         `json:"tenantId"`
	OperatorID   string         `json:"operatorId"`
	OccurredTime timex.DateTime `json:"occurredTime"`
}

InstanceWithdrawnEvent fired when applicant withdraws the instance.

func NewInstanceWithdrawnEvent

func NewInstanceWithdrawnEvent(instanceID, tenantID, operatorID string) *InstanceWithdrawnEvent

func (*InstanceWithdrawnEvent) EventType added in v0.24.0

func (*InstanceWithdrawnEvent) EventType() string

type NodeAutoPassedEvent

type NodeAutoPassedEvent struct {
	InstanceID   string         `json:"instanceId"`
	TenantID     string         `json:"tenantId"`
	NodeID       string         `json:"nodeId"`
	Reason       string         `json:"reason"`
	OccurredTime timex.DateTime `json:"occurredTime"`
}

NodeAutoPassedEvent fired when a node passes without any human decision: auto-pass execution type, empty-assignee auto-pass, or same-applicant auto-pass. Reason carries which rule produced the pass so audit timelines can render the skipped step.

func NewNodeAutoPassedEvent

func NewNodeAutoPassedEvent(instanceID, tenantID, nodeID, reason string) *NodeAutoPassedEvent

func (*NodeAutoPassedEvent) EventType added in v0.24.0

func (*NodeAutoPassedEvent) EventType() string

type NodeData

type NodeData interface {
	// Kind returns the node kind (start, end, approval, handle, cc, condition).
	Kind() NodeKind
	// GetName returns the display name of the node.
	GetName() string
	// GetDescription returns the optional description of the node.
	GetDescription() *string
	// ApplyTo applies this node data's configuration to the given FlowNode,
	// resolving omitted optional fields to their documented defaults so the
	// persisted node always carries complete, valid configuration.
	ApplyTo(node *FlowNode)
}

NodeData is the interface implemented by all node data types.

type NodeDefinition

type NodeDefinition struct {
	ID       string          `json:"id"`
	Kind     NodeKind        `json:"kind"`
	Position Position        `json:"position"`
	Data     json.RawMessage `json:"data,omitempty"`
}

NodeDefinition represents a node in the flow definition.

func (*NodeDefinition) ParseData

func (nd *NodeDefinition) ParseData() (NodeData, error)

ParseData parses Data into the appropriate typed struct based on Kind.

type NodeKind

type NodeKind string

NodeKind represents the kind of a flow node. It defines the different types of nodes that can exist in a workflow.

const (
	NodeStart     NodeKind = "start"     // Start node: the entry point of a workflow
	NodeApproval  NodeKind = "approval"  // Approval node: requires approval action from assignees
	NodeHandle    NodeKind = "handle"    // Handle node: requires processing/handling action from assignees
	NodeCondition NodeKind = "condition" // Condition node: branches the flow based on conditions
	NodeEnd       NodeKind = "end"       // End node: the terminal point of a workflow
	NodeCC        NodeKind = "cc"        // CC node: sends notifications to specified users
)

type OperatorInfo

type OperatorInfo struct {
	ID             string  `json:"id"`
	Name           string  `json:"name"`
	DepartmentID   *string `json:"departmentId,omitempty"`
	DepartmentName *string `json:"departmentName,omitempty"`
}

OperatorInfo bundles operator identity for action logging.

func (OperatorInfo) NewActionLog

func (o OperatorInfo) NewActionLog(instanceID string, action ActionType) *ActionLog

NewActionLog creates an ActionLog with the operator fields pre-filled.

type PassRule

type PassRule string

PassRule represents the strategy for passing the node (for Parallel/Or methods).

const (
	PassAll   PassRule = "all"   // All assignees must approve; any rejection fails the node
	PassAny   PassRule = "any"   // At least one assignee must approve
	PassRatio PassRule = "ratio" // A certain percentage of assignees must approve
)

func (PassRule) IsValid added in v0.29.0

func (r PassRule) IsValid() bool

IsValid reports whether the pass rule is one of the defined values.

type PassRuleContext

type PassRuleContext struct {
	ApprovedCount int
	RejectedCount int
	TotalCount    int
	PassRatio     float64
}

PassRuleContext provides context for pass rule evaluation.

type PassRuleResult

type PassRuleResult int

PassRuleResult indicates the outcome of pass rule evaluation.

const (
	PassRulePending  PassRuleResult = iota // Still waiting for more actions
	PassRulePassed                         // Node passed
	PassRuleRejected                       // Node rejected
)

type PassRuleStrategy

type PassRuleStrategy interface {
	// Rule returns the pass rule this strategy handles.
	Rule() PassRule
	// Evaluate determines the pass/reject/pending outcome based on task approval counts.
	Evaluate(ctx PassRuleContext) PassRuleResult
}

PassRuleStrategy evaluates whether a node passes based on task results.

type Permission

type Permission string

Permission represents the permission level.

const (
	PermissionVisible  Permission = "visible"
	PermissionEditable Permission = "editable"
	PermissionHidden   Permission = "hidden"
	PermissionRequired Permission = "required"
)

type Position

type Position struct {
	X float64 `json:"x"`
	Y float64 `json:"y"`
}

Position represents the visual position of a node on the canvas.

type PrincipalDepartmentResolver added in v0.20.1

type PrincipalDepartmentResolver interface {
	// Resolve extracts the department ID and name from the given security principal.
	Resolve(ctx context.Context, principal *security.Principal) (departmentID, departmentName *string, err error)
}

PrincipalDepartmentResolver resolves department info from a security principal. Implemented by host apps since Principal.Details is business-specific.

type PrincipalTenantResolver added in v0.24.0

type PrincipalTenantResolver interface {
	// Resolve returns the tenant identifier for the given principal.
	Resolve(ctx context.Context, principal *security.Principal) (string, error)
}

PrincipalTenantResolver extracts the caller's tenant ID from a security principal. Implemented by host applications because Principal.Details is schema-less; the framework cannot know where the host stores tenant affiliation.

Returning an empty string is only meaningful for super-admin or system-internal callers, which bypass tenant scoping. For an ordinary authenticated principal an empty tenant fails closed: CallerContext.Authorize returns ErrCrossTenantAccess and TenantScopeFilter / ResolveWriteTenant likewise reject it, rather than waving the request through.

type ResolvedAssignee

type ResolvedAssignee struct {
	UserID        string
	UserName      string
	DelegatorID   *string
	DelegatorName *string
}

ResolvedAssignee represents a resolved assignee with optional delegation info.

type RoleMembershipChecker added in v0.29.0

type RoleMembershipChecker interface {
	// UserHasRole reports whether the user currently holds the role.
	UserHasRole(ctx context.Context, userID, roleID string) (bool, error)
}

RoleMembershipChecker is an optional capability of AssigneeService. Hosts that can answer "does this user hold this role" directly should implement it: membership checks (e.g. role-based initiation permission) then skip the GetRoleUsers full-listing fallback, which scales poorly for large roles. Detected via type assertion, so existing implementations keep working unchanged.

type RollbackDataStrategy

type RollbackDataStrategy string

RollbackDataStrategy represents the strategy for handling form data during rollback.

const (
	RollbackDataClear RollbackDataStrategy = "clear" // Clear form data
	RollbackDataKeep  RollbackDataStrategy = "keep"  // Restore the form snapshot captured when the target node was first entered
)

func (RollbackDataStrategy) IsValid added in v0.29.0

func (s RollbackDataStrategy) IsValid() bool

IsValid reports whether the rollback data strategy is one of the defined values.

type RollbackType

type RollbackType string

RollbackType represents the type of rollback allowed.

const (
	RollbackNone      RollbackType = "none"
	RollbackPrevious  RollbackType = "previous"  // To previous node
	RollbackStart     RollbackType = "start"     // To start node (applicant)
	RollbackAny       RollbackType = "any"       // To any node
	RollbackSpecified RollbackType = "specified" // To specified nodes
)

func (RollbackType) IsValid added in v0.29.0

func (t RollbackType) IsValid() bool

IsValid reports whether the rollback type is one of the defined values.

type SameApplicantAction

type SameApplicantAction string

SameApplicantAction represents the action when the assignee is the same as the applicant.

const (
	SameApplicantAutoPass         SameApplicantAction = "auto_pass"
	SameApplicantSelfApprove      SameApplicantAction = "self_approve"      // Default
	SameApplicantTransferSuperior SameApplicantAction = "transfer_superior" // Transfer to superior
)

func (SameApplicantAction) IsValid added in v0.29.0

func (a SameApplicantAction) IsValid() bool

IsValid reports whether the same-applicant action is one of the defined values.

type StartNodeData

type StartNodeData struct {
	BaseNodeData
}

StartNodeData contains data specific to start nodes.

func (*StartNodeData) ApplyTo

func (d *StartNodeData) ApplyTo(node *FlowNode)

ApplyTo applies start node data to a FlowNode.

func (*StartNodeData) Kind

func (*StartNodeData) Kind() NodeKind

Kind returns the node kind.

type StorageMode

type StorageMode string

StorageMode represents the storage mode of form data at the FlowVersion level. It determines the physical storage location and format of form data, and is fixed when a version is published. This is different from BindingMode (Flow-level), which controls how the workflow integrates with business systems.

Usage scenarios:

  • JSON mode: Flexible schema, suitable for frequently changing form fields, limited query capabilities
  • Table mode: Structured storage, suitable for complex queries and data analysis, requires predefined schema
const (
	// StorageJSON stores form data in the apv_instance.form_data JSONB column.
	// It is currently the only implemented storage mode.
	StorageJSON StorageMode = "json"
)

type Task

type Task struct {
	orm.BaseModel `bun:"table:apv_task,alias:at"`
	orm.FullAuditedModel

	TenantID         string           `json:"tenantId" bun:"tenant_id"`
	InstanceID       string           `json:"instanceId" bun:"instance_id"`
	NodeID           string           `json:"nodeId" bun:"node_id"`
	AssigneeID       string           `json:"assigneeId" bun:"assignee_id"`
	AssigneeName     string           `json:"assigneeName" bun:"assignee_name"`
	DelegatorID      *string          `json:"delegatorId" bun:"delegator_id,nullzero"`
	DelegatorName    *string          `json:"delegatorName" bun:"delegator_name,nullzero"`
	SortOrder        int              `json:"sortOrder" bun:"sort_order"`
	Status           TaskStatus       `json:"status" bun:"status"`
	ReadAt           *timex.DateTime  `json:"readAt" bun:"read_at,nullzero"`
	ParentTaskID     *string          `json:"parentTaskId" bun:"parent_task_id,nullzero"`
	AddAssigneeType  *AddAssigneeType `json:"addAssigneeType" bun:"add_assignee_type,nullzero"`
	Deadline         *timex.DateTime  `json:"deadline" bun:"deadline,nullzero"`
	IsTimeout        bool             `json:"isTimeout" bun:"is_timeout"`
	IsPreWarningSent bool             `json:"isPreWarningSent" bun:"is_pre_warning_sent"`
	FinishedAt       *timex.DateTime  `json:"finishedAt" bun:"finished_at,nullzero"`
}

Task represents an approval task.

type TaskApprovedEvent

type TaskApprovedEvent struct {
	TaskID       string         `json:"taskId"`
	TenantID     string         `json:"tenantId"`
	InstanceID   string         `json:"instanceId"`
	NodeID       string         `json:"nodeId"`
	OperatorID   string         `json:"operatorId"`
	Opinion      *string        `json:"opinion,omitempty"`
	OccurredTime timex.DateTime `json:"occurredTime"`
}

TaskApprovedEvent fired when a task is approved.

func NewTaskApprovedEvent

func NewTaskApprovedEvent(taskID, tenantID, instanceID, nodeID, operatorID, opinion string) *TaskApprovedEvent

func (*TaskApprovedEvent) EventType added in v0.24.0

func (*TaskApprovedEvent) EventType() string

type TaskCanceledEvent added in v0.29.0

type TaskCanceledEvent struct {
	TaskID       string         `json:"taskId"`
	TenantID     string         `json:"tenantId"`
	InstanceID   string         `json:"instanceId"`
	NodeID       string         `json:"nodeId"`
	AssigneeID   string         `json:"assigneeId"`
	AssigneeName string         `json:"assigneeName"`
	Reason       string         `json:"reason"`
	OccurredTime timex.DateTime `json:"occurredTime"`
}

TaskCanceledEvent fired when the engine cancels a task that no longer needs a decision — its node completed through other votes, or the whole instance was withdrawn, rolled back, or terminated. Subscribers use it to retract pending to-do entries for the canceled assignee. Reason carries the triggering operation.

func NewTaskCanceledEvent added in v0.29.0

func NewTaskCanceledEvent(taskID, tenantID, instanceID, nodeID, assigneeID, assigneeName, reason string) *TaskCanceledEvent

func (*TaskCanceledEvent) EventType added in v0.29.0

func (*TaskCanceledEvent) EventType() string

type TaskCreatedEvent

type TaskCreatedEvent struct {
	TaskID       string          `json:"taskId"`
	TenantID     string          `json:"tenantId"`
	InstanceID   string          `json:"instanceId"`
	NodeID       string          `json:"nodeId"`
	AssigneeID   string          `json:"assigneeId"`
	AssigneeName string          `json:"assigneeName"`
	Deadline     *timex.DateTime `json:"deadline,omitempty"`
	OccurredTime timex.DateTime  `json:"occurredTime"`
}

TaskCreatedEvent fires the moment a task row is inserted, not the moment it becomes actionable. Under sequential approval, tasks after the first start with Status=Waiting and a nil Deadline; subscribers should treat a nil Deadline as the cue that the task is queued behind a predecessor and must not yet surface a "new pending task" notification. When the predecessor finishes and the task transitions to Pending, no new event is published — subscribers reading the live task table see the change. If this contract proves insufficient, the right extension is to add a dedicated TaskActivatedEvent rather than overloading TaskCreatedEvent.

func NewTaskCreatedEvent

func NewTaskCreatedEvent(taskID, tenantID, instanceID, nodeID, assigneeID, assigneeName string, deadline *timex.DateTime) *TaskCreatedEvent

func (*TaskCreatedEvent) EventType added in v0.24.0

func (*TaskCreatedEvent) EventType() string

type TaskDeadlineWarningEvent

type TaskDeadlineWarningEvent struct {
	TaskID       string         `json:"taskId"`
	TenantID     string         `json:"tenantId"`
	InstanceID   string         `json:"instanceId"`
	NodeID       string         `json:"nodeId"`
	AssigneeID   string         `json:"assigneeId"`
	AssigneeName string         `json:"assigneeName"`
	Deadline     timex.DateTime `json:"deadline"`
	HoursLeft    int            `json:"hoursLeft"`
	OccurredTime timex.DateTime `json:"occurredTime"`
}

TaskDeadlineWarningEvent fired when a task is approaching its deadline.

func NewTaskDeadlineWarningEvent

func NewTaskDeadlineWarningEvent(taskID, tenantID, instanceID, nodeID, assigneeID, assigneeName string, deadline timex.DateTime, hoursLeft int) *TaskDeadlineWarningEvent

func (*TaskDeadlineWarningEvent) EventType added in v0.24.0

func (*TaskDeadlineWarningEvent) EventType() string

type TaskHandledEvent

type TaskHandledEvent struct {
	TaskID       string         `json:"taskId"`
	TenantID     string         `json:"tenantId"`
	InstanceID   string         `json:"instanceId"`
	NodeID       string         `json:"nodeId"`
	OperatorID   string         `json:"operatorId"`
	Opinion      *string        `json:"opinion,omitempty"`
	OccurredTime timex.DateTime `json:"occurredTime"`
}

TaskHandledEvent fired when a handle-type task is completed.

func NewTaskHandledEvent

func NewTaskHandledEvent(taskID, tenantID, instanceID, nodeID, operatorID, opinion string) *TaskHandledEvent

func (*TaskHandledEvent) EventType added in v0.24.0

func (*TaskHandledEvent) EventType() string

type TaskNodeData

type TaskNodeData struct {
	Assignees                []AssigneeDefinition  `json:"assignees,omitempty"`
	ExecutionType            ExecutionType         `json:"executionType,omitempty"`
	EmptyAssigneeAction      EmptyAssigneeAction   `json:"emptyAssigneeAction,omitempty"`
	FallbackUserIDs          []string              `json:"fallbackUserIds,omitempty"`
	AdminUserIDs             []string              `json:"adminUserIds,omitempty"`
	IsTransferAllowed        *bool                 `json:"isTransferAllowed,omitempty"`
	IsOpinionRequired        bool                  `json:"isOpinionRequired,omitempty"`
	TimeoutHours             int                   `json:"timeoutHours,omitempty"`
	TimeoutAction            TimeoutAction         `json:"timeoutAction,omitempty"`
	TimeoutNotifyBeforeHours int                   `json:"timeoutNotifyBeforeHours,omitempty"`
	UrgeCooldownMinutes      int                   `json:"urgeCooldownMinutes,omitempty"`
	CCs                      []CCDefinition        `json:"ccs,omitempty"`
	FieldPermissions         map[string]Permission `json:"fieldPermissions,omitempty"`
}

TaskNodeData contains fields shared by approval and handle nodes.

IsTransferAllowed is a pointer because its default is true: an omitted field must resolve to "allowed", which a plain bool zero value cannot represent.

func (*TaskNodeData) GetAssignees

func (d *TaskNodeData) GetAssignees() []AssigneeDefinition

GetAssignees returns the assignee definitions from TaskNodeData.

func (*TaskNodeData) GetCCs

func (d *TaskNodeData) GetCCs() []CCDefinition

GetCCs returns the CC definitions from TaskNodeData.

type TaskReassignedEvent

type TaskReassignedEvent struct {
	TaskID       string         `json:"taskId"`
	TenantID     string         `json:"tenantId"`
	InstanceID   string         `json:"instanceId"`
	NodeID       string         `json:"nodeId"`
	FromUserID   string         `json:"fromUserId"`
	FromUserName string         `json:"fromUserName"`
	ToUserID     string         `json:"toUserId"`
	ToUserName   string         `json:"toUserName"`
	Reason       *string        `json:"reason,omitempty"`
	OccurredTime timex.DateTime `json:"occurredTime"`
}

TaskReassignedEvent fired when an admin reassigns a task to a different user.

func NewTaskReassignedEvent

func NewTaskReassignedEvent(taskID, tenantID, instanceID, nodeID string, from, to UserInfo, reason string) *TaskReassignedEvent

NewTaskReassignedEvent builds the event for an admin reassigning a task. from/to are UserInfo values so the id↔name pairs cannot be transposed.

func (*TaskReassignedEvent) EventType added in v0.24.0

func (*TaskReassignedEvent) EventType() string

type TaskRejectedEvent

type TaskRejectedEvent struct {
	TaskID       string         `json:"taskId"`
	TenantID     string         `json:"tenantId"`
	InstanceID   string         `json:"instanceId"`
	NodeID       string         `json:"nodeId"`
	OperatorID   string         `json:"operatorId"`
	Opinion      *string        `json:"opinion,omitempty"`
	OccurredTime timex.DateTime `json:"occurredTime"`
}

TaskRejectedEvent fired when a task is rejected.

func NewTaskRejectedEvent

func NewTaskRejectedEvent(taskID, tenantID, instanceID, nodeID, operatorID, opinion string) *TaskRejectedEvent

func (*TaskRejectedEvent) EventType added in v0.24.0

func (*TaskRejectedEvent) EventType() string

type TaskStatus

type TaskStatus string

TaskStatus represents the status of an approval task.

const (
	TaskWaiting     TaskStatus = "waiting"
	TaskPending     TaskStatus = "pending"
	TaskApproved    TaskStatus = "approved"
	TaskRejected    TaskStatus = "rejected"
	TaskHandled     TaskStatus = "handled"
	TaskTransferred TaskStatus = "transferred"
	TaskRolledBack  TaskStatus = "rolled_back"
	TaskCanceled    TaskStatus = "canceled"
	TaskRemoved     TaskStatus = "removed"
	TaskSkipped     TaskStatus = "skipped"
)

func (TaskStatus) IsFinal

func (s TaskStatus) IsFinal() bool

func (TaskStatus) String

func (s TaskStatus) String() string

type TaskTimedOutEvent added in v0.24.0

type TaskTimedOutEvent struct {
	TaskID       string         `json:"taskId"`
	TenantID     string         `json:"tenantId"`
	InstanceID   string         `json:"instanceId"`
	NodeID       string         `json:"nodeId"`
	AssigneeID   string         `json:"assigneeId"`
	AssigneeName string         `json:"assigneeName"`
	Deadline     timex.DateTime `json:"deadline"`
	OccurredTime timex.DateTime `json:"occurredTime"`
}

TaskTimedOutEvent fired when a task times out.

func NewTaskTimedOutEvent added in v0.24.0

func NewTaskTimedOutEvent(taskID, tenantID, instanceID, nodeID, assigneeID, assigneeName string, deadline timex.DateTime) *TaskTimedOutEvent

func (*TaskTimedOutEvent) EventType added in v0.24.0

func (*TaskTimedOutEvent) EventType() string

type TaskTransferredEvent

type TaskTransferredEvent struct {
	TaskID       string         `json:"taskId"`
	TenantID     string         `json:"tenantId"`
	InstanceID   string         `json:"instanceId"`
	NodeID       string         `json:"nodeId"`
	FromUserID   string         `json:"fromUserId"`
	FromUserName string         `json:"fromUserName"`
	ToUserID     string         `json:"toUserId"`
	ToUserName   string         `json:"toUserName"`
	Reason       *string        `json:"reason,omitempty"`
	OccurredTime timex.DateTime `json:"occurredTime"`
}

TaskTransferredEvent fired when a task is transferred.

func NewTaskTransferredEvent

func NewTaskTransferredEvent(taskID, tenantID, instanceID, nodeID string, from, to UserInfo, reason string) *TaskTransferredEvent

NewTaskTransferredEvent builds the event for a task moved from one user to another. The from/to identities are passed as UserInfo values rather than four flat strings so the id↔name pairs cannot be transposed at a call site.

func (*TaskTransferredEvent) EventType added in v0.24.0

func (*TaskTransferredEvent) EventType() string

type TaskUrgedEvent

type TaskUrgedEvent struct {
	InstanceID     string         `json:"instanceId"`
	TenantID       string         `json:"tenantId"`
	NodeID         string         `json:"nodeId"`
	TaskID         string         `json:"taskId"`
	UrgerID        string         `json:"urgerId"`
	UrgerName      string         `json:"urgerName"`
	TargetUserID   string         `json:"targetUserId"`
	TargetUserName string         `json:"targetUserName"`
	Message        *string        `json:"message,omitempty"`
	OccurredTime   timex.DateTime `json:"occurredTime"`
}

TaskUrgedEvent fired when a task assignee is urged/reminded.

func NewTaskUrgedEvent

func NewTaskUrgedEvent(instanceID, tenantID, nodeID, taskID string, urger, target UserInfo, message string) *TaskUrgedEvent

NewTaskUrgedEvent builds the event for an urge/reminder. The urger and target identities are passed as UserInfo values so the id↔name pairs cannot be transposed at a call site.

func (*TaskUrgedEvent) EventType added in v0.24.0

func (*TaskUrgedEvent) EventType() string

type TimeoutAction

type TimeoutAction string

TimeoutAction represents the action to take when a task times out.

const (
	TimeoutActionNone          TimeoutAction = "none"           // Mark timeout only, no auto action
	TimeoutActionAutoPass      TimeoutAction = "auto_pass"      // Automatically approve the task
	TimeoutActionAutoReject    TimeoutAction = "auto_reject"    // Automatically reject the task
	TimeoutActionNotify        TimeoutAction = "notify"         // Send notification only
	TimeoutActionTransferAdmin TimeoutAction = "transfer_admin" // Transfer to node admin
)

func (TimeoutAction) IsValid added in v0.29.0

func (a TimeoutAction) IsValid() bool

IsValid reports whether the timeout action is one of the defined values.

type UrgeRecord

type UrgeRecord struct {
	orm.BaseModel `bun:"table:apv_urge_record,alias:aur"`
	orm.Model
	orm.CreationTrackedModel

	InstanceID     string  `json:"instanceId" bun:"instance_id"`
	NodeID         string  `json:"nodeId" bun:"node_id"`
	TaskID         *string `json:"taskId" bun:"task_id,nullzero"`
	UrgerID        string  `json:"urgerId" bun:"urger_id"`
	UrgerName      string  `json:"urgerName" bun:"urger_name"`
	TargetUserID   string  `json:"targetUserId" bun:"target_user_id"`
	TargetUserName string  `json:"targetUserName" bun:"target_user_name"`
	Message        string  `json:"message" bun:"message"`
}

UrgeRecord represents an urge/reminder record.

type UserInfo

type UserInfo struct {
	ID   string `json:"id"`
	Name string `json:"name"`
}

UserInfo represents a user identity with ID and display name.

type UserInfoResolver

type UserInfoResolver interface {
	// ResolveUsers returns user info for the given IDs.
	// Missing IDs should be returned with empty Name (not omitted).
	ResolveUsers(ctx context.Context, userIDs []string) (map[string]UserInfo, error)
}

UserInfoResolver resolves user display names by IDs (implemented by host app).

type ValidationRule

type ValidationRule struct {
	MinLength *int     `json:"minLength,omitempty"`
	MaxLength *int     `json:"maxLength,omitempty"`
	Min       *float64 `json:"min,omitempty"`
	Max       *float64 `json:"max,omitempty"`
	Pattern   string   `json:"pattern,omitempty"`
	Message   string   `json:"message,omitempty"`
}

ValidationRule contains validation constraints for a form field.

type VersionStatus

type VersionStatus string

VersionStatus represents the status of a flow version.

const (
	VersionDraft     VersionStatus = "draft"
	VersionPublished VersionStatus = "published"
	VersionArchived  VersionStatus = "archived"
)

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL