1. 页面概览

1.1 是什么

「Action 测试」页面(页面标题「Action 写路径测试」)是 LightFoundry 提供的 Action 唯一写路径测试台。在 Foundry 本体体系中,业务数据的写入只能通过对象类型上声明的 Action(动作)完成——Action 携带 param_schema(参数模式)、validation_rules(提交前校验规则)、write_back_config(回写声明,含 SQL 模板与乐观锁配置)等定义,经过后端 writepath 安全流水线(Schema 校验 → 校验规则 → RBAC → 记录级 → 属性级 → 审计 → 幂等 + 事务)后才能落库。

本页面的源码位于 action/web/src/views/ActionTesterPage.vue,共约 395 行。它做的事情是把某个对象类型上的 Action 以可交互表单的形式暴露出来:选择对象与动作后,前端按 param_schema 动态生成参数表单(含必填标记、类型标注、枚举下拉),填好参数与幂等键后点击「执行(VALIDATE_AND_EXECUTE)」即可真实触发写路径;同时提供「重放上次执行」按钮演示幂等重放(replayed)语义。

页面顶部注释块概括了核心设计:选择对象与动作 → 按 param_schema 生成参数表单 → 执行(VALIDATE_AND_EXECUTEidempotency_key 自动生成/手动输入);幂等演示:记录上次执行的 idempotency_key,「重放上次执行」复现 replayed。因此本页既是开发调试工具,也是幂等与写路径机制的演示窗口——这正是本手册第 5、6 章重点展开的内容。

1.2 核心价值

能力说明对应操作
对象/动作选择从对象列表进入,加载该对象声明的全部动作「对象类型」「动作」双下拉
参数表单自动生成param_schema 动态渲染字段(类型/枚举/必填)参数表单区
写路径真实执行VALIDATE_AND_EXECUTE 模式执行动作,先校验再落库「执行(VALIDATE_AND_EXECUTE)」
幂等键管理自动生成或手动输入 idempotency_key(运行模式必填)「生成」按钮 + 输入框
幂等重放演示复用上次执行的幂等键再次提交,复现 replayed 状态「重放上次执行」
结果透视结构化展示 status/mode/rows_affected/affected_records/validation_result/edits/operation_id/replayed_from_run_id「执行结果」卡片

1.3 一句话总结

「Action 测试」是 Foundry 写路径的试金石与演示台:用真实动作、真实参数、真实幂等键触发完整安全流水线,让"参数校验、乐观锁、幂等重放、强制审计"这些机制在浏览器里肉眼可见。

2. 访问入口

2.1 路由与菜单

项目
路由 path/foundry/actions(文档 slug 为 action-tester,与 markdown/index.md 规划一致)
路由 nameFoundryActions
侧边栏入口FoundryLayout 侧边栏「Action 测试」
父路由/foundry(组件 FoundryLayout
前端源码action/web/src/views/ActionTesterPage.vue
路由注册文件action/web/src/router/index.js(约 221-225 行)

访问方式:登录后从 Foundry 左侧菜单点击「Action 测试」,或直接访问 /foundry/actions

2.2 认证与权限

2.3 端口与 API 前缀

3. 界面布局

本页为四张纵向卡片流:

┌─────────────────────────────────────────────────────┐
│  Action 写路径测试                                   │
│  ┌─────────────────────────────────────────────────┐ │
│  │ [alert 提示条(出现时展示,右上角"关闭")]          │ │
│  └─────────────────────────────────────────────────┘ │
│  ┌─ 选择对象与动作 ─────────────────────────────────┐ │
│  │  对象类型 [下拉]  动作 [下拉]                     │ │
│  │  action_name — description [edit_type 标签]      │ │
│  └─────────────────────────────────────────────────┘ │
│  ┌─ 参数(按 param_schema 生成)────────────────────┐ │
│  │  提示:ValidationRules 提交前校验...(B7-6)      │ │
│  │  字段名*(type)  [输入框/枚举下拉]               │ │
│  │  idempotency_key(运行模式必填)[输入框][生成]     │ │
│  │  [执行(VALIDATE_AND_EXECUTE)] [重放上次执行]    │ │
│  │  上次执行幂等键:<key>                            │ │
│  └─────────────────────────────────────────────────┘ │
│  ┌─ 执行结果(result 存在时展示)───────────────────┐ │
│  │  status / mode / rows_affected / affected_records │ │
│  │  validation_result.result / edits.changed_records │ │
│  │  operation_id / replayed_from_run_id              │ │
│  │  validation(JSON)  result(JSON)               │ │
│  └─────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
板块职责
页面标题区显示「Action 写路径测试」
Alert 提示条展示执行/重放/加载的成败信息;执行成功但业务失败(如 validation_failed)用 alert-info,真成功用 alert-success
选择对象与动作卡片双下拉(对象类型 / 动作)+ 动作描述区(名称、描述、edit_type 标签)
参数表单卡片param_schema 动态渲染;含校验规则提示条、幂等键输入区、执行/重放按钮、上次幂等键提示
执行结果卡片结构化网格展示执行响应各字段 + 可选 validation/result JSON 区块

4. 交互元素详解

4.1 对象类型下拉框

项目说明
位置选择卡片左半格
含义选择要测试动作所属的对象类型
选项文案{{ item.object_type.name }}(#{{ item.object_type.id }})(以内部 id 标注)
操作效果@change="handleSelectObject":重置动作选择与结果,然后请求该对象详情并填充动作列表
触发的后端调用GET /ontology/objects/:id(返回对象详情含 actions 数组)

4.2 动作下拉框

项目说明
位置选择卡片右半格
含义选择要执行的动作
选项文案{{ a.name }}(动作名)
操作效果@change="handleSelectAction":找到选中动作并重置结果/参数;随后为每个 schema 字段初始化空值
动作描述区选中后显示 {{ selectedAction.name }}(加粗)+ 描述 + edit_type 标签(如 modify

4.3 参数表单(按 param_schema 生成)

项目说明
生成逻辑schemaFields computed:取 selectedAction.param_schemapropertiesrequired,每个字段输出 {name, type, enum, required}
标签渲染{{ field.name }} + 必填时红色 * + 类型标注 ({{ field.type }})
输入控件字段有 enum(非空数组)时渲染下拉(占位「请选择」+ 枚举选项);type=boolean 渲染 true/false 下拉(选中值即 JSON 布尔 true/false,不再提交字符串);其余渲染 input(:type 统一 text,number/integer 加 inputmode=decimal),placeholder 为该字段 type
默认值选择动作后初始化:boolean 为「请选择」(null),其余为 '';提交时空值/空串/null 一律省略不发送(不显式传 null,避免后端按 param_schema 严格类型校验误报)
校验规则提示卡片顶部黄色提示条(rule-hint)说明:动作配置的 ValidationRules(如 amount > 0 && status == 'open')将在写入前校验,不通过时拒绝执行并返回 validation_rules 失败信息(B7-6)

4.4 idempotency_key 输入与「生成」按钮

项目说明
含义幂等键(运行模式必填),用于识别"同一次业务操作"
输入框placeholder="自动生成或手动输入"
「生成」按钮generateKey():优先 crypto.randomUUID(),不可用时回退 'demo-' + Date.now() + '-' + Math.random().toString(36).slice(2, 10)
必填性handleExecute 前置校验:!idempotencyKey.value.trim() 时 alert「运行模式需要 idempotency_key(可点击"生成")」并中止
后端强制服务端 handleActionExecute 对非 VALIDATE 模式同样强制:idempotency_key is required for run modes (RUN/ASYNC/VALIDATE_AND_EXECUTE)

4.5 执行(VALIDATE_AND_EXECUTE)按钮

项目说明
含义VALIDATE_AND_EXECUTE 模式执行选中动作:先完整校验(VALIDATE 语义),通过后落库执行
可用条件:disabled="busy"
前置校验未选动作 alert「请先选择动作」;幂等键为空 alert「运行模式需要 idempotency_key(可点击"生成")」
请求体{action_name, object_type_id, params, idempotency_key, mode: 'VALIDATE_AND_EXECUTE'}
触发的后端调用POST /ontology/actions/:name/execute
操作效果成功后写入 result、记录 lastKey = idempotencyKey,alert「执行完成:{status}」(successalert-success,其余 alert-info

4.6 重放上次执行按钮

项目说明
含义复用上次执行的 idempotency_key 再次提交,复现幂等重放(replayed)
可用条件:disabled="busy || !lastKey"title="使用上次执行的 idempotency_key 再次提交,复现幂等重放"
操作效果idempotencyKey = lastKey 后以同一 key 重新执行;后端发现幂等键已处理,返回 status: replayed + 首次执行结果;alert「重放完成:{status}」(一律 alert-success
结果字段replayed_from_run_id 标注首次执行的 run id;edits.changed_records 回放首次执行的 rows_affected

4.7 上次执行幂等键提示与执行结果卡片

项目说明
上次执行幂等键v-if="lastKey",文案「上次执行幂等键:<code>{{ lastKey }}</code>
结果网格8 格:status(按状态着色,success 绿/replayed 蓝/validation_failed 黄/conflict 红/forbidden·record_denied 红)、moderows_affectedaffected_records(空显示 -)、validation_result.result(空显示 -)、edits.changed_records(空显示 -)、operation_id(空显示 -)、replayed_from_run_id(空显示 -
validation 区块result.validation 非空时展示 JSON.stringify(validation, null, 2)
result 区块result.result 非空时展示 JSON.stringify(result, null, 2)(即 ExecuteResponse.result

5. 后端关联

5.1 API 客户端

项目
客户端文件action/web/src/api/client.js
baseURL/api/v1(Vite 代理到 Foundry 后端 18081)
超时30000ms
请求拦截器localStorage aip_tokenAuthorization: Bearer <aip_token>
响应拦截器HTTP 401 → 清 token 跳 /login
导出export default apiClient

5.2 端点表

方法路径请求体说明
GET/ontology/objects对象类型列表
GET/ontology/objects/:id对象详情(含 actions 数组)
POST/ontology/actions/:name/execute{action_name, object_type_id, params, idempotency_key, mode}执行动作(mode 默认 VALIDATE_AND_EXECUTE
POST/ontology/actions/:name/validate同上(mode 强制 VALIDATE仅校验不落盘(本页未使用,API 可用)
GET/ontology/actions/runs/:idASYNC 模式运行状态轮询(operation_id 形如 run_<数字>;本页为同步执行,未使用)

execute 请求体字段:action_name(或路径参数 :name)、object_type_idparams(map)、idempotency_keymodeVALIDATE/RUN/ASYNC/VALIDATE_AND_EXECUTE)。

5.3 响应结构

重要契约:HTTP 200 ≠ 成功。业务失败(validation_failed/forbidden/conflict 等)时后端仍返回响应体(含 mode/status/validation/edits/latest_state 等),HTTP 状态取错误的 ierr 状态(如 422/403/409);幂等重放(ACTION_IDEMPOTENT_REPLAY)时返回 200 语义并携带首次结果。前端 doExecute 因此在 catch 中特殊处理:if (err.response?.data?.data) return err.response.data.data,把错误响应体里的 data 也当作结果返回。

执行成功响应:

{
  "code": 0,
  "data": {
    "mode": "VALIDATE_AND_EXECUTE",
    "status": "success",
    "validation": {},
    "validation_result": { "result": "VALID", "criteria_results": [] },
    "edits": { "changed_records": 1 },
    "affected_records": 1,
    "rows_affected": 1,
    "latest_state": { ... },
    "result": { ... },
    "operation_id": "run_42",
    "replayed_from_run_id": ""
  }
}

幂等重放响应(status=replayed,HTTP 200):

{
  "code": 0,
  "data": {
    "mode": "VALIDATE_AND_EXECUTE",
    "status": "replayed",
    "validation_result": { "result": "VALID" },
    "replayed_from_run_id": "42",
    "operation_id": "run_42",
    "result": { ... 首次执行结果 ... },
    "edits": { "changed_records": 1 }
  }
}

5.4 关联模块表

后端包/文件职责
foundry/writepath/pipeline.go写路径执行器:ExecuteRequest/ExecuteResponse 契约、5 步流水线(Schema→RBAC→记录级→属性级→审计→幂等+事务)、Mode/Status/错误码常量、WriteBackConfig/OptimisticLock 结构、乐观锁预检
foundry/writepath/idempotency.go幂等登记:Begin(查重/登记 pending/回放)、Finish(标记结果)、ActionRunResult
foundry/writepath/step_validation_rules.go提交前校验规则(B7-6):ValidationRule{expr, message},失败聚合,坏表达式 fail-closed
foundry/server/ontology_handlers.gohandleActionExecute/handleActionValidate/handleActionRunStatusactionExecuteRequestwriteActionResponsebuildExecuteRequest
foundry/ontology/models.goOntologyAction(param_schema/validation_rules/write_back_config/requires_approval/idempotency_key_field/edit_type)
foundry/server/seed.go演示数据动作:update_customer_credit/update_order_status/flag_order_risk

5.5 关键机制(写路径五步流水线 + 幂等 + 乐观锁)

0 模式分派

Execute 按 mode 分派——VALIDATEvalidate()(仅步骤 1-4 + 冲突预检,不落盘不幂等);RUN/ASYNCrun()(全流程);VALIDATE_AND_EXECUTEvalidate() 通过(Status==success)再 run()

流水线步骤(run 全流程)

名称校验内容失败返回
1Schema 校验param_schema JSON Schema 校验参数;布尔参数先归一(JSON 布尔/字符串/数值 true/false1/0 均可,字符串大小写不敏感,B1)validation_failed(422,VALIDATION_FAILED
1bValidationRules表达式条件(vars = action params + record 现值);空规则零开销跳过;失败聚合全部 message;坏表达式 fail-closedvalidation_failed(422,VALIDATION_FAILED
2逐动作 RBACAuthorizeUser(..., "action:"+name)forbidden(403,FORBIDDEN
3记录级权限目标记录集 SELECT 主键 + RLS 可见性;任一记录不可见整批拒绝record_denied(403,RECORD_ACCESS_DENIED
3b属性级写权限回写涉及属性逐属性判定,缺 EditProperty/EditPolicyProperty 拒绝forbidden(403,PROPERTY_EDIT_DENIED
4强制审计ACTION_EXECUTE 事件(含 action_name/params(脱敏)/idempotency_key/mode/prior_state)不阻断
5幂等+事务Begin 登记 pending → 构建 SQL → 乐观锁 → 连接器回写 → Finish 标记见下文

幂等(补偿式登记)

action_runs 表(foundry 库)与数据源回写(外部库)无法跨库事务,采用"先登记 pending → 外部执行 → 标记结果"补偿式。idempotency_key 有唯一索引;Begin 先查已存在 → 返回 ACTION_IDEMPOTENT_REPLAY(HTTP 409)并附首次结果,writeActionResponse 检测到 status==replayed 时把 HTTP 状态降为 200。并发竞态下 CreateActionRunOnConflict{DoNothing:true} 吞冲突并重查幂等键兜底(run.ID=0 场景 BUG-2 已修复)。

乐观锁(G2)

write_back_config.optimistic_lock 支持两种形态——true(默认列 updated_at、参数 expected_updated_at)或 {column, expected_param}。SQL 模板中写入 AND updated_at=:expected_updated_at,预检(optimisticConflictCheck)与执行时行数 0 命中即判定冲突,返回 conflict(409,ACTION_CONFLICT)。演示数据用毫秒精度 strftime('%Y-%m-%d %H:%M:%f','now') 写回,避免同秒乐观锁盲区。

Status 常量与错误码

Status:success / validation_failed / forbidden / conflict / record_denied / replayed / rejected。错误码:VALIDATION_FAILED / FORBIDDEN / RECORD_ACCESS_DENIED / PROPERTY_EDIT_DENIED / ACTION_CONFLICT / ACTION_IDEMPOTENT_REPLAY / WRITE_CONFIG_ERROR

审计口径

stepAudit 调用 LogEventRef(userID, "ACTION_EXECUTE", "ontology_action", action.Name, "ACTION_EXECUTE", details, string(result));details 含 action_nameparams(经 maskParams 脱敏)、idempotency_keymode,有 prior_rows 时含 prior_state;validate/run 各失败分支也分别记录对应 status 的审计。

6. 核心流程详解

6.1 主流程:执行一个动作

  1. 页面挂载执行 fetchObjects()GET /ontology/objects 填充对象下拉。
  2. 选择对象 → handleSelectObject()
    • 重置 selectedActionName/selectedAction/result/paramValues
    • GET /ontology/objects/:id,把返回 data.data.actions 填入动作下拉;失败 alert「加载动作失败:...」。
  3. 选择动作 → handleSelectAction()
    • selectedAction = actions.find(a => a.name === selectedActionName)
    • 重置结果与参数,按 schemaFields 初始化空值。
  4. 填写参数;必要时点「生成」产生幂等键或手动输入。
  5. 点击「执行(VALIDATE_AND_EXECUTE)」→ handleExecute()
    • 前置校验(选动作/幂等键)→ doExecute(name, 'VALIDATE_AND_EXECUTE')
    • doExecute 组装 params(number/integer 空值转 null)、body {action_name, object_type_id, params, idempotency_key, mode}POST /ontology/actions/:name/execute
    • 成功或业务失败(err.response.data.data)均把 res.data.data 返回;
    • result = resplastKey = idempotencyKey,alert「执行完成:{status}」。
  6. 「执行结果」卡片展示各字段;非空 validation/result 以 JSON 区块展开。

6.2 分支流程:重放(幂等演示)

  1. 首次执行成功后 lastKey 已记录,页面出现「上次执行幂等键:<key>」。
  2. 点击「重放上次执行」→ handleReplay()idempotencyKey = lastKey,同一动作、同一 key 再次 VALIDATE_AND_EXECUTE
  3. 后端 Begin 发现幂等键已处理 → 返回 status: replayed(HTTP 200)+ 首次结果;前端 alert「重放完成:replayed」,结果卡片中 replayed_from_run_id 显示首次 run id。
  4. 演示意义:同一业务操作重复提交不会产生第二次数据变更rows_affected 回放首次值而非重新执行。

6.3 状态机/终态语义

status含义典型触发数据是否落库
success执行成功校验与权限全过、乐观锁命中、回写成功
validation_failed参数/校验规则失败必填缺失、类型不符、ValidationRules 表达式不成立
forbiddenRBAC/属性级权限拒绝action:<name> 权限、缺属性写权限
record_denied记录级越权目标记录 RLS 不可见
conflict乐观锁冲突expected_updated_at 与当前行不一致
replayed幂等重放同一 idempotency_key 重复提交否(返回首次结果)
rejected动作被拒(预留)审批/策略拒绝类场景

6.4 执行时序(VALIDATE_AND_EXECUTE 单请求)

前端 POST /ontology/actions/:name/execute
  → Executor.Execute(mode=VALIDATE_AND_EXECUTE)
    → validate(): 1 Schema → 1b Rules → 2 RBAC → 3 记录级 → 3b 属性级 → 4 审计 → 5 乐观锁预检
    → 通过则 run(): 1-4 同上 → 5 Begin(idempotency_key 查重/登记) → buildWriteSQL
      → ConnectorFor(dataSourceID) 取连接器 → 编辑态落盘/血缘/事件(可选)→ 回写执行
      → Finish 标记 success/failed → 响应

7. 权限与安全

8. 常见问题与排错

问题 1:执行后 status 为 validation_failed,参数看起来都对

现象:alert「执行完成:validation_failed」,结果卡 validation 区块有错误明细。

原因:参数未满足 param_schema(必填缺失/类型不符)或 validation_rules 表达式不成立(如 amount > 0 但传了 0)。

排查步骤

  1. 展开结果卡片的 validation JSON 区块,看具体字段错误;
  2. 核对必填标记 * 与类型标注(number 字段空值会被转 null);
  3. 检查动作的 validation_rules(可在对象 YAML 导出中查看)条件是否满足;
  4. 用「重放上次执行」不会改变结果——先修正参数重新执行。

问题 2:执行报 forbidden,但页面能正常加载动作列表

现象:alert「执行失败:...user ... is not authorized for action ...」或「执行完成:forbidden」。

原因:当前用户缺少 action:<name> 权限点(RBAC),或属性级写权限不足(PROPERTY_EDIT_DENIED)。

排查步骤

  1. 在后台/RBAC 配置中给当前用户授权对应动作权限点;
  2. 若错误含具体属性名,确认用户对回写涉及的属性具备写权限;
  3. 用管理员账号重试区分"权限配置问题"还是"动作本身问题"。

问题 3:执行返回 conflict(乐观锁冲突)

现象:status 为 conflicterror_codeACTION_CONFLICT

原因expected_updated_at 参数与目标行当前 updated_at 不一致,说明期间数据被其他请求修改。

排查步骤

  1. 重新查询目标行的最新 updated_at 填入 expected_updated_at
  2. 确认演示数据写回使用毫秒精度时间戳(秒级 CURRENT_TIMESTAMP 有同秒盲区);
  3. 若频繁冲突,检查是否有其他写路径同时修改同一行。

问题 4:幂等键报错 / 重放不是 replayed

现象:执行时报"idempotency_key is required...",或重放后 status 不是 replayed。

原因:运行模式(RUN/ASYNC/VALIDATE_AND_EXECUTE)强制要求幂等键;重放时若 lastKey 为空(首次执行失败未记录)则按钮禁用;若重放键与上次不一致(被手动修改),会被当作新操作执行而非重放。

排查步骤

  1. 点击「生成」自动产生 key 后执行;
  2. 确认「上次执行幂等键」提示存在后再点「重放上次执行」;
  3. 重放时不要手动改动幂等键输入框;
  4. 若需验证幂等,用同一 key 连续执行两次,第二次应得到 replayed

问题 5:动作下拉为空 / 加载动作失败

现象:选择对象后「动作」下拉无选项,alert「加载动作失败:...」。

原因GET /ontology/objects/:id 失败,或该对象没有声明任何动作。

排查步骤

  1. 检查 Network 面板该请求的状态码与错误信息;
  2. 确认对象详情响应 data.actions 非空(对象需先声明动作);
  3. 用「YAML 导入导出」页导出该对象 YAML 检查 actions 段;
  4. 若是 401,重新登录。

9. 已知缺陷与边界

缺陷/边界说明
同步执行、无轮询前端用 VALIDATE_AND_EXECUTE 同步等待;ASYNC 模式与 runs/:id 轮询端点后端已支持,但本页未暴露 UI
mode 固定页面固定 VALIDATE_AND_EXECUTE,未提供 VALIDATE(纯校验)切换入口(API 支持 /actions/:name/validate
return_edits 契约不生效请求体虽接受 return_edits,但 writepath.ExecuteRequest 尚无该字段,透传跳过(MVP 仅接受不产明细)
结果展示无分页/折叠validation/result JSON 全量展开,超长结果可能撑高页面
参数类型覆盖有限param_schemaformat/嵌套 object/数组等高级类型未在表单中专门渲染(已覆盖:enum 下拉 / boolean true-false 下拉 / text 输入)
无数据源选择动作回写目标由 write_back_config.data_source_id 决定,页面不能切换数据源

注:执行/重放提示语义混淆已于 2026-09-06 修复(按实际返回 status 判定成功/失败:success/replayed 标绿,其余失败以 alert-error 标红展示)。

注:type=boolean 参数此前落入 text 输入框,提交的字符串("true"/"1" 等)被后端严格类型校验判为不符,导致测试页无法执行;已于 2026-09-07 修复(#IKDX27)——前端对 boolean 渲染 true/false 下拉(提交 JSON 布尔),后端 normalizeBoolParams 补充字符串形态布尔归一兜底直接 API 调用。