Skip to content

platform-objects: the Setup Users list has no column that says who holds platform-admin standing, now that the legacy role scalar leaves it (#28105 D1 follow-up) #28793

Closed

Description

@objectstack-fleet

domain:server#5 · os-project-manager · session_01FEkQ5oc56xChD6Wrc4cGSt

Ruled: 6566734829 · letter C · 2026-11-02T01:16Z
Ruling-ref: 6566734829

Filing gate: ① the follow-up card the director's ruling on #28105 binds (comment 6557746678, letter D1, maintainer 「同意」 on 2026-11-01 at 12:10Z): "a follow-up card is filed at p3". Filed by domain:server seat 5, the seat executing #28105. Graded p3 by the ruling.

Reach: every Setup admin scanning the Users list for who is a platform admin, the need #27863 stated.
Reader who acts: domain:server (packages/platform-objects for the list view; packages/core owns the predicate).

What is true today (read on origin/main d9c4cdc4e)

Wanted

Blocked-by: #28105 (it removes the role column this card's column replaces).

Dedupe: searched platform admin column users list; only #28105 and #27855. New.

Links back: #28105 (ruling 6557746678), #27863, PR #28065.

Activity

  1. objectstack-fleet commented on Nov 1, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-project-manager · session_01FEkQ5oc56xChD6Wrc4cGSt

    Unblocked · pm:queue

    ⛔ Not a claim.

  2. objectstack-fleet commented on Nov 1, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-project-manager · session_01FEkQ5oc56xChD6Wrc4cGSt

    Claim: PM loop round 1 · card thread · 2026-11-01T22:06Z
    Session: session_01FEkQ5oc56xChD6Wrc4cGSt (the seat); the card thread is session_01FEkQ5oiX3eWpVYxchQ9C3V
    Account: os-project-manager (the seat's linked user; the card's assignee)
    Branch: claude/project-thread-7qef1r (the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)
    Worktree: the card thread's own container checkout
    Domain: domain:server
    Seat: domain:server#5
    Ruling-ref: 6557746678 (director ruling on #28105, letter D1, which binds this follow-up at p3) and the seat's rulings in the order; pick announced to seats 1-3, seats 1 and 3 confirmed no clash.
    File surface: packages/platform-objects/src/identity/ (the sys_user object and its tests), the read seam that serves the derived value (measured by the dev: packages/objectql, packages/metadata-protocol or packages/core/src/security), changesets, any content/docs/** page listing the Users columns. ⛔ No packages/spec/**.
    Container & model: M, mode:thread, model and effort as the thread was opened
    Clause-②: no (expected; the dev corrects it from the diff if a published shape widens)
    Metadata-protocol: none
    Responsibility: the code is the sys_user Users list and the read path that can serve a derived value beside it · no column says who holds platform-admin standing, now that the legacy role scalar is gone · reached by every Setup admin opening Users
    Thread-read: 6564514014
    Premise: holds (read at origin/main 1e019240f, which contains PR #28810: all_users has no role column; hasPlatformAdminStanding is per-user, async, fail-closed)
    Serial constraints cleared: none needed.
    Resume-from: none — fresh from origin/main

    State: pm:queue → pm:dispatched, assigned to os-project-manager, recorded by this claim. Order: pm-orders/server/order-28793.md in the project.

  3. objectstack-fleet commented on Nov 1, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-project-manager · session_01FEkQ5oiX3eWpVYxchQ9C3V

    os-dev-report
    {
    "issue": 28793,
    "status": "needs_decision",
    "branch": "claude/project-thread-7qef1r",
    "pr": null,
    "session": "session_01FEkQ5oiX3eWpVYxchQ9C3V",
    "premise_still_valid": true,
    "summary": "Stopped before code at the ruling 3 exit, on seat 5's instruction (no file surface amendment). Measured at origin/main 1e01924: the premise holds (all_users has no standing column; hasPlatformAdminStanding is per-user, async, fail-closed), but no existing mechanism serves a per-row derived value as a list column without a new pattern. PM assumption 1 is false: a columns entry must name a DECLARED field (lint validate-list-view-field-refs errors; clients echo columns as the projection, which REST answers 400 INVALID_FIELD for an unknown plain column). The only declared field type with no column is formula (driver-sql schema-drift.ts fieldHasColumn), and CEL cannot call the predicate. No production read seam serves a per-row derived key today: the only afterFind hook in production is plugin-audit read-audit (observes, does not shape); plugin-approvals payload-redaction-middleware rewrites a stored column on result rows. Branch carries no commit; nothing pushed.",
    "tests": "No repo test added (no code). One scratch measurement, deleted after the run: real ObjectQL + SqlDriver better-sqlite3 in-memory + real SecurityPlugin (fallback member_default), sys_user/sys_member/sys_organization + the security plugin's grant objects, 50 users, Default Organization org_default, admin_full_access grant owned by org_default on usr_000, the same set granted on org_other to usr_002, median of 7 runs on the shared box. Lock VERDICT command-exit 0, Tests 2 passed. Readings: (1) grant-only: the all_users page read (8 columns, limit 50) = 0.99 ms and 1 driver read; hasPlatformAdminStanding over the page = 81.5 ms concurrent / 109.8 ms sequential and 200 driver reads (4 per user); answers: only usr_000 true, usr_002 (grant scoped to a non-default org) false. (2) OS_PLATFORM_OWNER_EMAIL = usr_001's verified email: page 0.66 ms / 1 read; predicate 53.0 ms concurrent / 75.7 ms sequential, 200 reads; answers usr_000 and usr_001 true. So per-row evaluation multiplies the page's reads by about 200x and its latency by about 50-100x on in-memory SQLite; on a networked database every read is a round trip. Not cheap under ruling 4. (3) Engine-level filter on a formula field (sys_member.display_title, system context) = INVALID_FIELD 400 'filters on display_title, a virtual formula field ... no driver materialises'. Not a stock boot: the os dev closure build failed on my own flag (see deviations), so the live probe was NOT MEASURED; it is moot with no code.",
    "gates": "NOT MEASURED: no diff, so dispatch-gates --commands derives nothing and no gate applies.",
    "dogfood_battery": "n/a: no PR",
    "mcp_calls": "3 reads (issue_read get on #28793, issue_read get_comments on #28793 twice); no MCP write",
    "api_writes": "1: this os-dev-report comment via scripts/pm/post-stamped.mjs (POST issues/28793/comments through the fleet relay); no git push, no pr_create, no label write",
    "deviations": "Ran the closure build as pnpm --filter '@objectstack/example-todo^...' build --concurrency=2: pnpm forwarded the flag into each package's build script, which broke @objectstack/cli's build script (sh syntax error, lock VERDICT command-exit 2). The packages the scratch measurement imports (core, objectql, plugin-security, platform-objects, driver-sql) built; the cli dist did not, so no stock boot was measured. Nothing was committed from that build.",
    "open_questions": [
    {
    "question": "Which mechanism serves platform-admin standing on the Setup Users list, given ruling 3 (no spec edit, existing mechanism) and ruling 4 (bounded cost)?",
    "options": [
    "A. A platform_admin formula field on sys_user (returnType boolean, constant false expression as the fail-closed default, sortable false, Admin field group so the shipped FLS withholds it from org peers like phone_number) whose value an afterFind hook registered by plugin-auth fills on a user-context read from hasPlatformAdminStanding(ql, row.id). Files: platform-objects sys-user.object.ts + the four translation bundles (en/zh-CN/ja-JP/es-ES, check-i18n-bundles --write needs a full build), plugin-auth new module + auth-plugin.ts registration + tests. Spec edit: no. ui edit: no. Cost: 200 extra driver reads per 50-row page, 53-110 ms on in-memory SQLite vs about 1 ms for the page itself. Filter and sort: refused, not silently empty: engine filter on a formula = INVALID_FIELD 400 (measured), sort refused at the REST door (metadata-protocol protocol-query-validation.ts UNMATERIALIZED_SORT_TYPES) and in the engine (assertOrderByIsMaterializable). Seat 5's objections stand: the declared expression false contradicts the served cell, and every user-context sys_user read that does not name its fields (record page, unprojected reads) pays the per-row cost; system reads skip it (the predicate reads sys_user under system context, so the hook must skip them to avoid recursion).",
    "B. A declared non-formula derived or virtual field kind outside spec. Measured: none exists. driver-sql fieldHasColumn answers no column only for formula; summary and autonumber are stored columns. A new kind is a FieldType edit in packages/spec, excluded by ruling 3.",
    "C. No column: a served answer to 'who are the platform admins'. The predicate's own header forbids widening the per-user predicate to the population question; the one population enumerator that exists is resolveAdminUserIds inside plugin-auth last-admin-guard.ts (a closure, not exported): it enumerates the in-window admin_full_access grants owned by the Default Organization plus the owner-list users, and confirms through the resolver's predicate. Exporting it as a reader and serving it through a Setup action or route is cheap (one grant read plus the owner-list users, then the predicate for the few candidates), has no metadata contradiction and no per-row cost. Files: plugin-auth (export + door or action handler), platform-objects (action or view declaration), and a route mount if it is a new door (dispatcher-plugin.ts). Spec edit: no for an action; NOT MEASURED whether an existing action result surface renders a user list without a ui edit. Filter and sort: not applicable (a separate answer, not a list column). Risk: the candidate enumeration names the two anchor sources, so a future third anchor needs this reader updated (the admin-standing-surface.ts ledger is the existing pin for that drift).",
    "D. A ui-side column fed by a dedicated read: a new door that answers standing for a page of user ids (or the population from C) and a Console grid change for the sys_user all_users view. Files: plugin-auth or rest door + dispatcher mount, ui/ grid. ui edit: yes, which ruling 6 bars unless no served mechanism can be shown. Cost: one extra request per page; server side the same 4 reads per user unless it rides C's population reader. Filter and sort: client-side over one page only.",
    "E. Batch the predicate: one grant read for the page's user ids through the same resolver. Measured: no batched form of resolveUserAuthzGrants exists (it is per-user, with request memo, cross-request cache and batch-equivalence goldens keyed per user), so this is new core code in packages/core/src/security plus the same formula-field question as A for the column itself."
    ],
    "recommendation": "C, because it is the only option that needs no spec edit, puts no contradiction into sys_user metadata and keeps the Users list read at one driver read, while answering the need #27863 states (seeing who the platform admins are). If the decision is that it must be a column on the list, A is the only no-spec, no-ui route, and it should land with E's batching later only if the per-page cost is accepted as measured."
    }
    ],
    "out_of_scope_findings": [
    "carrier: seat 5 · reading, not filed: the seat's note expected a filter on a formula field to evaluate the declared constant and return nobody; measured, the engine refuses it 400 INVALID_FIELD (and the REST door refuses the sort), so option A's filter cost is a refusal, not wrong data",
    "carrier: none · reading, not filed: reader population of all_users, read from plugin-security default-permission-sets.ts, not live-measured: member_default grants sys_user select under sys_user_self and sys_user_org_members (id in current_user.org_user_ids), so every org peer can list their organization's users; the Admin field group is withheld from member_default, viewer_readonly and external_default and kept for admin_full_access and organization_admin. Any option that serves standing to org admins tells an organization admin which of their members are platform operators, which is NOT MEASURED as readable to them today; options A and C both need that called out in the ruling"
    ]
    }


    Generated by Claude Code

  4. objectstack-fleet commented on Nov 1, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-project-manager · session_01FEkQ5oc56xChD6Wrc4cGSt

    Needs decision · 2026-11-01T22:20Z

    The dev's measure-first report (6564729313, status: needs_decision, no commit pushed) shows ruling D1's wording, "the Users list gains a column", has no clean route under this seat's order. A list column must name a declared field; the only column-less declared kind is formula, so a column means a formula field whose declared expression contradicts the cell its hook fills, and per-row standing costs about 200 driver reads per 50-row page (53–110 ms against about 1 ms for the page). No batched resolver and no non-formula derived kind exists without a spec edit.

    The seat recommends option C: no column, but a served answer to "who are the platform admins" built from the population enumerator already inside plugin-auth's last-admin-guard.ts. That changes the shape the maintainer approved in D1, so it goes to the maintainer as a decision in the seat-5 thread (options: C no column with a separate answer, recommended; A keep the column with its cost and no filter/sort; park). The privacy reading the dev flagged (serving standing to organization admins names platform operators) rides whichever is chosen.

    State: pm:dispatched → needs-user-decision, seat unassigned. The dev thread keeps its worktree for a follow-up order once ruled.

  5. 1 remaining item

  6. zhuangjianguo commented on Nov 2, 2026

    @zhuangjianguo
    Collaborator

    director#1 · zhuangjianguo · session_01QsR2jV5sBSzCYNEDgpYLJw

    Brief-claimed-by: session_01QsR2jV5sBSzCYNEDgpYLJw · batch #450 · 2026-11-02T00:46Z

    Director seat (第 56 场). Presented to the maintainer in batch #450, item 3. The card came back to the inbox through seat 5's needs-decision note 6564742308 on the dev's measure-first report 6564729313, without the decision-card body (no 一句话问题, no options × workload table, no os-decision-facets block), so this seat's increment completes it. ⛔ 不是裁决。

    前提复核(本席实读 origin/main 00918c88): packages/core/src/security/resolve-authz-context.ts:2145 exports hasPlatformAdminStanding (per user, async, fail-closed: the OS_PLATFORM_OWNER_EMAIL list, or under the single posture an unscoped admin_full_access grant); PR #28810 (1e019240f9) removed the legacy role column from all_users, so the Users list says nothing about standing today. The dev measured (6564729313): a list column must name a declared field; the only column-less declared kind is formula, whose declared expression would be a constant false that contradicts the cell a hook fills; per-row standing over a 50-row page costs about 200 extra driver reads (53–110 ms against about 1 ms for the page, in-memory SQLite). Premise holds. Re-check: git grep -n "export async function hasPlatformAdminStanding" origin/main -- packages/core/src/security/resolve-authz-context.ts.

    一句话问题: 设置里的「用户」列表现在看不出谁是平台管理员(误导人的旧 role 列已按 #28105 D1 移除);D1 当时说「加一列」,但实测加一列要么让每页多付约 200 次数据库读取,要么让元数据里声明一个与实际值相反的常量,所以要重新定:到底是要「每个用户旁边一个标记」,还是要「一张可靠的平台管理员名单」。

    Governing text: #28105 ruling 6557746678 D1 ("a follow-up card is filed at p3 … the Users list gains a column"); #9652 veto 5334874978 (nothing is written into sys_user.role); ADR-0068's 2026-10-06 note and ADR-0095 D3 (PLATFORM_ADMIN standing derives from held grants, judged only by hasPlatformAdminStanding, never a second copy of the rule). 协议声明:不改 packages/spec(ruling 3 of the order),Clause-②: no,Metadata-protocol: none。

    选项 × 真实代价 × 工作量:

    选项 做什么 客户可感知后果 工作量
    A sys_user 加 platform_admin formula 字段(声明常量 false),由 plugin-auth 的 afterFind 钩子在用户态读取时逐行填入真实判定 列表上每个用户旁有 ✓;但每页多约 200 次读、慢 50–100 倍;这一列不能筛选不能排序(引擎直接 400 拒绝);元数据里写着 false、页面上显示 true,读元数据的 AI 会被误导 domain:server · 1 PR(platform-objects 字段 + 4 个翻译包 + plugin-auth 钩子)· 无先例
    B 新增一种"派生/虚拟"字段类型 同 A 的列,无矛盾 改 packages/spec(FieldType),被本单 ruling 3 排除 · 无先例
    C(推荐) 不加列。把 plugin-auth 里已有的平台管理员枚举器(last-admin-guard.ts 的 resolveAdminUserIds:默认组织内的 admin_full_access 授权 + 所有者邮箱名单,再逐个用同一判定确认)导出,作为一个设置动作/路由对外回答「谁是平台管理员」,用户列表页放一个入口 管理员一键得到一张准确、不可能漏判的名单;列表本身仍一次读完;只对有平台管理员资格的人开放,组织管理员看不到谁是平台运维 domain:server · 1 PR(plugin-auth 导出 + 动作处理器,platform-objects 动作声明);动作结果能否不改 ui 就渲染成用户列表 NOT MEASURED,若不能则 ui 车道再补 1 个小 PR · 先例:last-admin-guard.ts 已在用同一枚举
    D 新开一个按页回答资格的接口,前端表格加一列 同 A 的列,少了元数据矛盾;每页多 1 个请求 domain:server + domain:ui · 2 PR · ruling 6 禁止 ui 改动除非证明无服务端方案 · 无先例
    E 把判定批量化(一次授权读取覆盖整页) 代价降下来,但列本身还是 A 的 formula 矛盾 packages/core 新代码 + A · 无先例

    业务含义直译: A = 在花名册每一行旁边贴标签,但贴之前要逐人跑一趟人事部;C = 人事部直接给你一张「管理员名单」,要看就打开它;D = 让前端自己去问;E = 先把人事部查询批量化再贴标签。

    四维(业务立场):

    • ① 长远:管理员要回答的是「谁能管这个平台」这个审计问题,主流平台都用一张专门的名单回答(Google Workspace 的 Admin roles 页、Salesforce 的 System Administrator 视图),不是把它做成每行一个排序列。C 把它收成一个只读入口,复用唯一的判定与唯一的枚举器(ADR-0095 D3 的"never a second copy");A 在 sys_user 元数据里留下一条"声明 false、实际 true"的永久矛盾;D/E 各加一个表面。
    • ② 拉动:Setup → Users list has no Status (banned) or Role column, so a banned user or an admin cannot be spotted from the list (#27521 row 15) #27863 的需求是"在用户列表看出谁是平台管理员";今天没有任何入口;C 一个 PR 就回答,A 回答得更贵且不能筛选。
    • ③ 防 AI 犯错:A 的声明值与服务值相反,AI 读 schema 会把所有人判成非管理员(静默错误);C 的名单由同一判定逐个确认,fail-closed,错了也是"少列"不是"多列"。
    • ④ 不扩散:C 不加字段、不改 spec、不加列类型;A 加一个带矛盾的字段;B 改 spec;D 加接口与 ui 列。

    只看①选 C;②③④ 是否翻转:否。

    推荐:C。 置信缺口:动作结果面能否直接渲染用户列表未测,若需要 ui 改动则是一个小 PR,不翻字母。执行参数同笔裁定:(1) 名单只对持平台管理员资格的调用者开放(组织管理员看不到平台运维名单——dev 报告的隐私点);(2) 导出的枚举器挂进 admin-standing-surface.ts 台账,日后第三个资格来源出现时门禁会红。不取:A(贵、不能筛选、元数据撒谎),B(改 spec),D(ui 先行),E(新 core 代码却仍留 A 的矛盾)。

    裁后执行: C ⇒ 本卡摘 needs-user-decision 回 pm:queue(domain:server p3),seat 5 按原 worktree 重派,验收 = 平台管理员在设置→用户页一键得到名单、名单与 hasPlatformAdminStanding 逐人一致、非平台管理员得到拒绝、列表页请求数不变;A ⇒ 同车道 1 PR,另记元数据矛盾为已接受偏差;D ⇒ 先立 ui 卡。

    os-decision-facets
    ① 长远:C 用一张专门名单回答审计问题,复用唯一判定与枚举器;A 留下声明 false / 实际 true 的元数据矛盾
    ② 拉动:#27863 今天无入口;C 一 PR 回答
    ③ 防 AI 犯错:A 的 schema 对 AI 撒谎(静默);C fail-closed,错只会少列
    ④ 不扩散:C 不加字段不改 spec;A/B/D/E 各加一个表面
    Prior rulings read: platform.?admin standing|hasPlatformAdminStanding over origin/main AGENTS.md, docs/adr, packages/spec/src → ADR-0068 note 2026-10-06, ADR-0095 D3 (standing from grants, one predicate), ADR-0057 :210; thread: 6557746678 (#28105 D1), 5334874978 (#9652 veto), 6564729313 (dev measure), 6564742308 (seat 5)


    Generated by Claude Code

  7. zhuangjianguo commented on Nov 2, 2026

    @zhuangjianguo
    Collaborator

    director#1 · zhuangjianguo · session_01QsR2jV5sBSzCYNEDgpYLJw

    Ruling: batch #450 item 3 · letter C · maintainer 「同意」 2026-11-02T01:12Z

    Director seat (第 56 场; decision card completed and brief-claimed above in 6566203119, presented in the 总监决裁 thread as batch #450 item 3 with the business read-out). The maintainer answered the batch verbatim: 「同意」 = every item's recommendation, so this card's letter is C: no derived column on the Users list; a platform-admin roster answered by the one enumerator.

    What the ruling binds (letter C):

    • sys_user gains no platform_admin field, formula or otherwise; packages/spec is untouched (ruling 3 of platform-objects: the Setup user screens show the raw stored platform role (user, admin) because sys_user.role is a text field #28105 stands); the Users list page stays one read per page.
    • plugin-auth's existing enumerator (last-admin-guard.ts, resolveAdminUserIds: the admin_full_access grants in the default organisation plus the owner-email list, each confirmed through hasPlatformAdminStanding) is exported and served as a Setup action or route that answers "who holds platform-admin standing", and the Users list page carries one entry to it.
    • The roster is served only to a caller who holds platform-admin standing themselves; an organisation administrator is refused and learns nothing about platform operators (the privacy point in the dev's report 6564729313).
    • The exported enumerator is pinned in the admin-standing-surface.ts ledger, so a third source of standing reds the gate instead of silently diverging (ADR-0095 D3, one predicate, never a second copy).
    • Not taken: A (a column that costs about 200 extra reads per page, cannot filter or sort, and declares false in metadata while showing true), B (a new field type in spec), D (a per-page endpoint plus a ui column, ui first), E (batching the predicate but keeping A's contradiction).
    • Confidence gap, carried into execution: whether the action's result renders as a user list with no ui change was not measured; if it cannot, the ui part is one small follow-up PR and does not turn the letter.

    Workload of the letter: domain:server · one PR (plugin-auth export + action handler + ledger pin, platform-objects action declaration and its four translation packs) · precedent: last-admin-guard.ts already runs the same enumeration; no same-shape landed PR with line counts. Plus, if needed, one small domain:ui PR for the roster rendering.

    Execution: domain:server seat 5 re-dispatches on the original worktree with Ruling-ref: naming this comment; the needs-user-decision → pm:queue stroke (p3 stands) is that seat's. Acceptance: a platform administrator reaches the roster from Setup → Users in one step; the roster agrees user by user with hasPlatformAdminStanding; a non-platform-admin caller is refused; the Users list page's request count is unchanged. The Ruled: body line is written by this act.


    Generated by Claude Code

  8. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-steve · session_01ADHkmQKwKFCp1m6Pb6Q8c1

    Claim: PM loop round 10 · card thread · 2026-11-02T13:05Z
    Session: session_01ADHkmQKwKFCp1m6Pb6Q8c1 (the seat); the card thread is named in a follow-up claim once it reports its branch
    Account: os-steve (the seat's linked user; the card's assignee)
    Branch: pending: the card thread's harness-pinned branch, written into a follow-up claim before the dev starts
    Worktree: the card thread's own container checkout (the original claude/project-thread-7qef1r is gone from the remote, so the work starts fresh)
    Domain: domain:server
    Seat: domain:server#5
    Ruling-ref: 6566734829 (batch #450 item 3 · letter C · no derived column; a platform-admin roster answered by the one enumerator)
    File surface: packages/plugins/plugin-auth/src/ (last-admin-guard.ts: export the enumerator resolveAdminUserIds, plus the roster action or route handler), packages/platform-objects/src/identity/ (the Setup → Users roster action declaration and its four translation packs), packages/core/src/security/admin-standing-surface.ts (the ledger pin for the exported enumerator), their tests, changeset. ⛔ No packages/spec/**, and no sys_user field.
    Container & model: M, mode:thread, model: the thread's default judgement tier, effort: high (identity surface)
    Clause-②: no
    Metadata-protocol: none
    Responsibility: the code is plugin-auth's admin-standing enumerator and the Setup Users page · a platform administrator cannot see who else holds platform-admin standing · reached by platform operators
    Thread-read: 6566734829
    Premise: holds (read at origin/main 5780fa49d: resolveAdminUserIds lives in plugin-auth/src/last-admin-guard.ts unexported; sys_user has no platform-admin column)
    Serial constraints cleared: no open PR touches last-admin-guard.ts or admin-standing-surface.ts; the dev re-reads open PRs on these paths before opening its own.
    Resume-from: none: fresh from origin/main

    State: pm:queue → pm:dispatched, assigned to os-steve, recorded by this claim.

  9. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-steve · session_01ADHkmQKwKFCp1m6Pb6Q8c1

    Claim: PM loop round 10 · card thread · branch named · 2026-11-02T13:08Z (supersedes 6577882461, which had the branch pending)
    Session: session_01ADHkmQKwKFCp1m6Pb6Q8c1 (the seat); the card thread is session_01ADHkmQKiS3hSX11hKXCKNN
    Account: os-steve (the seat's linked user; the card's assignee)
    Branch: claude/project-thread-ymgvae (the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)
    Worktree: the card thread's own container checkout (the original claude/project-thread-7qef1r is gone from the remote, so the work starts fresh)
    Domain: domain:server
    Seat: domain:server#5
    Ruling-ref: 6566734829 (batch #450 item 3 · letter C · no derived column; a platform-admin roster answered by the one enumerator)
    File surface: packages/plugins/plugin-auth/src/ (last-admin-guard.ts: export the enumerator resolveAdminUserIds, plus the roster action or route handler), packages/platform-objects/src/identity/ (the Setup → Users roster action declaration and its four translation packs), packages/core/src/security/admin-standing-surface.ts (the ledger pin for the exported enumerator), their tests, changeset. ⛔ No packages/spec/**, and no sys_user field.
    Container & model: M, mode:thread, model: the thread's default judgement tier, effort: high (identity surface)
    Clause-②: no
    Metadata-protocol: none
    Responsibility: the code is plugin-auth's admin-standing enumerator and the Setup Users page · a platform administrator cannot see who else holds platform-admin standing · reached by platform operators
    Thread-read: 6577882461
    Premise: holds (read at origin/main 5780fa49d: resolveAdminUserIds lives in plugin-auth/src/last-admin-guard.ts unexported; sys_user has no platform-admin column)
    Serial constraints cleared: no open PR touches last-admin-guard.ts or admin-standing-surface.ts; the dev re-reads open PRs on these paths before opening its own.
    Resume-from: none: fresh from origin/main

    State: pm:queue → pm:dispatched, assigned to os-steve, recorded by this claim.

  10. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-steve · session_01ADHkmQKiS3hSX11hKXCKNN
    os-dev-report

    {
      "issue": 28793,
      "status": "done",
      "branch": "claude/project-thread-ymgvae",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/29427",
      "session": "session_01ADHkmQKiS3hSX11hKXCKNN",
      "premise_still_valid": true,
      "summary": "Ruling C implemented. last-admin-guard.ts's standing reads move, text unchanged, into a module-level createStandingReader shared by the guard and the new export resolveAdminUserIds(engine, { standing: 'administrators' | 'platform-admins', maxScan }), where platform-admins skips the organization owner/admin half. POST /api/v1/auth/admin/list-platform-admins (gateAdmin, then the handler confirms the caller via hasPlatformAdminStanding; candidates confirmed one by one through the predicate; no body read) feeds a new sys_user list_toolbar action list_platform_admins whose resultDialog draws the roster as a code-list. Measured: it renders as a list with no ui change. ADMIN_STANDING_PLATFORM_ENUMERATION in core's admin-standing-surface.ts classifies every standing input as a candidate source or not, and plugin-auth's pin requires a sole-source fixture per candidate, so a third source of standing reds a gate.",
      "tests": "Head 6b5dd985, after the origin/main merge and a closure rebuild, all under os-verify-lock. plugin-auth test: Test Files 212 passed, Tests 3655 passed / 10 skipped. platform-objects test: 109 files, 1507 passed. core test: 129 files, 2913 passed. typecheck on the 3 packages: exit 0. New: platform-admin-roster.pin.test.ts (12, real ObjectQL + better-sqlite3 + the real hasPlatformAdminStanding), sys-user-platform-admin-roster-action.test.ts (4), 4 classification tests in admin-standing-surface.test.ts. Ablations via ablation-replace, each restored with blob == HEAD: (1) caller check -> if(false): 4 refusal cases red, 'expected 200 to be 403'. (2) membership-half skip removed: the org-owner exclusion test red, while agreement stayed green because the predicate confirmation still drops the org owner. The first attempt was refused by the tool (replacement was a substring of the anchor) and redone. (3) owner-email half disabled: 5 red incl. agreement, expected [usr_grant] to equal [usr_grant, usr_owner]. (4) sys_member dropped from notCandidates: core classification red. (5) third candidate added in core src, core rebuilt, ablation-dist-preflight marker present in 2 dist files: the plugin-auth pin red with its 'no fixture here: teach the enumerator' message. Restore: core rebuilt, --absent marker gone from 14 files, tree clean. UI measurement: a scratch ui/ vitest rendering ActionResultDialog with this resultDialog and the route body passed 1/1 (the rows' code text equals entries). The scratch file was deleted.",
      "gates": "dispatch-gates --commands at 6b5dd985: 72 derived. --ran with exit codes recorded: 71 run, 1 NOT-MEASURED, 0 UNRUN. 70 exited 0. check-plugin-teardown-shape --self-test exited 3 (shallow clone lacked the pinned control commit 621a48760), then 0 after fetching that object. check:dual-build-cjs-loads: self-test pass, scan exit 3 PREREQUISITE NOT MET (10 packages outside the closure have no dist), NOT MEASURED locally, CI Lint & Repo Gates reads it. Lint: eslint --no-inline-config over the 16 touched ts/mjs files, exit 0. CI check-runs: in_progress at report time.",
      "dogfood_battery": "n/a: no ! in the PR title and no (narrowing) arm. The dogfood sweep admin-route-nonadmin-refusal gained this route's entry; CI runs it.",
      "mcp_calls": "3: issue_read get #28793, issue_read get_comments #28793 (x2, pages 1 and 2). No MCP write.",
      "api_writes": "2 through the fleet relay: pr_create (repository_dispatch fw-20261102T144056Z-4d8354 -> PR #29427 draft, plus its assignee leg, os-steve), and this os-dev-report comment via post-stamped.mjs (POST issues/28793/comments). Plus git push (not REST). REST reads via gh api, no writes: search/issues x4, pulls/{n}/files x4 (open-PR overlap check), pulls/29427 x2 (read-back).",
      "line_budget": "17 files, +846 / -14 at 6b5dd985 vs merge base (dispatch-gates count 860, under the 3000 threshold).",
      "files_changed": [
        ".changeset/28793-platform-admin-roster.md",
        "packages/core/src/security/admin-standing-surface.ts",
        "packages/core/src/security/admin-standing-surface.test.ts",
        "packages/core/src/security/index.ts",
        "packages/plugins/plugin-auth/src/last-admin-guard.ts",
        "packages/plugins/plugin-auth/src/platform-admin-roster.ts",
        "packages/plugins/plugin-auth/src/platform-admin-roster.pin.test.ts",
        "packages/plugins/plugin-auth/src/auth-plugin.ts",
        "packages/plugins/plugin-auth/src/auth-route-ledger.ts",
        "packages/plugins/plugin-auth/src/auth-route-ledger.conformance.test.ts",
        "packages/platform-objects/src/identity/sys-user.object.ts",
        "packages/platform-objects/src/identity/sys-user-platform-admin-roster-action.test.ts",
        "packages/platform-objects/src/apps/translations/{en,zh-CN,ja-JP,es-ES}.objects.generated.ts",
        "packages/qa/dogfood/test/admin-route-nonadmin-refusal.dogfood.test.ts"
      ],
      "deviations": "(1) pr_create carries assignees [os-steve]: os-dev.md says no assignees, but AGENTS.md (which wins) says the PR's assignee mirrors the card's, set by the executor at pr_create. Flagged in open_questions. (2) Read-only REST via gh api beyond search (PR files, PR read-back). (3) Card-thread checkout: the primary checkout was detached and a linked worktree ../objectstack-issue-28793 was made on the pinned branch, to satisfy the worktree hook. (4) Forced test edits named in the PR: auth-route-ledger.conformance.test.ts objectstack-mount list, dogfood admin-route sweep expectation table.",
      "open_questions": [
        {
          "question": "Should sys_user.list_platform_admins be assessed for agent exposure (ADR-0011 identity-actions note)?",
          "options": [
            "A leave it human-only (no ai block), as shipped",
            "B assess it as bucket E (read-only, no credential) in a governed ADR-0011 amendment plus an ai block"
          ],
          "recommendation": "A: the roster names platform operators, and agent reach should be a deliberate maintainer ruling, not a rider here."
        },
        {
          "question": "The roster renders as plain text rows (Name, email) with per-row copy, not links to the user records. Is a richer list wanted?",
          "options": [
            "A no: the ruling's need (see who the platform admins are) is met with no ui change",
            "B one small domain:ui PR to render roster rows as record links"
          ],
          "recommendation": "A: measured to render with no ui change; B only if Steve asks."
        },
        {
          "question": "os-dev.md forbids assignees on pr_create while AGENTS.md says the executor sets the PR assignee to mirror the card. Which binds?",
          "options": [
            "A AGENTS.md (followed here)",
            "B os-dev.md; the seat removes the assignee"
          ],
          "recommendation": "A: AGENTS.md declares it wins on conflict. The os-dev.md line should be reconciled by its owner."
        },
        {
          "question": "Changeset level: patch on plugin-auth, platform-objects and core although new exports land. OK?",
          "options": [
            "A patch (repo rule: Metadata-protocol none -> patch)",
            "B minor"
          ],
          "recommendation": "A: follows the Post-Task Checklist rule. Clause-② no holds: a new read for a narrower audience, and nothing an existing caller relied on changed."
        }
      ],
      "security_findings": "none",
      "out_of_scope_findings": [
        "carrier: none · noted, not filed: the dogfood sweep's header prose says objectstack-gate (11 routes) while the table held 12 before this PR (13 now). Stale prose, left alone.",
        "carrier: none · noted: open PR #29387 also edits packages/core/src/security/index.ts (export list), so at most a mechanical conflict."
      ]
    }
  11. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-steve · session_01ADHkmQKwKFCp1m6Pb6Q8c1

    Landed · #28793 · 2026-11-02T17:06Z

    reading value
    PR #29427 MERGED by the merge queue at 2026-11-02T16:02:48Z, merge commit 92b7bbd127 (head 365488e517, ACCEPT on the PR)
    this card closed by Fixes #28793

    State: pm:dispatched removed. The card thread is released.

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

Metadata

Metadata

Assignees

Labels

area:identityLogin and identity — sign-up, sessions, organization membership, SSOdomain:serverDomain lane — seat card indexed by label:pm:seatenhancementNew feature or requestpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions