feat: persist workflow admission and initial execution graph

This commit is contained in:
2026-09-18 01:43:01 +08:00
parent 0f7ac5203b
commit 553a0bf522
19 changed files with 1381 additions and 10 deletions

113
weave/instance/service.go Normal file
View File

@@ -0,0 +1,113 @@
// Package instance 定义流程实例的受理与查询边界;执行推进由持久化存储运行器负责。
package instance
import (
"context"
"encoding/json"
"strings"
"time"
"unicode/utf8"
"git.yuntuo.hk/k2safe/orbit/internal/fault"
core "git.yuntuo.hk/k2safe/orbit/model"
"git.yuntuo.hk/k2safe/orbit/platform/auth"
"git.yuntuo.hk/k2safe/orbit/pulse/service"
"git.yuntuo.hk/k2safe/orbit/weave/definition"
weave "git.yuntuo.hk/k2safe/orbit/weave/model"
)
const StartAction = "workflows:start"
const ReadAction = "workflows:read"
const MaxUnfinished = 10000
// StartInput 使用显式版本;业务追踪键不能替代请求幂等键。
type StartInput struct {
DefinitionID core.ID `json:"definition_id"` // DefinitionID 同命名空间的稳定定义身份。
Version string `json:"version"` // Version 已发布的固定版本名,不解析latest别名。
Input json.RawMessage `json:"input" nullable:"true"` // Input 按固定版本验证的输入,显式null与未提供不同。
BusinessKey string `json:"business_key,omitempty"` // BusinessKey 最多191个Unicode字符,保留尾部空格。
}
// View 是实例当前权威视图;StartResult中的View冻结在受理事务,不随重报更新。
type View struct {
ID core.ID `json:"id"` // ID 实例身份。
NamespaceID core.ID `json:"namespace_id"` // NamespaceID 授权及数据隔离范围。
DefinitionID core.ID `json:"definition_id"` // DefinitionID 稳定定义身份。
DefinitionVersionID core.ID `json:"definition_version_id"` // DefinitionVersionID 固定版本记录身份。
Version string `json:"version"` // Version 启动时固定的发布版本名。
State weave.InstanceState `json:"state"` // State 执行状态,受理不代表业务成功。
Revision uint64 `json:"revision,string"` // Revision 实例并发版本。
Paused bool `json:"paused"` // Paused 独立暂停标志,不表示在途工作已停止。
ControlRevision uint64 `json:"control_revision,string"` // ControlRevision 控制操作并发版本。
Input json.RawMessage `json:"input"` // Input 不变的受理输入,保留数字文本。
Output json.RawMessage `json:"output,omitempty"` // Output 成功输出;缺失与显式null不同。
BusinessKey string `json:"business_key"` // BusinessKey 原始追踪键。
DeadlineAt time.Time `json:"deadline_at"` // DeadlineAt 数据库时间确定的固定总截止。
CancelRequestedAt *time.Time `json:"cancel_requested_at,omitempty"` // CancelRequestedAt 已保存的取消意图时间。
CompletedAt *time.Time `json:"completed_at,omitempty"` // CompletedAt 终结时间,非终态为空。
CreatedAt time.Time `json:"created_at"` // CreatedAt UTC受理时间。
UpdatedAt time.Time `json:"updated_at"` // UpdatedAt 最近状态更新时间。
}
type StartResult struct {
Instance View `json:"instance"` // Instance 原受理回执的实例快照。
Deduplicated bool `json:"deduplicated"` // Deduplicated 原键恢复,不新建工作或续期。
}
// StepView 只列元数据,避免把最多128个大输入输出聚合为无界响应。
type StepView struct {
ID core.ID `json:"id"` // ID 实例内逻辑步骤身份。
Key string `json:"key"` // Key 固定定义步骤名称。
Kind weave.NodeKind `json:"kind"` // Kind 固定节点类型。
State weave.StepState `json:"state"` // State 当前步骤状态。
Revision uint64 `json:"revision,string"` // Revision 步骤并发版本。
ActivationID *core.ID `json:"activation_id,omitempty"` // ActivationID 当前激活,未激活时为空。
TaskID *core.ID `json:"task_id,omitempty"` // TaskID TASK的直接任务;POLL任务通过轮次查询。
}
type StepsView struct {
InstanceID core.ID `json:"instance_id"` // InstanceID 所属实例。
Revision uint64 `json:"revision,string"` // Revision 与步骤读取一致的实例版本。
Steps []StepView `json:"steps"` // Steps 按键排序,最多128个,不含内部根检查点。
}
type Store interface {
Start(context.Context, auth.Token, string, StartInput) (StartResult, error)
Get(context.Context, auth.Token, core.ID) (View, error)
Steps(context.Context, auth.Token, core.ID) (StepsView, error)
}
type Service struct{ store Store }
func New(store Store) *Service { return &Service{store: store} }
func (s *Service) Start(ctx context.Context, t auth.Token, key string, in StartInput) (StartResult, error) {
if err := service.ValidateKey(key); err != nil {
return StartResult{}, err
}
if in.DefinitionID.Validate() != nil || !versionName(in.Version) || !utf8.ValidString(in.BusinessKey) || utf8.RuneCountInString(in.BusinessKey) > 191 || strings.ContainsRune(in.BusinessKey, 0) {
return StartResult{}, fault.Invalid("valid workflow definition, version and business key required")
}
if err := definition.ValidateData(definition.Type{Kind: "JSON"}, in.Input); err != nil {
return StartResult{}, fault.Invalid("valid workflow input required, maximum 256 KiB")
}
return s.store.Start(ctx, t, key, in)
}
func versionName(v string) bool {
if len(v) == 0 || len(v) > 64 {
return false
}
for i, r := range v {
if !(r >= 'a' && r <= 'z' || r >= 'A' && r <= 'Z' || r >= '0' && r <= '9' || i > 0 && (r == '_' || r == '.' || r == '-')) {
return false
}
}
return true
}
func (s *Service) Get(ctx context.Context, t auth.Token, id core.ID) (View, error) {
if id.Validate() != nil {
return View{}, fault.Invalid("invalid workflow instance id")
}
return s.store.Get(ctx, t, id)
}
func (s *Service) Steps(ctx context.Context, t auth.Token, id core.ID) (StepsView, error) {
if id.Validate() != nil {
return StepsView{}, fault.Invalid("invalid workflow instance id")
}
return s.store.Steps(ctx, t, id)
}