业务故事站
P2 Foundry

Action 写路径:受控回写与安全流水线

数据要能读,更要能"安全地改"。Foundry 的 Action 是业务数据写回的唯一入口:先 VALIDATE 预校验再执行,逐动作 RBAC、记录级 RLS、乐观锁、幂等、审计五道防线全自动。本主题 5 个故事覆盖正常改单、并发冲突、幂等重试、权限拒绝与审计追责。

运营人员 数据工程师 管理员 乐观锁 幂等 审计 共 5 个故事

能 / 不能速览

✅ 这个主题能做
  • 四种执行模式:VALIDATE(dry run 不落盘)/ RUN / ASYNC / VALIDATE_AND_EXECUTE
  • 逐动作 RBAC(权限点 action:<name>)+ 记录级 RLS(RECORD_ACCESS_DENIED)
  • 乐观锁:SQL 模板 WHERE ... AND updated_at=:expected_updated_at,冲突返回 409 ACTION_CONFLICT
  • 幂等:按 idempotency_key 防重放,重复提交返回首次结果 ACTION_IDEMPOTENT_REPLAY
  • 强制审计(ACTION_EXECUTE 事件)+ 血缘 Action 写边
⛔ 这个主题做不了
  • 仅支持 SQL 类回写(UPDATE / DELETE),HTTP / API 类对接外部系统未实现
  • HTTP 200 不等于成功,必须看响应体里的 status 字段
  • 无 WHERE 的无边界写会被直接整批拒绝(防整表 UPDATE / DELETE)
  • demo 数据源重启重建,回写源表的改动会还原,但编辑态与审计记录保留

适用角色

本主题面向三个角色:

  • 运营人员:执行 Action 改订单状态,需要对应的权限点(action:update_order_status)。
  • 数据工程师:设计 / 维护 Action 定义(参数 Schema、写回模板、乐观锁、幂等键)。
  • 管理员 / 审计员:授权动作权限、在审计日志里追查每一次写操作。

普通只读用户无法执行写路径,越权会被 403 拒绝。

能力速览(能做什么)

四模式执行

VALIDATE 预校验不落盘、RUN 同步执行、ASYNC 异步语义、VALIDATE_AND_EXECUTE 先复核再执行(默认模式)。

逐动作 RBAC

权限精确到动作(action:<name>),没权限直接 403 FORBIDDEN;记录级 RLS 再兜一层越权记录拦截。

乐观锁

SQL 模板带 expected_updated_at 期望值,受影响行数 0 即 409 ACTION_CONFLICT,绝不静默覆盖。

幂等防重放

按 idempotency_key 唯一登记,重复提交返回首次执行结果(ACTION_IDEMPOTENT_REPLAY),重试安全。

审计 + 血缘

每次写操作强制落审计(ACTION_EXECUTE),血缘图记录对象 → 动作写边,追责有据。

调整指南(怎么调整)

  • 想改订单:用 update_order_status,先 VALIDATE 预校验,再 VALIDATE_AND_EXECUTE 正式执行。
  • 想防冲突:执行前现查 updated_at 作为 expected_updated_at,操作密集的订单缩短"查→改"间隔。
  • 想防重放:每次执行传一致的 idempotency_key(如 order:2:shipped),用稳定规则生成。
  • 想控权限:给角色配 action:update_order_status 权限点;只读用户不授执行权限。
  • 想追责:去审计日志按操作类型 ACTION_EXECUTE 过滤,结合幂等键还原完整操作序列。

做得好的场景

Action 把"业务改数"这件事做成了安全、可重试、可追责的流水线,特别适合以下场景:
  • 受控数据变更:改订单状态走唯一写入口,杜绝绕过审计的裸 UPDATE。
  • 并发不打架:乐观锁让后写者拿到 409 和最新状态,账目永远对得上。
  • 重试安全:网络闪断后重试不重复写,幂等键兜底。
  • 合规追责:审计 + 血缘双留痕,"谁在何时改了什么"一查便知。

限制与不足

以下是明确的边界,使用前先知道:
  • 仅 SQL 类回写:只支持 UPDATE / DELETE 模板,HTTP / API 类(对接外部系统)未实现。
  • HTTP 200 ≠ 成功:成功与否看 status(success / validation_failed / forbidden / conflict / record_denied / replayed)。
  • 无 WHERE 拒绝:无边界写(整表 UPDATE / DELETE)直接被记录级检查整批拒绝。
  • 参数脱敏:审计里含 password / secret / token 的参数会打码。
  • demo 源表重启还原:回写源表的改动会被重建,但编辑态与审计保留在平台库。

场景故事

故事 1 小陈把 pending 订单改 shipped:先 VALIDATE 再正式执行
背景
小陈是订单运营。上午 9:30 仓库通知 order_id=2(李四,995 元)货已发出,她要把状态从 pending 改成 shipped。她不能直接碰库,只能走唯一写路径 update_order_status。
传统做法对比
以前要么找 DBA 手写 UPDATE(无审计无回滚,出了事查不到谁改的),要么走线下 OA 审批再人工同步,一次状态变更动辄半天。现在平台里 3 分钟搞定,每一步都留痕。
角色
运营人员(需有 action:update_order_status 动作的执行权限)。
操作步骤
  1. 在"对象查询"查 order,记下 order_id=2 及其 updated_at(乐观锁期望值)
  2. 打开"Action 测试",选择 update_order_status
  3. 填写 order_id=2、status=shipped、expected_updated_at=刚查到的值、idempotency_key=order:2:shipped
  4. 先点 VALIDATE 做预校验(dry run,不落盘)
  5. 再点 VALIDATE_AND_EXECUTE 正式执行
系统响应
VALIDATE 返回 {mode:"VALIDATE", status:"success", validation:{}}(不落盘);正式执行返回:
{
  "mode": "VALIDATE_AND_EXECUTE",
  "status": "success",
  "rows_affected": 1,
  "result": {"rows_affected": 1},
  "operation_id": "run_7"
}
再查 order_id=2 状态已变为 shipped。
结果洞察
回写已落源表 orders;编辑态(ontology_edits)记录修改前后值;审计日志出现 ACTION_EXECUTE 事件。重要:HTTP 200 不等于成功,必须看 status=success。
调整建议
批量改先 VALIDATE 跑一遍再执行,避免无效写;expected_updated_at 传最新值减少 409;幂等键用稳定规则生成,保持每次一致。
动手试一试
页面路径:对象查询 → Action 测试。输入内容:order_id=2、status=shipped、expected_updated_at=查询到的 updated_at、idempotency_key=order:2:shipped。预期结果:VALIDATE 返回 status=success;执行后返回 rows_affected=1,再查询订单状态为 shipped。
限制提示
HTTP 200 不等于成功,必须看响应体 status;写路径目前只支持 SQL 类回写;demo 数据源重启重建,回写源表的改动会还原,但编辑态与审计保留在平台库。
故事 2 两人同时改同一订单:乐观锁 409 冲突,谁也不会被覆盖
背景
下午 2:00,小陈把 order_id=2 改成 shipped 的同时,仓库同事老赵也把同一订单改成 cancelled(因为发现缺货)。两人都带了 expected_updated_at,但都是从"旧状态"查到的。
传统做法对比
以前后写覆盖先写,谁赢全看运气,账目对不上要翻半天日志;现在乐观锁让后写的人拿到 409,系统还告诉他发生了什么,不会静默覆盖。
角色
两位运营人员(都有 update_order_status 权限,但改的是同一行)。
操作步骤
  1. 两人分别查 order_id=2 的 updated_at(都拿到同一旧值)
  2. 小陈先执行,成功,源表 updated_at 变化
  3. 老赵执行 → 返回 409 ACTION_CONFLICT
  4. 老赵重新查最新 updated_at,带新期望值重试
系统响应
老赵收到 HTTP 409,响应体(错误码 ACTION_CONFLICT):
{
  "code": 0,
  "data": {
    "mode": "VALIDATE_AND_EXECUTE",
    "status": "conflict",
    "operation_id": "run_9"
  }
}
action_runs 记录该失败 run,审计记录 status=conflict。
结果洞察
SQL 模板带 WHERE order_id=:order_id AND updated_at=:expected_updated_at,命中 0 行即冲突,绝不静默覆盖;老赵重查最新值重试即可,账目始终对得上。
调整建议
执行前总是现查 updated_at;操作密集的订单缩短"查→改"间隔;冲突后先跟业务确认最新状态再决定改哪个值。
动手试一试
页面路径:Action 测试。输入内容:开两个会话同时对 order_id=2 执行不同 status,都带同一旧 expected_updated_at。预期结果:一个成功,另一个返回 409 ACTION_CONFLICT。
限制提示
乐观锁只对"启用 optimistic_lock 的动作"生效;不带 expected_updated_at 也能执行,但会失去冲突保护;demo 源表重启还原。
故事 3 网络中断重试:幂等重放返回首次结果,绝不二次执行
背景
上午 11:00 小陈执行订单状态变更时 Wi-Fi 闪断,页面超时无响应。她不确定"到底写没写",于是带同一个 idempotency_key 原样重试了一次。
传统做法对比
以前网络闪断后"到底写没写"全靠猜,重试可能造成重复写、双重扣减;现在幂等键让重复提交返回首次结果,重试安全、写不重不丢。
角色
运营人员(对同一操作重试)。
操作步骤
  1. 第一次执行 order_id=3、status=shipped、idempotency_key=order:3:shipped
  2. 网络中断,页面无响应
  3. 用完全相同的请求(同一 idempotency_key)重试
  4. 查看返回结果
系统响应
第二次返回 HTTP 409(错误码 ACTION_IDEMPOTENT_REPLAY),响应体为首次执行结果:
{
  "code": 0,
  "data": {
    "mode": "VALIDATE_AND_EXECUTE",
    "status": "replayed",
    "replayed_from_run_id": "11",
    "result": {"rows_affected": 1},
    "operation_id": "run_11"
  }
}
action_runs 里该幂等键只有一条记录。
结果洞察
幂等登记表按 idempotency_key 唯一索引拦截重复提交,返回首次执行结果;重试不会造成二次写,业务侧可以放心重发。
调整建议
idempotency_key 用"业务键:操作:目标值"这类稳定规则生成;请求体完全一致时返回的 result 也一致;重放也算一条审计事件。
动手试一试
页面路径:Action 测试。输入内容:同一 idempotency_key=order:3:shipped 执行两次。预期结果:第二次返回 status=replayed、replayed_from_run_id 指向首次 run。
限制提示
run 类模式(RUN / ASYNC / VALIDATE_AND_EXECUTE)idempotency_key 为必填,不带会报参数错误;VALIDATE 模式不写幂等登记。
故事 4 无动作权限被拒:逐动作 RBAC + 记录级 RLS 双保险
背景
客服新人小赵想帮客户把订单改成 cancelled,但他只被授予只读角色,没有 action:update_order_status 权限点;管理员还配了记录级 RLS,让他只能看到自己区域的数据。
传统做法对比
以前要么 DBA 给普通员工开 UPDATE 权限(大炮打蚊子),要么全公司共用一个账号(无法追责);现在权限精确到"某个动作 + 某几条记录",越权寸步难行。
角色
运营人员(被拒方);管理员(授权与 RLS 配置方)。
操作步骤
  1. 小赵打开 Action 测试,执行 update_order_status
  2. 系统返回 403 FORBIDDEN(RBAC 层拦截)
  3. 管理员给客服角色授予 action:update_order_status 后再试
  4. 若目标记录对用户不可见,返回 RECORD_ACCESS_DENIED
