// 引擎操作入口 —— 移植自 rust engine.rs impl JeeflowEngineImpl(async ops)

///|
/// 发起流程(async)
pub async fn[R : @spi.ProcessRepository, E : @spi.ProcessExtRepository] Engine::start_async(
  self : Engine[R, E],
  define_id : Int64,
  operator : String,
  args : @json.FlowData
) -> @model.ProcessInstance raise @error.JeeflowError {
  // 1. 加载定义
  let define_info = match (self.repo()).find_define_by_id(define_id) {
    Some(d) => d
    None => raise @error.DefineNotFound(define_id)
  }
  // 2. 解析模型
  let model = @parser.parse_model(define_info.content)
  // 3. 构建 args(合并 + 用户信息 + autoGenTitle)
  let full_args = @json.FlowData::new()
  full_args.merge(args)
  self.add_user_info(full_args, operator)
  let title = gen_auto_title(full_args, define_info.display_name)
  full_args.insert_str("autoGenTitle", title)
  // 4. 创建实例
  let instance = @model.ProcessInstance::create(define_info, operator, full_args)
  // 5. expireTime(issues/137 A 裁定 A:实例级这一列=定义级表达式的**求值结果**,不是原串)
  //    基准逐字=jeeflow-java JeeflowEngineImpl.java:93-96(`model.getExpireTime()` **非空才**
  //    `instance.setExpireTime(FlowUtil.processTime(expireTime, args))`)+ boot2
  //    ProcessInstanceServiceImpl.java:157-160 同形状。尺子**复用本栈既有那一枚**
  //    @model.process_time(issues/126 案 A 移植、任务级四处写点同款),不新造第二把。
  //    修前形状=原串搬运 `Some(et) => instance.expire_time = Some(et)`:把 "2h" 这类表达式**原文**
  //    写进 wf_process_instance.expire_time(datetime 列)。SQL 仓绑定点
  //    repository-mysql/repo/repository.mbt:168 `opt_v(instance.expire_time)` 原样塞参 ⇒ 真库
  //    `@@sql_mode` 含 STRICT_TRANS_TABLES 时是 1292/SQLSTATE 22007
  //    `Incorrect datetime value: '2h' for column 'expire_time'`(rust/php 本轮实测;137 案文原写
  //    1366 已按实测更正),比留 NULL 更糟——整条发起 INSERT 直接失败。
  //      · 变量源=`full_args`(已 add_user_info + autoGenTitle 的那份,对齐 java 在
  //        addUserInfoToArgs/addAutoGenTitle **之后**才取 args 的位置)
  //      · 定义没配(缺键 / JSON null)⇒ `None` 臂 ⇒ 不碰这一列 ⇒ 保持 create 时的 NULL
  //        (不赋 now()、不赋空串;owner 2026-09-28 口径「不设置就是没有到期时间」)
  //      · 配了但算不出 ⇒ process_time 给 None ⇒ 同样落 NULL(空串/纯空白走它内部 ⓪ 档,
  //        误配/负数档/裁空白边界沿用 §3-2、§3-3 已定的落穿语义,**不兜底 now**)
  match model.expire_time {
    Some(et) => instance.expire_time = @model.process_time(Some(et), full_args)
    None => ()
  }
  // 6. 分配 ID
  self.assign_ids(instance)
  // 7. 落库
  (self.repo()).save_instance(instance)
  // fire 发起事件(spec §11.3 code 1「实例行 insert 之后」;先于建单/抄送,
  // 使单条流从发起到办结的可见序为 [1,3,5,2],对齐 §11.8 L2-30 断言)
  // 载荷直传键 instanceId(§11.3):集成层 sink 靠它把一条流的流水归到一个键下。
  self.notify_instance_start(instance.instance_id)
  // 8. 抄送人(发起路径 f_ccActors,issues/56 E28 数组/逗号串双收)
  let cc_actors = parse_cc_actors(full_args.inner().get("f_ccActors"))
  if !cc_actors.is_empty() {
    // issues/141 G2 写侧判重=幂等空操作(spec 06 §4):改走 create_cc_instance_if_absent,
    // fire 的入参换成它返回的**实际新建子集**——重复抄送没发生"创建" ⇒ 不发码 4(spec §11.2 原则 1
    // 「码=事实」),子集为空整支不发(不空转、也不照旧按原始请求全量 fire)。
    // 解析器 parse_cc_actors 的语义一字未动(两形态等价由 issues/141 G6 钉住),判重只落在建行那一层。
    let created = (self.repo()).create_cc_instance_if_absent(instance.instance_id, operator, cc_actors)
    if !created.is_empty() {
      self.notify_cc_create(instance.instance_id, created)
    }
  }
  // 9. 从 start 节点执行
  let exec = @exec.Execution::make(instance.clone(), model, define_info, operator, full_args)
  match exec.process_model.get_start() {
    Some(start) => self.execute_node(exec, start)
    None => ()
  }
  // 10. 新任务落库(原片赋 id)
  self.persist_tasks(exec)
  // 11. 回写实例
  (self.repo()).update_instance(exec.process_instance)
  exec.process_instance
}

