feat: expose bounded workflow activation and signal history

This commit is contained in:
2026-09-18 07:56:18 +08:00
parent ae0d08584a
commit 17a8e0126c
35 changed files with 1756 additions and 59 deletions

173
sdk/go/workflow_history.go Normal file
View File

@@ -0,0 +1,173 @@
package orbit
import (
"context"
"errors"
)
func workflowActivationPath(id, key string, activation ...string) (string, error) {
if !validID(id) || !workflowVersionName(key) {
return "", errors.New("invalid workflow step identity")
}
path := "/v1/workflows/" + id + "/steps/" + key + "/activations"
for _, v := range activation {
if !validID(v) {
return "", errors.New("invalid activation id")
}
path += "/" + v
}
return path, nil
}
func workflowSignalPath(id string, signal ...string) (string, error) {
if !validID(id) {
return "", errors.New("invalid workflow id")
}
path := "/v1/workflows/" + id + "/signals"
for _, v := range signal {
if !validID(v) {
return "", errors.New("invalid signal id")
}
path += "/" + v
}
return path, nil
}
func historyResponseSize(n int, in HistoryPageRequest) bool {
limit := in.Limit
if limit == 0 {
limit = 20
}
return n <= limit
}
// ListWorkflowActivations 返回指定逻辑步骤的各次激活摘要,代次倒序;跨页状态可继续变化。
func (c *Client) ListWorkflowActivations(ctx context.Context, id, key string, in HistoryPageRequest) (WorkflowActivationPage, error) {
var out WorkflowActivationPage
path, err := workflowActivationPath(id, key)
if err != nil {
return out, err
}
query, err := historyPageQuery(in)
if err != nil {
return out, err
}
if err = c.call(ctx, "GET", path+query, "", nil, &out); err != nil {
return WorkflowActivationPage{}, err
}
if out.InstanceID != id || !validID(out.StepID) || out.Revision == 0 || out.Items == nil || !historyResponseSize(len(out.Items), in) {
return WorkflowActivationPage{}, errors.New("invalid activation page")
}
for i, a := range out.Items {
if !validID(a.ID) || a.StepID != out.StepID || a.Sequence == 0 || a.Revision == 0 || i > 0 && a.Sequence >= out.Items[i-1].Sequence {
return WorkflowActivationPage{}, errors.New("invalid activation summary")
}
}
return out, nil
}
// GetWorkflowActivation 明确读取给定历史激活,Step仍为逻辑步骤当前视图,不能用它替代历史Activation。
func (c *Client) GetWorkflowActivation(ctx context.Context, id, key, activation string) (WorkflowActivationDetail, error) {
var out WorkflowActivationDetail
path, err := workflowActivationPath(id, key, activation)
if err != nil {
return out, err
}
if err = c.call(ctx, "GET", path, "", nil, &out); err != nil {
return WorkflowActivationDetail{}, err
}
if out.InstanceID != id || out.Step.Key != key || out.Activation.ID != activation || !validID(out.Step.ID) || out.Activation.StepID != out.Step.ID || out.Activation.Sequence == 0 || out.Revision == 0 {
return WorkflowActivationDetail{}, errors.New("activation detail identity mismatch")
}
return out, nil
}
// ListWorkflowPollRounds 只读取一个激活内的查询轮次;人工恢复的同一业务轮可分属不同激活。
func (c *Client) ListWorkflowPollRounds(ctx context.Context, id, key, activation string, in HistoryPageRequest) (WorkflowPollRoundPage, error) {
var out WorkflowPollRoundPage
path, err := workflowActivationPath(id, key, activation)
if err != nil {
return out, err
}
query, err := historyPageQuery(in)
if err != nil {
return out, err
}
if err = c.call(ctx, "GET", path+"/rounds"+query, "", nil, &out); err != nil {
return WorkflowPollRoundPage{}, err
}
if out.InstanceID != id || out.ActivationID != activation || out.Revision == 0 || out.Items == nil || !historyResponseSize(len(out.Items), in) {
return WorkflowPollRoundPage{}, errors.New("invalid poll round page")
}
for i, r := range out.Items {
if !validID(r.ID) || !validID(r.TaskID) || r.ActivationID != activation || r.Round == 0 || i > 0 && r.Round >= out.Items[i-1].Round {
return WorkflowPollRoundPage{}, errors.New("invalid poll round summary")
}
}
return out, nil
}
// ListWorkflowSignals 不携带信号正文;单条原始内容用GetWorkflowSignal读取。
func (c *Client) ListWorkflowSignals(ctx context.Context, id string, in HistoryPageRequest) (WorkflowSignalPage, error) {
var out WorkflowSignalPage
path, err := workflowSignalPath(id)
if err != nil {
return out, err
}
query, err := historyPageQuery(in)
if err != nil {
return out, err
}
if err = c.call(ctx, "GET", path+query, "", nil, &out); err != nil {
return WorkflowSignalPage{}, err
}
if out.WorkflowID != id || out.Revision == 0 || out.Items == nil || !historyResponseSize(len(out.Items), in) {
return WorkflowSignalPage{}, errors.New("invalid signal page")
}
for _, v := range out.Items {
if !validID(v.ID) || v.WorkflowID != id {
return WorkflowSignalPage{}, errors.New("signal summary identity mismatch")
}
}
return out, nil
}
// ListWorkflowSignalOperations 需要独立workflows:audit:read权限,只返回信号资源的操作审计摘要。
func (c *Client) ListWorkflowSignalOperations(ctx context.Context, id, signal string, in HistoryPageRequest) (WorkflowSignalOperationPage, error) {
var out WorkflowSignalOperationPage
path, err := workflowSignalPath(id, signal)
if err != nil {
return out, err
}
query, err := historyPageQuery(in)
if err != nil {
return out, err
}
if err = c.call(ctx, "GET", path+"/operations"+query, "", nil, &out); err != nil {
return WorkflowSignalOperationPage{}, err
}
if out.Items == nil || !historyResponseSize(len(out.Items), in) {
return WorkflowSignalOperationPage{}, errors.New("invalid signal operation page")
}
for _, v := range out.Items {
if !validID(v.ID) || v.WorkflowID != id || v.SignalID != signal {
return WorkflowSignalOperationPage{}, errors.New("signal operation identity mismatch")
}
}
return out, nil
}
func (c *Client) GetWorkflowSignalOperation(ctx context.Context, id, signal, operation string) (WorkflowSignalOperation, error) {
var out WorkflowSignalOperation
path, err := workflowSignalPath(id, signal)
if err != nil {
return out, err
}
if !validID(operation) {
return out, errors.New("invalid signal operation id")
}
if err = c.call(ctx, "GET", path+"/operations/"+operation, "", nil, &out); err != nil {
return WorkflowSignalOperation{}, err
}
if out.ID != operation || out.WorkflowID != id || out.SignalID != signal {
return WorkflowSignalOperation{}, errors.New("signal operation detail identity mismatch")
}
return out, nil
}