fix(service-automation): resolve triggering user's grants for runAs:'user' (#3356)#3389
Merged
Merged
Conversation
…user' (#3356) Follow-up to #1888. `runAs:'user'` record-change flows ran their data ops with a zero-grant member/everyone principal — 403 on private objects, silent field strips on public ones — because the ObjectQL hook session carries only a `userId`, never the writer's positions/permission sets, and nothing resolved them. - core: factor the userId-driven core of `resolveAuthzContext` into a new exported `resolveUserAuthzGrants(ql, userId, opts)` (single reader of sys_member / sys_user_position / sys_*_permission_set); the HTTP resolver delegates to it, behaviour byte-identical. - service-automation: add `AutomationEngine.setUserGrantsResolver`, wired by the plugin to `resolveUserAuthzGrants`. A `runAs:'user'` run whose trigger left the authz envelope unresolved now resolves the user's positions + permission sets once at run setup and threads them into every data node's context. Contexts that already carry `permissions` (REST / on-behalf-of agent ceiling) are left untouched; `runAs:'system'` is unchanged; a resolver error fails safe (warns, never elevates). - trigger-record-change: stop forwarding the misleading half-populated `positions`; forward `userId` + tenant only and let the engine resolve. Adds unit coverage (core resolver + engine resolve-at-setup guardrails) and a plugin-wiring integration test that boots the plugin and proves the bridge end-to-end. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SU5JeLSqHYqhw1P1vP82bH
Contributor
📓 Docs Drift CheckThis PR changes 3 package(s): 27 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
os-zhuang
marked this pull request as ready for review
July 21, 2026 15:19
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
背景
修复 #3356(#1888 的后续)。自 #1888 起引擎已消费
flow.runAs(system提权正常),但runAs:'user'的凭证传播是空心的:record_change 触发的runAs:'user'流,其数据节点(update_record等)按只剩 member/everyone 兜底的零权限集用户执行——即便触发用户本身对目标对象有完整授权。病征分两副面孔:private对象直接 403(not permitted for positions [org_member, everyone]);public_read_write对象则 readonly/FLS 字段被静默剥离,节点仍报success。根因
ObjectQL record-change hook session 只携带
userId,从不携带 writer 的 positions / permission sets;buildContext()只能转发一个「空壳 positions + 永远缺省的 permissions」,而两端(trigger 的buildContext与 service-automation 的resolveRunDataContext)各自「正确」,中间没人负责把触发用户的岗位/权限集查出来。注释承诺的「enforces RLS exactly as the user who made the change」因此落空。采用 issue 首选方案(修实现):在运行建立时,用 REST 请求同一套解析逻辑解析触发用户的真实授权。
改动
@objectstack/core:把resolveAuthzContext中由 userId 驱动的解析抽出为新导出resolveUserAuthzGrants(ql, userId, opts)——它是读取sys_member/sys_user_position/sys_*_permission_set并推导 positions、permission-set 名、platform_admin、posture 的唯一实现;HTTP 解析器改为委托它,行为逐字节不变(21 条契约测试仍全绿)。@objectstack/service-automation:新增AutomationEngine.setUserGrantsResolver,由插件桥接到resolveUserAuthzGrants。当runAs:'user'运行的触发上下文未解析出授权(无permissions)时,引擎在运行建立处(所有触发类型共享的单点)一次性解析出该用户的 positions + permission sets,注入每个数据节点的 ObjectQLcontext。已携带permissions的上下文(REST 触发、以及 ADR-0090 代理在 on-behalf-of 场景下始终非空的 ceiling)保持原样——绝不把被刻意收窄的身份重新放大;runAs:'system'不变;解析失败故障安全(告警,保留裸用户,决不提权)。@objectstack/trigger-record-change:buildContext不再转发误导性的空壳positions,只转发userId+ 租户,交由引擎权威解析。未接入 ObjectQL 引擎时(裸引擎 / 测试)解析器不挂载,运行身份与改动前一致。
测试
resolveUserAuthzGrants单测 + 与resolveAuthzContext的同源一致性对拍。system/ 无 user / 解析失败 fail-safe / 未挂载)。runAs:'user'运行最终把触发用户解析出的 positions + permission set 带进数据操作的context(端到端验证 bridge)。未在本 PR 覆盖(建议拆单)
issue 的次级问题——
update_record在 readonly 字段被剥离时仍记success、缺可观测告警——是独立的可观测性增强(issue 亦标注「可拆单」)。本 PR 修复了授权用户的根因(权限集已正确传播,授权用户不再被剥离),该告警建议作为后续单独跟进。Generated by Claude Code