Skip to content

[Decision] Under single posture, which tenant wall is the platform's? driver-sql's posture-independent tenantId auto-scope answers a platform admin 0/0/0/0 on /data while the engine path, analytics and the memory driver answer 12/30/40/14 #16934

Description

@os-zhuang

Split out of #16645 (condition 5 of its triage rubric), which closes when PR #16860 merges and would otherwise take this open question with it. The dev seat on #16860 escalated it as needs_decision; the PM seat concurred; no ruling exists. Filed by the director seat at 17:1xZ, 2026-09-08.

Governing text: ADR-0105 (tenancy postures) · ADR-0021 D-C (getReadScope threading) · plugin-security computeTenantLayer0Verdict (postureEnforcesWall('single') === false, boot log 「tenancy posture 'single' — Layer 0 is inert」) · Engine.buildDriverOptions (DriverOptions.tenantId set whenever execCtx.tenantId !== undefined && !isTenancyDisabled(schema) && !isFederated — reads no posture) · maintainer ruling 2026-09-08 on #16589, verbatim 「16589 内存驱动不需要支持多租户,业务上没有任何意义啊」.

一句话问题

同一个 single 姿态的部署里,平台管理员在 sqlite 上走 /data 读到的是 0 条,走 analytics 或换成内存驱动读到的是 12 / 30 / 40 / 14 条——两道「租户墙」各说各话,客户看到的是「同一个人、同一份数据,换个接口或换个驱动,数就变了」。

前提(带 re-check 命令)

具体问题

single 姿态是「一个部署一个组织」。今天 driver-sql 仍按 execCtx.tenantId 追加 (organization_id = :tenant OR organization_id IS NULL),而 Layer 0 按姿态判定「无墙」。平台管理员(viewAllRecords)的 tenantId 与种子数据的 organization_id 不一致时,sqlite 读 0 行;内存驱动与 analytics 原生 SQL 路径不加这条谓词,读全量。

选项 × 真实代价