///|
/// 提交任务前公共加载:任务 + 聚合水合(Rust 89:tasks 从仓储回填)+ define/model + args 合并 + submitType 注入
async fn[R : @spi.ProcessRepository, E : @spi.ProcessExtRepository] Engine::load_exec_for_task(
  self : Engine[R, E],
  task_id : Int64,
  operator : String,
  args : @json.FlowData
) -> (@model.ProcessTask, @model.ProcessInstance, @model.ProcessDefine, @parser.ProcessModel, @exec.Execution, Int64?) raise @error.JeeflowError {
  let task = match (self.repo()).find_task_by_id(task_id) {
    Some(t) => t
    None => raise @error.TaskNotFound(task_id)
  }
  if task.task_state != @model.TaskState::Doing.code() {
    raise @error.InvalidState("Task \{task_id} is not DOING")
  }
  let instance = match (self.repo()).find_instance_by_id(task.process_instance_id) {
    Some(i) => i
    None => raise @error.InstanceNotFound(task.process_instance_id)
  }
  // 聚合水合:任务在独立仓储集合,读侧回填(issues/89)
  instance.tasks = (self.repo()).find_history_tasks(instance.instance_id)
  let define_info = match (self.repo()).find_define_by_id(instance.define_id) {
    Some(d) => d
    None => raise @error.DefineNotFound(instance.define_id)
  }
  let model = @parser.parse_model(define_info.content)
  // issues/115 契约第 5 条(spec/06 §transfer-5,八栈同构):办理提交的变量合并序
  // 「实例变量为底 ← 任务既有变量 ← 本次提交参数(最高)」。
  // 缺中间层则转办写在任务变量上的留痕(tf_transferHistory / submitType=7 槽位)
  // 读不到;把任务变量置于 args 之下,则 B 办结的 1/2/20 能覆盖转办槽位,
  // 而任务既有变量也不会反噬本次提交(Go 上轮正是把它放在了 args 之上)。
  let full_args = @json.FlowData::from_map(clone_map(instance.variables.inner()))
  full_args.merge(task.variables)
  // issues/150 病灶一(spec/09 §4.2 1.8.2):本次提交的参数先过**入口**字段权限过滤再入变量,
  // 与 java `JeeflowEngineImpl.java:264 args = FlowUtil.filterFieldByPerm(args, model, taskName)` 同位。
  // 少了这一层,只读/隐藏字段的值会留在实例变量里,被下一个"没声明该字段权限"的节点写进业务表;
  // 写侧 filter_editable 只挡当次提交,挡不住跨节点。
  full_args.merge(@itl.filter_args_by_node_permission(args, model, task.task_name))
  self.add_user_info(full_args, operator)
  // 权限守卫
  if !task.is_allowed(operator) {
    raise @error.PermissionDenied("Operator \{operator} not allowed on task \{task_id}")
  }
  // submitType 双收(数字/字符串,C3 同源)
  let submit_type = match full_args.get_i64("submitType") {
    Some(v) => Some(v)
    None =>
      match full_args.get_str("submitType") {
        Some(s) =>
          // parse_int64 是 `-> Int64 raise`:解析失败 ⇒ "没有数字档"(旧 Err ⇒ None)
          try {
            Some(@string.parse_int64(s))
          } catch {
            _ => None
          }
        None => None
      }
  }
  if submit_type == Some(20L) {
    instance.variables.insert_str("countersignDisagreeFlag", "1")
    full_args.insert_str("countersignDisagreeFlag", "1")
  }
  // 完成聚合内任务 + 合并 f_ 变量
  self.complete_task_in_aggregate(instance, task_id, operator, full_args)
  if submit_type == Some(20L) {
    for t in instance.tasks {
      if t.task_id == task_id {
        t.variables.insert_str("countersignDisagreeFlag", "1")
      }
    }
  }
  self.update_task_by_id(task_id, instance)
  // issues/132 事件代码腿:任务办结/退回事件(code 5/6)。任务行 state 已由 update_task 落库
  // (load_exec_for_task 唯一持久化点),此后即"落库之后"。5/6 互斥按 submitType 分派:
  // 2 拒绝 / 3 退回上一步 / 6 退回发起人 / 20 会签软拒绝 ⇒ TASK_REJECT;
  // 0 申请 / 1 同意 / 4 跳转 / 5 重提(含未带 submitType 的引擎直调)⇒ TASK_COMPLETE。
  let is_reject = match submit_type {
    Some(s) => s == 2L || s == 3L || s == 6L || s == 20L
    None => false
  }
  self.notify_task_result(
    if is_reject { @event.TaskReject } else { @event.TaskComplete },
    instance.instance_id,
    task_id,
    operator,
    submit_type,
  )
  let node = model.get_node(task.task_name)
  let exec = @exec.Execution::make(instance, model, define_info, operator, full_args)
  exec.process_task = Some(task.clone())
  // 9.5 完成节点自身的后置拦截器(1.8.0 SYNC 演进)
  match node {
    Some(cur) => {
      exec.current_node = Some(cur)
      self.fire_post_interceptors(exec)
    }
    None => ()
  }
  (task, instance, define_info, model, exec, submit_type)
}

