fix(lint,cli): os lint 不再放行另外两个命令拒绝的 flow - #4394
Conversation
`validateReadonlyFlowWrites` 手工接进了 `os validate` 和 `os compile`,唯独
漏了 `os lint`。在 showcase 上植入一处违规(`runAs:'user'` 的 update_record
写静态 readonly 字段)实测:
os lint os validate
修复前 exit 0 放行 exit 1 拒绝
修复后 exit 1 拒绝 exit 1 拒绝
这条规则是 **gate** 级(静态 readonly + 字面字段是确定的 no-op:引擎从
UPDATE payload 里剥掉它,步骤照样报成功,#2948/#3425),所以漏接不是少一条
警告 —— 而是 `os lint` 放行了一个 `os validate` 会拦下的构建。
现接入 `REFERENCE_INTEGRITY_RULES`,两处手工接线随之删除,三个命令的答案
由构造保证一致,而不是靠三个人记得。这正是 suite 设立要终结的漂移
(#3583 §5 D5),而 suite 自己的头注释一直把这条规则当作该漂移的现行证据。
两件事让这个接线从"不整洁"变成"站不住":
- `validateFlowNodeWrites`(#4369)走的是**同一张** `config.fields` map,问的
是同一问题的另一半("字段存在吗" vs "字段可写吗"),而它已经在 suite 里。
一张 map、两个检查、两套命令集合。
- 两处手工接线连喂进去的输入都不一致:`validate` 喂 pre-parse 的
`normalized`,`compile` 喂 post-parse 的 `result.data`。合并到 suite 的
post-parse 输入前已实测两者对本规则等价,不丢 finding。
规则行为零变化:id、严重级、文案都不动。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…writes-lint-drift
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 2 package(s): 20 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
|
补一条上下文,顺便说明我这边的处理,避免重复劳动。 我在做 #4384(同一个缺陷,从 #4369/#4374 的收尾核查里发现并开的 issue)时撞上了这个 PR —— 你先合入了。我在 main 上用同一形状的 fixture 复验过,症状确已消失: 所以我把自己那版重构 abort 了,只留下一条它没覆盖的东西:失败模式本身。 本 PR 修掉了那一例,但没有任何东西能阻止下一条规则再被接进三个命令里的两个 —— 而这类缺陷不会有任何测试变红(每个命令自己的测试都过,规则的单测也过,唯一症状是三个命令答案不一致)。上一次就是这么从 #3425 活到今天的。 #4402 加了一条源码扫描式的接线守卫:三个命令都调 另有一点我没有在 #4402 里动,留给你判断: |
…ite (#4384) (#4402) #4394 removed the instance — `validateReadonlyFlowWrites` was hand-wired into `os validate` and `os compile` and never into `os lint`, so an `error`-level gate left `os lint` returning clean for stacks `os compile` refuses. It did not remove the failure MODE. A rule wired into two commands out of three produces no failing assertion anywhere: every command's own tests pass, the rule's unit tests pass, and the only symptom is the three commands disagreeing about the same stack. That is how the last one survived from #3425 until #4394, with the suite's own header naming it "the standing proof" of the drift the suite exists to end — a comment doing a test's job. The invariant worth pinning is `os lint` ⊇ `os compile`'s gate set: lint is the cheap pre-flight, compile is the gate, and a green lint followed by a red compile makes the pre-flight worthless — worse than worthless for an agent, whose remaining options are re-verifying everything or learning to distrust the signal. The guard asserts each command calls `validateReferenceIntegrity` and that none imports a suite member directly — the import is what a second call site starts with, so catching it closes the window between adding the import and adding the call. It scans source rather than spying, for the same reason lazy-deps.test.ts scans src: vitest inlines imports through its transform, so a module-cache probe cannot prove which symbols a command file actually reaches for. A third assertion keeps the other two from passing vacuously if the suite were emptied or its export renamed. Verified to FAIL on a reintroduced direct import in compile.ts, not merely to pass today. Test-only; no runtime or behaviour change. Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…4340 后续) (#4417) * fix(lint,cli): os lint / os compile 不再放行一个 os validate 会拒绝的 react 页面 (#4340 后续) `validateReactPageProps` 只手工接在 `os validate` 上,`os lint` 和 `os compile` 从来没跑过它。在 showcase 的 react 页面植入一处 gating 违规 (`<ListView filters={['no_such_col','=',stage]}>` —— 谓词命中不了任何行, SqlDriver 吞掉 "no such column" 后返回 [],列表回空且与「本来就没数据」无法区分) 实测: os lint os compile os validate 修复前 exit 0 放行 exit 0 放行 exit 1 拒绝 修复后 exit 1 拒绝 exit 1 拒绝 exit 1 拒绝 #4340 之后这条规则已经是整个 react 页面表面唯一的字段解析闸门:<ListView> 的 columns/fields/sort/grouping/userFilters、<ObjectForm> 的 fields/initialValues/sections/subforms、record:* 一族(与元数据表面共用同一张 COMPONENT_FIELD_SPECS)、<ObjectChart> 的 aggregate/axes,以及 searchableFields。 所以漏接不是少几条警告 —— 而是这些绑定在 build 路径上完全没人看,包括其中会 gate 的那些。 现接入 REFERENCE_INTEGRITY_RULES,`os validate` 那处手工接线随之删除,三个命令的 答案由构造保证一致。这正是 suite 设立要终结的漂移(#3583 §5 D5),也是 validateReadonlyFlowWrites 在 #4394 里刚走过的同一条路:那次是「一张 map、 两个检查、两套命令集合」,这次是「一次 JSX parse、七个 rule id、一套命令集合」。 按 charter,react-prop-missing-required / react-prop-typo 是 shape 而非解析问题, 本不属于 suite。它们随行的理由与 validateActionBodyWrites 完全相同(其注释已写明): 落在同一次 parse 的同一份 source 上,拆成第二个 member 就要把每个 react 页面 parse 两遍,而改回手工接线正是 suite 要终结的那种漂移。同时更正了那条注释里 "The ONE member here that emits two rule ids" 的说法 —— 现在不止一个。 规则行为零变化:id、严重级、文案都不动;输入也不变(`os validate` 原本就传 result.data,suite 拿到的是同一个)。#4402 的接线守卫会在下次有人想再手工接一条 规则时直接报错——本次删除 validate.ts 的直接 import 也正是被它逼出来的。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012rK2McPogrTJBKdUsThrGs * docs: validating-metadata 反映 react 页面字段绑定现在三个命令都跑 本 PR 把 react 页面的字段绑定检查从「只在 os validate」变成「suite 成员,三个命令 都跑」,这份文档有三处随之失真 —— 而 docs-drift 的包级扇出恰好没点到它(它按 @objectstack/cli / @objectstack/lint 列了 20 份,漏了语义上唯一真正耦合的这份)。 - "one gate, two entry points" 表补一行 React page block field bindings(两列都 ✓)。 - 表下那句把 "JSX/React page sources" 整体算作 os validate 独有 —— 现在只有 「源码能否 parse」这个 SHAPE 闸门是独有的,里面的字段绑定属于 reference-integrity, suite 跑到哪它就跑到哪(含 os lint)。改写为分开陈述,并记下这里曾经的漂移与 #4384 的接线守卫。 - 示例 os validate 输出里的 `→ Checking React-source page props (ADR-0081)...` 随手工接线一并删除,已按真实 CLI 输出核对:示例里的每一行 step 现在都能在 showcase 上逐字复现(comm 比对为空)。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012rK2McPogrTJBKdUsThrGs --------- Co-authored-by: Claude <noreply@anthropic.com>
…ands (#4409) (#4445) * fix(cli): every author-time rule that can gate runs on all three commands (#4409) `os validate`, `os build` and `os lint` each hand-wired their own subset of the author-time rules. Nothing connected the three lists, so "which rules run here?" was answerable only by diffing three 800-line files by eye — and the answer drifted every time a rule landed. The audit found 23 of 26 rules running on some strict subset, nine of them able to emit `error`. The worst direction was the least obvious: `os build` — the command that PUBLISHES — was the weakest gate of the three. A flow whose expression approver does not parse (`approval-expression-invalid`) built and published green; only `os lint` stopped it, and CI usually runs the other two. This is the same failure mode's fifth appearance (#3583, #3782, #4384/#4394, #4402). Each earlier repair removed an instance and left the MODE: a rule's command coverage was whatever its author remembered to type, and forgetting was silent. #4402's guard could not catch the rest — it filtered on the current member names of one suite, so a rule hand-wired into two commands from outside that suite passed it without a word. Replace remembering with a table: - `packages/cli/src/lint/authoring-rules.ts` declares all 26 rules as data — tier (gating/advisory), which stack tier they read (pre-parse vs parsed), which commands run them, and a written reason for the one narrowing. All three commands consume it through `runAuthoringRules()`; the three command files shrink by ~1000 lines between them. - `authoring-rule-wiring.test.ts` upgrades the guard from a name list to a ratchet: a gating rule on fewer than three commands fails, a narrowed rule with no reason fails, a command that calls a rule directly fails, and an `advisory` claim is checked against the rule's own source so a gate cannot wear an advisory label to buy partial coverage. Remaining direct calls are listed with reasons in `DIRECT_CALL_RATCHET` / `LINT_IMPORT_RATCHET`. - `authoring-rule-command-parity.test.ts` proves the verdict, not just the wiring: one case per previously-blind gating rule, plus the issue's own repro driven through the real CLI — exit 1 on all three commands where it was 1/0/0 before. Two things fall out of one report per run rather than exiting at the first failing gate: an author with three unrelated problems sees all three, and `--strict` now covers every advisory instead of the roughly half that happened to be printed inline. Also closes the same hole one gate over: `collectAndLintDocs` failed `os build` and never ran on `os validate`, invisible because the parity guard keyed on the `lint*`/`validate*` naming convention and that gate is called `collect*`. The guard now names each shared non-registry gate explicitly. Cost is not what argued against this: the heavy deps (typescript, sucrase) are already lazy, and the heaviest rule of the set has run on all three commands as a suite member since #4340 without anyone noticing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sz61GE1CWCSnGW4qShSEXs * chore(changeset): author-time rule command-coverage registry (#4409) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sz61GE1CWCSnGW4qShSEXs * docs: name the ADR-0085 pointers by field, not by the reserved word (#4409) The role-word ratchet (ADR-0090 D3) counts occurrences per file, and two doc lines describing `validateSemanticRoles` reintroduced the banned word for a non-permission concept. Name what the rule actually checks instead: `stageField` / `highlightFields` pointers. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sz61GE1CWCSnGW4qShSEXs --------- Co-authored-by: Claude <noreply@anthropic.com>
validateReadonlyFlowWrites手工接进了os validate和os compile,唯独漏了os lint。实测(showcase 植入一处违规:
runAs:'user'的 update_record 写静态 readonly 字段)os lintos validate这条规则是 gate 级(静态
readonly+ 字面字段是确定的 no-op:引擎把它从 UPDATE payload 剥掉,步骤照样报成功,#2948/#3425),所以漏接不是"少一条警告",而是os lint放行了一个os validate会拦下的构建。改动
接入
REFERENCE_INTEGRITY_RULES,两处手工接线随之删除 —— 三个命令的答案由构造保证一致,而不是靠三个人记得。这正是 suite 设立要终结的漂移(#3583 §5 D5),而 suite 自己的头注释一直把这条规则当作该漂移的现行证据;那句话现在也改了。规则行为零变化:id、严重级、文案都不动。
两件让这个接线从"不整洁"变成"站不住"的事
validateFlowNodeWrites(feat(lint): flow update_record 写不存在字段从盲区变为 gating error (#4271) #4369)走的是同一张config.fieldsmap,问的是同一问题的另一半 —— "字段存在吗" vs "字段可写吗" —— 而它已经在 suite 里。一张 map、两个检查、两套命令集合。两处手工接线连输入都不一致:
validate喂 pre-parse 的normalized,compile喂 post-parse 的result.data。这是第二重漂移,之前没人注意。合并到 suite 的 post-parse 输入前,我实测了两者对本规则等价(同一 stack 两边都产出error:flow-update-readonly-field),确认不丢 finding。测试
suite 的活体 fixture 加了一个独立的
lock节点,而不是复用 #4369 的stamp节点 —— 一个节点同时带两种缺陷,会让其中一条规则的沉默藏在另一条的 finding 后面。验证
@objectstack/lint@objectstack/cli已合并 #4388(让 flow 规则看进 try_catch / loop / parallel 区域,改的正是本规则的实现),与本 PR 的 4 个文件零重叠,合并后全部复跑。
🤖 Generated with Claude Code