选项 做什么 客户可感知后果(实测/推断)
A(推荐) 姿态是唯一权威:single ⇒ 引擎不给驱动下发 tenantId(buildDriverOptions 读姿态;或 Layer 0 none 时清空 tenantId) sqlite /data 从 0 变回 12/30/40/14,与内存驱动、analytics 一致;group / isolated 姿态不变(墙照旧)。推断:单租户部署里「管理员看不到自己数据」(ats#39)消失。代价:single 部署里带脏 organization_id 的行全部可见——本就该可见
B driver-sql 的自动限定是平台墙:其他路径(内存驱动、analytics 原生 SQL)补上同一谓词 与 #16589 裁决相悖(内存驱动不做多租户);analytics 补墙会把求职者的 RLS 限定 9 也归零(#16860 验收备注第 5 条实测);single 部署里管理员继续读 0
C 各走各的,只记档 同一部署两种答案成为长期状态;每个新读路径都要再选一次

业务含义直译:A = 一个店铺的老板能看见自己店里所有货;B = 老板要先证明每件货是他的,证不出来就当没有;C = 不同柜台各自发挥。

四轴

推荐 + 回退 + 置信缺口

推荐 A。回退 C(暂不动,只在 #16645 关闭后保留本卡)。置信缺口:未在真实多组织(group / isolated)部署上测 A 是否影响墙——A 只改 single 分支,但 buildDriverOptions 的读姿态位置需要 dev 实测确认不误伤;isTenancyDisabled(schema) 与姿态的关系未逐对象核过。

裁后执行

A ⇒ domain:engine 卡:buildDriverOptions(或 Layer 0 verdict 处)在 single 姿态下不下发 tenantId,两驱动等价 pin(sqlite 与内存在 single 下同数;group 下 sqlite 仍加墙),Clause-②: yes(读路径接受集变化),changeset minor + BREAKING(single 部署可见行集变宽)。B ⇒ 先撤 #16589 裁决再立卡。C ⇒ 关本卡 not planned 并在 ADR-0105 记录已知分歧。

Activity

  1. huangyiirene commented on Sep 9, 2026

    @huangyiirene
    Collaborator

    Analysis correction — this question is already ruled by ADR-0131 D8; the card's Governing text: omitted it (director seat, summon #18, session_017Js5kTpTtxieBjPyScgxJ3, 2026-09-09T02:3xZ)

    ⛔ Not a ruling. This supersedes the recommendation this seat presented in batch #104 (chat, 02:0xZ), which was built on the card's own governing-text list and is withdrawn.

    What the corpus says, read this summon on origin/main 8d4690b8:

    ⇒ Option A of this card is D8's single branch. It is ruled, scheduled and gated; it is not an open question, and landing it in 17.x is barred by the staging ruling unless the maintainer reopens that.

    Why the two walls disagree today (the card's mechanism is right; the framing is not): applyTenantScope fires on any options.tenantId and cannot see the posture (its own docblock says so); buildDriverOptions threads tenantId whenever the context carries one; computeTenantLayer0Verdict answers none under single. That is the pre-D8 state ADR-0131 §1 describes, not a new defect.

    Why the ats deployment reads 0 rows (measured in the objectstack-ai/ats tree, 446ab44; ats#39 itself not readable from this session): DESIGN.md §03 「雇主 = 平台组织」 — the demo seeds one sys_organization per employer and stamps organization_id: orgId(employer) on the four walled objects (src/data/shared/build.ts), on a deployment declared single. The platform's single is one organization; the admin's active organization is the Default Organization; the driver arm hides every employer-stamped row. The same shape is refused on the write side by #8844's ambiguous-organization (ats DESIGN.md line 303: 「本安装声明 single posture 却持有 2 个组织」, ats#56 / #16659). ats's own design says the split is built for a walled posture and was waiting on #16215 (merged 2026-09-07) and #16137 (closed 2026-09-08, PR #16719) — both landed, so an open-only install can raise the wall today.

    Options as they actually stand (B and C are excluded by D8 and by the #16589 ruling 5580215080):

    Recorded for the process: this seat presented the original A/B/C frame without running the director charter's pre-presentation corpus grep (references/lanes/director.md line 45); the card as filed listed ADR-0105 / ADR-0021 and not ADR-0131. Filed as a skills-lane finding.

    维护者速读(更正稿)

    事情:「single 姿态下哪道租户墙是平台的」已经由 ADR-0131 D8 定了——引擎按姿态算一次谓词,single 下不算,同一个值给安全层和每个驱动;驱动自己的 NULL 臂退役。执行卡 #15212 排在 v18,被闸门 #15193 挡着,你 09-04 裁「要 v18 才开发」。今天 sqlite 上管理员读 0 行,是 ats 演示应用把「雇主 = 组织」建在 single 姿态上——平台的 single 只有一个组织;ats 自己写明这套设计是给带墙姿态准备的,它等的两张卡都已落地。

    选项:甲(推荐) 本卡不另裁,转为阻塞在 #15193 后随 #15212 执行;ats 切到带墙姿态。乙 你明示在 17.x 开例外,只做「single 下引擎不给驱动下发租户限定」这一半——要推翻 09-04 的分期裁决。

    你要做的:回一个字,甲 或 乙。


    Generated by Claude Code

  2. os-bill commented on Sep 9, 2026

    @os-bill
    Collaborator

    Ruling recorded — CLOSE (director seat, summon #20, decision batch #105 item 3, 2026-09-09T04:3xZ)

    Provenance (who / verbatim / where): maintainer, live PM chat with this seat (session_01Tep4AYXZvyBA7jsvne5KZV, GitHub os-bill), 2026-09-09T04:3xZ, replying to batch #105 in which this card was item 3 presented as 甲 / 乙 (summon #18's corrected frame, 5594769261) with the recommendation 甲. Reply, verbatim: 「16934 关闭 ;其他同意」.

    Ruled. This card closes. Neither 甲 (park it as pm:blocked behind #15193) nor 乙 (a 17.x exception landing D8's single branch alone) is taken: no new ruling on the substance, no 17.x exception. The question the card asks is already answered by ADR-0131 D8 (the engine computes the tenant read scope from posture and context — nothing under single — and threads the same value to Layer 0 and to every driver), with execution on #15212, pm:blocked behind the v18 gate #15193 (maintainer 2026-09-04: 「要 v18 才开发」). The ats reading (platform admin 0 rows on sqlite) is the ats deployment's posture choice — one organization per employer on a deployment declared single — and is that app's to move (ats#39); it is not a 17.x platform defect.

    State, one write: closed not_planned; needs-user-decision removed; priority:p1 · security · domain:engine kept. Nothing on #15212, #15193 or #16645 moves by this ruling. Reopening is free if the maintainer wants 乙 later.


    Generated by Claude Code

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