///|
fn clone_map(src : Map[String, Json]) -> Map[String, Json] {
  let m : Map[String, Json] = Map([])
  for k, v in src {
    m[k] = v
  }
  m
}

///|
/// complete_task:rust 里聚合方法返回 Result 映射 Business;这里保持同语义
fn[R, E] Engine::complete_task_in_aggregate(
  self : Engine[R, E],
  instance : @model.ProcessInstance,
  task_id : Int64,
  operator : String,
  args : @json.FlowData
) -> Unit raise @error.JeeflowError {
  let _ = self
  let mut found : @model.ProcessTask? = None
  for t in instance.tasks {
    if t.task_id == task_id {
      found = Some(t)
    }
  }
  match found {
    Some(task) => {
      for k, v in args.inner() {
        // issues/115 契约第 5 条:args 已是「实例 ← 任务 ← 本次」的合并结果,
        // 逐键**合并写入**任务变量(等价于 Go/Node 的 task.Variables = vars,
        // 但取并集而非替换,多一重保险),转办留痕才活得过办结。
        task.variables.insert(k, v)
        if k.has_prefix("f_") {
          instance.variables.insert(k, v)
        }
      }
      try {
        task.finish(operator)
      } catch {
        e => match e {
          @error.PermissionDenied(msg) => raise @error.Business(msg)
          @error.InvalidState(msg) => raise @error.Business(msg)
          other => raise other
        }
      }
    }
    None => raise @error.Business("Task \{task_id} not found in instance \{instance.instance_id}")
  }
}

///|
/// 按任务 id 从聚合内取并 update(rust: tasks.iter().find + repo.update_task)
async fn[R : @spi.ProcessRepository, E : @spi.ProcessExtRepository] Engine::update_task_by_id(
  self : Engine[R, E],
  task_id : Int64,
  instance : @model.ProcessInstance
) -> Unit raise @error.JeeflowError {
  for t in instance.tasks {
    if t.task_id == task_id {
      (self.repo()).update_task(t)
      return
    }
  }
}

