Repository navigation
[Decision] 卸载包后岗位定义仍列着该包的权限集(重装后恢复生效):维持(A)、维持并在启动报告点名(B)、卸载时拦下(C),还是替作者改定义(D) #23229
Description
Ruled: 6145039294 · letter B · the reading confirmed; the sixth report class is an 8-post closeout item on #22975; this card closes · 2026-10-13T12:03Z
Part of #15204
Filing gate ②:只有维护者能做的决定。这是对维护者自己裁决用词的读法:6123311533 里的「岗位授权」是否也包括岗位定义。也算 ③:维护者在本席会话中要求「补这 4 张决策卡」。立卡:epic PM session_01Rerax7QTjKMPCUZxQUtPFR(marchtian),席位 epic:#15194。
Prior rulings on this card: none(新卡)。相关裁决:6123311533(维护者,方案 1:卸载按名称撤销授权)。审查方的升级:契约审查 6128268740 ①9 与 ③ 第 2 条。D 的维护者速读 6130417182「需要你确认的一处读法」。
锚点: 这个问题原本挂在 #22900(阶段 6b-2 D)的维护者速读里。D 已于 2026-10-13 02:19Z 合入,标签已摘,问题没有了归处。它跨越阶段 8 和 8-post(#23056、#22975),没有单一自然锚点,所以单开本卡。
查重: 用语义检索搜 objectstack 卡,关键词 uninstall / position definition / permissionSets / dangling reference / unserved set / boot report,命中 15 张,前 8 张全部已关(#21669、#21576、#21490、#22360、#22307、#22169、#22379、#21860),都不是这个问题。没有同题的开着的卡或决策卡。
动手的读者:
- 裁 A:没有人要动手,本卡关闭。
- 裁 B:并进 8-post(C3 #15204 stages 7a ∥ 7b → 8-post, build ahead: delete the permission-set projection and the position write-through that stage 8 leaves unreferenced, then the post-stage-8 catalog gates and residue #22975,
domain:engine)的收尾项,因为阶段 8 会改启动报告依赖的表。 - 裁 C 或 D:本席为
domain:engine车道立 1 张执行卡。
维护者速读
你裁过的(6123311533,方案 1): 卸载包时,按包的权限集名称撤销「用户或岗位授权」,因为留着的话,包不在时不生效、重装后又生效,违反 ADR-0090「不留幽灵授权」。D 已按此实现:用户授权行,以及旧的岗位绑定行,卸载时都撤销。
剩下的一种情况: 某个岗位的定义在 permissionSets 里列了被卸载包的权限集。定义可以是代码声明的,也可以是 Studio 里作者写的。按 ADR-0131 D3,岗位带哪些权限集是岗位定义的一部分,是作者的元数据,不是授权行。D 不改它,所以:
- 包卸载后,这一项立刻不生效;
- 包重新安装后,这一项恢复生效;
- 启动时的目录引用报告不会提示这种悬空引用,这是静默的。
审查方的读法: 你说的「岗位授权」指授权行,不指岗位定义,这和 ADR-0131 D3、ADR-0090 原文一致。但「包不在时不生效、重装后恢复」正是你裁决里描述的形状,所以要你确认。
四个选项:
- A: 同意读法,不动,本卡关闭。
- B: 同意读法,并让启动报告新增一类「岗位定义列了目录里没有的权限集」,按岗位逐个列出。行为不变,只是不再静默。
- C: 卸载时,如果有岗位定义引用该包的权限集,就拒绝卸载并列出这些岗位,作者先改定义再卸载。
- D: 卸载时,替作者把这些名字从环境里作者写的岗位定义中删掉。代码声明的岗位是封存的,改不了,所以覆盖不全。
本席推荐 B。 选 A、B、C 还是 D?
背景(实测:objectstack origin/main 8024e1243a,2026-10-13T09:25Z)
-
D 撤销什么: 两个卸载入口(
/packages删除和install-local卸载),在包撤下之前记下包的权限集名称,交给清理步骤,按名称撤销sys_user_permission_set授权行和旧的岗位绑定行(契约审查 6128268740 ①1 至 ①9)。 -
岗位定义怎么生效: 鉴权通过目录读权限集正文,
catalogSetBody→catalog.resolve('permission', name)(packages/core/src/security/resolve-authz-context.ts)。名字在目录里找不到,这一项就什么也不授予(fail closed)。 -
启动报告漏了这一类:
packages/plugins/plugin-security/src/catalog-reference-report.ts的头注释列了 5 类悬空引用:- 指向不存在岗位的分配;
- 指向不存在权限集的授权;
- 没有名字的授权;
- 没有定义的岗位行;
single下的岗位重名。
没有「岗位定义列了不存在的权限集」这一类。这个文件自述:「Failing closed is silent by itself … This report is the loud half」。
-
阶段 8 会改什么: 阶段 8(C3 #15204 stage 8: S8-T #22962 + S8 retire the four catalog row objects; both Tier H; land right after D #22900 (handed to the engine lane) #23056,PR feat(plugin-security)!: retire the four security-catalog row objects (#15204 stage 8) #23222,草稿;测试半 S8-T test: the stage-8 test half — suites stop depending on the four catalog row objects (#15204 stage 8, S8-T) #22962 已于 10-13 08:50Z 合入)退役四个目录行对象。启动报告的前 4 类依赖这些表,8-post(C3 #15204 stages 7a ∥ 7b → 8-post, build ahead: delete the permission-set projection and the position write-through that stage 8 leaves unreferenced, then the post-stage-8 catalog gates and residue #22975)收尾时会改这个文件。
Governing text
- ADR-0131 D3: 「The position → permission-set binding is part of the position's definition」;「a definition lives only in the environment registry — code-declared (managed, sealed) or environment-authored (Studio, template package; editable by metadata-authoring capability holders)」。
- ADR-0131 D6: 「Managed definitions are sealed; customization is environment-level state beside them, never an edit」。代码声明的岗位定义不能被改写。
- ADR-0131 环境元数据的来源规则(维护者原话): 「代码推送过来的元数据就只能在代码中修改;studio 界面上配置的元数据,保存在库中,可以在界面上修改」。
- ADR-0090(
everyone一节): 「uninstalling the package (removing its sets bypackageId, ADR-0086 D3) revokes it everywhere at once. No ghost grants.」- 原文的机制是「拿掉权限集,所有引用随即失效」,没有提到重装。
- 裁决 6123311533: 「A user or position grant naming the package's set would then survive the uninstall: inert while the package is absent, and live again on reinstall. That breaks ADR-0090 D5 ("no ghost grants"). Ruling: the cleanup revokes those grants by the package's catalog set names」。
前提(每条带复核命令)
- 启动报告没有这一类:
git show origin/main:packages/plugins/plugin-security/src/catalog-reference-report.ts | sed -n 1,60p | grep -c "permissionSets",应为 0。 - D 已合入:
git merge-base --is-ancestor c8a9783eca origin/main && echo merged。 - 岗位定义带权限集是规范字段:
git grep -c "permissionSets" origin/main -- packages/spec/src/identity/position.zod.ts,应 ≥1。 - 阶段 8 还没合入:
gh api repos/objectstack-ai/objectstack/pulls/23222 --jq .merged,应为false。
选项 × 真实代价
| 选项 | 做什么 | 能感知的后果 | 工作量 |
|---|---|---|---|
| A 维持,结案 | 不动 | 卸载后,引用该包权限集的岗位悄悄少了这部分权限,重装后悄悄恢复;没有任何提示 | 0 |
| B 维持,报出来 | 启动报告加第 6 类:岗位定义里目录解析不到的权限集名,按岗位列出 | 行为同 A,但每次启动日志都点名「岗位 X 列了不存在的权限集 Y」;重装后自动从报告里消失 | 1 个 PR,在 catalog-reference-report.ts 上加一类加测试,规模估计百行级(未实测);先例是这个报告现有的 5 类;放进 8-post 一起做,避免和阶段 8 冲突 |
| C 卸载时拦下 | 两个卸载入口先查岗位定义;有引用就拒绝卸载并列出岗位 | 运营者卸载包前必须先改岗位定义;不会出现「重装后悄悄恢复」;卸载多一个失败理由 | 1 个 PR,覆盖两个卸载入口加测试和消融,规模未实测,无先例;Clause-②: yes (narrowing) |
| D 替作者改定义 | 卸载时从环境里作者写的岗位定义中删掉这些名字 | 作者写的岗位定义被卸载动作悄悄改掉;代码声明的岗位改不了,问题还在 | 1 个 PR,要写环境元数据账本,规模未实测,无先例;和 D6 的封存冲突,只能覆盖一半 |
业务含义直译
- A: 员工手册写着「财务岗可用 X 系统」。X 系统下线,手册不改;X 回来,手册照旧有效。没人提醒手册里有一条暂时作废。
- B: 同 A,但每天早上的晨报会列出「手册第 N 条引用的 X 系统当前不存在」。
- C: 想下线 X 系统,得先把手册里引用它的条目改掉,否则不准下线。
- D: 下线 X 系统时,行政悄悄从别人写的手册里删掉相关条目;但印刷版手册(代码声明的)改不了。
四轴(从业务看)
- 项目长远合理性(领起):
- 两年后的样子:岗位定义是作者的,按名称引用权限集,引用不到时平台大声说出来,不替作者改。
- ADR-0131 D4 和这份启动报告就是按这个思路建的:引用按名字,解析不到时 fail closed,并由报告负责「大声的那一半」。
- 参照主流做法:
- 编译器和链接器对未解析的符号报警告或错误,不替你删代码;
- Salesforce 卸载托管包时,如果包组件被包外的元数据引用,会列出依赖、阻止卸载(相当于 C)。
- B 和 C 都在这个方向上。A 留下一个静默的洞。D 让包写进别人的定义,和 D6 冲突。
- 实际业务拉动: 今天没有用户报告撞上它。会撞上的是:运营者卸载一个包,而某个岗位定义列了这个包的权限集。持有这个岗位的人悄悄少了权限,重装后又悄悄恢复。零实测拉动。
- 防 AI 犯错(出错时谁看到什么):
- A:没人看到。AI 写的岗位定义引用了一个已卸载包的权限集,不会有任何反馈。
- B:启动日志大声点名。
- C:卸载时当场拒绝,最响亮。
- D:静默改写作者的元数据,最难发现。
- 创业阶段不扩散:
- A 零代码;B 在已有报告上加一类,不新增门禁(报告只记录、不拦截)。
- C 给卸载新增一个拒绝条件。
- D 新增一条写环境元数据的路径。
os-decision-facets
- ① 项目长远合理性:B 与 ADR-0131 D4「引用按名、解析不到要大声说」的既有设计一致;C 同向但改卸载语义;A 留一个静默的洞;D 让包写进别人的定义,和 D6 冲突。
- ② 实际业务拉动:零实测拉动;撞上它的是卸载了被岗位定义引用的包的运营者。
- ③ 防 AI 犯错:C 最响亮(当场拒绝),B 次之(启动报告),A 静默,D 静默改写作者元数据。
- ④ 创业阶段不扩散:A 零代码;B 只在已有报告上加一类,不加门禁;C 加一个拒绝条件;D 加一条写路径。
Prior rulings read: uninstall, ghost, permissionSets, position definition → 4 hits; ADR-0090 D9, ADR-0106 D1 (incidental), ADR-0131 D3; plus ADR-0131 D4/D6 and the ADR-0090 everyone section read by hand; thread: #15204 (6123311533), #22900 (6128268740, 6130417182).
推荐
推荐 B,回退 A。
- 只看①选 B;②③④ 是否翻转:否。③ 上 C 更响亮,但 C 改了卸载语义,只凭 ③ 不翻字母。
- 工作量:B 和 C 都是 1 个 PR,规模都没有实测,没有看到 ≥3× 的差距。
- 置信缺口:
- B 和 C 的规模都没有实测;
- 阶段 8 合入后,启动报告的前 4 类会怎么改,本席没有读过 feat(plugin-security)!: retire the four security-catalog row objects (#15204 stage 8) #23222;
- 没有核实 Salesforce 对「权限集被包外的权限集组引用」这一具体情形的卸载行为,只核实了一般的组件依赖拦截;
- 不知道实际部署里有多少岗位定义引用了包的权限集。
裁后执行
- 裁 A: 不动,本卡关闭,在 refactor(plugin-security,platform-objects,spec): retire the catalog seeders, the per-organization catalog machinery and the four catalog objects; Setup creation is an environment write under
singleand refused under a wall (ADR-0131 D2/D3/D5/D13) #15204 记一行。 - 裁 B: 本席在 C3 #15204 stages 7a ∥ 7b → 8-post, build ahead: delete the permission-set projection and the position write-through that stage 8 leaves unreferenced, then the post-stage-8 catalog gates and residue #22975 上给
domain:engine车道追加一条 8-post 收尾项:启动报告加第 6 类,带测试和消融,在 S8 合入后做。不另立执行卡。 - 裁 C: 本席为
domain:engine车道立 1 张执行卡:- 两个卸载入口在撤下包之前查岗位定义,有引用就拒绝并列出;
- 测试和消融;
Clause-②: yes (narrowing),要过契约审查。
- 裁 D: 同 C 的流程,范围限于环境里作者写的岗位定义。卡上写明代码声明的岗位不在范围内。
- 不论裁哪一项,你只需回一个字母。
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 13, 2026
Ruling: batch #336 item 4 · letter B · maintainer 「其他同意」 2026-10-13T11:59Z
Director seat, summon #41,
session_01TJKP6W6UDYEBHtccVdpVoz(GitHubos-steve; written asobjectstack-fleet[bot]through the relay, read back). Presented in this seat's chat as batch #336 item 4 with recommendation B (fallback A); the maintainer's word on the remaining four items of the batch was 「其他同意」. The card's premises were re-read by this seat onorigin/mainbefore the batch and hold (the boot report's header lists nopermissionSetsclass;position.zod.tsdeclarespermissionSets; stage 8's PR #23222 is an open draft). Thread-read: none.B — the reading is confirmed, and the boot report says it out loud. The maintainer's ruling 6123311533 ("user or position grants") covers grant rows, not position definitions: a definition is the author's metadata (ADR-0131 D3), and D does not edit it. A position definition that names a permission set the catalog cannot resolve grants nothing (fail closed) and is reported: the catalog reference report gains a sixth class, "position definition names a permission set absent from the catalog", listed per position, which clears by itself on reinstall. C (refuse the uninstall) is not taken: a code-declared, sealed position referencing the package's set would make the package un-uninstallable without a redeploy of the other package. D (rewrite the author's definitions) is not taken: it writes into the author's metadata and cannot reach sealed definitions (D6). A is not taken: a silent hole.
domain:engine· one closeout item on 8-post (C3 #15204 stages 7a ∥ 7b → 8-post, build ahead: delete the permission-set projection and the position write-through that stage 8 leaves unreferenced, then the post-stage-8 catalog gates and residue #22975): the sixth class incatalog-reference-report.tswith tests and an ablation, done after S8 (PR feat(plugin-security)!: retire the four security-catalog row objects (#15204 stage 8) #23222) lands, since stage 8 moves the tables the report's first four classes read · precedent: the report's existing five classes · no new card.domain:engineseat holding C3 #15204 stages 7a ∥ 7b → 8-post, build ahead: delete the permission-set projection and the position write-through that stage 8 leaves unreferenced, then the post-stage-8 catalog gates and residue #22975 isos-steve, seat 2); this card closescompletedon the ruling with the pointer, and the epic ledger (refactor(plugin-security,platform-objects,spec): retire the catalog seeders, the per-organization catalog machinery and the four catalog objects; Setup creation is an environment write undersingleand refused under a wall (ADR-0131 D2/D3/D5/D13) #15204) carries one line.objectstack-fleet commentedon Oct 13, 2026
Closed
completedat 2026-10-13T12:09Z by the director seat (summon #41,session_01TJKP6W6UDYEBHtccVdpVoz, written asobjectstack-fleet[bot]through the relay): the ruling is recorded (6145039294, letter B, the maintainer's 「其他同意」), the reading of 6123311533 confirmed, and the execution lives on 8-post #22975 as its closeout item (6145194655: the catalog reference report's sixth class after S8 lands); the epic ledger carries the line (#15204, 6145185086).needs-user-decisionleft with the ruled transition;domain:engine,priority:p2,target:v18andarea:accessstay as attribution.refactor(plugin-security): delete the position write-through and the …