fix(lint): flow-runas-unscoped 用一句两读皆真的措辞命名有效身份 (#5693) - #6060
Open
hotlong wants to merge 1 commit into
Open
Conversation
规则原先按 `typeof flow.runAs === 'string'` 分支消息:作者写了就打
`runAs:'user'`,没写就打 `the default runAs:'user'`。这个区分是真的、也有
用,但规则观察不到它 —— 作者拿到哪一支取决于**面**,而不是取决于他们的文件:
- CLI 恒为 explicit 支。`FlowSchema.runAs` 带 `.default('user')`,registry 把
该规则接为 `input: 'parsed'`;`os lint` 虽不 Zod-parse,但 `defineStack` /
`defineFlow` 在**定义时**就 parse,所以连它拿到的也是默认值已物化的 stack。
- runtime publish gate(#4463)判的是逐字原文 body,两支都可达。
于是同一个 flow 被两个 shipped 面告知两件不同的事,而在作者最先遇到的那个面上,
拿到的恰是读起来像指控的那支:对一个没写过 runAs 的人说「runs as `runAs:'user'`」。
改法:删掉分支,换成一句对两种作者输入都为真的措辞 —— `runs under
\`runAs:'user'\` (the default when none is declared)`。括号是对**取值**的陈述,
不是对作者的指控,所以对确实写了 `runAs:'user'` 的人同样为真。这是本仓已有的
写法而非新发明:`flow-draft-status-ambiguous` 在同一机制下说
`has status 'draft' (the default when none is authored)`。
只有措辞变了:命中的 flow、`severity: 'error'`、`where`、`hint`、嵌套证据节点的
region 从句都不变。
测试同批把可达性钉住:parsed 形状(`FlowSchema.parse`)证明两种作者输入到规则
手上已是同一个对象;unparsed 形状(runtime gate 的输入)是唯一还能区分二者的
输入,因而是防止分支复活的那道闸。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BDmDsu2575gDxeMCxXhDE3
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
Author
|
CI 收敛记录(2026-08-06,GitHub Actions 自 ~15:14Z 起波状故障)。 门禁族:
平台签名验签(逐个):
ESLint job 的内容已在本地按其步骤单跑,全绿: ESLint 一旦拿到 runner 即为真结论;在此之前本 PR 不作绿判,记录在此供复核。 Generated by Claude Code Generated by Claude Code |
hotlong
marked this pull request as ready for review
August 6, 2026 17:20
hotlong
enabled auto-merge
August 6, 2026 17:20
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.
Fixes #5693
按分诊两轮裁定走方向 (3):一句两读皆真的措辞,无新输入、无死支。⛔ 方向 (1)(规则增读 pre-parse stack)未做;也没有需要升级到
needs_decision的开放问题 —— 理由写在「改了什么」一节里(本仓在同一机制上已有裁定过的先例)。前提复核(先证伪再实现)
issue 的核心断言成立,并且我实测出一处它没有的修正,这处修正让方向 (3) 更站得住:
runAsrunAs:'user'os validate/os buildos lintFlowSchema.runAs带.default('user'),registry 把规则接为input: 'parsed';os lint本身不 Zod-parse(runAuthoringRules('lint', { normalized: config }),authoring-rules.ts:1035无parsed时回落 normalized),照理应该逃过 —— 但defineStack/defineFlow在定义时就parse(stack.zod.ts:1245ObjectStackDefinitionSchema.safeParse后返回result.data),所以连os lint拿到的也是默认值已物化的 stack。实测确认:两条命令对「没写 runAs」的作者打的都是 explicit 支。protocol.ts:7724注释 "Keep the body verbatim",schema 检查后request.item不被 parsed 结果替换),所以那一面两支都可达。也就是说,缺陷比 issue 描述的更整齐一点:同一个 flow 被两个 shipped 面告知两件不同的事,而在作者最先遇到的那个面上,拿到的恰是读起来像指控的那支。
改了什么
删掉
typeof flow.runAs === 'string'的三元,换成一个常量RUNAS_EFFECTIVE_IDENTITY:括号是对取值的陈述,不是对作者的指控,所以对确实写了
runAs:'user'的人同样为真。这不是新发明的写法,而是本仓在同一机制上已有的做法 ——flow-draft-status-ambiguous(validate-flow-trigger-readiness.ts:480)说has status 'draft' (the default when none is authored),它的行内注释写的正是同一个原因:「authored or defaulted(defineFlowparses at definition time, so the two are the same here)」。这条先例也是不需要升级决策的理由:同一诊断措辞合同在本仓已被裁定过一次,方向 (3) 就是那次的结论,不存在两种读法导向不同架构的岔口。'user'写死而不是从flow.runAs插值,是刻意的:分支已排除'system',enum 只有这两个值,而每一个能走到这句话的面上取值都已被 schema 认可(CLI 是 parsed;runtime gate 在 gate 之前已按 overlay schemasafeParse)。插值会重新长出一条只有 off-spec 字面量才能喂到的支 —— 正是这次要删掉的那个形状。只有措辞变了:命中的 flow、
severity: 'error'、where、hint、以及嵌套证据节点的 region 从句都不变。实跑证据(照 issue 的实测方法)
examples/app-todo的overdue_escalation,只改runAs一行(测完已还原,不在本 PR diff 里):BEFORE — 作者未写
runAsBEFORE — 作者写了
runAs:'user':与上面逐字相同(这就是「区分不可达」的直接证据)。AFTER — 两种输入 × 两条命令,四次输出完全相同:
runtime publish gate 直调
runRuntimeAuthoringRules({ type: 'flow', item }),改前无runAs键走 default 支、有键走 explicit 支;改后两者同样收敛到上面这一句。三个 example app 的
os validate均通过(app-todo / app-crm / app-showcase),无新增发现。测试:把「可达性」钉住,不再让死支只被裸字面量喂绿
新增
#5693一组四个用例,并且两个形状分工不同、只有第二个有牙 —— 这点写在用例注释里,不含糊:FlowSchema.parse,production 输入):先断言parsed.runAs === 'user'对两种作者输入都成立,再断言消息逐字相同。它证明的是分支为什么无意义;分支若复活,这个用例照样绿 —— 这正是缺陷本身。AUTHORING_RULES里lintFlowPatterns的input必须是'parsed'。既有走 default 支的两个用例按新措辞更新(含 #5633 那条逐字断言的 top-level twin)。新 fixture 补齐了
FlowSchema要求的label(flow 与每个 node),否则它根本 parse 不过 —— parse 不过的 fixture 证明不了任何关于 parsed 面的事。反向验证(方向先声明后跑)。 第一次把整条三元原样放回时,四个用例连同两个老用例一起红 —— 但那一次同时撤掉了括号,证不出想证的东西,如实记在这里。于是做了隔离版:只让「CLI 不可达的那一支」不同,可达支保留新措辞。预期 parsed 绿 / unparsed 红,实测正是如此:
Changeset
带 changeset,
@objectstack/lintpatch:lint是发布包,这句话是每个os validate/os build/os lint使用者都会看到的诊断正文,措辞变更用户可见 —— 但没有 API/行为/severity 变化,所以是 patch 而非 minor。顺带改了 #5633(PR #5695)那份尚未发布的 changeset 里的样例输出:它逐字引用了旧句子,与我这份同处一个发布窗口,不改的话编译出来的 release notes 会自相矛盾地给出一句从未 ship 过的措辞。只动样例和一句「byte-identical」的措辞,不动它的任何结论。
验证
pnpm --filter @objectstack/lint test→Test Files 61 passed (61)/Tests 1457 passed (1457)pnpm --filter @objectstack/lint typecheck→ 干净eslint packages/lint/src/lint-flow-patterns{,.test}.ts→ 无输出pnpm check:nul-bytes→OK (scanned 5780 tracked text file(s) … no raw ASCII control bytes);另按逐字节自扫改动文件,无控制字节check:release-notes/check:adr-anchors/check:doc-authoring/check:engine-double-contract/check:error-code-casing全绿范围
严格限于申报文件面:⚠️ 同文件家族的 #5700(loop fixture 的
packages/lint/src/lint-flow-patterns.ts、其测试文件、.changeset/。itemVar)本轮未触碰 —— 本 PR 新增的 loop 类 fixture 一律用 canonical 的iteratorVariable。🤖 Generated with Claude Code
https://claude.ai/code/session_01BDmDsu2575gDxeMCxXhDE3