///|
/// 提交任务(async;含会签门控 issues/94 全逻辑)
pub async fn[R : @spi.ProcessRepository, E : @spi.ProcessExtRepository] Engine::execute_task_async(
  self : Engine[R, E],
  task_id : Int64,
  operator : String,
  args : @json.FlowData
) -> Array[@model.ProcessTask] raise @error.JeeflowError {
  let (_task, _instance, _define, _model, exec, submit_type) = self.load_exec_for_task(task_id, operator, args)
  let node = exec.current_node
  // 10. 会签门控
  let is_countersign = match node {
    Some(n) => n.is_countersign()
    None => false
  }
  if is_countersign {
    let node_ref = node.unwrap()
    let node_id = node_ref.id
    // 同节点任务计数(预计算)
    let node_task_counts : Array[(Bool, Int)] = []
    for t in exec.process_instance.tasks {
      if t.task_name == node_id {
        node_task_counts.push((t.is_finished(), t.task_state))
      }
    }
    let total = node_task_counts.length()
    let mut finished_count = 0
    let mut doing_count = 0
    let mut all_finished = true
    for pair in node_task_counts {
      let (f, s) = pair
      if f {
        finished_count = finished_count + 1
      }
      if s == @model.TaskState::Doing.code() {
        doing_count = doing_count + 1
      }
      if !f {
        all_finished = false
      }
    }
    let cs_type = node_ref.countersign_type()
    let cond = node_ref.countersign_completion_condition()
    let cs_upper = cs_type.to_upper()
    let is_sequential = cs_upper == "SEQUENTIAL" || cs_upper == "SERIAL"
    // 一票否决门
    let veto_matched = match cond {
      Some(c) => c.trim(chars=" \t\r\n").to_lower() == "one_vote_veto"
      None => false
    }
    let veto_hit = submit_type == Some(20L) && veto_matched
    if veto_hit {
      self.abandon_countersign_remaining(exec, node_id)
      exec.is_merged = true
    } else if is_sequential {
      // issues/131:名册与序号从**刚完成那一条任务的变量**读——java CountersignHandler.java:71-77
      // 读的正是 completed.getVariables();实例变量 csv_* 那一支整体删除(存量兼容不做)。
      let roster_key = "operatorList_" + node_id
      let lc_key = "loopCounter_" + node_id
      let operator_list = _task.variables.get_string_array(roster_key)
      let lc = _task.variables.get_i64_or(lc_key, 0L)
      if lc + 1L < operator_list.length().to_int64() {
        // 未到最后一任:建下一串行任务,不下钻边
        let next_lc = lc + 1L
        let new_tasks = exec.process_instance.create_countersign_tasks(
          node_id,
          node_ref.display_name,
          [operator_list[next_lc.to_int()]],
          exec.operator,
          @model.TaskType::from_code(node_ref.task_type()),
          node_ref.form_key(),
          None,
        )
        // 三件重写在新任务上(java CountersignHandler.java:154-157):名册原样带过去、序号 +1、
        // 总数不变 ⇒ 代理人只扩"当一步"的参与者,改不了票数。
        if new_tasks.length() > 0 {
          seed_countersign_bookkeeping(new_tasks[0], node_id, operator_list, next_lc)
        }
        // issues/126 案 A 写点④=§1.8 更正补的那一处:串行会签推进出的**下一位成员**。
        // 这一支绕过 create_task_with_assignment 直建任务行;基准侧 boot2 的串行推进是回调
        // createCountersignTask(ProcessTaskServiceImpl:485,内含 :524 那处到期写)
        // ⇒ 不补就是"首成员有到期、第二三位没有"。java 同款落点见 cb541d4 applyNodeExpireTime。
        apply_node_expire_time(exec, node_ref, new_tasks)
        for t in new_tasks {
          exec.new_tasks.push(t)
        }
        self.persist_tasks(exec)
        (self.repo()).update_instance(exec.process_instance)
        return exec.new_tasks
      } else {
        self.abandon_countersign_remaining(exec, node_id)
        exec.is_merged = true
      }
    } else {
      let has_expr_cond = match cond {
        Some(c) => {
          let cc = c.trim(chars=" \t\r\n").to_owned()
          !cc.is_empty() && cc.to_lower() != "one_vote_veto"
        }
        None => false
      }
      if has_expr_cond {
        // 表达式条件:注入门控变量后求值
        exec.gate_vars.insert_i64("nrOfInstances", total.to_int64())
        exec.gate_vars.insert_i64("nrOfActivateInstances", doing_count.to_int64())
        exec.gate_vars.insert_i64("nrOfCompletedInstances", finished_count.to_int64())
        exec.process_instance.variables.insert_i64("nrOfInstances", total.to_int64())
        exec.process_instance.variables.insert_i64("nrOfActivateInstances", doing_count.to_int64())
        exec.process_instance.variables.insert_i64("nrOfCompletedInstances", finished_count.to_int64())
        let cond_str = cond.unwrap().trim(chars=" \t\r\n").to_owned()
        let merged = self.simple_eval(cond_str, exec)
        if merged {
          self.abandon_countersign_remaining(exec, node_id)
          exec.is_merged = true
        } else {
          self.persist_tasks(exec)
          (self.repo()).update_instance(exec.process_instance)
          return exec.new_tasks
        }
      } else {
        // 无条件(或 ONE_VOTE_VETO 未触发)→ 全员完成才 merged(C8 软拒绝语义)
        if all_finished {
          self.abandon_countersign_remaining(exec, node_id)
          exec.is_merged = true
        } else {
          self.persist_tasks(exec)
          (self.repo()).update_instance(exec.process_instance)
          return exec.new_tasks
        }
      }
    }
  }
  // 10b. 下钻:普通任务恒走;会签 merged 后走(issues/94 fall-through)
  match node {
    Some(n) => {
      let next_nodes : Array[@parser.NodeModel] = []
      for e in exec.process_model.get_output_edges(n.id) {
        match exec.process_model.get_target_node(e) {
          Some(next) => next_nodes.push(next)
          None => ()
        }
      }
      for next in next_nodes {
        self.execute_node(exec, next)
      }
    }
    None => ()
  }
  // 11. 任务级抄送(tf_ccActors,issues/141 G6)
  // 与发起路径 f_ccActors(上面第 8 步)**同一个解析器** parse_cc_actors:
  // 逗号串与 JSON 数组两种形态等价。此前这一支只走 get_str(仅认 String 形态),
  // 传 ["userB","userC"] 时取不到值 ⇒ 不建 cc 行、不 fire 码 4,而同一份定义在 java 等栈会建
  // (java JeeflowEngineImpl.handleCcActors 两形态通吃)。
  // 逗号串形态的切分口径与旧 split_actors 逐字相同(按 , 切 / trim / 丢空段)⇒ 无行为变化。
  let cc_actors = parse_cc_actors(exec.args.get("tf_ccActors"))
  if !cc_actors.is_empty() {
    // issues/141 G2 写侧判重=幂等空操作(spec 06 §4):改走 create_cc_instance_if_absent,
    // fire 的入参换成它返回的**实际新建子集**——重复抄送没发生"创建" ⇒ 不发码 4(spec §11.2 原则 1
    // 「码=事实」),子集为空整支不发(不空转、也不照旧按原始请求全量 fire)。
    // 解析器 parse_cc_actors 的语义一字未动(两形态等价由 issues/141 G6 钉住),判重只落在建行那一层。
    let created = (self.repo()).create_cc_instance_if_absent(exec.process_instance.instance_id, operator, cc_actors)
    if !created.is_empty() {
      self.notify_cc_create(exec.process_instance.instance_id, created)
    }
  }
  // 12. 落库
  self.persist_tasks(exec)
  (self.repo()).update_instance(exec.process_instance)
  exec.new_tasks
}

