Skip to content

fix(lint,cli): os lint 不再放行另外两个命令拒绝的 flow - #4394

Merged
os-zhuang merged 2 commits into
mainfrom
claude/readonly-flow-writes-lint-drift
Jul 31, 2026
Merged

fix(lint,cli): os lint 不再放行另外两个命令拒绝的 flow#4394
os-zhuang merged 2 commits into
mainfrom
claude/readonly-flow-writes-lint-drift

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

validateReadonlyFlowWrites 手工接进了 os validateos 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 自己的头注释一直把这条规则当作该漂移的现行证据;那句话现在也改了。

规则行为零变化:id、严重级、文案都不动。

两件让这个接线从"不整洁"变成"站不住"的事

  1. validateFlowNodeWrites(feat(lint): flow update_record 写不存在字段从盲区变为 gating error (#4271) #4369)走的是同一张 config.fields map,问的是同一问题的另一半 —— "字段存在吗" vs "字段可写吗" —— 而它已经在 suite 里。一张 map、两个检查、两套命令集合。

  2. 两处手工接线连输入都不一致:validate 喂 pre-parse 的 normalized,compile 喂 post-parse 的 result.data。这是第二重漂移,之前没人注意。合并到 suite 的 post-parse 输入前,我实测了两者对本规则等价(同一 stack 两边都产出 error:flow-update-readonly-field),确认不丢 finding。

测试

suite 的活体 fixture 加了一个独立lock 节点,而不是复用 #4369stamp 节点 —— 一个节点同时带两种缺陷,会让其中一条规则的沉默藏在另一条的 finding 后面。

验证

结果
@objectstack/lint 693 passed
@objectstack/cli 629 passed
typecheck / eslint clean
showcase 端到端 见上表(合并 #4388 后复跑一致)

已合并 #4388(让 flow 规则看进 try_catch / loop / parallel 区域,改的正是本规则的实现),与本 PR 的 4 个文件零重叠,合并后全部复跑。

🤖 Generated with Claude Code

os-zhuang and others added 2 commits July 31, 2026 18:42
`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>
@vercel

vercel Bot commented Jul 31, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Jul 31, 2026 10:45am

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Jul 31, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/cli, @objectstack/lint.

20 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/skills-reference.mdx (via packages/cli)
  • content/docs/api/client-sdk.mdx (via @objectstack/cli)
  • content/docs/api/data-flow.mdx (via @objectstack/cli)
  • content/docs/api/environment-routing.mdx (via @objectstack/cli)
  • content/docs/api/error-catalog.mdx (via @objectstack/cli)
  • content/docs/automation/hook-bodies.mdx (via packages/cli, @objectstack/lint)
  • content/docs/deployment/backup-restore.mdx (via @objectstack/cli)
  • content/docs/deployment/cli.mdx (via @objectstack/cli)
  • content/docs/deployment/self-hosting.mdx (via @objectstack/cli)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/cli)
  • content/docs/kernel/runtime-services/data-service.mdx (via packages/cli)
  • content/docs/kernel/runtime-services/index.mdx (via packages/cli)
  • content/docs/permissions/authentication.mdx (via @objectstack/cli)
  • content/docs/permissions/authorization.mdx (via @objectstack/lint)
  • content/docs/plugins/index.mdx (via @objectstack/cli)
  • content/docs/plugins/packages.mdx (via @objectstack/cli)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/cli)
  • content/docs/protocol/kernel/realtime-protocol.mdx (via @objectstack/cli)
  • content/docs/releases/implementation-status.mdx (via @objectstack/cli)
  • content/docs/releases/v16.mdx (via @objectstack/cli)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@os-zhuang
os-zhuang merged commit 7fec5d6 into main Jul 31, 2026
17 checks passed
@os-zhuang
os-zhuang deleted the claude/readonly-flow-writes-lint-drift branch July 31, 2026 10:58
@os-zhuang

Copy link
Copy Markdown
Contributor Author

补一条上下文,顺便说明我这边的处理,避免重复劳动。

我在做 #4384(同一个缺陷,从 #4369/#4374 的收尾核查里发现并开的 issue)时撞上了这个 PR —— 你先合入了。我在 main 上用同一形状的 fixture 复验过,症状确已消失:

                os lint     os validate
本 PR 之后      exit 1      exit 1

所以我把自己那版重构 abort 了,只留下一条它没覆盖的东西:失败模式本身

本 PR 修掉了那一例,但没有任何东西能阻止下一条规则再被接进三个命令里的两个 —— 而这类缺陷不会有任何测试变红(每个命令自己的测试都过,规则的单测也过,唯一症状是三个命令答案不一致)。上一次就是这么从 #3425 活到今天的。

#4402 加了一条源码扫描式的接线守卫:三个命令都调 validateReferenceIntegrity、都不直接 import suite 成员(第二个调用点总是从这行 import 开始),外加一条防空转断言。做过反向验证 —— 在 compile.ts 加回直接 import 会让它失败,不是"今天能过"就算数。纯测试,不碰你的实现。

另有一点我没有#4402 里动,留给你判断:validateReadonlyFlowWrites 问的是"名字解析得到、但写会不会生效",而 suite 头注释里的判据写的是"名字能否解析"。本 PR 的理由是"它走的是同一张 map"——那是邻近性论证。判据表述要不要跟着调,是取舍,我不越俎代庖。

os-zhuang added a commit that referenced this pull request Jul 31, 2026
…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>
os-zhuang added a commit that referenced this pull request Jul 31, 2026
…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>
os-zhuang added a commit that referenced this pull request Aug 1, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant