Skip to content

fix(lint): 删除 validateOrgAxisRedLines 里 spec 合法 stack 到不了的四条分支 (#5009) - #5018

Merged
xuyushun441-sys merged 1 commit into
mainfrom
claude/issue-5009-org-axis-dead-branches
Aug 4, 2026
Merged

fix(lint): 删除 validateOrgAxisRedLines 里 spec 合法 stack 到不了的四条分支 (#5009)#5018
xuyushun441-sys merged 1 commit into
mainfrom
claude/issue-5009-org-axis-dead-branches

Conversation

@xuyushun441-sys

Copy link
Copy Markdown
Contributor

Fixes#5009

validate-org-axis-red-lines.ts 注册为 input: 'parsed' —— 它看到的是 ObjectStackSchema 解析后的产物。#4984 修掉了 sharing rule 字段那一层的 ?? 别名读法,同一文件里还留着四条同形分支(议题点名三条,核对时又发现第四条),读的键 spec 都不声明。

逐条核实与处置

每一条都用真实 schema 的 .shapesafeParse 实测核过,不是靠读注释。

原读法spec 事实(实测)处置
cfg.permissions ?? cfg.permissionSetsObjectStackSchema.shapepermissionSets。stack 根 strip 未声明键:safeParse({ manifest, permissionSets: [...] })成功,但返回的 data 里没有该键收敛为 cfg.permissions
cfg.sharingRules ?? cfg.sharing(两处)同上,sharing 也被 strip收敛为 cfg.sharingRules
str(rule.object ?? rule.objectName)(议题未列,同形第四条)SharingRuleSchema.strict(),按名拒绝:Unrecognized key(s) on this sharing rule: objectName``;而 object必填,解析过的规则上不可能缺收敛为 str(rule.object)
asArray(object.rowLevelSecurity ?? object.rls)整段(约 20 行)ObjectSchema.shape 两个键都没有,且 ObjectSchema.strict() —— 带对象级 RLS 的 stack 在 os validate / os build整包拒绝:Unrecognized key(s) on this object: rowLevelSecurity``删除

对象级 RLS 从来不是可授权面:authorable-surface.json 里唯一一条是 security/PermissionSet:rowLevelSecurity。删除依据(schema 名 + 该键不存在的证明)已写进 commit message 和规则自己的 ## Scope 段。

危险不在死代码,在误导

这四条对任何 spec 合法 stack 都不执行,所以漏报为零。真正的代价是:那段死代码连 objects[N].rowLevelSecurity[M].using 的诊断 path 都写好了,足以让下一位作者(人或 AI)相信对象级 RLS 是真实授权面并照着写更多代码 —— #5008 的 dev 差点就照它复制了一条同样惰性的遍历。

别名容忍属于 producer 的拒绝,不属于 consumer(Prime Directive #12)。它还把一条指名道姓的 schema 拒绝,悄悄换成了一条永不触发的 gate。

反向验证

  • 删除的分支:把新测试跑在改动前的实现上 —— 29 条由 safeParse fixture 驱动的断言全绿,8 条转红的全部是 (a) 扫源码的 meta-guard 或 (b) 喂非 spec 合法 stack 的新钉子测试。即:删除前后对全部 spec 合法 fixture 行为完全一致,它本来就死。
  • canonical 化的三条:这里要如实说明,与议题预设的 "before 判绿 / after 判红" 不同 —— canonical 键本来就是 ?? 的第一顺位,spec 合法的违规 stack 改动前已经判红。别名 limb 只是额外在 schema 会拒绝的形状上可达。所以正确的反向验证是反过来的:改动前对 permissionSets / objectName 拼法的非法 stack 会判红,改动后判绿,并由钉子测试证明 schema 才是那道门。(validate-expressions.ts:205 那条就没这么幸运 —— canonical 排第三顺位,见 validate-expressions / validate-security-posture 也有同形的 spec 不声明键的 ?? 别名读法(#5009 建议 3 的核对结果) #5017。)
  • 变异验证:四条分支各自加回去跑一遍,每条都至少让两条测试转红。

meta-guard 全覆盖(#4992 模式)

留下的每条遍历分支都要有过 safeParse 的 fixture 能走到;走不到的不允许存在。

  1. declared-key guard —— 规则源码里从 stack / permission set / RLS policy / object / sharing rule 上读的每一个键,必须出现在对应 schema 自己的 Zod .shape 里。扫源码而不是扫行为是刻意的:不可达分支根本没有行为可断言,这正是问题本身。计算键(policy[clause])另用一条断言把 ['using', 'check'] 钉在 RowLevelSecurityPolicySchema 上。
  2. reachability guard(议题提到的"覆盖率式结构性断言",本 PR 落地了)—— 从源码里抽出每个 findings.push 调用点的 rule:path: 模板,断言现存三个点各自被至少一条过 safeParse 的 fixture 触达;并反向断言没有 fixture 产出源码未声明的 path。加回删掉的第四个点会同时触发数量断言和可达性断言。
  3. 规则 ① 的 fixture 现在也走 PermissionSetSchema.safeParse(此前只有 sharing rule 和 object fixture 有这层保护)—— 顺带暴露出旧 fixture 缺 objects / operation,并不 spec 合法。

真实元数据零新红

  • examples/default-permission-sets.ts没有parent_organization_id,规则 ① 无从触发;
  • examples 里唯一的 rowLevelSecurity 用法在 permission set 上(app-showcase/src/security/permission-sets.ts:57),正是保留的那条分支;
  • 出现的 sharing: 都在 view 定义内(inquiry.view.ts / lead.view.ts),不是 stack 根键,本规则从不读它。

改动纯粹是减法,findings 集合单调不增,不可能新增红。

顺带核对(不在本 PR)

议题建议 3 要求核对邻居规则。validate-expressions.ts(三处)与 validate-security-posture.ts(两处)有同形别名读法,其中 validate-expressions.ts:205/421 更糟 —— canonical 的 condition 排在两个被拒别名之后。已按 Prime Directive #10 记为 #5017(未认领),不在本 PR 修。

验证

pnpm --filter @objectstack/lint test → 55 files, 1029 passed | 4 skipped
pnpm --filter @objectstack/lint typecheck → tsc --noEmit, clean
npx eslint <两个改动文件> → clean

Changeset:.changeset/org-axis-dead-alias-branches.md(@objectstack/lint patch)。改动限于 packages/lint


Generated by Claude Code

该规则注册为 `input: 'parsed'`,看到的是 `ObjectStackSchema` 解析后的产物。
#4984 修掉了 sharing rule 字段层的 `??` 别名读法,同一文件里还留着四条同形
分支,读的键 spec 都不声明 —— 逐条对着 schema 的 `.shape` 与 `safeParse`
实测核过:
- `cfg.permissions ?? cfg.permissionSets` → `cfg.permissions`。
`ObjectStackSchema.shape` 无 `permissionSets`;stack 根 strip 未声明键,
实测 `safeParse({ manifest, permissionSets: [...] })` 成功但 `data` 里
没有该键 —— 规则看到 stack 之前它已经不存在。
- `cfg.sharingRules ?? cfg.sharing`(两处)→ `cfg.sharingRules`。同上。
- `str(rule.object ?? rule.objectName)` → `str(rule.object)`。
`SharingRuleSchema` 是 `.strict()`,`objectName` 被按名拒绝
("Unrecognized key(s) on this sharing rule: `objectName`");`object`
又是必填,解析过的规则上不可能缺。
- `asArray(object.rowLevelSecurity ?? object.rls)` 整段遍历(约 20 行)
**删除**。依据:`ObjectSchema.shape` 两个键都没有(实测键表里只有
`sharingModel` / `access` / `tenancy` 等,无 `rowLevelSecurity`、无 `rls`),
且 `ObjectSchema` 是 `.strict()` —— 带对象级 RLS 的 stack 在
`os validate` / `os build` 被整包拒绝,报 "Unrecognized key(s) on this
object: `rowLevelSecurity`"。对象级 RLS 从来不是可授权面
(`authorable-surface.json` 里只有 `security/PermissionSet:rowLevelSecurity`)。
对任何 spec 合法的 stack,判定结果不变 —— 反向验证:新测试跑在改动前的
实现上,29 条由 `safeParse` fixture 驱动的断言全绿,8 条转红的全部是
(a) 扫源码的 meta-guard,或 (b) 喂非 spec 合法 stack 的新钉子测试。
代价从来不是漏报,是误导:那段死代码连 `objects[N].rowLevelSecurity[M].using`
的诊断 path 都写好了,足以让下一位作者相信对象级 RLS 是真实授权面并照着写
(#5008 差点如此)。别名容忍属于 producer 的拒绝,不属于 consumer(Prime
Directive #12)。
meta-guard(#4992 模式),让下一条死分支在 review 前就红:
- declared-key guard:规则源码里从 stack / permission set / RLS policy /
object / sharing rule 上读的每个键,必须出现在对应 schema 自己的 `.shape`
里。扫源码而非行为是刻意的 —— 不可达分支没有行为可断言。
- reachability guard:每个 `findings.push` 调用点都必须被至少一条过
`safeParse` 的 fixture 触达(现存三个点,全覆盖)。
- 规则 ① 的 fixture 现在也走 `PermissionSetSchema.safeParse`。
四条分支各自做过变异验证:加回任意一条,至少两条测试转红。
真实元数据零新红:examples/ 与 default-permission-sets 中无
`parent_organization_id`,唯一的 `rowLevelSecurity` 用法在 permission set
(保留的那条分支)上,`sharing:` 出现在 view 定义内而非 stack 根。
邻居规则同形别名读法已另行记账为 #5017(未认领),不在本 PR 范围。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018iARDqtrhQgz6fVHDeDkbQ
@vercel

vercelBot commented Aug 3, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectstackIgnoredIgnoredAug 3, 2026 8:42pm

Request Review

@github-actionsgithub-actionsBot added size/l documentation Improvements or additions to documentation tests tooling labels Aug 3, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/lint.

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

  • content/docs/automation/hook-bodies.mdx(via @objectstack/lint)
  • content/docs/permissions/authorization.mdx(via @objectstack/lint)
  • content/docs/releases/v17.mdx(via @objectstack/lint)

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.

@xuyushun441-sys
xuyushun441-sys marked this pull request as ready for review August 3, 2026 20:44
@xuyushun441-sys
xuyushun441-sys added this pull request to the merge queueAug 3, 2026
Merged via the queue into main with commit b821b29Aug 4, 2026
24 checks passed
@xuyushun441-sys
xuyushun441-sys deleted the claude/issue-5009-org-axis-dead-branches branch August 4, 2026 00:04
os-zhuang pushed a commit that referenced this pull request Aug 8, 2026
…ger (#6320)
The #6148 gate asks a declared-breaking changeset what its ledger disposition
is. Answered as `registered` rather than exempted, and the three mechanical
categories are all genuinely closed to this changeset:
- `unpublished` -- @objectstack/driver-sql and @objectstack/driver-sqlite-wasm
are both published (private: false), so it fails outright.
- `already-registered` -- no existing id covers this surface.
- `no-migration-prescription` -- the changeset body carries a worked FROM -> TO
migration section, so the gate refuses this category by
construction. Using it would be a dodge, not an answer.
The entry says up front that it records a TYPE being added rather than a surface
being withdrawn, because that distinction decides who has to do anything. It
renders into the upgrade guide's "Semantic (delegated to you)" section, carries
no `migrate meta` implication and names no stored field -- the same disposition
four existing code-surface entries already carry: data-driver-find-stream-retired
(#4484), storage-service-list-retired (#5540), actor-user-roles-to-positions
(#6011), driver-aggregate-undeclared-key-aliases-removed (#6321).
The reason an entry is owed at all: the narrowing is compile-time only, so an
untyped JS caller gets neither an error nor a behaviour change -- and that caller
is exactly the one sitting on the silent-widening defect, before and after. The
generated upgrade guide is the only channel that reaches them.
Also moves both runtime pins in the driver-sql suite onto inline argument
positions. A `const envelope: FilterCondition = ...` proves only that the alias
admits the shape; the claim being pinned is that it reaches THIS PARAMETER
uncast, which only an argument position can show (#5018 / #4984 dead-pin shape).
Claude-Session: https://claude.ai/code/session_01WyvqvKMG6asi9aXjKE6xtx
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/lteststooling

Projects

None yet

2 participants

@xuyushun441-sys@claude