///|
/// 跳转执行(jump / rollback / 退回发起人共用)
/// 跳转落点的三种语义(issues/121 P2:3「退回上一步」与 6「退回发起人」必须分开)
priv enum JumpMode {
  Named(String)
  FirstNode
  Rollback
}

///|
pub async fn[R : @spi.ProcessRepository, E : @spi.ProcessExtRepository] Engine::execute_and_jump_async(
  self : Engine[R, E],
  task_id : Int64,
  operator : String,
  args : @json.FlowData,
  target_name : String?
) -> Array[@model.ProcessTask] raise @error.JeeflowError {
  // audit8 R86①:空串与"没带"同档=血缘回退。基准=java JeeflowEngineImpl.java:184 的
  // ``StringUtils.isEmpty(nodeName)``(go/py/node/php/csharp 五栈同判据,都在**引擎内**分这一档);
  // 本栈此前只分 ``Option::None``,而门面把 "taskName 传了空串" 喂成 ``Some("")`` ⇒ 同一份请求
  // 两栈给出不同答案(那六栈走回退、本栈静默 no-op)。
  let named = match target_name {
    Some(n) if n.length() != 0 => Some(n)
    _ => None
  }
  match named {
    Some(n) => {
      // audit8 R86②:跳转目标节点名在**任何状态变更前**校验。六栈的报错点在 prepare 之后,
      // 靠外层事务回滚兜住"任务未被动"那一腿;本栈的门面/仓储没有事务模板这一层 ⇒ 校验必须
      // 前置,否则报红的同时任务已 DONE、办结事件已发=实例原地失联,还留下半截事实。
      // 摘掉这一处校验 ⇒ 下方 dispatch 支给出的是 Internal 文案,R86 用例照红(判据有牙)。
      self.require_jump_node(task_id, n)
      self.execute_jump_mode_async(task_id, operator, args, Named(n))
    }
    // issues/121 P2:target 为空=血缘版"退回上一步"(submitType=3)。
    // 此前这条与"退回发起人"(6)共用 get_first_task_node(),两语义塌成同值(issues/119)。
    None => self.execute_jump_mode_async(task_id, operator, args, Rollback)
  }
}

