package main import ( "context" orbit "git.yuntuo.hk/k2safe/orbit/sdk/go" ) // verifiedTask 保存已核验分支的任务和尝试,最终与本地调用逐个关联。 type verifiedTask struct { sequence int // sequence 实例请求序号。 taskID string // taskID 分支唯一任务身份。 attemptID string // attemptID 唯一成功尝试身份。 } func verifySteps(ctx context.Context, cfg config, api workflowAPI, instance orbit.WorkflowInstance, runID string, sequence int, steps orbit.WorkflowSteps) ([]verifiedTask, string) { if steps.InstanceID != instance.ID || steps.Revision != instance.Revision || len(steps.Steps) != 3 { return nil, "STEP_COUNT_OR_PARENT_MISMATCH" } keys, ids, tasks, activations := map[string]bool{}, map[string]bool{}, map[string]bool{}, map[string]bool{} for _, s := range steps.Steps { kind := map[string]string{"pair": "PARALLEL", "left": "TASK", "right": "TASK"}[s.Key] if kind == "" || s.Kind != kind || s.State != "SUCCEEDED" || !validID(s.ID) || s.Revision == 0 || ids[s.ID] || keys[s.Key] || s.ActivationSequence != 1 || s.ActivationID == nil || !validID(*s.ActivationID) || activations[*s.ActivationID] { return nil, "STEP_MISMATCH" } keys[s.Key] = true ids[s.ID] = true activations[*s.ActivationID] = true if kind == "PARALLEL" { if s.TaskID != nil { return nil, "JOIN_TASK_MISMATCH" } continue } if s.TaskID == nil || !validID(*s.TaskID) || tasks[*s.TaskID] { return nil, "DUPLICATE_OR_INVALID_TASK" } tasks[*s.TaskID] = true } proofs := make([]verifiedTask, 0, 2) for _, step := range steps.Steps { if step.Kind != "TASK" { continue } call, stop := context.WithTimeout(ctx, cfg.Timeout) task, err := api.GetTask(call, *step.TaskID) stop() if err != nil { return nil, "TASK_" + classify(err) } result, ok := decodePayload(task.Result) if task.ID != *step.TaskID || !matchesTask(task, cfg, runID, sequence) || task.NamespaceID != instance.NamespaceID || task.State != orbit.TaskSucceeded || task.EffectUncertain || task.AttemptCount != 1 || task.CompletedAt == nil || task.CancelRequestedAt != nil || !ok || result != (workflowPayload{RunID: runID, Sequence: sequence}) { return nil, "TASK_MISMATCH" } if task.CreatedAt.Before(instance.CreatedAt) || !task.AvailableAt.Equal(task.CreatedAt) || !task.DeadlineAt.Equal(instance.DeadlineAt) || task.CompletedAt.Before(task.CreatedAt) || instance.CompletedAt == nil || task.CompletedAt.After(*instance.CompletedAt) { return nil, "TASK_TIMESTAMP_MISMATCH" } call, stop = context.WithTimeout(ctx, cfg.Timeout) history, err := api.ListTaskAttempts(call, task.ID, orbit.HistoryPageRequest{Limit: 2}) stop() if err != nil { return nil, "HISTORY_" + classify(err) } if len(history.Items) != 1 || history.NextCursor != "" { return nil, "ATTEMPT_COUNT_MISMATCH" } a := history.Items[0] if !validID(a.ID) || a.TaskID != task.ID || a.State != orbit.AttemptSucceeded || a.Epoch != 1 || a.AuthorizedAt == nil || a.EndedAt == nil || a.ErrorCode != "" || a.Retryable || a.ConcurrencyToken != 0 { return nil, "ATTEMPT_MISMATCH" } if a.CreatedAt.Before(task.AvailableAt) || a.AuthorizedAt.Before(a.CreatedAt) || a.EndedAt.Before(*a.AuthorizedAt) || !a.EndedAt.Equal(*task.CompletedAt) { return nil, "ATTEMPT_TIMESTAMP_MISMATCH" } call, stop = context.WithTimeout(ctx, cfg.Timeout) source, err := api.GetTaskSource(call, task.ID) stop() if err != nil { return nil, "SOURCE_" + classify(err) } s := source.Workflow if source.TaskID != task.ID || source.Kind != "WORKFLOW" || source.RetryOf != nil || source.Schedule != nil || s == nil || s.WorkflowID != instance.ID || s.DefinitionID != instance.DefinitionID || s.DefinitionVersionID != instance.DefinitionVersionID || s.Version != instance.Version || s.StepID != step.ID || s.StepKey != step.Key || s.StepKind != "TASK" || s.ActivationID != *step.ActivationID || s.ActivationSequence != 1 || s.PollRound != 0 || s.PollRoundID != nil || s.RetryOf != nil { return nil, "SOURCE_MISMATCH" } proofs = append(proofs, verifiedTask{sequence: sequence, taskID: task.ID, attemptID: a.ID}) } return proofs, "" } func matchesTask(t orbit.Task, c config, runID string, sequence int) bool { input, ok := decodePayload(t.Payload) return validID(t.ID) && t.QueueID == c.QueueID && t.Handler == handlerName(runID) && t.HandlerVersion == "v1" && t.PayloadVersion == "v1" && t.BusinessKey == "" && t.ConcurrencyKey == "" && t.Priority == 0 && t.RetryOf == nil && t.StartDeadlineAt == nil && t.RecoverySafety == orbit.RecoveryReconcileOnly && t.Retry == (orbit.RetryPolicy{MaxAttempts: 1, AttemptTimeoutMS: uint64(c.RunTimeout.Milliseconds())}) && ok && input == (workflowPayload{RunID: runID, Sequence: sequence}) }