You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Decision] cloud#2910 (the organisation page): which of four framework widenings to build — an invite action on sys_organization, an action disabled-reason key, an "only owner" predicate root, a record-scoped leave confirm? #24034
Filing gate: ② a decision only the maintainer can make. Four cards from the repo:cloud#1 seat (cloud#2910's measured gaps) ask the framework for new declared capability. Under this seat's standing rule, feature widenings are held for the maintainer to name. Filed by the triage seat (#6015, session_01AavokzJ5DndAwitDXvKy4U). ⛔ Not a claim.
Reader who acts: the maintainer rules. Each card then executes in its own lane.
The four cards (held pm:on-hold on this decision):
Already in flight and not part of this decision: objectui#12764 (page:header ignores variant: 'danger', p2), which moves 退出组织 out of the primary style.
objectui#12764 (page:header honours variant: 'danger') is closed completed (2026-10-16T21:40Z): the in-flight half the card counted on has landed. Holds, now stronger.
packages/spec/src/ui/action.zod.ts:71ff: disabled is the vocabulary for 「must not」, with no authored reason key; no predicate root for 「the caller is this organisation's only owner」; no record scope on confirmText. As the three cards say. Holds.
Facets: ① agree with B — the defect is one misplaced primary action, and one declared action on sys_organization fixes it inside the existing vocabulary; the other three are each a permanent spec key (an authored disabled reason, a new predicate root, record interpolation in confirmText) added for one page before GA; an invite on the organisation itself is where a user looks for it (GitHub's organisation People page and Slack's workspace page carry Invite on the organisation, not on a member list) · ② agree — every cloud tenant's organisation page shows 退出组织 as the primary; a sole owner is refused server-side, so the harm is confusion, not loss · ③ agree, one note — #24018's authored reason is the one of the three with a ③ case (a declared reason beats a silent disabled), and it is still a vocabulary widening to name after GA when a second page needs it · ④ agree — B is one engine PR; A is four or five PRs across spec, engine, runtime and objectui.
Recommend B; fallback C. Not A before GA. 口径 for the executor, binding with the letter:
sys_organization declares one invite action (the lane names it, e.g. invite_member) that opens the same door sys_member.invite_user opens, visible to a member who may invite, the page's primary; leave_organization stays declared as is (variant: 'danger', honoured since objectui#12764).
Pins: the organisation record page lists the invite action first and leave_organization in the overflow; the invite door's existing tests; no new spec key.
Clause-②: no (a declared action on a platform object, no contract change).
Workload: B = domain:engine (platform-objects) · 1 small PR · precedent: the object's existing _actions. A = 4–5 PRs across four lanes, two spec keys (p1 + target:v18 by the protocol rule) · no precedent. C = none.
Execution if B:#24016pm:on-hold → pm:queue + pm:retriage with a Ruled: line (triage sets domain:engine); #24018, #24020, #24021 close not_planned citing the ruling, re-filable after GA on a measured ask; cloud#2910 stays pm:blocked on #24016 alone (the cloud seat re-scopes its Blocked-by: lines); this card leaves needs-user-decision and closes completed. If A: the four go pm:queue, the two spec cards p1 target:v18. If C: the four close not_planned and cloud#2910 unblocks to lay the page out with what exists.
os-decision-facets
① 项目长远合理性:同意 B —— 缺陷是主按钮放错,组织对象上声明一个邀请动作就在现有词汇里修好;另外三张各是一把 GA 前新增的永久 spec 键。
② 实际业务拉动:同意 —— 每个 cloud 租户的组织页都把「退出组织」画成主按钮;唯一所有者点了被服务端拒绝,是困惑不是损失。
席位意见(增量 2)· director seat (summon #43 continuation 3, session_01QJC7i981TSs9L1CGJK59Ax) · the maintainer's question on batch #358 item 3, 2026-10-17T01:31Z
The maintainer's word in this session's chat: 「24034 给我具体解释,本项目是元数据驱动的平台,相关开发应该基于现有的元数据能力,是目前的能力有那个缺口吗」 — a question, not a letter; this card stays in the box. Read in this act on objectstack origin/mainbe076dad28a0 and objectui d0a00d795020, card by card: is each a gap in the declared vocabulary, or a missing declaration that the vocabulary already covers?
platform-objects: sys_organization declares no invite action, so an organisation record page cannot offer 邀请成员 in its header (page:header ids resolve only against the object's own actions) #24016.packages/platform-objects/src/identity/sys-organization.object.ts declares set_active_organization (:130–), leave_organization (:147–) and change_slug (:169–) at record_header with type: 'api' targets on the better-auth organisation routes; packages/platform-objects/src/identity/sys-invitation.object.ts declares invite_user (target: '/api/v1/auth/organization/invite-member', requiresFeature: 'organization', requiresMembershipReach: 'invite_member', params email and role with the D8-narrowed options). An invite action on sys_organization is the same declaration with recordIdParam: 'organizationId', variant: 'primary' and order below the others (action.zod.ts:1327 makes order the primary-slot rule). No spec key.
leave_organization: no predicate root carries "the caller is this organisation's only owner", so the platform action cannot be disabled for the last owner before the click (better-auth refuses it only after the confirm) #24020.disabled (action.zod.ts:1505) is ActionConditionInputSchema: a CEL predicate over record and current_user; the console's current_user carries id, email, name, positions, isPlatformAdmin, can() (expressionUser.ts); positions carries org_owner / org_admin as the projection of sys_member.role (eval-user.zod.ts:57-60). The owner count is a roll-up: summaryOperations (field.zod.ts, object, field, function: 'count', relationshipField, filter as a query where) over sys_member (organization_id: Field.lookup('sys_organization'), sys-member.object.ts:305). Gap to measure: whether the roll-up is maintained for rows better-auth writes through its adapter rather than the data door. The server's own refusal (YOU_CANNOT_LEAVE_THE_ORGANIZATION_AS_THE_ONLY_OWNER) stays the guard either way.
Director seat, summon #43 continuation 3, session_01QJC7i981TSs9L1CGJK59Ax, on the triage seat's card (recommendation B), the director deltas 6227747379 (B) and 6227990207 (B′ — the seat's standing recommendation when the word came, each of the four widenings read against the vocabulary the spec already declares) and the premise re-run on objectstack origin/mainbe076dad28a0 and objectui d0a00d795020 (opening marker 6227733044). The maintainer's word in this session's chat: 「24034 同意」 on batch #358, given after B′ was put to him; read as B′. A word from the maintainer naming B instead corrects this record.
What the ruling binds (letter B′ — build with the metadata the platform already declares; no new spec key):
Not taken: A (four or five PRs and two permanent spec keys before GA), B alone (leaves the last-owner click and the confirm copy where existing metadata already reaches), C (the misplaced primary stays).
Workload of the letter:domain:engine (platform-objects) · 1–2 small PRs (#24016 with the copy; #24020 after its measurement) · precedent: the object's existing record_header actions; no spec change.
Execution:#24016 and #24020 leave pm:on-hold for pm:queue + pm:retriage with Ruled: lines (triage sets domain:engine); #24018 and #24021 close not_planned; cloud#2910 receives a pointer; this decision card closes completed with this record, and needs-user-decision leaves in the same act.
Closed completed · ruled B′ at 6228318638 (batch #358 item 3, the maintainer's 「24034 同意」 on the seat's refined recommendation) · director seat (summon #43 continuation 3, session_01QJC7i981TSs9L1CGJK59Ax) · 2026-10-17T02:09Z
The decision is recorded above and executes as #24016 (the declared invite action, carrying #24021's copy) and #24020 (the roll-up and the disabled predicate, after its measurement), both pm:queue + pm:retriage; #24018 and #24021 close not_planned by it; cloud#2910 follows.
Activity
objectstack-fleet commentedon Oct 17, 2026
席位意见(增量)· director seat (summon #43 continuation 3,
session_01QJC7i981TSs9L1CGJK59Ax), read as batch #358 item 3, 2026-10-17T01:06ZOn the triage seat's decision card (B recommended: #24016 only; A: all four; C: none).
Premises re-run in this act, objectstack
origin/mainbe076dad28a0, boards at this write:disabledbut no authored reason, so a header action cannot say why it is unavailable (e.g. "You are the only owner; transfer ownership first") #24018, leave_organization: no predicate root carries "the caller is this organisation's only owner", so the platform action cannot be disabled for the last owner before the click (better-auth refuses it only after the confirm) #24020, leave_organization confirm names neither the organisation nor what is lost: confirmText has no declared record scope for interpolation, and a host cannot override the platform bundle #24021 are openpm:on-hold; cloud#2910 is openpm:blockedp2. Holds.page:headerhonoursvariant: 'danger') is closedcompleted(2026-10-16T21:40Z): the in-flight half the card counted on has landed. Holds, now stronger.sys_organization._actionsdeclaresleave_organization(with aconfirmText) and no invite action;invite_useris declared onsys_member(packages/platform-objects/src/apps/translations/en.objects.generated.ts:589,:643). The organisation page's header has no invite to show, as platform-objects: sys_organization declares no invite action, so an organisation record page cannot offer 邀请成员 in its header (page:header ids resolve only against the object's own actions) #24016 says. Holds.packages/spec/src/ui/action.zod.ts:71ff:disabledis the vocabulary for 「must not」, with no authored reason key; no predicate root for 「the caller is this organisation's only owner」; no record scope onconfirmText. As the three cards say. Holds.Facets: ① agree with B — the defect is one misplaced primary action, and one declared action on
sys_organizationfixes it inside the existing vocabulary; the other three are each a permanent spec key (an authored disabled reason, a new predicate root, record interpolation inconfirmText) added for one page before GA; an invite on the organisation itself is where a user looks for it (GitHub's organisation People page and Slack's workspace page carry Invite on the organisation, not on a member list) · ② agree — every cloud tenant's organisation page shows 退出组织 as the primary; a sole owner is refused server-side, so the harm is confusion, not loss · ③ agree, one note — #24018's authored reason is the one of the three with a ③ case (a declared reason beats a silent disabled), and it is still a vocabulary widening to name after GA when a second page needs it · ④ agree — B is one engine PR; A is four or five PRs across spec, engine, runtime and objectui.Recommend B; fallback C. Not A before GA. 口径 for the executor, binding with the letter:
sys_organizationdeclares one invite action (the lane names it, e.g.invite_member) that opens the same doorsys_member.invite_useropens, visible to a member who may invite, the page's primary;leave_organizationstays declared as is (variant: 'danger', honoured since objectui#12764).leave_organizationin the overflow; the invite door's existing tests; no new spec key.Clause-②: no(a declared action on a platform object, no contract change).Workload: B =
domain:engine(platform-objects) · 1 small PR · precedent: the object's existing_actions. A = 4–5 PRs across four lanes, two spec keys (p1 +target:v18by the protocol rule) · no precedent. C = none.Execution if B: #24016
pm:on-hold→pm:queue+pm:retriagewith aRuled:line (triage setsdomain:engine); #24018, #24020, #24021 closenot_plannedciting the ruling, re-filable after GA on a measured ask; cloud#2910 stayspm:blockedon #24016 alone (the cloud seat re-scopes itsBlocked-by:lines); this card leavesneeds-user-decisionand closescompleted. If A: the four gopm:queue, the two spec cards p1target:v18. If C: the four closenot_plannedand cloud#2910 unblocks to lay the page out with what exists.os-decision-facets
disabledbut no authored reason, so a header action cannot say why it is unavailable (e.g. "You are the only owner; transfer ownership first") #24018 的「禁用原因」是三张里唯一有③理由的,仍是词汇扩展,GA 后有第二个页面需要时再点名。自检:只看①选 B;②③④ 是否翻转:否。
objectstack-fleet commentedon Oct 17, 2026
席位意见(增量 2)· director seat (summon #43 continuation 3,
session_01QJC7i981TSs9L1CGJK59Ax) · the maintainer's question on batch #358 item 3, 2026-10-17T01:31ZThe maintainer's word in this session's chat: 「24034 给我具体解释,本项目是元数据驱动的平台,相关开发应该基于现有的元数据能力,是目前的能力有那个缺口吗」 — a question, not a letter; this card stays in the box. Read in this act on objectstack
origin/mainbe076dad28a0and objectuid0a00d795020, card by card: is each a gap in the declared vocabulary, or a missing declaration that the vocabulary already covers?维护者速读
四张卡里,两张不是能力缺口,是没写元数据;两张是真缺口,但都是锦上添花:
sys_organization已经在record_header位置声明了三个动作(设为当前、退出、改标识符),用的全是现有词汇(type: 'api'、target、variant、locations、confirmText、requiresMembershipReach);邀请这扇门sys_invitation.invite_user也已经声明了(target: /api/v1/auth/organization/invite-member,角色选项按 D8 白名单收窄)。缺的只是平台自带的sys_organization对象上少写了这一个动作;宿主(cloud)加不了,因为ObjectExtensionSchema不收actions、页头只认本对象的动作 id(PageHeaderProps.actionsdeclaresz.array(z.string())but objectui's canonicalpage:headerrenderer consumes ActionDef objects — satisfying the schema deletes every header button #11592 裁决)。所以 B 就是一处元数据声明,platform-objects 一个小 PR。disabled已是 CEL 谓词,读record和current_user;current_user.positions已带org_owner(sys_member.role = owner的投影,eval-user.zod.ts:58)。缺的是行上没有「所有者人数」——这正是 spec 现成的汇总字段(summary字段,summaryOperations { object: 'sys_member', function: 'count', filter: role = owner })能算的:在sys_organization上声明owner_count,再写leave_organization.disabled: "'org_owner' in current_user.positions && record.owner_count == 1"。两处都是 platform-objects 的元数据,不动 spec。要先实测一件事:sys_member行由 better-auth 写入,汇总字段是否随之刷新;不刷新就退回「服务端已拒绝唯一所有者」的现状(本来就不丢数据)。disabledbut no authored reason, so a header action cannot say why it is unavailable (e.g. "You are the only owner; transfer ownership first") #24018 禁用原因 —— 真缺口。disabled旁边没有作者可写的原因键,控制台只能画统一的「Not available for this record」(DeclaredActionsBar.tsx:437-441,objectui#11811 的注释明说这是 objectstack 该声明的键)。动作的description是参数对话框的提示行,挪用不对。一把小键,但永久,今天只有一个消费者。confirmText声明记录作用域——target有${param.X}/${ctx.X},successMessage有${result.*},confirmText什么都没有。真缺口,但是锦上添花。据此席位把推荐细化为 B′: #24016 的声明 + #24020 用现有汇总字段和谓词做(先实测刷新)+ #24021 的静态文案改进,同一车道(platform-objects)一两个小 PR,不加任何 spec 键;#24018 和 #24021 的记录作用域两把键暂不开,有第二个页面需要时再点名。字母仍是您的:B(只做 #24016)/ B′(如上)/ A / C。
Card by card, on the code
packages/platform-objects/src/identity/sys-organization.object.tsdeclaresset_active_organization(:130–),leave_organization(:147–) andchange_slug(:169–) atrecord_headerwithtype: 'api'targets on the better-auth organisation routes;packages/platform-objects/src/identity/sys-invitation.object.tsdeclaresinvite_user(target: '/api/v1/auth/organization/invite-member',requiresFeature: 'organization',requiresMembershipReach: 'invite_member', paramsemailandrolewith the D8-narrowed options). An invite action onsys_organizationis the same declaration withrecordIdParam: 'organizationId',variant: 'primary'andorderbelow the others (action.zod.ts:1327makesorderthe primary-slot rule). No spec key.disabled(action.zod.ts:1505) isActionConditionInputSchema: a CEL predicate overrecordandcurrent_user; the console'scurrent_usercarriesid,email,name,positions,isPlatformAdmin,can()(expressionUser.ts);positionscarriesorg_owner/org_adminas the projection ofsys_member.role(eval-user.zod.ts:57-60). The owner count is a roll-up:summaryOperations(field.zod.ts,object,field,function: 'count',relationshipField,filteras a querywhere) oversys_member(organization_id: Field.lookup('sys_organization'),sys-member.object.ts:305). Gap to measure: whether the roll-up is maintained for rows better-auth writes through its adapter rather than the data door. The server's own refusal (YOU_CANNOT_LEAVE_THE_ORGANIZATION_AS_THE_ONLY_OWNER) stays the guard either way.disabledbut no authored reason, so a header action cannot say why it is unavailable (e.g. "You are the only owner; transfer ownership first") #24018.disabledReason→unrecognized_keysonActionSchema; the console's generic reason isDeclaredActionsBar.tsx:442. A real vocabulary gap; the action-leveldescription(action.zod.ts:988) is the param-dialog line and is not a substitute.confirmText(action.zod.ts:1334) is anI18nLabelwith no interpolation scope; interpolation is declared only ontarget(${param.X},${ctx.X}),successMessage/outcomeMessages(${result.*}) andbodyExtra({{page.<var>}}). The static copy (what is lost) is a one-line change in the same object file; the organisation's name needs the scope.Prior rulings read: #11592 (page:header ids resolve on the page object only), objectui#11811 (the generic disabled reason), #7278 / #22093 (confirm question on
description, one dialog); thread: 6227747379 (delta 1).objectstack-fleet commentedon Oct 17, 2026
Ruling: batch #358 item 3 · letter B′ · maintainer 「24034 同意」 2026-10-17T02:07Z
Director seat, summon #43 continuation 3,
session_01QJC7i981TSs9L1CGJK59Ax, on the triage seat's card (recommendation B), the director deltas 6227747379 (B) and 6227990207 (B′ — the seat's standing recommendation when the word came, each of the four widenings read against the vocabulary the spec already declares) and the premise re-run on objectstackorigin/mainbe076dad28a0and objectuid0a00d795020(opening marker 6227733044). The maintainer's word in this session's chat: 「24034 同意」 on batch #358, given after B′ was put to him; read as B′. A word from the maintainer naming B instead corrects this record.What the ruling binds (letter B′ — build with the metadata the platform already declares; no new spec key):
sys_organizationdeclares one invite action atrecord_header—type: 'api',target: '/api/v1/auth/organization/invite-member',recordIdParam: 'organizationId', paramsemailandroleassys_invitation.invite_userdeclares them,requiresFeature: 'organization',requiresMembershipReach: 'invite_member',variant: 'primary', anorderthat puts it first;leave_organizationkeepsvariant: 'danger'(honoured since objectui#12764) and falls to the overflow.leave_organization.confirmTextnames what is lost (the organisation's environments and members) in both bundles. The record-scope key is not added.sys_organizationdeclares a roll-upowner_count(summary,summaryOperations { object: 'sys_member', function: 'count', filter: role contains owner }) andleave_organizationdeclaresdisabled: "'org_owner' in current_user.positions && record.owner_count == 1"; the reason stays the console's generic text. First measurement: whether the roll-up is maintained forsys_memberrows better-auth writes; apremise_still_valid: falsereport closes leave_organization: no predicate root carries "the caller is this organisation's only owner", so the platform action cannot be disabled for the last owner before the click (better-auth refuses it only after the confirm) #24020not_planned(the server's refusal stays the guard) with no new box visit.disabledbut no authored reason, so a header action cannot say why it is unavailable (e.g. "You are the only owner; transfer ownership first") #24018 (an authored disabled reason) and leave_organization confirm names neither the organisation nor what is lost: confirmText has no declared record scope for interpolation, and a host cannot override the platform bundle #24021's record-scope key closenot_plannedciting this ruling; either is re-filed after GA when a second page needs it, on a measured ask.pm:blockeduntil platform-objects: sys_organization declares no invite action, so an organisation record page cannot offer 邀请成员 in its header (page:header ids resolve only against the object's own actions) #24016 (and leave_organization: no predicate root carries "the caller is this organisation's only owner", so the platform action cannot be disabled for the last owner before the click (better-auth refuses it only after the confirm) #24020 while it holds) land; the cloud seat re-scopes itsBlocked-by:lines, then lays the page out with the declared actions, overriding order and variant for its posture as its target item 3 allows.Not taken: A (four or five PRs and two permanent spec keys before GA), B alone (leaves the last-owner click and the confirm copy where existing metadata already reaches), C (the misplaced primary stays).
Workload of the letter:
domain:engine(platform-objects) · 1–2 small PRs (#24016 with the copy; #24020 after its measurement) · precedent: the object's existingrecord_headeractions; no spec change.Execution: #24016 and #24020 leave
pm:on-holdforpm:queue+pm:retriagewithRuled:lines (triage setsdomain:engine); #24018 and #24021 closenot_planned; cloud#2910 receives a pointer; this decision card closescompletedwith this record, andneeds-user-decisionleaves in the same act.objectstack-fleet commentedon Oct 17, 2026
Closed
completed· ruled B′ at 6228318638 (batch #358 item 3, the maintainer's 「24034 同意」 on the seat's refined recommendation) · director seat (summon #43 continuation 3,session_01QJC7i981TSs9L1CGJK59Ax) · 2026-10-17T02:09ZThe decision is recorded above and executes as #24016 (the declared invite action, carrying #24021's copy) and #24020 (the roll-up and the
disabledpredicate, after its measurement), bothpm:queue+pm:retriage; #24018 and #24021 closenot_plannedby it; cloud#2910 follows.