Appearance
规范 08 · 合规测试
每个语言实现必须通过以下核心场景测试(使用同一套 JSON 流程定义文件)。 场景按能力主题组织(不再按版本分表——版本归属见 changelog)。
测试约定:测试中 start 后先模拟 startAndExecute 自动完成 apply 节点,再断言后续 审批节点。测试流程 JSON 文件唯一编辑源在 jeeflow-java 仓库的 test/resources/flows/ (15 个);各语言仓自带 flows/ 入库副本,测试读本仓副本,维护者机器执行时由 resolver 精确镜像同步(全量复制+删孤儿)。
引擎核心(v1.0)
| # | 场景 | 验证点 |
|---|---|---|
| 1 | 简单线性 | start→apply→task→end,实例完成 |
| 2 | 多级审批 | 3 个 task 顺序审批,每个操作人校验 |
| 3 | 条件分支 | decision 根据 args 选分支 |
| 4 | 并行分支 | fork→[A,B]→join→end |
| 5 | 并行会签 | 3 个 actor 并行创建,全部完成 |
| 6 | 串行会签 | 2 个 actor 顺序创建 |
| 7 | 按比例会签 | 4 个 actor 并行,阈值完成 |
| 8 | 退回发起人 | submitType=6 后第一个任务节点重执行(参与者=发起人),发起人收到新待办,实例保持 10 |
| 9 | 权限校验 | 非 actor 不可操作任务 |
| 10 | 拦截器 + 事件 | pre/post 拦截器;事件的可判定条目见本文 §事件契约(旧写"start/task/finish 事件"一行,已由规范 11 逐码值展开) |
引擎增强(v1.0.1 ~ v1.1.0)
| # | 场景 | 验证点 |
|---|---|---|
| 11 | assignee 变量解析 | 变量命中 / 字面量回退 / tf_nextNodeOperator 优先(规范 02 §assignee) |
| 12 | 系统代执行 | flow.auto / flow.admin 放行 + 跳过用户注入(规范 04) |
| 13 | 定义写操作 | saveDefine/updateDefine/updateDefineState/removeDefine(规范 05) |
| 14 | updateInstance 级联 | 聚合根任务状态随实例更新落库 |
| 15 | 门面路由 | flow(action, map) 各 action 返回 code=0 + 正确 data(规范 06) |
视图端点(v1.2.0)
| # | 场景 | 验证点 |
|---|---|---|
| 16 | 视图端点 | getLastByName / approvalRecord / taskDetail / jumpAbleTaskNameList / latest / 抄送操作 |
| 17 | 高亮 | highLight 活跃/历史节点与路径补全 |
| 18 | 候选人 | candidatePage 候选配置映射 / 用户搜索钩子 |
对齐修复(v1.3.0)
| # | 场景 | 验证点 |
|---|---|---|
| 19 | 抄送分页 | ccList 按抄送人分页 + 关联定义名/版本 |
| 20 | 参与者追加 | addTaskActor 追加不覆盖、去重 |
元数据能力(v1.4.0)
| # | 场景 | 验证点 |
|---|---|---|
| 21 | 枚举字典 | 7 个 key 对齐 boot3,value/label 与 Java enums 一致(规范 07) |
| 22 | SPI 清单 | HandlerRegistry 注册 → 按类型/分组列出,order 升序 |
撤回 / 转办 / 委托(issues 113~116)
| # | 场景 | 验证点 |
|---|---|---|
| 23 | 撤回状态码与级联落库 | 门面 processInstance/withdraw 后,原进行中任务在内存仓与 SQL 仓均 taskState=30(不是 99);已完成(20)/已终止(40) 任务行不被改写;AbandonAllDoing 与会签一票否决路径仍写 99(规范 03、06 §withdraw) |
| 24 | 撤回鉴权 | 发起人撤回成功;实例任一进行中任务的参与者可撤回整单;无关第三人 → 99999999 + msg;缺 operator → 明确报错,不得回落 user1;flow.auto/flow.admin 放行;实例 update_user 与被撤任务的 update_user = 真实撤回人 |
| 25 | 转办 | processTask/transfer 后 A 待办消失、B 待办出现(同一 processTaskId,高亮图/节点进度不变);审批记录(approvalRecord 的 variable/ext)出现 submitType=7 与 tf_transferHistory 一条 {submitType,fromActor,toActor,reason,time,operator};B 再转给 C 后 A→B 那条仍在,B 办结后两条留痕都仍在(变量按"实例←任务←本次提交参数"合并,不得整体替换,见 06 §transfer 5);会签节点只摘 fromActor 一行、其余成员不受影响;转办不得覆写任务 actor_id/operator——转办后撤回/终止该单,fromActor 的 doneList 里不得凭空出现这条他没办过的单(pageDoneTasks 按 state<>10 AND operator=? 过滤,Node 实测踩到);toActor 已是参与者 / fromActor 非参与者 / 任务非进行中 / operator != fromActor → 均明确报错;加签 processTask/surrogate 语义不变(原人保留) |
| 26 | 委托自动生效 | 窗口内配"张三→李四"后新单到达任务节点时,李四待办出现该单且张三待办保留(任一可办);窗外 / enabled=0 / 自己委托给自己 → 代理人不收到;未配置 IProcessExtRepository 时建单不被打断(静默跳过);显式关闭后回到"仅台账"行为 |
| 27 | 委托查询判据双仓一致 | 同一份委托数据,内存仓与 SQL 仓给出同一结论:空 processName 全流程兜底、时间窗 NULL=不限、surrogate <> operator 自委托过滤、enabled 只认 1 且脏值不当启用(规范 05) |
事件契约(issues/127/132)
判据源=规范 11 · 事件契约。每支都要用 recorder 监听器断"收到 + 顺序 + 时机",只断"出现过"不算过 (顺序与缺支正是本案两个病灶)。镜像层跨栈格=
L2-30。
| # | 场景 | 可判定条目(发/不发、载荷、时机) |
|---|---|---|
| 28 | 实例发起 | 实例行 insert 之后 fire PROCESS_INSTANCE_START(1) 恰一次,sourceId=instanceId;发起前先 fire ⇒ 红 |
| 29 | 待办生成 | 每个任务行落库(分到 taskId)之后各 fire 一次 PROCESS_TASK_START(3),载荷带 instanceId/taskId/actors;sourceId 可被 findTaskById 反查。会签逐人、回退复活行、子流程任务都要有 |
| 30 | 任务办结 | 任务 state 更新为已完成并落库之后 fire TASK_COMPLETE(5),带 operator/submitType;与 31 互斥:走退回的这一次不得再发 5 |
| 31 | 任务退回 | 退回/拒绝(含退发起人、软拒绝、跳转回退)落库之后 fire TASK_REJECT(6);实例终态另发 2 |
| 32 | 实例终态 | 实例 state 落库为 20/45(流程自己走到终点)之后 fire PROCESS_INSTANCE_END(2) 一次,载荷 state =落库后的整数;办结与拒绝共用这一支(规范名不拆)。撤回(30)/终止(40) 不发 2,各发 8/9;同轮既发 8 又发 2 ⇒ 红 |
| 33 | 抄送三条路径 | 发起 f_ccActors、办理 tf_ccActors、手动 processInstance/createCCInstance 三条路径都要:cc 行落库 → 逐抄送人 fire CC_CREATE(4)(ccActorId 直传,不反查)。缺办理腿(go/python/node 旧状)⇒ 红;集成层"主动补发"必须摘除,否则重复 |
| 34 | 转办 / 撤回 / 终止 | TASK_TRANSFER(7) 带 fromActor/toActor/operator、TASK_WITHDRAW(8) 每轮撤回 fire 一次(实例 state=30 落库后,且不补发 2)、INSTANCE_TERMINATED(9)(state=40 落库后,不补发 2)。⚠️ 9 这一档本轮按 unreachable 记账:八栈门面都没有"终止实例"action、聚合根 Interrupt 生产路径零调用者 ⇒ 门禁与跨栈用例造不出这一支,由引擎栈内直造 40 档的用例钉(php 用 transfer 测试直调造 40 就是这种形状),不许为凑绿新增门面 action。撤回守卫见场景 23/24 与 issues/134 |
| 35 | 不发清单 | 超时/催办/自动通过、定义生命周期(save/updateState/redeploy/remove)、updateInstance/updateTask 变量写入、委托生效 ⇒ 一律不 fire(多发即红) |
| 36 | 订阅形状与异常隔离 | 同码注册两个监听器 ⇒ 两个都被调到(python 旧单回调⇒红);某监听器抛异常 ⇒ 主流程不回滚、后续监听器仍被调;零注册时 fire 安全返回 |
| 37 | ccList 行形状(issues/138) | 每一行必须来自 wf_process_instance:有 id(实例 id)键、有 operator 键、operator 值=流程发起人且不得等于查询者本人(投 cc.actor_id 的栈在被抄送人查询里恒等于自己,"非空"判据照不出来 ⇒ 必须比"值对到具体那一方")。镜像层格=L2-31,条文见 规范 06 §ccList 行形状三要件 |
委托:与内置版的方向差异 + 归属不变式(issues/152)
条文见 规范 06 §4.5「与内置版(mldong-wf)的三条方向差异」与「processSurrogate/page 归属不变式」、 规范 05 §getSurrogate。每档都要配阳性对照(坏形状必须判红),只断"没出事"不算过。
| # | 场景 | 验证点 |
|---|---|---|
| 38 | 作用域优先级(内置版反向) | 同授权人并存「全流程」(process_name 空)+「精确」两条且都窗内 ⇒ 精确那条接管,全流程那条不得盖住它;另一腿:精确那条判否(窗外 / enabled=0 / 脏值 / 自委托)⇒ 仍兜底全流程,不得判否即止。内置 mldong-wf 版是"全流程优先、命中即返回",迁过来必须按本行判据,见 06 §4.5 差异表 |
| 39 | 择优顺序(停用不自愈) | 同一作用域内先建"窗内 + enabled=1"、再建一条更新的无效记录(窗外 / enabled=0 / enabled 脏值 / surrogate = operator 四形)⇒ 一律不并人;对照:只有一条窗内有效记录 ⇒ 并人。判据形状="先按 id 取最新一条再裁决",写成"先按判据过滤再取最新"即红(那等于上一条窗内委托永久生效,issues/123 的病根、也是内置版的现形) |
| 40 | 委托分页归属不变式(两层都要) | 门面层:processSurrogate/page 注入 t.operator EQ(值=§2.5 归一后的 operator,缺键/空串/全空白 ⇒ user1),带 operator ⇒ 只见自己授出的行,不带 ⇒ 只出 user1 的行,绝不许返回全库;仓储层:绕过门面直调 pageSurrogates 时归属列上的空值 ⇒ 空页(不是"这条条件不加"),且只收归属列、m_LIKE_* 的"空值当没填"放行不动;内存仓与 SQL 仓同答案 |
| 41 | 写侧 operator 不得落空串 | processSurrogate/save 三档入参(缺键/空串/全空白)一律落归一缺省值,行必须能被该授权人的后续待办命中(正面判据:以该用户建单 ⇒ 代理人进参与者表);空串行=死行(台账看得见、永不生效),落进去即红。update 的缺省档保留原授权人不变 |
定义生命周期:redeploy 原地替换(audit8 R5)
| # | 场景 | 验证点 |
|---|---|---|
| 42 | redeploy 原地替换保留 version/state | processDefine/redeploy 与 processDesign/redeploy 均须version 不变、state 不变:停用(state=0)中的定义 redeploy 后仍停用,版本号不递增也不回落;判据的标准形状=先 /upAndDown 停用并把 version 抬到非默认值再 redeploy(默认 1 与仓储兜底误写同形、照不出来)。条文见 规范 06 §processDefine/redeploy;镜像层格=L2-41(含判据自证 --selftest-l2-41) |
字段权限族(audit8 R11)
条文=规范 02 §4.1(
PERMISSION_*双兼容键形与1/2/3值语义)+ 规范 06 §taskModel.ext(ext原样透传properties.field)。 该面旧态在 08 全 42 条零覆盖(只在镜像格L2-07半格隐现——且只照写侧一半),本场景把三件套一次钉齐。
| # | 场景 | 验证点 |
|---|---|---|
| 43 | 字段权限三件套(入口过滤/写侧过滤/读取面) | 同一审批节点在 properties.field 配 PERMISSION_f_amount=1(只读)、PERMISSION_f_title=2(可编辑)、PERMISSION_f_secret=3(隐藏):①入口过滤——办理提交同时带该三个 f_* 键的新值 ⇒ 只读/隐藏两键被剔除、不入任务变量(任务行变量与 approvalRecord.variable 均不得出现该两键新值;对照=可编辑键入,缺省值档〔无 PERMISSION_* 键=可编辑〕也要有一腿);②写侧过滤(SYNC persist)——任务节点落库 UPDATE 只更新可编辑列,只读列保持旧值(隐藏列同只读档不落);③读取面——processTask/detail 的 taskModel.ext 逐键等于该节点 properties.field(含三条 PERMISSION_*);键形双兼容各留一腿:PERMISSION_f_x 与 PERMISSION_x 并存时按前者(含 f_ 前缀者优先),只写旧形单键时也要认。镜像层格=L2-07(既有半格=写侧②;入口①与读取③两半现零格,补格随 §3.3 零-2 议题另行排期——本场景先立契约)。阳性对照:把只读键值改成能观察到的新值 ⇒ 若该新值入了变量/落了列 ⇒ 红 |
必填参数与空值放行(audit8 R13)
出处=issues/179(C99 §3.1⑬/假-9 升级)。条文立法(06 §必填参数档)挂 R67 卡;本场景先把 「空名不得落通用放行」钉到可判定形状。查询构造侧的「空值当没填」通用放行本身有正当用途 (
m_LIKE_*等可选过滤的「没填就忽略」),本场景只钉必填入参与实体name两类不得借用它。
| # | 场景 | 验证点 |
|---|---|---|
| 44 | getLastByName 空名/save 三腿缺 name 不得被「空值当没填」放行吃掉条件 | ①processDefine/getLastByName 的 processDefineName 缺键/空串/全空白三档 ⇒ 显式报错(code≠0)且不得返回任何行——空名落到查询构造侧被通用放行丢弃时,t.name 条件整条蒸发、查询退化成全表,会 code=0 返回他流程定义行(修前的 java/csharp 形状);②processDefine/deploy/processDesign/deploy/processDesign/redeploy 三腿内容缺 name ⇒ 明确报错且不落新行(缺名落 save 腿会拿他流程最新版本 +1、缺名内容还能借放行查询命中他行并替换其内容)。阳性对照:库里预置一条别流程定义+正常名档六键回归(挡住空名的同时不得误伤正常档);判据要读得出"行真的没落"(定义总数不变),只断"报了错"不算过。镜像层格=L2-45(含判据自证 --selftest-l2-45;save 二轴按修复面四壳硬判、其余栈只记读数——rust/moon/php save 腿现读无检查,见 issues/179 §2) |
元数据未命中档与 persist 静默不落腿(audit8 R29)
条文=规范 10 §3「元数据未命中=两条许可档」。本场景把"档"钉到可判定形状—— 旧态是条文只写回落档一种,而 rust 走的是未命中即失败,两边对同一段契约的理解相反(issues/195)。 两档并存=明文不承诺跨语言统一,故本场景按"实现所选档"分别判,不许拿另一档打另一档。
| # | 场景 | 验证点 |
|---|---|---|
| 45 | 元数据未命中两档各自成立、但都不许静默 | 同一份没有元数据的表(provider 未命中)跑一次 SYNC/ARCHIVE 落库:①回落探测档(缺省,参考实现 java)⇒ 动作照常落库、全列按 NORMAL 处理,与不挂元数据的 1.6.x 行为逐字一致(判据=行存在+列名取物理表原名);②未命中即失败档(许可,rust)⇒ 动作以显式错误结束,错误文案必须带表名(可归因),且不得留下半行/不得记 Ok 后什么都不写。两档的共同红线=既不落库也无错误也无日志的第三态即红(Ok(()) 静默整单不落=把配置错误藏起来,正是本场景要钉死的旧形状)。同族第二条判据挂在未命中以外的静默面上:persistMode 写了但不认识、字段权限过滤/列匹配后空集 ⇒ 这两条腿不落库时必须留一条可诊断日志(缺日志即红,日志须含表名与成因) |
委托 update 缺键档(audit8 R68)
| # | 场景 | 判据 |
|---|---|---|
| 46 | update 四腿缺键=清空,只有 operator 保留 | processSurrogate/update 只带 id、不带 processName/surrogate/startTime·endTime/enabled 四腿 ⇒ 四列一律落空值档(processName/surrogate 落 null 或 ''(读侧等价)、时间窗落 NULL、enabled 复位 1),不得保留原值;唯一例外=operator 缺省保留原授权人(见 规范 06 §4.5 的专句与缺键档)。标准夹具=先 save 一条「精确作用域+带时间窗+enabled=0」的委托,再用一枚只带 id 的 update 打它 ⇒ 读回该行必须变成「全流程+无窗+enabled=1」且 operator 未变(四腿原值仍在=该栈走了保留派,红)。正面档=完整六键回填、逐键等于新值(防把"清空"实现成"整行重置默认值")。⚠ 现读违约方=rust(四腿 unwrap_or(原值)=全保留)/moon(processName·surrogate 保留+窗·enabled 清空=混合第三形),修法入 audit8 行为并批轮(issues/236 §5);镜像层格本轮先不落=契约先行(115/154 先例),补格随该轮排期 |