fix(lark): 权限自愈只申请缺失的 scope,不再全量导入完整清单 - #1042
Conversation
checkRequiredScopes 用「缺失项」当触发器,但 tryAutoFixScopes 调 automateOpenPlatformSetup 时没传 scopeManifest,导致 automation 回退到 readDefaultScopeManifest() 的整份清单(300+ 项),把日历/文档/表格等 botmux 自己都不校验的权限一并 operation:'add' 申请进去——补一个 im:feed_group_v1:read 会连带申请一大批无关权限,还伴随一次发版。 改动: - open-platform-automation 新增纯函数 filterScopeManifest:按名字裁剪 清单,tenant/user 分桶归属沿用 manifest(不自己猜 bucket),并导出 readDefaultScopeManifest 供调用方复用。 - tryAutoFixScopes 用 missingCritical+missingOptional 的名字裁出精简 manifest 再传给 automation,申请集合与缺失集合一致。 - 99991672「鸡生蛋」路径改为传完整 BOTMUX_REQUIRED_SCOPES(而非仅 self_manage),保留「一次补齐、下次重启即自检通过」的原意,同样不 再过度申请。 影响面:仅 im/lark 的启动权限自检链路(feishu)。VC 事件自动订阅 (ensureVcMeetingEventsSubscribed)与 setup/onboarding 建应用链路不传 scopeManifest、行为不变。scope/update 仍是 operation:'add',不会删除 应用已有权限。 验证:bun run build 通过;bun run test 中 test/scope-optional-autofix.test.ts(14)、 test/setup-open-platform-automation.test.ts(90,含新增 filterScopeManifest 5 例)全绿。仓库其余失败用例在本机 clean 工作树上同样失败(/private/var realpath、loopback 绑定、FNM symlink 轮转等环境相关),与本改动无关。
自动评审初步意见(首审)先说结论:方向和实现我认为是对的,问题定位(「用缺失项当触发器,却拿完整清单当申请集合」)和修法都很准确,尤其是 复核过的部分(都实测跑过,不是推理)
🟡 一条合前建议:
|
scopeCount |
skippedScopeCount |
实际打出的文案 | |
|---|---|---|---|
| master(全量 manifest) | 5 | 296 | 5 项权限已导入(296 项跳过) |
| 本 PR(精简 manifest) | 0 | 2 | 所有必需权限已在应用清单中 |
即恰恰在「想补的那两项一项都没补上」时,日志反而报「已齐全」,并且照样发了一个新版本。建议在 skippedScopeCount > 0 时改说成「N 项在租户目录中不可授予」之类,别让它落到「已齐全」那句。
补充两点让优先级更清楚:① 这条路径是 silent: true,只进日志不发 DM,所以影响是「排查时被日志误导」而非「误告知管理员」;② master 在同场景下文案也不算准确(5 项已导入(296 项跳过) 同样看不出目标那两项没成),所以这不是你引入的新缺陷,只是裁剪让它更容易被踩到。如果你倾向于保持本 PR 改动面最小、把它留到单独 PR,我也认为完全合理——只是希望它别被忘掉。
顺带一个观察(不属于本 PR,无需处理)
readDefaultScopeManifest() 在编译态(bun --compile 单文件二进制)会抛 找不到 botmux lark-scopes.json——/$bunfs 下没有这个 JSON。我实测确认master 同样会抛,而且本 PR 因为把 manifest 解析提到了任何网络写操作之前,抛得更早(writesBefore=[],master 那时已经写过一次 safe_setting/update)。所以这是既有缺陷,本 PR 反而略微改善,列在这里只是留个记录。
以上是自动评审的初步意见,最终以维护者审阅为准。感谢这个 PR——「300+ 项过度申请 + 顺带发版」这个问题定位得很到位。
复审收敛 + 更正我上一条评论里的一处错误复审已完成,双审一致:无阻断项,方向和实现都对。但我要先更正自己上一条评论没写、却在评审内部判错的一点,因为它把风险方向说反了: 更正:不存在「99991672 路径从 1 项扩大到 17 项」这回事。 复核确认的其余部分
两条非阻断建议(都可留作后续 PR,不必卡这个 PR)
另外顺带修正一处我们复核中的表述以免误导: 以上仍为自动评审意见,最终以维护者审阅为准。就我们两轮复核而言,这个 PR 无需改动即可合并,上面两条留作后续即可。再次感谢,这个问题定位得很准。 |
已按维护者授权直接在本分支补上两条建议(
|
| 成因 | 判据 | 文案 |
|---|---|---|
| 开放平台整批拒绝 | scopeWarning 有值 |
0 项权限已导入——开放平台拒绝了本次权限申请(…) |
| 不在该租户权限目录 | skippedScopeCount > 0 |
0 项权限已导入——这 N 项不在当前租户的开放平台权限目录中 |
| 确实无事可做 | 两者皆无 | 所有必需权限已在应用清单中(保留原文案) |
同时新增 autoFixEffective:一项都没落地时日志降为 warn 且不再说 succeeded,管理员 DM 的标题也从「✅ 已自动修复了缺失的权限」改成「
② 补 99991672 分支的测试断言
这处改用完整 BOTMUX_REQUIRED_SCOPES 之前零覆盖。补断言时也把「为什么这次必须传对」写进了注释:过去传 self_manage 一项只喂日志/DM 文案、不参与请求集合,现在申请集合由这份名单裁出,传错就会让下次重启的自检依然缺权限。
③ 额外补了一条行为测试(不只是源码文本断言)
真实跑 automation 取回 scopeCount / skippedScopeCount / scopeWarning 三元组,验证那三种成因确实可区分——否则调用方无论怎么写文案都只能靠猜。四个分支:成功落地 {2,0,false}、不在目录 {0,2,false}、被拒 {0,0,true}、无事可做 {0,0,false}。
验证
tsc --noEmit0 错;bun run build绿。test/scope-optional-autofix.test.ts18 例全绿;相关三文件 134/135,唯一失败是collectBotmuxRedirectUrls那条环境基线用例(本机 tunnel URL 注入,未改动的 master 上同断言同样失败)。- 四种成因逐一执行真实文案逻辑确认输出正确(不是读代码推断):
(1) applied | INFO / ✅ 已自动修复 | 2 项权限已导入
(2) not-in-catalog | WARN / ⚠️ 没能申请成功 | 0 项权限已导入——这 2 项不在当前租户的开放平台权限目录中…
(3) rejected | WARN / ⚠️ 没能申请成功 | 0 项权限已导入——开放平台拒绝了本次权限申请(code=1 …)
(4) nothing-missing | INFO / ✅ 已自动修复 | 所有必需权限已在应用清单中
- 反向变异确认新测试有牙:还原旧的单条三元文案 → 2 例变红;把 99991672 改回只传
self_manage→ 1 例变红。
接下来会再走一轮复审,通过后由维护者合并。如果你对以上任何一处改法有不同看法,直接说,我改回去或按你的想法调整都可以——毕竟是你的 PR。
裁剪申请集合后 scopeCount 的语义变了:从「整份清单导入了多少」变成「缺失 的那几项里成功了多少」。于是 0 不再等于「本来就齐」,最常见的成因反而是 「一项都没补上」——而文案仍是单条三元表达式,恰在全部失败时输出「所有必需 权限已在应用清单中」。这句同时进 daemon 日志和管理员 DM,DM 开头还写着 「✅ 已自动修复了缺失的权限」,自相矛盾,管理员会据此以为不用管;而走到这条 路径的下游(缺 critical scope)恰恰最需要人工介入。 改动: - 按三种互相矛盾的成因分开措辞:scopeWarning 有值 = 开放平台整批拒绝; skippedScopeCount > 0 = 这些名字不在该租户的权限目录中、不可授予;两者 皆无才是真的「所有必需权限已在应用清单中」。 - 新增 autoFixEffective:一项都没落地时日志降为 warn 且不再说 succeeded, 管理员 DM 标题改为「没能申请成功,需要你手动开通」。 - 补 99991672「鸡生蛋」分支的测试断言。该分支改用完整 BOTMUX_REQUIRED_SCOPES 之前没有任何覆盖:过去传 self_manage 一项只喂日志/DM 文案、不参与请求集合, 现在申请集合由这份名单裁出,传错就会让下次重启的自检依然缺权限。 - 新增行为测试验证三种 scopeCount===0 的成因在 automation 结果里确实可区分 (真实跑 automation 取 scopeCount/skippedScopeCount/scopeWarning 三元组), 否则调用方无论怎么写文案都只能靠猜。 验证: - tsc --noEmit 0 错;bun run build 绿。 - test/scope-optional-autofix.test.ts 18 例全绿; test/setup-open-platform-automation.test.ts 新增 1 例行为测试(4 个成因分支)。 相关三文件 134/135,唯一失败为 collectBotmuxRedirectUrls 那条环境基线用例 (本机 tunnel URL 注入,未改动的 master 上同断言同样失败)。 - 四种成因逐一执行真实文案逻辑确认:已落地 → INFO/「N 项权限已导入」; 不在目录 → WARN/「这 N 项不在当前租户的开放平台权限目录中」;被拒 → WARN/「开放平台拒绝了本次权限申请」;无事可做 → INFO/「所有必需权限已在 应用清单中」。 - 反向变异确认新测试有牙:还原旧的单条三元文案 → 2 例变红;把 99991672 改回 只传 self_manage → 1 例变红。
86d0865 to
61ab9a6
Compare
|
补一处小修(已 amend 进 复审指出 (同文件里既有的 重验: 两轮复审均无保留意见,接下来交由维护者决定合入。 |
补一条合并前的知情项:VC(视频会议)权限不再被「顺带」申请维护者问到会议权限自检,顺着查出本 PR 有一个副作用,PR 描述和此前复审都没提到。不是缺陷、不影响功能正确性,但值得写进记录,免得日后有人困惑。 现象:master 上自愈申请的是全量 301 项,而那 301 项里恰好包含全部 4 个 VC 权限( 同一租户 catalog 下实测对照:
为什么这仍然是对的:VC 权限不在 真实影响:已开通 VC 权限的存量 bot 不受影响( 如果希望 VC 权限也能自动补:正确做法是按 |
|
🚀 Released in v3.18.0 |
与 master 上的 #1042(权限自愈只申请缺失 scope)语义合并: - `tryAutoFixScopes` 同时保留 #1042 的 `scopeManifest`(裁成缺失项)与 #1044 的 `grantedScopeNames`(按 tenant/user 分桶做差)。两者叠加安全: 缺失判定用的扁平集合是分桶集合的超集,本路径上二次差集恒为空操作。 - 日志保留 #1042 的三成因措辞、`autoFixEffective` 降级与 privilegeRange 明细,并融入 #1044 的 `versionDetail`(跳过发版 vs 未解析到 versionId)。 - 修合并新引入的缺口:`narrowRequiredPrivilegeRanges` 会真发 `privilege/update`,属一次落地变更,必须计入 `mutated`,否则「只收敛了 权限数据范围」的那一轮会走无变更短路、不发版,改动留在草稿里不生效。 短路返回补 `privilegeRangeCount` / `privilegeRangeWarning`(类型要求)。 - 新增回归用例锁死上一条(反向变异删掉置位即转红)。 验证:tsc 净、bun run build exit 0、全量单测对同期 master 基线零回归 (合并树失败集是基线的子集,5 files/7 tests ⊂ 6 files/8 tests)。 Co-Authored-By: Claude Code <noreply@anthropic.com>
背景 / 问题
checkRequiredScopes(src/im/lark/event-dispatcher.ts)用缺失的 scope当触发器,但tryAutoFixScopes调automateOpenPlatformSetup时没有传scopeManifest。于是 automation 回退到readDefaultScopeManifest()读取整份src/setup/lark-scopes.json(171 tenant + 130 user = 301 项,含大量 botmux 自身根本不校验的 calendar / docs / sheets / wiki / vc 等权限),再以operation:'add'一次性申请。实际后果:"缺 2 项
im:feed_group_v1:read/write" 会触发把整份清单里能映射的权限(如日志所见约 299 项)全部追加进应用,并创建 + 发布一个新 app version。即"用缺失项当触发器,却拿完整清单当申请集合",造成权限过度申请与不必要发版。改动
open-platform-automation.tsfilterScopeManifest(manifest, wantedNames):按名字裁剪清单,tenant / user 分桶归属沿用 manifest(不自己猜 bucket——im:feed_group_v1:*只在 user 桶、im:resource只在 tenant 桶,同名权限也可能同时落两桶)。readDefaultScopeManifest供调用方复用。event-dispatcher.tstryAutoFixScopes用missingCritical + missingOptional的名字裁出精简 manifest 再传给 automation,申请集合 = 缺失集合。self_manage都没有、查不到 scope 列表)改为传完整BOTMUX_REQUIRED_SCOPES(而非仅self_manage),保留"一次补齐、下次重启即自检通过"的原意,同样不再过度申请整份清单。影响面
im/lark的启动权限自检链路(brand=feishu)。ensureVcMeetingEventsSubscribed与 setup / onboarding 建应用链路不传scopeManifest、行为不变(建应用本就该申请完整清单)。scope/update仍是operation:'add',不会删除应用已有权限。验证
bun run build通过(tsc + 打包)。bun run test中相关用例全绿:test/scope-optional-autofix.test.ts(14)— 新增源级断言:tryAutoFixScopes现在传入filterScopeManifest(readDefaultScopeManifest(), wantedScopeNames)。test/setup-open-platform-automation.test.ts(90,含新增filterScopeManifest5 例:分桶归属、双桶保留、不点名不申请、不存在落空、空列表)。/private/varrealpath、loopback 绑定、FNM symlink 轮转等 macOS 环境相关),与本改动无关。