package mysql import ( "context" "database/sql" "errors" core "git.yuntuo.hk/k2safe/orbit/model" "git.yuntuo.hk/k2safe/orbit/weave/engine" ) type workflowTaskLink struct { Owner sql.NullString Activation *core.ID Gate *core.ID Linked bool } // appendWorkflowResult 在原Pulse结果事务内创建内部交付,必须先于外部结果流行锁。 // 这里只核验稳定归属并写入队列,不锁实例/激活、不推进流程、不锁对外订阅。 func appendWorkflowResult(ctx context.Context, tx *sql.Tx, ns, eventID, taskID core.ID, revision uint64, snapshot []byte, link workflowTaskLink) error { owner, ownerID, gate := link.Owner, link.Activation, link.Gate if !owner.Valid || owner.String != engine.TaskOwnerKind { if owner.Valid && owner.String != "schedule" { return errors.New("unrecognized task owner") } // 无外键情况下,丢失owner不能把已有流程关联静默降级成普通任务。 if link.Linked { return errors.New("workflow result ownership missing") } return nil } if ownerID == nil || gate == nil { return errors.New("workflow result owner or gate missing") } var workflow core.ID var kind string var activationTask *core.ID // 即使旧激活不再是current,也要保存内部事件,消费方再核对当前激活并留痕。 err := tx.QueryRowContext(ctx, `SELECT a.workflow_id,s.kind,a.task_id FROM weave_activations a JOIN weave_steps s ON s.namespace_id=a.namespace_id AND s.id=a.step_id AND s.workflow_id=a.workflow_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_definition_versions v ON v.namespace_id=i.namespace_id AND v.id=i.definition_version_id AND v.definition_id=i.definition_id JOIN weave_definitions d ON d.namespace_id=i.namespace_id AND d.id=i.definition_id WHERE a.namespace_id=? AND a.id=?`, gate, ns, ownerID).Scan(&workflow, &kind, &activationTask) if errors.Is(err, sql.ErrNoRows) { return errors.New("workflow result associations invalid") } if err != nil { return err } switch kind { case "TASK": if activationTask == nil || *activationTask != taskID { return errors.New("workflow activation task mismatch") } case "POLL": if activationTask != nil { return errors.New("poll activation cannot own a direct task") } var round uint32 err = tx.QueryRowContext(ctx, `SELECT r.round 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=? AND r.round BETWEEN p.first_round AND p.used_rounds`, ns, workflow, ownerID, taskID).Scan(&round) if errors.Is(err, sql.ErrNoRows) { return errors.New("workflow poll task association invalid") } if err != nil { return err } default: return errors.New("workflow task owner kind invalid") } id, err := core.NewID() if err != nil { return err } _, err = tx.ExecContext(ctx, `INSERT INTO weave_inbox(namespace_id,id,workflow_id,event_id,available_at,source_task_id,source_activation_id,source_revision,snapshot) VALUES (?,?,?,?,UTC_TIMESTAMP(6),?,?,?,?)`, ns, id, workflow, eventID, taskID, ownerID, revision, string(snapshot)) return err }