Documentation
¶
Index ¶
- Constants
- Variables
- type Application
- type ApproverResolver
- type ApproverResolverFunc
- type ReviewLog
- type Service
- func (s *Service) Approve(ctx context.Context, applicationID, operatorID string) (Application, ReviewLog, error)
- func (s *Service) CreateDraft(ctx context.Context, app Application) error
- func (s *Service) GetApplication(ctx context.Context, applicationID string) (Application, error)
- func (s *Service) ListLogs(ctx context.Context, applicationID string) ([]ReviewLog, error)
- func (s *Service) RejectClose(ctx context.Context, applicationID, operatorID, comment string) (Application, ReviewLog, error)
- func (s *Service) Resubmit(ctx context.Context, applicationID string) (Application, ReviewLog, error)
- func (s *Service) Return(ctx context.Context, applicationID, operatorID, comment string) (Application, ReviewLog, error)
- func (s *Service) RevokeApprove(ctx context.Context, applicationID, operatorID, comment string) (Application, ReviewLog, error)
- func (s *Service) SaveTemplate(ctx context.Context, tpl WorkflowTemplate) error
- func (s *Service) Submit(ctx context.Context, applicationID string) (Application, ReviewLog, error)
- func (s *Service) Withdraw(ctx context.Context, applicationID, comment string) (Application, ReviewLog, error)
- type Store
- type WorkflowEngine
- func (e *WorkflowEngine) Approve(app Application, template WorkflowTemplate, operatorID string, ...) (Application, ReviewLog, error)
- func (e *WorkflowEngine) CanEditForm(app Application, userID string) bool
- func (e *WorkflowEngine) CanResubmit(app Application, userID string) bool
- func (e *WorkflowEngine) CanReview(app Application, template WorkflowTemplate, userID string) bool
- func (e *WorkflowEngine) CanRevokeApprove(app Application, template WorkflowTemplate, operatorID string, ...) bool
- func (e *WorkflowEngine) CanView(app Application, template WorkflowTemplate, userID string) bool
- func (e *WorkflowEngine) CanWithdraw(app Application, userID string) bool
- func (e *WorkflowEngine) RejectClose(app Application, template WorkflowTemplate, operatorID string, comment string) (Application, ReviewLog, error)
- func (e *WorkflowEngine) ResetToFirstSubmit(app Application, template WorkflowTemplate) (Application, ReviewLog, error)
- func (e *WorkflowEngine) Resubmit(app Application, template WorkflowTemplate) (Application, ReviewLog, error)
- func (e *WorkflowEngine) Return(app Application, template WorkflowTemplate, operatorID string, comment string) (Application, ReviewLog, error)
- func (e *WorkflowEngine) RevokeApprove(app Application, template WorkflowTemplate, operatorID string, comment string, ...) (Application, ReviewLog, error)
- func (e *WorkflowEngine) Submit(app Application, template WorkflowTemplate) (Application, ReviewLog, error)
- func (e *WorkflowEngine) Withdraw(app Application, template WorkflowTemplate, comment string) (Application, ReviewLog, error)
- type WorkflowStage
- type WorkflowTemplate
Constants ¶
const ( StatusDraft = "Draft" StatusInReview = "In_Review" StatusReturned = "Returned" StatusApproved = "Approved" StatusRejectedClosed = "Rejected_Closed" )
Application status values.
const ( ReturnModeStrict = "STRICT" ReturnModeDirect = "DIRECT" )
Return mode values for WorkflowTemplate.
const ( ReviewTypeSingle = "SINGLE" ReviewTypeAll = "ALL" )
Review type values for WorkflowStage.
const ( ActionSubmit = "SUBMIT" ActionApprove = "APPROVE" ActionReturn = "RETURN" ActionRejectClose = "REJECT_CLOSE" // ActionWithdraw is recorded when the owner pulls a still-in-review // application back to Draft. ActionWithdraw = "WITHDRAW" // ActionRevokeApprove is recorded when a reviewer cancels their own, // not-yet-decisive approval within the current round. ActionRevokeApprove = "REVOKE_APPROVE" )
Action values for ReviewLog.
Variables ¶
var ( ErrInvalidStatus = errors.New("workflow: invalid application status for this operation") ErrNoPermission = errors.New("workflow: operator has no permission to perform this action") ErrCommentRequired = errors.New("workflow: comment is required for this action") ErrTemplateEmpty = errors.New("workflow: template has no stages") ErrTemplateMismatch = errors.New("workflow: template does not match application") ErrAlreadyApproved = errors.New("workflow: operator has already approved in current round") ErrInvalidTemplate = errors.New("workflow: invalid template configuration") ErrStaleReturnStage = errors.New("workflow: stored return-stage index is out of range for the current template") ErrRevokeNotAllowed = errors.New("workflow: approval cannot be revoked for this stage type") ErrNothingToRevoke = errors.New("workflow: operator has no live approval to revoke in the current round") )
Engine errors.
var ErrNotFound = errors.New("workflow: record not found")
ErrNotFound is returned by Store implementations when a record cannot be located.
Functions ¶
This section is empty.
Types ¶
type Application ¶
type Application struct {
ID string
WorkflowID string
OwnerID string
Status string
CurrentStageIndex int
ReturnStageIndex int
}
Application represents a single workflow instance and its mutable state.
type ApproverResolver ¶
type ApproverResolver interface {
Resolve(app Application, stage WorkflowStage) ([]string, error)
}
ApproverResolver returns the user IDs allowed to approve a stage for a specific application. Implementations may consult external state (RBAC, org chart, per-application metadata) so reviewers do not have to be hard-coded in the template.
Stability contract: the result MUST be stable for a given (app, stage) within a single approval round. ALL multi-sign counting compares the number of distinct approvals in the round against len(Resolve(...)), so a list that grows or shrinks mid-round will confuse the engine.
An empty list disables the stage: SINGLE can never advance, ALL auto-advances on the first call. Both are almost always bugs — callers should arrange for Resolve to fail loudly instead of returning [].
type ApproverResolverFunc ¶
type ApproverResolverFunc func(app Application, stage WorkflowStage) ([]string, error)
ApproverResolverFunc adapts a plain function to ApproverResolver.
func (ApproverResolverFunc) Resolve ¶
func (f ApproverResolverFunc) Resolve(app Application, stage WorkflowStage) ([]string, error)
Resolve implements ApproverResolver.
type ReviewLog ¶
type ReviewLog struct {
ID string
ApplicationID string
StageIndex int
OperatorID string
Action string
Comment string
CreatedAt time.Time
}
ReviewLog records a single action performed against an Application.
type Service ¶
type Service struct {
Engine *WorkflowEngine
Store Store
}
Service is a convenience facade that combines a WorkflowEngine with a Store. Each public method loads the application, invokes the engine, then persists the new state and log inside a single transaction.
Authorization boundary: Service is *not* an authentication layer. Submit, Resubmit and SaveTemplate do not take an operator argument and trust the caller to have already verified that the requester is allowed to perform the action (typically: requester == app.OwnerID for Submit/Resubmit, and a workflow admin role for SaveTemplate). Approve / Return / RejectClose do take an operatorID and the engine checks it against the stage's ApproverIDs, but that is a workflow-routing check, not a caller-identity check — wire authentication in the layer above.
func NewService ¶
func NewService(store Store, engine *WorkflowEngine) *Service
NewService wires an Engine (or a default one) with the provided Store.
func (*Service) Approve ¶
func (s *Service) Approve(ctx context.Context, applicationID, operatorID string) (Application, ReviewLog, error)
Approve records a stage approval, advancing the workflow when conditions are met.
func (*Service) CreateDraft ¶
func (s *Service) CreateDraft(ctx context.Context, app Application) error
CreateDraft persists a brand new draft application. It is a thin helper — callers can also write the application themselves via Store.SaveApplication.
func (*Service) GetApplication ¶
GetApplication loads a snapshot of the application state.
func (*Service) RejectClose ¶
func (s *Service) RejectClose(ctx context.Context, applicationID, operatorID, comment string) (Application, ReviewLog, error)
RejectClose terminally closes the application.
func (*Service) Resubmit ¶
func (s *Service) Resubmit(ctx context.Context, applicationID string) (Application, ReviewLog, error)
Resubmit puts a Returned application back into the review queue.
func (*Service) Return ¶
func (s *Service) Return(ctx context.Context, applicationID, operatorID, comment string) (Application, ReviewLog, error)
Return sends the application back to the owner for revision.
func (*Service) RevokeApprove ¶ added in v0.2.0
func (s *Service) RevokeApprove(ctx context.Context, applicationID, operatorID, comment string) (Application, ReviewLog, error)
RevokeApprove cancels operatorID's own pending approval at the current stage. Only valid for an ALL stage whose quorum is still outstanding.
func (*Service) SaveTemplate ¶
func (s *Service) SaveTemplate(ctx context.Context, tpl WorkflowTemplate) error
SaveTemplate persists a workflow template definition and, in the same transaction, reflows every in-flight application bound to that template.
Reflow semantics: every application whose Status is In_Review or Returned is reset to the post-first-Submit state (Status=In_Review, CurrentStageIndex=1, ReturnStageIndex=0) and gets a new SUBMIT log appended. Approved / Rejected_Closed applications are terminal and are left untouched.
This is intentionally aggressive — calling SaveTemplate again with the same definition still reflows. Callers who want a no-op update should compare templates upstream and skip the call.
func (*Service) Withdraw ¶ added in v0.2.0
func (s *Service) Withdraw(ctx context.Context, applicationID, comment string) (Application, ReviewLog, error)
Withdraw pulls a still-in-review application back to Draft on the owner's behalf. Like Submit/Resubmit it trusts the caller to have verified that the requester is the owner; gate with Engine.CanWithdraw upstream if needed.
type Store ¶
type Store interface {
// Templates
SaveTemplate(ctx context.Context, t WorkflowTemplate) error
GetTemplate(ctx context.Context, id string) (WorkflowTemplate, error)
// Applications
SaveApplication(ctx context.Context, a Application) error
GetApplication(ctx context.Context, id string) (Application, error)
// ListInFlightApplications returns every application bound to workflowID
// whose Status is In_Review or Returned. Used by Service.SaveTemplate to
// reflow in-flight applications whenever the template is updated.
ListInFlightApplications(ctx context.Context, workflowID string) ([]Application, error)
// Review logs
AppendLog(ctx context.Context, l ReviewLog) error
ListLogs(ctx context.Context, applicationID string) ([]ReviewLog, error)
// Transact runs fn inside a single transaction. The Store handed to fn is
// scoped to that transaction; commits/rollbacks are managed by the impl.
Transact(ctx context.Context, fn func(s Store) error) error
}
Store abstracts the persistence layer for templates, applications and logs.
Implementations must keep ReviewLog ordering stable (insertion order) since the engine treats the latest SUBMIT entry as the "current round" boundary.
type WorkflowEngine ¶
type WorkflowEngine struct {
// IDGenerator produces ReviewLog IDs. Swap for deterministic IDs in tests.
IDGenerator func() string
// Clock returns "now" when writing CreatedAt on ReviewLog entries.
Clock func() time.Time
// Resolver decides which user IDs may approve each stage. Defaults to a
// resolver that returns stage.ApproverIDs as-is. Swap in a custom
// implementation to do role/group/department-based routing without
// changing the template schema.
Resolver ApproverResolver
}
WorkflowEngine is the state-driven core of the library. It is stateless across calls; callers persist Application and ReviewLog values returned by the engine.
func NewEngine ¶
func NewEngine() *WorkflowEngine
NewEngine returns a WorkflowEngine wired with default ID/clock implementations and the static ApproverIDs resolver.
func (*WorkflowEngine) Approve ¶
func (e *WorkflowEngine) Approve(app Application, template WorkflowTemplate, operatorID string, logs []ReviewLog) (Application, ReviewLog, error)
Approve records an approval. With ReviewTypeAll it counts approvals collected in the current round; the stage advances only when the full set has approved.
func (*WorkflowEngine) CanEditForm ¶
func (e *WorkflowEngine) CanEditForm(app Application, userID string) bool
CanEditForm reports whether userID may modify the application form fields. Only the owner, and only while in Draft or Returned, may edit.
func (*WorkflowEngine) CanResubmit ¶
func (e *WorkflowEngine) CanResubmit(app Application, userID string) bool
CanResubmit reports whether userID may re-submit a returned application.
func (*WorkflowEngine) CanReview ¶
func (e *WorkflowEngine) CanReview(app Application, template WorkflowTemplate, userID string) bool
CanReview reports whether userID is one of the approvers for the current stage. It performs the static permission check; the "already approved in current round" check is enforced inside Approve.
func (*WorkflowEngine) CanRevokeApprove ¶ added in v0.2.0
func (e *WorkflowEngine) CanRevokeApprove(app Application, template WorkflowTemplate, operatorID string, logs []ReviewLog) bool
CanRevokeApprove reports whether operatorID may cancel their own approval at the current stage. Revocation is only meaningful for an ALL (multi-sign) stage that has NOT yet collected enough approvals to advance: once the stage advances, the operator is no longer the current-stage reviewer and CanReview already returns false. SINGLE stages advance on the first approval, so there is never a pending approval to take back.
func (*WorkflowEngine) CanView ¶
func (e *WorkflowEngine) CanView(app Application, template WorkflowTemplate, userID string) bool
CanView reports whether userID is allowed to read the application. Owner can always view. Approvers in stages reached so far can view as well.
func (*WorkflowEngine) CanWithdraw ¶ added in v0.2.0
func (e *WorkflowEngine) CanWithdraw(app Application, userID string) bool
CanWithdraw reports whether userID may pull the application back to Draft. Only the owner, and only while the case is still In_Review, may withdraw — once a case is Returned it is already in the owner's hands, and terminal states cannot be reopened.
func (*WorkflowEngine) RejectClose ¶
func (e *WorkflowEngine) RejectClose(app Application, template WorkflowTemplate, operatorID string, comment string) (Application, ReviewLog, error)
RejectClose terminally closes the application. Requires a comment.
func (*WorkflowEngine) ResetToFirstSubmit ¶
func (e *WorkflowEngine) ResetToFirstSubmit(app Application, template WorkflowTemplate) (Application, ReviewLog, error)
ResetToFirstSubmit rolls a non-terminal application back to the state it occupied immediately after the very first Submit: Status = In_Review, CurrentStageIndex = 1, ReturnStageIndex = 0. A fresh SUBMIT log is produced so the engine's round-boundary logic invalidates every stale APPROVE.
Service.SaveTemplate calls this on every in-flight application whenever the template definition changes, so reviewers re-approve the new flow.
func (*WorkflowEngine) Resubmit ¶
func (e *WorkflowEngine) Resubmit(app Application, template WorkflowTemplate) (Application, ReviewLog, error)
Resubmit moves a Returned application back into review. STRICT mode restarts from stage 1; DIRECT mode resumes at the original return stage.
func (*WorkflowEngine) Return ¶
func (e *WorkflowEngine) Return(app Application, template WorkflowTemplate, operatorID string, comment string) (Application, ReviewLog, error)
Return puts the application back to the owner for modification. Records the originating stage in ReturnStageIndex for DIRECT-mode resubmission.
func (*WorkflowEngine) RevokeApprove ¶ added in v0.2.0
func (e *WorkflowEngine) RevokeApprove(app Application, template WorkflowTemplate, operatorID string, comment string, logs []ReviewLog) (Application, ReviewLog, error)
RevokeApprove cancels the operator's own approval at the current stage, appending a REVOKE_APPROVE log that nets out their earlier APPROVE for the round. The application state is unchanged — the stage has not advanced, so only the running approval count drops by one and the operator is free to approve again later.
It is intentionally limited: a reviewer can only take back an approval that has NOT yet pushed the stage forward. That window exists only for ALL (multi-sign) stages with an outstanding quorum — see CanRevokeApprove.
- SINGLE stages advance immediately, so there is nothing pending to revoke (returns ErrRevokeNotAllowed).
- Once an ALL stage collects its last approval it advances, after which the operator fails CanReview (returns ErrNoPermission).
func (*WorkflowEngine) Submit ¶
func (e *WorkflowEngine) Submit(app Application, template WorkflowTemplate) (Application, ReviewLog, error)
Submit transitions a Draft application into In_Review at stage 1.
func (*WorkflowEngine) Withdraw ¶ added in v0.2.0
func (e *WorkflowEngine) Withdraw(app Application, template WorkflowTemplate, comment string) (Application, ReviewLog, error)
Withdraw lets the owner pull a still-in-review application back to Draft so it can be edited and submitted again (or abandoned). The originating stage is recorded on the WITHDRAW log for audit; the application's stage pointers are cleared. Any approvals collected so far are left in the log but become inert: the next Submit appends a fresh SUBMIT entry, which starts a new round and excludes them from the count.
Caller identity is trusted, mirroring Submit/Resubmit: the engine only enforces that the case is In_Review and stamps the log with app.OwnerID. Use CanWithdraw to gate the action on owner identity in the layer above.
type WorkflowStage ¶
WorkflowStage defines a single approval stage.
type WorkflowTemplate ¶
type WorkflowTemplate struct {
ID string
ReturnMode string
Stages []WorkflowStage
}
WorkflowTemplate defines the static configuration of a workflow.
Directories
¶
| Path | Synopsis |
|---|---|
|
examples
|
|
|
dynamic_resolver
command
Dynamic ApproverResolver example: reviewers are looked up at runtime from per-application context instead of being baked into the template.
|
Dynamic ApproverResolver example: reviewers are looked up at runtime from per-application context instead of being baked into the template. |
|
full_workflow
command
Comprehensive example covering every public flow:
|
Comprehensive example covering every public flow: |
|
quickstart
command
Quickstart example: shortest end-to-end usage.
|
Quickstart example: shortest end-to-end usage. |
|
Package gormstore provides a GORM-backed workflow.Store implementation.
|
Package gormstore provides a GORM-backed workflow.Store implementation. |