package engine import ( "encoding/json" "errors" "fmt" "strings" "unicode/utf8" "git.yuntuo.hk/k2safe/orbit/internal/jsonwire" core "git.yuntuo.hk/k2safe/orbit/model" "git.yuntuo.hk/k2safe/orbit/pulse/service" "git.yuntuo.hk/k2safe/orbit/weave/definition" weave "git.yuntuo.hk/k2safe/orbit/weave/model" ) // TaskOwnerKind 标识由Weave激活拥有的Pulse任务;TASK及POLL轮次都使用激活ID作为owner_id。 const TaskOwnerKind = "workflow_activation" // Program 持有独立拷贝的已验证规则,可并发读取;不暴露可变的内部映射。 type Program struct { spec definition.Specification nodes map[string]definition.Step } func Compile(spec definition.Specification) (*Program, error) { if _, err := definition.Check(spec); err != nil { return nil, err } raw, err := jsonwire.Marshal(spec) if err != nil { return nil, err } p := &Program{nodes: map[string]definition.Step{}} if err := json.Unmarshal(raw, &p.spec); err != nil { return nil, err } var visit func(definition.Block) visit = func(b definition.Block) { for _, s := range b.Steps { p.nodes[s.Key] = s if s.Branch != nil { for _, c := range s.Branch.Cases { visit(c.Body) } visit(*s.Branch.Default) } if s.Parallel != nil { for _, branch := range s.Parallel.Branches { visit(branch.Body) } } } } visit(p.spec.Body) return p, nil } // Step 返回定义副本,调用方修改不会改变已编译版本。 func (p *Program) Step(key string) (definition.Step, bool) { s, ok := p.nodes[key] if !ok { return definition.Step{}, false } raw, err := jsonwire.Marshal(s) if err != nil { return definition.Step{}, false } var out definition.Step if json.Unmarshal(raw, &out) != nil { return out, false } return out, true } // State 是存储适配层核验身份/激活/版本后提供的当前步骤快照,不是直接信任的外部请求。 type State struct { Status weave.StepState `json:"status"` // Status 逻辑步骤当前状态,BLOCKED可无激活。 Input json.RawMessage `json:"input,omitempty"` // Input 激活时冻结的参数或组合决策,重复推进不得重算。 Output json.RawMessage `json:"output,omitempty"` // Output 仅SUCCEEDED提供,显式null和未提供不同。 } // Snapshot 必须来自同一实例事务内的持久化视图;节点不以内存缓存授予执行权。 type Snapshot struct { WorkflowID core.ID // WorkflowID 固定实例标识,加入workflow引用上下文。 BusinessKey string // BusinessKey 业务追踪键,不代替幂等身份。 Input json.RawMessage // Input 启动时冻结且已保存的输入。 Steps map[string]State // Steps 当前激活投影;缺少的静态步骤视为BLOCKED。 AllowNew bool // AllowNew 已核验暂停、取消、截止时间和门控后是否允许激活新步骤。 } // WaitCommand 只描述需要持久化的等待;存储层用数据库激活时间固定绝对截止。 type WaitCommand struct { Kind string // Kind SIGNAL、TIMER或POLL最终回执。 Name string // Name 业务信号名称;TIMER为空。 PayloadVersion string // PayloadVersion 信号契约版本;TIMER为空。 Correlation string // Correlation 激活时冻结的业务操作,非定时等待必须非空。 TimeoutMS uint64 // TimeoutMS 相对激活时间的等待上界,还受实例绝对截止限制。 } // Change 是一个必须与激活、任务/等待、检查点和审计同事务落地的决策。 type Change struct { Key string // Key 对应静态步骤身份。 Operation string // Operation ACTIVATE、COMPLETE或SKIP。 Kind string // Kind 原定义节点类型,存储层仍需核对。 State State // State 变更后的步骤投影。 Task *service.SubmitInput // Task 已解析和校验的TASK/POLL输入,不包含实际任务ID或执行授权。 Wait *WaitCommand // Wait 需要持久化的等待信息;TIMER不占业务Worker。 } // Decision 不含数据库副作用;只有存储事务提交后Changes才可用于后续工作。 type Decision struct { Changes []Change // Changes 按依赖顺序的有界决策列表。 Complete bool // Complete 根块所有已选择步骤完成;不能替代存储层的在途结清校验。 Output json.RawMessage // Output Complete时的根块输出,已按契约验证。 More bool // More 因本轮步数预算停止,必须保存持久化待续记录。 } // Problem 是可审计的稳定分类,不携带任意业务参数。 type Problem struct { Key string Code string } func (e *Problem) Error() string { return fmt.Sprintf("workflow step %q: %s", e.Key, e.Code) } func problem(key, code string) error { return &Problem{Key: key, Code: code} } // prepareTask 把冻结定义映射为Pulse受理参数;实际截止及额度由存储事务继续限制。 func prepareTask(t definition.Task, env values) (service.SubmitInput, error) { raw, err := resolve(t.Input, t.InputType, env) if err != nil { return service.SubmitInput{}, err } in := service.SubmitInput{QueueID: t.QueueID, Handler: t.Handler, HandlerVersion: t.HandlerVersion, PayloadVersion: t.PayloadVersion, Payload: raw, TaskTimeoutMS: t.TimeoutMS, Retry: t.Retry, RecoverySafety: t.RecoverySafety, Priority: t.Priority} for _, key := range []struct { value *definition.Value target *string }{{t.BusinessKey, &in.BusinessKey}, {t.ConcurrencyKey, &in.ConcurrencyKey}} { if key.value != nil { raw, err := resolve(*key.value, definition.Type{Kind: "STRING"}, env) if err != nil { return in, err } if err := json.Unmarshal(raw, key.target); err != nil { return in, err } } } return service.NormalizeSubmit(in) } func prepareSignal(s definition.Signal, kind string, env values) (*WaitCommand, error) { raw, err := resolve(s.Correlation, definition.Type{Kind: "STRING"}, env) if err != nil { return nil, err } var correlation string if json.Unmarshal(raw, &correlation) != nil || correlation == "" || len(correlation) > 512 { return nil, errors.New("invalid runtime signal correlation") } return &WaitCommand{Kind: kind, Name: s.Name, PayloadVersion: s.PayloadVersion, Correlation: correlation, TimeoutMS: s.TimeoutMS}, nil } func validBusinessKey(s string) bool { return utf8.ValidString(s) && utf8.RuneCountInString(s) <= 191 && !strings.ContainsRune(s, 0) }