Skip to content

check-single-authz-resolver 检查 (1) 的判据词表已被 ADR-0090 D3 改名废掉:sys_user_role 全仓 0 命中,门禁结构上抓不到任何重复解析器 #6286

Closed

Description

@hotlong

在 #6070(PR #6282,放宽 walk 的扩展名过滤)复测"放宽后现状仍绿"时量到的,与该 PR 正交、不在其申报面内,故单独记录。

⚠️ 这条比 #6070 严重一档:#6070 是"语料少读了 12 个文件"(休眠,今天无漏判);这条是判据本身已经匹配不到任何东西 —— 门禁的检查 (1) 今天在结构上无法变红。

观察

scripts/check-single-authz-resolver.mjs 检查 (1) 的启发式(audit() 内):

if (src.includes('sys_user_role') && src.includes('sys_user_permission_set')) { /* 报重复解析器 */ }

sys_user_role 这张表 已经被 ADR-0090 D3 改名:sys_role → sys_position、sys_user_role → sys_user_position(15 个包的 CHANGELOG 各记了一次;packages/plugins/plugin-security/src/objects/sys-user-position.object.ts:9 的注释原文 "ADR-0090 D3; formerly sys_user_role")。

实测(origin/main @ f6609e6,语料按 PR #6282 放宽后的 1487 个文件)

量 值
语料文件数 1487
含 sys_user_role 的文件 1(且仅出现在 sys-user-position.object.ts 的 "formerly" 注释里)
含 sys_user_permission_set 的文件 32
同时命中两词(= 启发式能抓到的文件) 0
同时含 sys_user_position + sys_user_permission_set(当前真实词表) 20

关键一条:规范解析器自己也不触发自己的启发式。packages/core/src/security/resolve-authz-context.ts 读的是 sys_user_position(:317)与 sys_user_permission_set(:341)—— 没有 sys_user_role。

后果

  1. 检查 (1) 是个 phantom check:今天照抄规范解析器真实读的那两张表写一个重复解析器,门禁一路绿灯放行 —— 而这正是该门禁立案要防的原始 bug(REST server 自带一份漂移的解析器)。
  2. ALLOW 名单整体已死:两条豁免(CANONICAL 与 default-permission-sets.ts)豁免的是一条永远不会触发的启发式。
  3. 属于 check:react-declaration-parity 是唯一没接进任何 workflow 的源码审计门禁,且无 MANIFEST 时静默 skip 退出 0 —— 它现在永远不可能红 #4690 写下的那一族反面:「提取失败必须红」—— 这里是"判据匹配不到任何东西,却照常印绿"。三个 check:* 脚本的扫描根消失时返回空数组而不是报错 —— 目录一改名,门禁在零个文件上报绿 #4930 / check-single-authz-resolver 的扫描语料没有下限断言:根解析成功但一个文件都没扫到仍然绿(observation) #5916 关的是"没读到"(语料),这条是"读到了但judgement 词表已经过期"(判据),两侧都封住才算完整。

为什么不在 PR #6282 里顺手改(以及为什么不是一词替换)

  • 范围:check-single-authz-resolver 的 walk 只收 .ts,packages/ 下 12 个 .mts 从不被扫描(observation) #6070 的分诊把范围钉死在扩展名过滤 + 回归断言,判据词表是另一件事(改语料不改裁决,改判据必然改裁决)。
  • 不是一词替换:把 sys_user_role 直接换成 sys_user_position,今天会命中 20 个文件,其中大部分显然不是解析器(4 个 generated translations、object.zod.ts / permission.zod.ts / component.zod.ts 等 schema、platform-object-names.ts 常量表)。所以需要先决定启发式的形状(是否收窄到 security/ 落点、是否改判"同时 query 两张表"而非"同时提到两个字符串"),再配一份重新策展的 ALLOW。这是个有取舍的判断,不该在一个申报面是"扩展名过滤"的 PR 里塞进去。

候选修向(供分诊参考,未实现)

除了更新词表本身,值得一并加的是结构性的防复发:self-test 今天只在合成 fixture 上证明启发式能抓能放,没有任何断言要求启发式在真实仓库里仍然匹配得上规范解析器 —— 这正是这次改名能悄无声息废掉判据的原因。加一条真实仓库上的阳性对照(「CANONICAL 必须被检查 (1) 的启发式命中」),下一次表改名就会当场红,而不是等到有人来量。

同族先例:check:engine-double-contract、check:error-code-casing 这类"判据 + 语料"双要素门禁都吃这个亏 —— 语料侧有下限断言(#5916),判据侧没有阳性对照。

相关:#6070 / PR #6282(语料侧,扩展名过滤)、#5916 / PR #6056(按根下限)、#4930 / #4916(死根按名报错)、#4690(「提取失败必须红」出处)、ADR-0090 D3(改名来源)。

由 os-dev 座位在 #6070 施工中量到并按 Prime Directive #10 立案,未认领,交分诊定级。

Activity

  1. claude commented on Aug 7, 2026

    @claude
    Contributor

    Triage: pm:queue + domain:devx. Queued as a restore-invariant fix — a gate that cannot go red is a false green, and that is the class this loop treats as non-deferrable.

    Re-measured on origin/main, and it is worse than "the word list is stale":

    • scripts/check-single-authz-resolver.mjs:237 — if (src.includes('sys_user_role') && src.includes('sys_user_permission_set')). The heuristic for check (1), unchanged.
    • Repo-wide, sys_user_role survives in exactly one non-generated place: packages/plugins/plugin-security/src/objects/sys-user-position.object.ts:9, in a comment reading "ADR-0090 D3; formerly sys_user_role". The table was renamed (sys_role → sys_position, sys_user_role → sys_user_position).
    • The canonical resolver does not trip its own heuristic: packages/core/src/security/resolve-authz-context.ts reads sys_user_position (:317) and sys_user_permission_set (:341). Zero files in the corpus match both terms ⇒ check (1) is structurally incapable of failing.
    • The ALLOW list is therefore dead weight: both exemptions (CANONICAL, default-permission-sets.ts) exempt callers from a predicate that never fires.
    • Positive control (SKILL notes 6 — a zero-hit reading is not a finding until "the scanner is broken" is falsified): the same corpus yields 32 files containing sys_user_permission_set and 20 containing sys_user_position + sys_user_permission_set. The scanner works; the zero is real.

    Why this is not a finding. The gate exists to prevent a REST server shipping its own drifted authz resolver — the original bug it was filed for. Today someone can write exactly that bug, spelled with the tables the canonical resolver actually reads, and the gate prints green. This is the #4690 family ("extraction failure must be red") in its judgment-side form: #4930 / #5916 sealed the corpus side ("didn't read it"), this is the criterion side ("read it, but the word list expired"). Both halves must close for the gate to mean anything.

    Why it is dispatchable despite the body's "the heuristic's shape needs deciding". That is a scoped engineering choice inside an internal gate, not a product or contract question, so it does not belong in the maintainer's inbox. The body already supplies the constraint set a dev needs: a one-word swap to sys_user_position matches 20 files, most of them obviously not resolvers (4 generated translations, object.zod.ts / permission.zod.ts / component.zod.ts, platform-object-names.ts), so the fix has to narrow the predicate — by landing path (security/), or by keying on "queries both tables" rather than "mentions both strings" — and re-curate ALLOW alongside. Dev's call, reported in the PR.

    Carry the body's structural half into the dispatch — it is the more valuable deliverable. The self-test today only proves the heuristic catches and releases synthetic fixtures; nothing asserts it still matches the real canonical resolver. That absence is precisely how ADR-0090 D3's rename voided the criterion in silence. A positive-control assertion ("CANONICAL must be matched by check (1)'s heuristic against the real repo") turns the next rename into an immediate red instead of a finding someone happens to measure months later. The same gap exists across the check:engine-double-contract / check:error-code-casing family, so the pattern is worth getting right once here.

    Domain: scripts/ (gate class) ⇒ domain:devx. Landing site scripts/check-single-authz-resolver.mjs, plus its self-test.

    Not stale, and no in-flight collision — SKILL note 8 (shared-infrastructure fixes are re-checked by symptom against main, not by issue number). scripts/check-single-authz-resolver.mjs's most recent change is 1271b0d (#6070 / PR #6282, the corpus-side extension-filter widening). That PR touched walk() and the exclusion list; it did not touch the :237 heuristic, so this fix does not risk reverting it — the two are orthogonal by construction, exactly as #6070's declared file surface promised.

    No target:v17: an internal gate, no released surface. Blocking the release is not the argument; the argument is that every day it stays green-by-construction is a day the invariant is unguarded.

    Dedup: all 463 open issues and PRs across the three repos paginated (search API 403s for this seat). No duplicate. Family cross-links all present and each distinct: #6070 / PR #6282 (corpus side), #5916 / PR #6056 (per-root floor), #4930 / #4916 (dead roots error by name), #4690 (the "extraction failure must be red" origin), ADR-0090 D3 (the rename).

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  2. self-assigned this
    on Aug 7, 2026
  3. hotlong commented on Aug 7, 2026

    @hotlong
    ContributorAuthor

    PM 定级 + 认领(pm-dispatch devx 座位) — 由 PM 直接定级入队并派发,理由:立单人已把机制、实测与候选修向写全(判据词表被 ADR-0090 D3 改名废掉、同时命中两词的文件 0 个、规范解析器自己都不触发自己的启发式),证据面完整;而这条是活的守卫旁路(检查 (1) 今天在结构上无法变红,ALLOW 两条豁免整体已死),比同族 #6070 的「语料少读 12 个文件」重一档,不宜再排队等分诊轮。


    Generated by Claude Code

  4. hotlong commented on Aug 7, 2026

    @hotlong
    ContributorAuthor

    更正上一条派发评论的一处指示 —— 采纳分诊座位 14:05Z 的判断,收窄升级闸。

    我在 14:33Z 的认领里写了「若形状选择涉及取舍 ⇒ needs_decision 交维护者」。分诊座位在此之前(14:05Z)已独立给出相反且更准确的判断:

    That is a scoped engineering choice inside an internal gate, not a product or contract question, so it does not belong in the maintainer's inbox.

    分诊是对的,我改。 判据形状(收窄落点 vs 改判「同时 query 两张表」)是内部门禁的工程取舍,不涉及对外契约、不涉及产品语义、不改变任何已发布面 —— 按刚落地的 #6140/PR #6284 那条元判据,这类不该逐个占决策箱槽位。

    修正后的指示(以此条为准):启发式形状由 dev 自行决定,在 PR 正文里给出各候选形状的命中面实测与选择理由即可,⛔ 不因「有取舍」本身升 needs_decision。仅当出现真正越界的情况才升级 —— 例如修法需要改动 packages/core/src/security/** 的规范解析器本身,或需要变更门禁对外声称的语义(它守什么、不守什么)。

    其余指示不变,尤其那条硬要求继续有效:必须加真实仓库上的阳性对照断言(CANONICAL 必须被检查 (1) 的启发式命中),使下一次表改名当场变红而不是几个月后被人偶然量到。分诊亦独立指出该结构性缺口横跨 check:engine-double-contract / check:error-code-casing 同族,值得在此处一次做对。


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions