Repository navigation
[Decision] 用户页「分配岗位」「授予权限集」两个选择器改读注册表:加规范字段(A/A')、去掉用户页入口(B1),还是给授权的名称字段配注册表选择控件(B2) #22865
Description
Ruled: 6115690641 · letter B2 (Q3b B) · 2026-10-12T00:38Z
Filing gate ②:只有维护者能做的决定(产品取舍:用户页上「先打开用户,再给他分配」这个入口留不留、怎么留)。立卡:epic PM session_01Rerax7QTjKMPCUZxQUtPFR(marchtian),席位 epic:#15194。
锚点: 自然锚点是 objectstack-ai/objectui#7611(C9),但它的第二部分正在开发中,不能挂 needs-user-decision 退出活动队列,所以单开本卡。
查重: 两个仓库所有带 needs-user-decision 的卡(含已关),标题搜 picker / 选择器 / registry / spec 18,命中 0。测量报告另查了开着的 PR、分支名和卡,也没有在做这件事的。
动手的读者: 裁后由本席派 objectui#7611 的第三部分落实。它落地后,objectstack#15204 的 6b-2 才能合入、阶段 8 才能落地。
维护者速读
Setup 用户详情页上有两个「添加」按钮:分配岗位、授予权限集。按 ADR-0131,岗位和权限集只有一个来源,就是注册表;它们的数据库行在阶段 8 退役。这两个按钮弹出的选择框却还在读数据库行。
- 已经出现的空窗: 在 main(未发版)上新建一个库,「分配岗位」选择框一个声明的岗位都列不出来。实测 0 条,注册表里有 16 个。
- 即将出现的空窗: 6b-2 落地后,「授予权限集」也会变成这样。
- 为什么卡住: 两个空窗都被发版条件 (c) 挡在所有版本之外,但阶段 8 必须等这两个选择框先改好才能落地。
- 不受影响的部分: 按名称写入已经通了。实测在没有数据行的情况下,按岗位名分配返回 201;坑只在「列出可选项」这一步。
四条路:
- A 规范加字段: 给相关列表的「添加」选择框加一个「从注册表选」的规范字段。objectui 只能从已发布的规范拿到新字段,所以要先发 v18 预发布版,而发版条件 (c) 又挡着发版,形成循环。
- A' 同 A,但 objectui 先用、规范后发: 不用发版。但违反 objectui 的规则(「不许自造规范里没有的字段」),找不到允许的先例。
- B1 去掉用户页的两个「添加」: 改为在岗位或权限集的目录页里分配持有人(C9 第一部分已经做好)。1 个 PR,约 100 行。代价是「先打开用户、再给他分配」这个用法没了。
- B2 给授权记录的名称字段配一个「从注册表选」的控件: 用现有的
widget字段,和今天已合入的 objectstack#22794 / objectui#12136 同一做法。用户页的「新建」表单里就能从注册表选岗位或权限集。2 个 PR(objectui 约 450 行,objectstack 约 400 行),不改规范、不用发版。
本席推荐 B2(测量开发也推荐 B2),回退 B1。选 A、A'、B1 还是 B2?
背景(实测:objectstack origin/main 55382dc02a,objectui origin/main 158c75d908;测量报告 objectui#7611 6111055760)
- 选择框读的是数据行:
sys-user.page.ts两个add.picker.object分别是sys_position、sys_permission_set。规范RecordRelatedListProps.add.picker只认object一种来源。 - 新库实测(showcase,
--fresh,管理员令牌):GET /api/v1/data/sys_position返回 0 行,GET /api/v1/meta/position返回 16 项;GET /api/v1/data/sys_permission_set返回 17 行,与注册表 17 项同名。这一侧今天还对得上,6b-2 删除种子程序后就对不上了(阶段 8 测量 6110720794 的 E2)。
- 按名称写入已就绪:
POST /api/v1/data/sys_user_position,contributor(没有数据行)返回 201;everyone返回 403;不存在的名字返回 400。 - objectui 已有同类控件:
RecipientPickerField已能从注册表列出岗位、按名称保存。ObjectRefField对对象做同样的事。今天合入的ref-multi:permission只用于元数据表单,记录页用不上。 - 用这个入口的地方: 全仓手写的
add选择框共 5 处,其中 3 处指向目录对象:sys-user.page.ts两处,sys-position.page.ts一处(该页在阶段 8 退役)。示例应用 0 处,objectui 源码 0 处。 - 发版事实:
- npm 上
@objectstack/spec只有latest: 17.7.0和rc: 17.0.0-rc.6,没有 18.x; .changeset/pre.json是pre模式、next标签;- objectstack 发版时自带的控制台用本仓规范构建(
scripts/build-console.sh注入OBJECTSTACK_SPEC_DIST),但 objectui 自己的源码、类型和测试只认已发布的规范。
- npm 上
Governing text
- ADR-0131 D7(
docs/adr/0131-…md:510-511):「Pickers (assign a user to a position, choose a recipient) list the registry. Catalogs are tens of items; nothing about them needs a server-side merge.」同节:「no surface merges sources」。 - ADR-0131 D4(
:356):「a picker lists the registry, an assignment names one of its entries.」 - objectui#7611 正文: 「Pickers list the registry. There is no merged two-source list.」
- [Decision] ADR-0131 剩余部分(C2 余下阶段 + C3):继续分段、合并成一次切换,还是改成「registry 支撑的对象」 #22601 → B: 不做过渡件。所以不能把数据行留着专门喂选择框。
- objectstack AGENTS.md Prime Directive Add missing Field.phone() helper and factory methods for Action/Dashboard/Report #15: 「A version release is performed by the maintainer, by hand — no AI seat publishes, tags, cuts a Release, or triggers a release workflow」。
- objectui AGENTS.md #0 / #0.1: 「All schemas/JSON structures/types MUST follow
@objectstack/spec. Don't invent schema properties」,以及「declare it on the node type that reads it, by reference to the spec row」。 FieldSchema.widget(packages/spec/src/data/field.zod.ts:1942):「Form widget override — names a registered field component … Degrades to thetyperenderer when unregistered.」- 发版条件 (c)(6102862135,后续状态见 6110521846):C9 加阶段 7,再加 objectui 的 spec-18 pin。
前提(每条带复核命令)
- 两个选择框读数据行:
git grep -n "object: 'sys_position'\|object: 'sys_permission_set'" origin/main -- packages/platform-objects/src/pages/sys-user.page.ts,应各命中一次。 - main 上没有任何代码写
sys_position行:git grep -nE "(insert|create|upsert)\w*\(\s*['\"]sys_position['\"]" origin/main -- 'packages/**/*.ts' 'examples/**/*.ts' | grep -v test,应 0 命中。 widget是自由字符串:git grep -n "widget: z.string().optional()" origin/main -- packages/spec/src/data/field.zod.ts,应命中 1 次。- 没有 v18 规范:
npm view @objectstack/spec dist-tags,应只有latest、rc。
选项 × 真实代价
| 选项 | 做什么 | 客户能感知的后果 | 工作量 |
|---|---|---|---|
| A 规范加字段 | add.picker 增加一个注册表来源(类型限定为元数据类型枚举);objectui 的相关列表据此从注册表列出;用户页两处改用它 |
用户页「添加」照旧,列的是注册表 | 4 个 PR(规范约 200 行、objectui pin 18 约 100 行、渲染约 400 行、objectstack 改页面约 400 行),外加一次 v18 预发布(你手动);Clause-②: yes(放宽);在发版条件 (c) 下是死循环 |
| A' 先用后发 | 同 A,但 objectui 在已装规范还是 17 时先手写这个字段 | 同 A | 3 个 PR,不用发版;违反 objectui 规则 #0/#0.1,在 pin 移动前 objectui 自己的严格校验和手写类型会打架 |
| B1 去掉用户页入口 | 删掉用户页两个「添加」;分配改在目录页做(C9 第一部分已有持有人分配);Setup 导航指向目录页 | 用户页只能看、不能直接加;要先打开岗位或权限集再加人 | 1 个 objectstack PR,约 100–125 行;Clause-②: no |
| B2 名称字段配注册表控件 | objectui 新增一个字段控件,从注册表列出一种类型、存名称;objectstack 在两张授权表的名称字段上声明它,并删掉用户页的两个 add |
用户页点「新建」,表单里从注册表选;授权数据页的新建表单也同样能选 | 2 个 PR(objectui 约 450 行,objectstack 约 400 行,含 pin 移动),不改规范、不用发版;objectstack Clause-②: no |
(排除项:给 objectui 写死「这两个对象名改读注册表」违反 objectui #0.1;让数据门把注册表伪装成对象违反 D3/D7;保留数据行喂选择框是过渡件,违反 #22601 → B。)
业务含义直译
- A: 平台多了一种通用能力,以后任何页面的「添加」都能从注册表选。代价是先要发一个 v18 预发布版。
- B1: 像很多后台那样,「谁有这个角色」只在角色页上管理。
- B2: 像下拉框选角色:在用户页点「新建」,在表单里从下拉框选岗位或权限集。
四轴(从业务看)
- 项目长远合理性(领起,权重 ≥50%):
- A 是最「正」的声明式契约,但它给只有 2 处使用的场景新增规范面,并且要先解开发版循环。
- B2 复用今天刚合入的同一做法(feat(spec): the position form picks permissionSets from the permission-set registry (ref-multi:permission), with the console pin moved to 4997995022f8 #22794 / objectui#12136),不新增规范、没有临时补丁,D7 点名的「分配岗位的选择器」也保留了。
- B1 最省,但 D7 点名的这个选择器就没了。
- 实际业务拉动: 用户页这两个入口是 Setup 管理员日常给人授权的路径,是真实场景;示例应用和 objectui 里没有别的使用者。A 的通用能力目前只有这 2 处需要。
- 防 AI 犯错(出错时谁看到什么):
- A 最好,类型是枚举,写错会在发布时被拒。
- B2 的
widget写错会静默退化成文本框,但服务端对不存在的名字一律返回 400,所以写不进错行。 - B1 没有新东西可写错。
- 这一轴上 A 胜,与其他三轴冲突,取舍交你。
- 创业阶段不扩散: B1 最小,B2 次之(一个新控件),A 最大(规范新面加一次发版)。
os-decision-facets
- ① 项目长远合理性:B2 复用已合入的做法,不新增规范、保留 D7 点名的选择器;A 最声明式,但为 2 处使用新增规范面并且要先解发版循环;B1 拿掉了选择器。
- ② 实际业务拉动:用户页分配是真实日常路径;通用「从注册表添加」除这 2 处外无其他使用者。
- ③ 防 AI 犯错:A 最强(枚举,发布时拒);B2 写错控件名静默退化,但服务端 400 兜底,写不进错行;B1 无新增可错之处。
- ④ 创业阶段不扩散:B1 < B2 < A。
Prior rulings read: picker, registry, widget, release cut, spec-18 in docs/adr/ and on the thread → ADR-0131 D4/D7; #22601 → B; release-cut condition (c) of 6102862135 and 6104671088 (F1 added the spec-18 pin); seat answer 6110777865 (E2); objectstack#22794 / objectui#12136 (the widget precedent).
推荐
推荐 B2,回退 B1。
- 只看①选 B2;②③④不翻转(③上 A 更强,但 B2 有服务端 400 兜底)。
- 置信缺口: 未在浏览器里实测两点。一是这两张授权表的用户页相关列表是否提供「新建」;二是去掉
add后,行上的「移除」是否还在(今天的通用移除依赖add)。B2 和 B1 都受影响。派发时开发先实测;如果「移除」没了,作为第一件事报回,不自行加功能。 - 如果你想把「从注册表添加」做成平台通用能力,选 A,但要同时决定 v18 预发布的时机。
附带问题 Q2(不需要你现在答): 发版条件 (c) 里「objectui 的 spec-18 pin」这半句,照字面会自己卡死自己:objectui 要 pin 18,得先发 18,而 (c) 不许发。它的起因 F1(岗位页误报「不符合规范」)是在 objectui 自己的控制台(npm 规范 17.7.0)上测的,而 objectstack 发版用的控制台注入的是本仓规范。本席先派人在注入后的控制台上实测 F1:
- 不复现:本席提议把这半句从 (c) 中划掉,报你确认;
- 复现:要不要提前发一个
18.0.0-next.0预发布版,是你的发版决定,届时单独报你。
B1 和 B2 都不依赖 Q2。
裁后执行
- 裁 B2:
- objectui PR:注册表字段控件。
- objectstack PR:移动
.objectui-sha,在两张授权表的名称字段上声明控件,删掉用户页两处add。若 objectstack#22863 尚未合入,等它合入再落。 - 两个 PR 都可以在 6b-2 之前落地,之后 6b-2 合入、阶段 8 落地不再等选择器。
- 裁 B1: 1 个 objectstack PR(删两处
add,Setup 导航改指目录页),在 6b-2 之前落地。 - 裁 A: 先派规范 PR,同时报你 v18 预发布。在你发版之前,6b-2 可以带着空窗合入((c) 挡在所有版本外),阶段 8 等发版。
- 裁 A': 3 个 PR,需要你同时批准 objectui 规则 #0/#0.1 的例外。
- 不论裁哪一项,你只需回一个字母。附加条件随执行落地。
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 11, 2026
补充子问题 Q3b(与本卡同一类:objectui 读取它已装规范里还没有的字段)· epic PM
session_01Rerax7QTjKMPCUZxQUtPFR· 2026-10-11T16:49ZC9 盘点(objectui#7611 6111307114)又查到一处同类问题,按「一次裁清」并入本卡。你可以一起回,例如「B2,Q3b-B」;只回一个字母,就按本席推荐处理 Q3b。
问题: 审批流里「审批人是某个岗位」的显示和编辑。
sys_position数据行」。选项:
{ source: 'registry', type }从注册表列出岗位,只存名字。只改读取,不改任何编写面,所以objectui validate和严格校验都不变;规范升到 18 之后再去掉放宽的类型。1 个 PR,约 0.4–0.5k 行。四轴:
本席推荐 B。
同类、不需要你裁的一项(3a:审批时「决策输出」里的岗位选择)已经派出。
Generated by Claude Code
objectstack-fleet commentedon Oct 11, 2026
决裁详解(按决裁格式六项重写,取代正文的推荐段与工作量列)· epic PM
session_01Rerax7QTjKMPCUZxQUtPFR· 2026-10-11T17:09Z呈报前重跑的检索:
origin/main:git grep -n -iE 'add\.picker|picker\.object|registry picker|picker.*registry|lists? the registry' origin/main -- AGENTS.md docs/adr packages/spec/src。:510-511)、ADR-0131 C9 行(:946)、packages/spec/src/automation/approval.zod.ts:140-152(审批人的「数据行 / 注册表」两分声明,审批节点「处理人 Value」应改为记录 lookup(现查 metadata 端点→只能手填);附 approver value 语义核实 + queue 未实现 #3508、feat(spec)!: approver and decision-output positions are names from the metadata registry (ADR-0131 D4) #22710),以及component.zod.ts的add.picker说明。ApproverValueBinding:{ source: 'record', object, valueField } | { source: 'registry', type }。正文没写到,这里补上。主问题
一句话问题。 Setup 用户页上「分配岗位」「授予权限集」两个选择框,列的是即将删除的数据行,而不是岗位和权限集真正所在的注册表。
选项 × 真实代价 × 工作量:
sys-user.page.ts:206、:254两个add块各约 15 行,导航项约 25 行(阶段 8 测量),合计约 100–125 行add,改走「新建」表单(用户页第三处「加入业务单元」读的是业务数据
sys_business_unit,不受影响。)业务含义直译:
四轴(从业务看):
add选择框共 5 处,其中指向目录对象的 3 处,这 3 处里有 1 处随阶段 8 退役。推荐:B2,回退 B1。
add后,行上的「移除」还在不在(今天的通用移除依赖add)。B1 和 B2 都受影响。派发时开发先测;如果「移除」消失,作为第一件事报回,不自行加功能。裁后执行:
Part of objectui#7611)。.objectui-sha,在两张授权表的名称字段上声明控件,删掉用户页两处add。在 feat(plugin-security,plugin-auth,verify,platform-objects)!: a permission-set grant names its set by name — no writer fills permission_set_id (#15204 stage 6b-2-pre) #22863 合入之后落地。add,Setup 导航改指目录页(导航项从阶段 8 提前)。在 6b-2 之前落地。子问题 Q3b(附言 6111357879)
一句话问题。 审批流里「审批人是某个岗位」:在 objectstack 自带的控制台上,待审批的岗位审批人显示成截断的机器名,流程设计器里「岗位」一栏只能手打文字。原因是规范已改为「从注册表取岗位」,而 objectui 的读取端还按旧规范只认数据行。(代码读出,未在浏览器实测。)
选项 × 真实代价 × 工作量:
{ source: 'registry', type }从注册表列出、只存名字;只改读,不改编写面业务含义直译: A 是等官方补丁;B 是先按服务器实际返回的格式适配;C 是写死一个特例。
四轴:
推荐:B,回退 A。
裁后执行: B → 本席派 objectui 子代理(和已派出的 3a 不在同一批文件);objectui 升到规范 18 后去掉放宽的类型。
os-decision-facets
Prior rulings read:
add.picker,picker.object,registry picker,lists the registryin AGENTS.md, docs/adr and packages/spec/src → 8 hits; ADR-0131 D4/D7 and C9;approval.zod.tsApproverValueBinding(#3508, #22710); thread: 6110777865, 6111055760, 6111307114, 6111357879.回复方式: 一行即可,例如「B2,Q3b-B」。只回一个字母时,Q3b 按推荐的 B 处理。
Generated by Claude Code
objectstack-fleet commentedon Oct 12, 2026
Ruling: batch #318 item 1 · letter B2 (Q3b B) · maintainer 「其他同意?」 2026-10-12T00:37Z
Director seat, summon #37,
session_01LqNgdPZB2Jnr5HpGbaqLeB(GitHubos-zhuang; written asobjectstack-fleet[bot]via the relay). Batch #318 (five cards, this one first) was presented in chat with this seat's own readings and the recommendation "B2, fallback B1; Q3b → B"; the maintainer deferred one card (#22877) and answered 「其他同意?」 for the rest, which takes the recommendation here. Freshness gate: the body and both comments (the newest the epic PM's 决裁详解 6111537112) were re-read before this record. Thread-read: 6111537112.Premises re-run on
origin/main(3f694715e) in this act: the two pickers read rows (sys-user.page.ts:208,:258);FieldSchema.widgetis a free string (field.zod.ts:1942); npm@objectstack/specdist-tags arelatest: 17.7.0andrc: 17.0.0-rc.6, no 18.x; #22863 (a permission-set grant names its set by name) merged 2026-10-11T17:36Z, so B2's objectstack half no longer waits on it.The ruling
B2: the two grant tables' name fields declare a registry picker; the user page's two
addpickers go..objectui-shato the objectui head that carries the widget, and deletes the twoaddblocks on the user page (sys-user.page.ts, thesys_positionandsys_permission_setpickers). The user page's "new" form then lists the registry; the grant objects' own new forms list it the same way.add. If "remove" disappears, that is reported first; the dev adds no feature on its own.{ source: 'registry', type }approver binding from the registry and stores the name only, a read-side change under the contract the server already publishes, no authoring surface touched, soobjectui validateand the strict validation are unchanged; the loosened type is removed when objectui moves to spec 18. One PR, apart from item 3a already dispatched.add.pickerregistry source in the spec: a component-level contract for two call sites, behind a v18 pre-release that release condition (c) blocks); A' (the same, with objectui reading a field its installed spec does not declare: an exception to objectui rule #0/#0.1 the maintainer did not grant); B1 (removing the user-page entry: it takes away the "assign a user to a position" picker ADR-0131 D7 names). Q3b A (wait for spec 18) and C (a hand-written table outside the spec) not taken.Why on the first axis
Two years out, a field that references a registry entry declares what it references, and every form and list derives its picker from the field: the Salesforce lookup and ServiceNow reference shape. B2 puts the reference on the field (the right place) at display-hint strength; A puts a typed contract on one component (the wrong place) and becomes a redundant published key when the field-level declaration arrives; B1 removes the capability D7 names. Q3b's B reads the two-way
record | registrybinding the spec already declares. 自检:只看①选 B2;②③④ 是否翻转:否(③ 偏 A:枚举胜自由字符串;不改字母,因为服务端对不存在的名字一律 400,B2 出错时是响亮报错不是泄露)。B1 is more than 3× cheaper than B2; the first-axis readings are not close, so the letter stands.Workload (#22597's rule):
domain:ui+ the objectstack lane underepic:#15194· 2 PRs (objectui about 450 lines, objectstack about 400 lines including the pin move) · precedents objectui#12136 (+763 / −3) and objectstack#22794 (+372 / −84). Q3b B:domain:ui· 1 PR · about 400–500 lines · no precedent. Confidence gap: the "new" / "remove" browser readings above; the field-level end state is this seat's reading, not a spec proposal.Prior rulings read:
add.picker,picker.object,registry picker,lists the registryonorigin/main(AGENTS.md, docs/adr, packages/spec/src) → 8 hits as the epic PM listed (ADR-0131 D4 and D7;approval.zod.tsApproverValueBinding, #3508 / #22710); #22601 → B (no transition piece); release condition (c) 6102862135 / 6104671088;check-prior-rulings.mjs --card 22865in this act: 562 hits, ADR-0005 §5, ADR-0035 §3, ADR-0041 §4, ADR-0054 §3, ADR-0059 §3, ADR-0087 D3 / D4 named, none answering this question; thread: 6111357879, 6111537112.State and execution
completedin this act (the ruling is its deliverable);needs-user-decisionleaves with the close. TheRuled:line is added to the body.epic:#15194,session_01Rerax7QTjKMPCUZxQUtPFR): the objectui widget PR (Part of objectui#7611), then the objectstack PR (pin move, the two field declarations, the twoadddeletions), both before 6b-2; Q3b's objectui PR alongside, disjoint from item 3a's files. A pointer is posted on objectui#7611 in this act.objectstack-fleet commentedon Oct 12, 2026
Closed
completedon ruling 6115690641 (batch #318 item 1 → B2, Q3b → B): the decision is this card's deliverable, and the work is objectui#7611's third part, dispatched by the epic PM (epic:#15194; pointer objectui#7611 comment 6115700897). The four open PRs that name this card (#22897, #22898, #22899, #22900, allPart of #15204) name it as a gate they wait on, not as a card they fix; that gate is now "the pickers land", tracked on objectui#7611. Director seat, summon #37,session_01LqNgdPZB2Jnr5HpGbaqLeB, 2026-10-12T00:41Z.