///|
/// R86 前置校验:走「任务 → 实例 → 定义 → 模型」这条**只读**链,确认节点名在模型里存在。
/// 任一环节读不到就**放行**(那种形状由引擎主体给出它自己的 TaskNotFound/InstanceNotFound/
/// DefineNotFound,本函数不抢别人的档);只在"读到了、模型里却没有这个节点"时拦下。
async fn[R : @spi.ProcessRepository, E : @spi.ProcessExtRepository] Engine::require_jump_node(
  self : Engine[R, E],
  task_id : Int64,
  name : String
) -> Unit raise @error.JeeflowError {
  let task = match (self.repo()).find_task_by_id(task_id) {
    Some(t) => t
    None => return
  }
  let instance = match (self.repo()).find_instance_by_id(task.process_instance_id) {
    Some(i) => i
    None => return
  }
  let dfn = match (self.repo()).find_define_by_id(instance.define_id) {
    Some(d) => d
    None => return
  }
  match @parser.parse_model(dfn.content).get_node(name) {
    Some(_) => ()
    None => raise @error.Business("根据节点名称[\{name}]无法找到节点模型")
  }
}

///|
async fn[R : @spi.ProcessRepository, E : @spi.ProcessExtRepository] Engine::execute_jump_mode_async(
  self : Engine[R, E],
  task_id : Int64,
  operator : String,
  args : @json.FlowData,
  mode : JumpMode
) -> Array[@model.ProcessTask] raise @error.JeeflowError {
  let (_task, _instance, _define, _model, exec, submit_type) = self.load_exec_for_task(task_id, operator, args)
  // 目标节点:命名跳转 / 6 退回发起人(首任务节点)/ 3 退回上一步(血缘版)
  let model = exec.process_model
  match mode {
    Named(name) =>
      match model.get_node(name) {
        Some(node) => self.execute_node(exec, node)
        // R86:契约性报错在 ``execute_and_jump_async`` 的前置校验那一处(唯一出口,文案逐字对齐
        // 六栈)。走到这里还查不到="校验后模型变了"的不可能形状,留一条 Internal 兜底,
        // 不再像旧实现那样静默 no-op——那个 ``None => ()`` 就是本案病灶(对外 code=0+原地失联)。
        None => raise @error.Internal("跳转目标节点在入口校验后消失: \{name}")
      }
    FirstNode =>
      match model.get_first_task_node() {
        Some(node) => self.execute_node(exec, node)
        None => ()
      }
    Rollback => self.rollback_to_parent(exec)
  }
  // 20 注入在 jump 路径同样落任务变量
  if submit_type == Some(20L) {
    for t in exec.process_instance.tasks {
      if t.task_id == task_id {
        t.variables.insert_str("countersignDisagreeFlag", "1")
      }
    }
  }
  self.persist_tasks(exec)
  (self.repo()).update_instance(exec.process_instance)
  exec.new_tasks
}

///|
/// 驳回(jump to end):办结语义合并派——同样 fire ProcessInstanceEnd(issues/104 §2.3)
pub async fn[R : @spi.ProcessRepository, E : @spi.ProcessExtRepository] Engine::execute_and_jump_to_end_async(
  self : Engine[R, E],
  task_id : Int64,
  operator : String,
  args : @json.FlowData
) -> Array[@model.ProcessTask] raise @error.JeeflowError {
  let (_task, instance, _define, _model, _exec, submit_type) = self.load_exec_for_task(task_id, operator, args)
  // 驳回:实例 REJECT(45) + 废弃全部 DOING 任务(任务 99)
  instance.reject()
  instance.abandon_all_doing()
  (self.repo()).update_instance(instance)
  for t in instance.tasks {
    if t.task_state == @model.TaskState::Doing.code() {
      (self.repo()).update_task(t)
    }
  }
  // 20 注入驳回路径同样落任务变量(c29)
  if submit_type == Some(20L) {
    for t in instance.tasks {
      if t.task_id == task_id {
        t.variables.insert_str("countersignDisagreeFlag", "1")
      }
    }
    (self.repo()).update_instance(instance)
  }
  // fire 结束事件(办结+拒绝两路都 fire,C12/C13)
  // 载荷 instanceId + state(§11.3 code 2「state=落库后的实例状态整数」);
  // 取的是上面 update_instance 刚落库的那个值,不是再推一次的。
  self.notify_instance_end(instance.instance_id, instance.state)
  []
}

///|
/// 退回发起人(submitType=6):首任务节点重执行给发起人
pub async fn[R : @spi.ProcessRepository, E : @spi.ProcessExtRepository] Engine::execute_and_jump_to_first_async(
  self : Engine[R, E],
  task_id : Int64,
  operator : String,
  args : @json.FlowData
) -> Array[@model.ProcessTask] raise @error.JeeflowError {
  // issues/121 P2:不再借道 execute_and_jump_async(None)——那条现在是血缘回退(3)
  self.execute_jump_mode_async(task_id, operator, args, FirstNode)
}

/// 复活行该不该带这个键——控制类残留:上次提交的表单值(tf_)、会签计数簿记(csv_ /
/// loopCounter / nrOfInstances / operatorList)、当次操作的 submitType 与 taskName。
/// 留着会让新待办显示"我没提交这个怎么显示了",或让复活的会签节点从错位的序号继续推进。
fn is_control_var_key(k : String) -> Bool {
  if k == "submitType" { true }
  else if k == "taskName" { true }
  else if k.has_prefix("tf_") { true }
  else if k.has_prefix("csv_") { true }
  else if k.has_prefix("loopCounter") { true }
  else if k.has_prefix("nrOfInstances") { true }
  else if k.has_prefix("operatorList") { true }
  else { false }
}

/// 退回上一步(血缘版,规范 04 · 退回上一步):上一步来源=当前行的 parent_task_id,
/// 复活那条历史行;不按模型入边拓扑推。本栈 JeeflowError::code() 恒 99999999,
/// 对外 msg 用固定中文文案、不含引擎内部码(两格语义由文案区分,HTTP 出口仍 99999999)。
fn[R, E] Engine::rollback_to_parent(
  self : Engine[R, E], exec : @exec.Execution
) -> Unit raise @error.JeeflowError {
  let _ = self
  let current = match exec.process_task {
    None => raise @error.Business("上一步任务ID为空,无法驳回至上一步处理")
    Some(t) => t
  }
  let parent_id = match current.parent_task_id {
    None => raise @error.Business("上一步任务ID为空,无法驳回至上一步处理")
    Some(p) => p
  }
  if parent_id == 0L {
    raise @error.Business("上一步任务ID为空,无法驳回至上一步处理")
  }
  let instance = exec.process_instance
  // 本版本 Array 没有 find:显式扫聚合根里的行(含已办结的历史行)
  let mut hist : @model.ProcessTask? = None
  for t in instance.tasks {
    if t.task_id == parent_id {
      hist = Some(t)
    }
  }
  let history = match hist {
    None => raise @error.Business("上一步任务ID为空,无法驳回至上一步处理")
    Some(t) => t
  }
  if !exec.process_model.can_rejected(current.task_name, history.task_name) {
    raise @error.Business("无法驳回至上一步处理,请确认上一步骤并非fork、join、suprocess以及会签任务")
  }
  let src = history.variables
  let mut is_first = false
  match src.get("isFirstTaskNode") {
    Some(v) => is_first = @json.as_bool(v) == Some(true)
    None => ()
  }
  // 首任务节点那条由发起人提交 ⇒ 参与者取该行 u_userId;其余取该行办结人。
  // 老行没这个键 ⇒ 按 false 处理(宁可派给该行 actor_id,也不用带"仅进行中"判定的现算值)。
  let mut actor = ""
  if is_first {
    match src.get("u_userId") {
      Some(v) => actor = @json.as_str(v).unwrap_or("")
      None => ()
    }
    if actor == "" { actor = instance.operator }
  } else {
    actor = history.actor_id.unwrap_or("")
  }
  if actor == "" {
    raise @error.Business("上一步任务ID为空,无法驳回至上一步处理")
  }
  // 复活行只带数据类键:tf_*(上次表单提交)与 csv_*/会签簿记都是"上次提交"的残留
  let vars = @json.FlowData::new()
  for k, v in src.inner {
    if is_control_var_key(k) {
      continue
    }
    vars.insert(k, v)
  }
  vars.insert("isFirstTaskNode", @json.bool_of(is_first))
  let carry = match history.parent_task_id {
    None => 0L
    Some(p) => p
  }
  // 显式构造复活行(不改动历史行本身):进行中、actor_id 清空、actor_ids=算出的参与者、
  // parent 随行拷贝="上一步的上一步"(与 mldong-boot2 一致)、变量走净化后的那一份。
  let nt : @model.ProcessTask = {
    task_id: 0L,                        // persist_tasks 里统一分配真实 id
    process_instance_id: instance.instance_id,
    task_name: history.task_name,
    display_name: history.display_name,
    task_type: history.task_type,
    perform_type: history.perform_type,
    task_state: @model.TaskState::Doing.code(),
    actor_id: None,                     // 进行中任务该列恒无值
    actor_ids: [actor],
    finish_time: None,
    expire_time: None,  // issues/126:修前是 history.expire_time(『抄上一行』),改按下方表达式算
    form_key: history.form_key,
    parent_task_id: Some(carry),
    variables: vars,
    create_time: history.create_time,
    create_user: history.create_user,
    update_time: None,
    update_user: None,
  }
  // issues/126 案 A 写点⑤(回退/跳转新建):到期时间按**被回退掉的那个节点**(=当前节点)的
  // 表达式**重算**,变量源=随行拷贝那份 `vars`。基准逐字=boot2 ProcessTaskServiceImpl.java:384-387
  // (`((TaskModel)current).getExpireTime()` + `hisVariable`),jeeflow-java rejectTask 同形。
  // 修前形状是上面那行 `expire_time: history.expire_time`——那是"抄上一行"(上一行本栈从来没
  // 算过 ⇒ 恒空),不是"按节点表达式算",两者在"该不该有值"上给出不同答案。
  match exec.process_model.get_node(current.task_name) {
    Some(cur_node) => @model.apply_expire_time(
      nt, cur_node.prop_str(@model.expire_time_prop), vars
    )
    None => ()
  }
  exec.new_tasks.push(nt)
}

