package mysql import ( "context" "database/sql" "encoding/json" "errors" "git.yuntuo.hk/k2safe/orbit/internal/fault" core "git.yuntuo.hk/k2safe/orbit/model" "git.yuntuo.hk/k2safe/orbit/pulse/service" "git.yuntuo.hk/k2safe/orbit/weave/engine" ) // readTaskWorkflowSource 只核验创建时固定的来源,不按当前激活替换历史,也不取得运行器锁。 // 这里只向已授权任务暴露有限引用;完整流程及定义继续使用各自权限。 func readTaskWorkflowSource(ctx context.Context, tx *sql.Tx, ns, task, queue, activation, gate core.ID, direct bool) (service.TaskWorkflowSource, error) { var out service.TaskWorkflowSource var activationTask *core.ID var policyRaw []byte var handler, handlerVersion, payloadVersion string err := tx.QueryRowContext(ctx, "SELECT a.workflow_id,i.definition_id,i.definition_version_id,v.version,s.id,s.`key`,s.kind,a.id,a.sequence,a.task_id,i.policy_snapshot,t.retry_of,t.handler,t.handler_version,t.payload_version FROM weave_activations a JOIN weave_steps s ON s.namespace_id=a.namespace_id AND s.workflow_id=a.workflow_id AND s.id=a.step_id JOIN weave_instances i ON i.namespace_id=a.namespace_id AND i.id=a.workflow_id AND i.gate_id=? JOIN pulse_execution_gates g ON g.namespace_id=i.namespace_id AND g.id=i.gate_id JOIN weave_definitions d ON d.namespace_id=i.namespace_id AND d.id=i.definition_id JOIN weave_definition_versions v ON v.namespace_id=i.namespace_id AND v.definition_id=i.definition_id AND v.id=i.definition_version_id JOIN pulse_tasks t ON t.namespace_id=a.namespace_id AND t.id=? AND t.owner_kind='workflow_activation' AND t.owner_id=a.id AND t.gate_id=i.gate_id AND t.queue_id=? JOIN pulse_queues q ON q.namespace_id=t.namespace_id AND q.id=t.queue_id WHERE a.namespace_id=? AND a.id=?", gate, task, queue, ns, activation).Scan(&out.WorkflowID, &out.DefinitionID, &out.DefinitionVersionID, &out.Version, &out.StepID, &out.StepKey, &out.StepKind, &out.ActivationID, &out.ActivationSequence, &activationTask, &policyRaw, &out.RetryOf, &handler, &handlerVersion, &payloadVersion) if errors.Is(err, sql.ErrNoRows) { return out, fault.Unavailable } if err != nil { return out, err } version, err := readWorkflowVersion(ctx, tx, ns, out.DefinitionID, out.Version) if err != nil { return out, err } var policy workflowPolicy if version.ID != out.DefinitionVersionID || json.Unmarshal(policyRaw, &policy) != nil || policy != (workflowPolicy{1, version.ID, version.ContentDigest, version.Specification.FailurePolicy, version.Analysis.MaxTaskAttempts}) { return out, fault.Unavailable } program, err := engine.Compile(version.Specification) if err != nil { return out, err } node, ok := program.Step(out.StepKey) if !ok || node.Kind != out.StepKind || out.ActivationSequence == 0 { return out, fault.Unavailable } contract := node.Task wantsParent := out.ActivationSequence > 1 switch out.StepKind { case "TASK": if !direct || activationTask == nil || *activationTask != task || contract == nil || out.ActivationSequence > contract.Retry.MaxAttempts { return out, fault.Unavailable } case "POLL": if direct || activationTask != nil || node.Poll == nil { return out, fault.Unavailable } contract = &node.Poll.Task var first, used, maximum uint32 var roundID core.ID err = tx.QueryRowContext(ctx, "SELECT r.id,r.`round`,p.first_round,p.used_rounds,p.max_rounds FROM weave_poll_rounds r JOIN weave_poll_states p ON p.namespace_id=r.namespace_id AND p.workflow_id=r.workflow_id AND p.activation_id=r.activation_id WHERE r.namespace_id=? AND r.workflow_id=? AND r.activation_id=? AND r.task_id=?", ns, out.WorkflowID, activation, task).Scan(&roundID, &out.PollRound, &first, &used, &maximum) if errors.Is(err, sql.ErrNoRows) { return out, fault.Unavailable } if err != nil { return out, err } if first == 0 || first > out.PollRound || out.PollRound > used || used > maximum || maximum != node.Poll.MaxRounds || out.ActivationSequence == 1 && first != 1 || out.ActivationSequence > maximum*contract.Retry.MaxAttempts { return out, fault.Unavailable } out.PollRoundID = &roundID wantsParent = wantsParent && out.PollRound == first default: return out, fault.Unavailable } if contract.QueueID != queue || contract.Handler != handler || contract.HandlerVersion != handlerVersion || contract.PayloadVersion != payloadVersion || wantsParent != (out.RetryOf != nil) { return out, fault.Unavailable } if wantsParent { if *out.RetryOf == task { return out, fault.Unavailable } // 只读直接父任务,使用步骤/代次唯一索引;不扫描或递归整个恢复历史。 q := `SELECT COUNT(*) FROM weave_activations a JOIN pulse_tasks t ON t.namespace_id=a.namespace_id AND t.owner_kind='workflow_activation' AND t.owner_id=a.id AND t.gate_id=? AND t.queue_id=? AND t.id=? WHERE a.namespace_id=? AND a.workflow_id=? AND a.step_id=? AND a.sequence=? AND t.effect_uncertain=FALSE` args := []any{gate, queue, *out.RetryOf, ns, out.WorkflowID, out.StepID, out.ActivationSequence - 1} if out.StepKind == "TASK" { q += ` AND a.task_id=t.id AND t.state IN ('FAILED','CANCELLED')` } else { q += " AND a.task_id IS NULL AND t.state IN ('SUCCEEDED','FAILED','CANCELLED') AND EXISTS(SELECT 1 FROM weave_poll_rounds r JOIN weave_poll_states p ON p.namespace_id=r.namespace_id AND p.workflow_id=r.workflow_id AND p.activation_id=r.activation_id WHERE r.namespace_id=a.namespace_id AND r.workflow_id=a.workflow_id AND r.activation_id=a.id AND r.task_id=t.id AND r.`round`=? AND p.used_rounds=r.`round` AND p.first_round<=r.`round`)" args = append(args, out.PollRound) } var n int if err = tx.QueryRowContext(ctx, q, args...).Scan(&n); err != nil { return out, err } if n != 1 { return out, fault.Unavailable } } return out, nil }