feat: trace task sources to fixed workflow activations and rounds

This commit is contained in:
2026-09-18 07:14:55 +08:00
parent b9e4191c6b
commit ae0d08584a
27 changed files with 593 additions and 47 deletions

View File

@@ -137,6 +137,18 @@ func workflowAPIError(t *testing.T, err error, code string) {
}
}
func assertWorkflowSourceSDK(t *testing.T, ctx context.Context, api *Client, v WorkflowInstance, task string, sequence, round uint32, parent string) {
t.Helper()
source, err := api.GetTaskSource(ctx, task)
if err != nil || source.Workflow == nil || source.Kind != "WORKFLOW" {
t.Fatal("workflow source", err)
}
w := source.Workflow
if w.WorkflowID != v.ID || w.DefinitionID != v.DefinitionID || w.DefinitionVersionID != v.DefinitionVersionID || w.Version != v.Version || w.ActivationSequence != sequence || w.PollRound != round || parent == "" && w.RetryOf != nil || parent != "" && (w.RetryOf == nil || *w.RetryOf != parent) {
t.Fatal("fixed source mismatch", w)
}
}
func TestIntegrationWorkflowInstancesSDK(t *testing.T) {
api, queue, token := integrationWorkerClient(t)
ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
@@ -212,6 +224,7 @@ func TestIntegrationWorkflowInstancesSDK(t *testing.T) {
w := workflowWorkerSDK(t, ctx, api, queue, handler)
defer w.Close()
first := finishWorkflowSDK(t, ctx, w, WorkerSucceeded, workflowExact)
assertWorkflowSourceSDK(t, ctx, api, started.Instance, first.Task.ID, 1, 0, "")
work, err := api.GetWorkflowStep(ctx, id, "work")
if err != nil || work.Step.TaskID == nil || *work.Step.TaskID != first.Task.ID || work.Step.ActivationSequence != 1 {
t.Fatal("step metadata", err)
@@ -349,6 +362,8 @@ func TestIntegrationWorkflowInstancesSDK(t *testing.T) {
if err != nil || !repeated.Deduplicated || repeated.Task.ID != child.Task.ID {
t.Fatal(err)
}
assertWorkflowSourceSDK(t, ctx, api, v, child.Task.ID, 2, 0, *child.Task.RetryOf)
assertWorkflowSourceSDK(t, ctx, api, v, *child.Task.RetryOf, 1, 0, "")
continueWorkflowSDK(t, ctx, api, v.ID)
a := finishWorkflowSDK(t, ctx, w, WorkerSucceeded, workflowExact)
if a.Task.ID != child.Task.ID {
@@ -374,6 +389,7 @@ func TestIntegrationWorkflowInstancesSDK(t *testing.T) {
if err != nil {
t.Fatal(err)
}
assertWorkflowSourceSDK(t, ctx, api, v, source.ID, 1, 1, "")
reviewed, err := api.ReviewWorkflowTask(ctx, v.ID, workerTestID(t), ReviewWorkflowTaskRequest{WorkflowRecoveryDecision: workflowDecision(step).WorkflowRecoveryDecision, TaskID: source.ID, ExpectedTaskRevision: source.Revision, ExecutionSettled: true, Outcome: TaskSucceeded, Result: json.RawMessage(`"PENDING"`)})
if err != nil {
t.Fatal(err)
@@ -390,13 +406,36 @@ func TestIntegrationWorkflowInstancesSDK(t *testing.T) {
t.Fatal(err)
}
continueWorkflowSDK(t, ctx, api, v.ID)
finishWorkflowSDK(t, ctx, w, WorkerSucceeded, json.RawMessage(`"DONE"`))
last := finishWorkflowSDK(t, ctx, w, WorkerSucceeded, json.RawMessage(`"DONE"`))
assertWorkflowSourceSDK(t, ctx, api, v, last.Task.ID, 1, 2, "")
assertWorkflowSourceSDK(t, ctx, api, v, source.ID, 1, 1, "")
waitWorkflowSDK(t, ctx, api, v.ID, "SUCCEEDED")
final, err := api.GetWorkflowStep(ctx, v.ID, "query")
if err != nil || final.Poll.UsedRounds != 2 || final.Poll.DeadlineAt != step.Poll.DeadlineAt {
t.Fatal("poll budget changed", err)
}
})
t.Run("poll_recovery_source_keeps_business_round", func(t *testing.T) {
spec := workflowSDKSpec(queue, handler)
task := *spec.Body.Steps[0].Task
task.ResultType = WorkflowType{Kind: "STRING", Enum: []string{"PENDING", "DONE"}}
spec.Body = WorkflowBlock{Steps: []WorkflowStep{{Key: "query", Kind: "POLL", Poll: &WorkflowPoll{Task: task, MaxRounds: 3, TimeoutMS: 60000, IntervalMS: 1000, Pending: []string{"PENDING"}, Succeeded: []string{"DONE"}}}}, OutputType: task.ResultType, Output: WorkflowRef("query")}
v := startWorkflowSDK(t, ctx, api, spec)
w := workflowWorkerSDK(t, ctx, api, queue, handler)
defer w.Close()
old := finishWorkflowSDK(t, ctx, w, WorkerFailed, json.RawMessage(`"failure"`))
step := waitWorkflowStepSDK(t, ctx, api, v.ID, "query", func(v WorkflowStepDetail) bool { return v.Step.State == "NEEDS_REVIEW" })
child, err := api.RetryWorkflowStep(ctx, v.ID, workerTestID(t), RetryWorkflowStepRequest{WorkflowStepDecision: workflowDecision(step), SafeToRetry: true})
if err != nil || child.Task == nil {
t.Fatal(err)
}
assertWorkflowSourceSDK(t, ctx, api, v, child.Task.ID, 2, 1, old.Task.ID)
assertWorkflowSourceSDK(t, ctx, api, v, old.Task.ID, 1, 1, "")
continueWorkflowSDK(t, ctx, api, v.ID)
finishWorkflowSDK(t, ctx, w, WorkerSucceeded, json.RawMessage(`"DONE"`))
waitWorkflowSDK(t, ctx, api, v.ID, "SUCCEEDED")
assertWorkflowSourceSDK(t, ctx, api, v, child.Task.ID, 2, 1, old.Task.ID)
})
if !t.Failed() {
t.Log("public workflow start/query/control/signal, all six recovery commands, audit and real Worker execution verified without server imports")
}