///|
/// 发起(带父实例,子流程)
pub async fn[R : @spi.ProcessRepository, E : @spi.ProcessExtRepository] Engine::start_with_parent_async(
  self : Engine[R, E],
  define_id : Int64,
  operator : String,
  args : @json.FlowData,
  parent_id : Int64,
  parent_node_name : String
) -> @model.ProcessInstance raise @error.JeeflowError {
  let define_info = match (self.repo()).find_define_by_id(define_id) {
    Some(d) => d
    None => raise @error.DefineNotFound(define_id)
  }
  let model = @parser.parse_model(define_info.content)
  let full_args = @json.FlowData::new()
  full_args.merge(args)
  self.add_user_info(full_args, operator)
  let title = gen_auto_title(full_args, define_info.display_name)
  full_args.insert_str("autoGenTitle", title)
  let instance = @model.ProcessInstance::create_with_parent(define_info, operator, full_args, parent_id, parent_node_name)
  // issues/137 A 第二处写点(子流程发起腿):与主发起 `start_async` 同判据、同一把尺子——
  // 定义级表达式**求值**后写实例列,不是原串搬运;未配 ⇒ NULL;算不出 ⇒ NULL 不兜底 now。
  // 主发起那处的五条细节(基准 java:93-96 / boot2:157-160、真库 1292 的机理、变量源取
  // full_args 的理由)逐条照搬,此处不重述,只钉"两处同形"里被漏掉的这一处。
  match model.expire_time {
    Some(et) => instance.expire_time = @model.process_time(Some(et), full_args)
    None => ()
  }
  self.assign_ids(instance)
  (self.repo()).save_instance(instance)
  // fire 发起事件(spec §11.3 code 1「实例行 insert 之后」;先于建单/抄送 ⇒ 可见序 [1,3,5,2])
  // 载荷直传键 instanceId(§11.3)——子流程支与主发起支同口径。
  self.notify_instance_start(instance.instance_id)
  let cc_actors = parse_cc_actors(full_args.inner().get("f_ccActors"))
  if !cc_actors.is_empty() {
    // issues/141 G2 写侧判重=幂等空操作(spec 06 §4):改走 create_cc_instance_if_absent,
    // fire 的入参换成它返回的**实际新建子集**——重复抄送没发生"创建" ⇒ 不发码 4(spec §11.2 原则 1
    // 「码=事实」),子集为空整支不发(不空转、也不照旧按原始请求全量 fire)。
    // 解析器 parse_cc_actors 的语义一字未动(两形态等价由 issues/141 G6 钉住),判重只落在建行那一层。
    let created = (self.repo()).create_cc_instance_if_absent(instance.instance_id, operator, cc_actors)
    if !created.is_empty() {
      self.notify_cc_create(instance.instance_id, created)
    }
  }
  let exec = @exec.Execution::make(instance.clone(), model, define_info, operator, full_args)
  match exec.process_model.get_start() {
    Some(start) => self.execute_node(exec, start)
    None => ()
  }
  self.persist_tasks(exec)
  (self.repo()).update_instance(exec.process_instance)
  exec.process_instance
}