Skip to content

Config-derived platform-admin standing leaves no durable record of who held it and since when — boot already computes it and throws it into a log line #18412

Description

@os-project-manager

Platform-admin standing is moving from a stored grant row to config-derived, resolved at request time (#11663 re-anchor, ADR-0131). That migration is right, but it silently drops a property the stored row had: a durable record of who held administrator standing, and from when.

Filed at the maintainer's direction (2026-09-16T09:51Z).

What is lost, and why it is not obvious

stored grant row (today's anchor) config-derived standing (the target)
who holds it a row OS_PLATFORM_OWNER_EMAIL at request time
since when row timestamp ⛔ nothing in the data
who granted it row writer ⛔ nothing in the data
revocation leaves a deleted/changed row ⛔ an env change + a restart

⇒ After the migration, 「three months ago this account performed an admin action — what made them an administrator at that moment?」 is answerable only from the deployment's environment-variable history, which the product does not keep and an auditor cannot read.

⚠️ sys_audit_log still records the ACTIONS. What disappears is the basis for the authority behind them. Those are different questions, and only the first one currently has a writer.

Measured characteristics that make this concrete

Read on origin/main:

  • standing is resolved per request from config (resolvePlatformAdminEmails, memoized; resetPlatformAdminEmailMemo exists to clear it) ⇒ removing an administrator takes effect on config change + restart, leaving no data-side trace;
  • boot already COMPUTES exactly the record that is wanted — resolvePlatformAdminStanding(ql, platformAdminConfig) builds a per-entry summary (registered / verified / which userId holds standing) and logs it at info;
  • ⇒ the information exists at boot and is thrown away into a log line.

Proposal

Write that resolved standing as an audit record at boot: per declared entry — the declared spelling, whether an account exists, whether it is verified, and which user id (if any) actually holds standing.

Why this shape:

  • it gets ADR-0131's 「no NULL grant row」 and keeps a durable, queryable record of who could administer the deployment and from when;
  • ⛔ it adds no new configuration key — the input is already parsed and the summary already assembled;
  • it makes the walled-posture story complete: config declares, boot records, the audit ledger answers 「who, since when」.

⚠️ Open questions for whoever takes it, ⛔ not decided here: which object carries it; whether it writes on every boot or only when the resolved set CHANGES (a per-boot row on a frequently-restarted rig is noise); and how it is scoped for organization ownership under ADR-0131's own no-NULL rule — ⭐ note this record must not reintroduce the very NULL-organization shape the migration is removing.

Dedup words: platform admin standing audit record · config-derived standing no durable trace · who was admin and since when · resolvePlatformAdminStanding boot log · admin revocation leaves no trace


🔴 维护者速读 —— 已裁方案的验收条件做不到,需要你一个字母

domain:services 席位(objectstack#6021)session_01QGMBhvUoyD8t5zY8xHQhnP 写于 2026-09-17T16:14Z。dev 按令停手,零文件改动、零 PR;分支是空探针。⛔ 席位不自裁 —— 这需要放宽一条你已经批过的裁决条款。

改了什么 —— 还没改,一行代码都没写。

为什么卡住 —— 你在批次 #148 批了 A:开机时把「谁是平台管理员」的变化记一条审计。裁决里加了一条硬约束:这条记录不许是「无组织归属」的行。但实测下来,这条记录没有任何组织可以归属:

  • 这棵树上根本不存在「平台组织」,而且是当初特意决定不要的 —— ADR-0131 §1.5 原话说它是「自然的修法,但是错的……存在的唯一意义就是给 NULL 换个名字」。全仓零命中。
  • 更硬的一条:裁决要求的首次开机基线那一行,是在第一个组织还没被创建之前 1.3 秒写的 —— 那一刻世界上还没有任何组织 id 可以填
  • 填某一个租户的 id 是撒谎(这是全部署的事实,塞进一个租户的墙里),而且首次开机时那个 id 也不存在;每个组织写一行则和裁决自己说的「恰好一条」冲突。

⇒ 三个候选值全部走不通。不是实现难,是这条验收条件本身做不到。

风险与代价(含回滚) —— 现在零风险,因为什么都没做。代价是:平台管理员权限的变更今天仍然查不到(这正是本卡要修的审计缺口)。两个选项都可一次 revert 回退。

席位意见 —— 荐 A。理由不是图省事:这条记录按构造就是全部署级的,ADR-0131 §1.5 已经说过「给它发明一个主人」是错的方向,所以这里的 NULL 不是那条禁令针对的 NULL —— 那条禁令针对的是本该有主人的租户行。A 就是 ADR-0131 的 D7 将来会用「删掉这一列」正式确立的形状,只是提前用 NULL 表达。⚠️ 但这要放宽你批过的条款,所以必须你说了算。

你要做的(一个动作) —— 回一个字母:
A(这条记录允许无组织归属,现在就做)/ B(不放宽任何条款,本卡压后等 v18 线开、等 D7 删掉那一列)/ C(每个组织各写一行 —— ⛔ 席位不荐,且首次开机根本做不到)


四棱分析

① 实际业务需求(实测,⛔ 不是「读起来有用」) —— 审计缺口是真的:平台管理员从「存的授权行」变成「配置推导」之后,「谁持有过、从何时起」就没有留痕了,这是审计员的常规问题,裁决也引了 Salesforce Setup Audit Trail 作对照。但「必须有非空组织」这条要求的实测拉动是零:这棵树上的部署级审计行今天已经全部tenantId ?? null 落盘 —— 四个写入点 audit-writers.ts:1375read-audit.ts:654auth-event-audit.ts:314config-change-audit.ts:197 一致如此。⇒ A 不是新造一个例外,是沿用这棵树既有的、唯一的部署级写法

② 项目长远合理性 —— 指向 A / B 同向,⛔ 反对 C。ADR-0131 的方向是 D7:sys_audit_log 直接失去 organization 列,部署级的行不再假装有主人。A 是 D7 将来会正式确立的那个形状的提前表达;B 是等 D7 真的落地再做,结构上更干净但要等 v18 线开(而 #15193 这道闸门明写:在它关闭前,任何座位、任何仓都不得派发 ADR-0131 执行卡)。C 与 ADR 的「解混」方向正相反。

③ 防 AI 写代码犯错(尤其防写元数据) —— 这一棱指向 B,是唯一真正反对 A 的力量:A 写下的正是裁决字面禁止的形状,如果不把例外白纸黑字记在卡上和代码注释里,下一个作者读到裁决就会把它「修」回去。⚠️ 反过来,C 才是真正的陷阱形状 —— 给一个没有主人的事实发明一个主人,正是 ADR §1.5 说的「给 NULL 换个名字」,而且会让部署级事实泄漏进每个租户的审计视图。

④ 创业阶段不扩散 —— 两个选项都不扩面:不加对象、不加配置键、不加公开 API。B 把能力推迟到 v18;A 现在就补上审计缺口,成本是一条需要记录在案的例外。按「创业阶段不渐进」的既有指示,⛔ 不设分阶段过渡 —— 要么现在按 A 做完,要么按 B 整张压后,⛔ 不做「先写一半」

四棱结论 —— ①④ 中立偏 A,② A/B 同向,③ 指向 B。不同向 ⇒ ⛔ 不满足代裁置信门;且本卡踩两条人工地板(安全/权限边界、已发布契约语义),⛔ 席位无权自裁。

Governing text: docs/adr/0131-total-organization-ownership-no-null-organization-id.md §1.5(逐字:「The first draft of this record gave deployment-level rows an owner … it is the natural repair and the wrong one … exists only to give NULL a new name」,本席在 origin/main 上复读)+ 同记录 D7 + 裁决批次 #148 项 2 + 闸门卡 #15193(open,明文禁止派发 ADR-0131 执行卡)。

⛔ 本块与上方速读由席位落在卡面正文(非仅评论),依「决策卡落卡必带四棱卡面块与其上的维护者速读」。dev 的终报在 issuecomment-5717560616,其中 P1/P2 两条把本席派发令里的两个判断都纠正了 —— 见席位评论。


Generated by Claude Code

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions