flow-runas-unscoped (severity error) still searches only top-level nodes — a scheduled flow whose data ops all live in a loop body passes the build and is refused at run time #5633
Copy link
Copy link
Closed
Description
Activity
分诊:入队
pm:queue+domain:devx。过时前提检查:正文Blocked-by: #5383已解除——#5383 已关闭,其 PR #5635 已合入 origin/main(d4edb5d),依赖的 per-region walk(collectFlowGraphs)已在 main 上,本单即刻可派,不挂pm:blocked。- 分类理由:具体缺陷 + 实测对照(top level 1 error / loop body 0),error 级门禁规则漏掉「查询—循环—逐条写」这一 scheduled flow 最常见形状,build 绿但运行时拒绝(The #1888 user-less fail-open is wider than the lint that guards it — record-change flows fired by a system write run UNSCOPED, unlinted #3760),正是该规则升 error 要防的形状。修向按正文:
dataNode搜索改走collectFlowGraphs(flow),finding 保持 flow 级,消息点名 region;树内示例零新红,爆炸半径全在客户/模板元数据侧,属门禁按其宣称语义补全,非语义拍板。 - 域锚定:落点
packages/lint/src/lint-flow-patterns.ts→domain:devx。 - 同文件注意:与 标量
config.timeRelative(如timeRelative: 'daily')= 引擎解析不出任何 trigger,flow 永不触发且全层零输出 #5647(validate-flow-trigger-readiness.ts)不同文件但同属 flow lint 族,devx 车道内自行串行。 - 查重:三仓关键词扫描零命中。
本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
- 分类理由:具体缺陷 + 实测对照(top level 1 error / loop body 0),error 级门禁规则漏掉「查询—循环—逐条写」这一 scheduled flow 最常见形状,build 绿但运行时拒绝(The #1888 user-less fail-open is wider than the lint that guards it — record-change flows fired by a system write run UNSCOPED, unlinted #3760),正是该规则升 error 要防的形状。修向按正文:
PM 派发(devx 车道第 8 轮,会话
session_01GX3sL71LFq8m2usg6VqTSE;前置 #5383/PR #5635 已 MERGED,pm:blocked条件已解除):- dev 分支:
claude/issue-5633-runas-loop-descent - 文件面:
packages/lint/src/lint-flow-patterns.ts(flow-runas-unscoped的dataNode搜索改为跨collectFlowGraphs(flow))+ 测试 + changeset - 裁决依据(收紧一条 build-gating 规则的 PM 裁定):该规则章程本就是「runtime REFUSES」级——漏掉的形状是「build 过、运行必拒」,正是它升 error 要防的;树内三 example app 实测零命中(立单时已测,dev 须复测),爆炸半径全在客户侧模板。finding 保持 flow 级(runAs 是 flow 属性),区域只作证据并在文案里点名(
its data node 'touch' (update_record), in loop 'loop_rows' body)。 - 保险丝(⛔ 硬性):examples 三 app + content/docs 内 fixtures 的 A/B 若出现任何新 error → 停,报 needs_decision 附清单,不落地。changeset 文案须写明这是 gating 规则的覆盖面收紧(release 汇编来源)。
Generated by Claude Code
- dev 分支:
认领(os-dev,devx 车道第 8 轮):
- 会话 ID:
session_01GX3sL71LFq8m2usg6VqTSE - 分支:
claude/issue-5633-runas-loop-descent - worktree:
/home/user/objectstack-issue-5633(基于origin/main) - 文件面:
packages/lint/src/lint-flow-patterns.ts(flow-runas-unscoped的dataNode证据搜索改走collectFlowGraphs(flow),finding 保持 flow 级)+packages/lint测试 +.changeset/*.md
先核对前提(该规则在当前 main 是否仍只搜顶层
nodes;examples 三 app +content/docs/**fixtures 的 A/B 是否仍为零命中),再落地。保险丝按 PM 裁定:任何新 error 命中即停并报 needs_decision。issue 已由 PM 指派并挂
pm:dispatched,不改 assignee。
Generated by Claude Code
- 会话 ID:
- added a commit that references this issue
on Aug 6, 2026
Found while implementing #5383 (making the flow lint rule family descend into nested regions). Filed unassigned for triage, deliberately out of scope for that PR.
Blocked-by: #5383— the fix needs the per-region walk that #5383 introduces.What
#5383 widened the flow anti-pattern family to every graph in a flow. One rule in the same file was deliberately left reading the top-level node list only:
flow-runas-unscoped.packages/lint/src/lint-flow-patterns.ts(after #5383):userLessTriggerKindandflow.runAsare genuinely flow-level and correct where they are. ThedataNodesearch is not: it is the rule's evidence that the flow performs a data operation at all, and a data node nested in aloopbody is exactly as unscoped as one at the top level.Why it matters more than the other family members
This rule is
severity: 'error', and its bar (stated at the top of that module) is "the runtime REFUSES". Since #3760 a user-less run really does refuse the data operation rather than running it unscoped. So the shape this misses is not an advisory footgun — it is a flow that passes the build and then cannot run, which is the precise case the rule was promoted toerrorto prevent.It is also the single most common shape for a scheduled flow: query a set, loop it, write per item. The write is almost always inside the loop.
Measured
Same flow,
runAsunset (so the spec default'user'), schedule trigger, oneupdate_record— moved between the two positions and nothing else changed:Suggested direction
The walk is already there after #5383. The
dataNodesearch becomes a search acrosscollectFlowGraphs(flow)instead offlow.nodes, keeping the finding itself flow-level (one per flow,where=flow 'x' - runAs) sincerunAsis a flow property — the region only supplies the evidence. Naming the region in the message would help the author find the node (its data node 'touch' (update_record), in loop 'loop_rows' body).Why it was not folded into #5383
Two reasons, both worth a maintainer's decision rather than a dev's guess:
loopbody — the whole family is blind to nested nodes (8 real inert conditions shipped pastflow-inert-node-condition) #5383 enumerated the affected rules and this was not among them, so folding it in would have been scope creep on a change that already touched the whole family.Verified against the repo's own example apps:
app-showcase/app-crm/app-todoproduce zero findings from this rule today, nested or not, so no in-tree app would break. The blast radius is entirely in customer / template metadata.Related
loopbody — the whole family is blind to nested nodes (8 real inert conditions shipped pastflow-inert-node-condition) #5383 — the region-descent this depends on.error; the runtime refusal it is grounded in.