系统响应
RBAC 失败返回 HTTP 403(错误码 FORBIDDEN):
{
  "code": 0,
  "data": {
    "mode": "VALIDATE_AND_EXECUTE",
    "status": "forbidden",
    "operation_id": "run_6"
  }
}
记录级不可见则返回 HTTP 403、status=record_denied(错误码 RECORD_ACCESS_DENIED)。两者都会写审计(status=forbidden / record_denied)。
结果洞察
写路径第一步就按权限点 action:<name> 逐动作鉴权;记录级 RLS 再兜一层,目标记录不可见时整批拒绝,连"改得动"的机会都没有。
调整建议
权限最小化:只授需要的动作;记录级按区域 / 负责人配置 RLS;被拒后先查自己的角色权限再申请。
动手试一试
页面路径:Action 测试。输入内容:用一个只读账号执行 update_order_status。预期结果:返回 403 FORBIDDEN,审计记录 status=forbidden。
限制提示
权限点是 action:<name>(逐动作粒度);记录级检查已注入(2026-08-10 起,仅 modify 型动作生效,create/link/delete 跳过);无 WHERE 的写会被记录级检查直接整批拒绝。
故事 5 审计追查:谁在什么时候改了订单,一查便知
背景
周五复盘发现 order_id=4 的 cancelled 订单被人动过,财务要求查清"谁、何时、改了什么"。管理员打开审计日志,按操作类型 ACTION_EXECUTE 过滤定位。
传统做法对比
以前裸 UPDATE 无痕,追查只能翻 DBA 的 binlog,半天起步还容易漏;现在每次写操作都强制落审计,1 分钟定位到人。
角色
管理员 / 审计员(查审计日志);财务(消费结论)。
操作步骤
  1. 打开 Foundry"审计管理"页(/foundry/audit,走 /api/v1/audit/* 端点)
  2. 按操作类型 ACTION_EXECUTE、关联对象 order 过滤(GET /audit/events)
  3. 查看时间、操作用户、动作、参数(敏感字段脱敏)、prior_state、结果
  4. 结合数据血缘看对象 → 动作写边
系统响应
审计条目含 user_id、event_type=ACTION_EXECUTE、ref_type=ontology_action、ref_id=update_order_status、details={action_name, params(脱敏), idempotency_key, mode, prior_state}、result=success——prior_state 为写回前捕获的行快照,还原"改前 → 改后"。
结果洞察
"谁在何时改了订单、改了什么、结果如何"一查便知;prior_state 把修改前状态也钉进审计(仅 modify/delete 在写回前捕获);血缘图里 order → update_order_status 的写边辅助溯源;结合幂等键可还原完整操作序列。
调整建议
敏感参数(password / secret / token)自动脱敏,常规参数明文留档;把审计日志做定期归档;核心动作配合审批策略形成"先批后写"闭环。
动手试一试
页面路径:Foundry 审计管理(/foundry/audit,GET /audit/events)。输入内容:先执行一次 Action,再按 ACTION_EXECUTE 过滤。预期结果:看到该条审计记录,含用户、时间、动作、prior_state 与结果。
限制提示
审计会脱敏 password / secret / token 参数;demo 平台库持久保存审计,但源表数据重启还原;历史编辑态在 ontology_edits 中可回溯修改前状态。

常见问题

HTTP 200 就代表执行成功吗?

不是。必须看响应体里的 status:success / validation_failed / forbidden / conflict / record_denied / replayed。HTTP 200 只代表请求被受理。

为什么我执行报 409?

两种情况:乐观锁冲突(ACTION_CONFLICT,重查 updated_at 重试)或幂等重放(ACTION_IDEMPOTENT_REPLAY,直接采用返回的首次结果即可)。

idempotency_key 怎么填?

用稳定规则生成,如 order:2:shipped。run 类模式(RUN / ASYNC / VALIDATE_AND_EXECUTE)为必填;VALIDATE 模式不校验、也不写幂等登记。

我能直接 UPDATE 数据库吗?

平台写路径只认 Action。绕过平台直连源库没有审计与保护,且 demo 源表重启会还原;无 WHERE 的写还会被记录级检查整批拒绝。

写路径支持对接外部系统吗?

暂不支持。目前只支持 SQL 类回写(UPDATE / DELETE);HTTP / API 类对接外部系统的回写未实现。

主题小结

一句话:Action 是业务数据写回的唯一入口,RBAC → 记录级 RLS → 参数校验 → 乐观锁 → 幂等 → 审计的流水线,让每一次写都安全、可重试、可追责。记住三条使用习惯:先 VALIDATE 再执行、expected_updated_at 传最新值、幂等键稳定生成。