Repository navigation
[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
Activity
- addedpriority:p1High: required for production / M2High: required for production / M2
on Sep 8, 2026 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/main8d4690b8:- ADR-0093 / ADR-0105 D1:
single= one logical tenant; the platform bootstraps a Default Organization so the admin session carries anactiveOrganizationId(plugin-auth/src/ensure-default-organization.ts). ADR-0095 D1 and ADR-0105: undersingleLayer 0 is inert. - ADR-0131 D8 (accepted 2026-09-04): 「The engine computes the tenant read scope from posture and context —
= :tenantunderisolated,IN accessible_org_idsundergroup, nothing undersingle— and threads the same value to Layer 0 and to every driver. No layer has a NULL arm.」 D9: undersinglethe Default Organization is the only organization and the derived owner. D11: 「Postures differ only in enforcement …singleis never a reason for a row to be org-less.」 - Execution: feat(spec,drivers,objectql,plugin-security):
organization_idNOT NULL per cleared table; one predicate for Layer 0 and every driver; bothorWhereNullarms, the__global__sentinel and the #13491 ledger retire (ADR-0131 D1/D8/D9) — protocol 18 #15212 (C8, protocol 18) — 「The engine computes one tenant scope —= :tenant/IN accessible_org_ids/ none undersingle— and threads it tocomputeTenantLayer0Filterand toDriverOptions;applyTenantScopedrops bothorWhereNullarms」 —pm:blockedbehind [gate] the v18 development line is not open — ADR-0131 execution cards are blocked on this card #15193, the v18 gate: 「⛔ Nothing else of ADR-0131 ships in 17.x. A card that would land "just the harmless half" of any of D1–D13 in a 17.x release is out of order regardless of this gate.」 Maintainer, 2026-09-04: 「要 v18 才开发」.
⇒ Option A of this card is D8's
singlebranch. 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):
applyTenantScopefires on anyoptions.tenantIdand cannot see the posture (its own docblock says so);buildDriverOptionsthreadstenantIdwhenever the context carries one;computeTenantLayer0Verdictanswersnoneundersingle. 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/atstree,446ab44; ats#39 itself not readable from this session):DESIGN.md§03 「雇主 = 平台组织」 — the demo seeds onesys_organizationper employer and stampsorganization_id: orgId(employer)on the four walled objects (src/data/shared/build.ts), on a deployment declaredsingle. The platform'ssingleis 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'sambiguous-organization(atsDESIGN.mdline 303: 「本安装声明singleposture 却持有 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):
- 甲 (recommended) — no new ruling: this card →
pm:blocked,Blocked-by: #15193; feat(spec,drivers,objectql,plugin-security):organization_idNOT NULL per cleared table; one predicate for Layer 0 and every driver; bothorWhereNullarms, the__global__sentinel and the #13491 ledger retire (ADR-0131 D1/D8/D9) — protocol 18 #15212's acceptance gains one line (undersingle,/data, analytics and the memory driver answer the same user the same numbers — service-analytics: on driver-sql (sqlite) the NativeSQLStrategy answersPOST /analytics/dataset/querywithout the object-level grant or the tenant wall — a user with NO read grant on an object gets its row count (200) whereGET /data/OBJECTanswers 403; the memory driver's path refuses both #16645's table as the fixture); 17.x engine and driver untouched; ats moves to a walled posture (its side, ats#39). - 乙 — a maintainer-stated 17.x exception landing only D8's
singlebranch (engine withholdstenantIdundersingle); reverses the 2026-09-04 staging ruling;Clause-②: yes, minor + BREAKING; leaves the driver's NULL arm in 17.x beside a wall-lesssingle.
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.mdline 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
- ADR-0093 / ADR-0105 D1:
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, GitHubos-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:blockedbehind #15193) nor 乙 (a 17.x exception landing D8'ssinglebranch 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 undersingle— and threads the same value to Layer 0 and to every driver), with execution on #15212,pm:blockedbehind 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 declaredsingle— 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-decisionremoved;priority:p1 · security · domain:enginekept. Nothing on #15212, #15193 or #16645 moves by this ruling. Reopening is free if the maintainer wants 乙 later.
Generated by Claude Code
- added a commit that references this issue
on Sep 17, 2026 - added a commit that references this issue
on Sep 28, 2026
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 (getReadScopethreading) ·plugin-securitycomputeTenantLayer0Verdict(postureEnforcesWall('single') === false, boot log 「tenancy posture 'single' — Layer 0 is inert」) ·Engine.buildDriverOptions(DriverOptions.tenantIdset wheneverexecCtx.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姿态下 plugin-security 的 Layer 0 不生效:git grep -n "postureEnforcesWall" packages/plugins/plugin-security/src | head(阳性对照:git grep -c "computeTenantLayer0Verdict" packages/plugins/plugin-security/src≥ 1)。git grep -n "tenantId" packages/objectql/src/engine.ts | grep -i "buildDriverOptions\|isTenancyDisabled";git grep -n "organization_id IS NULL" packages/drivers/driver-sql/src | head。tenantId([finding] The InMemory driver silently ignores the engine's tenant scope for objects that OMIT atenancyblock — its guard only refuses an explicitenabled: true, so memory-driver runs show cross-organization rows a SQL driver refuses #16589,维护者已裁不需支持):git grep -n "tenantId" packages/drivers/driver-memory/src | head(零命中 + 阳性对照git grep -c "find(" packages/drivers/driver-memory/src≥ 1)。POST /analytics/dataset/querywithout the object-level grant or the tenant wall — a user with NO read grant on an object gets its row count (200) whereGET /data/OBJECTanswers 403; the memory driver's path refuses both #16645 正文(ats 应用,demo seed,两驱动)。具体问题
single姿态是「一个部署一个组织」。今天 driver-sql 仍按execCtx.tenantId追加(organization_id = :tenant OR organization_id IS NULL),而 Layer 0 按姿态判定「无墙」。平台管理员(viewAllRecords)的tenantId与种子数据的organization_id不一致时,sqlite 读 0 行;内存驱动与 analytics 原生 SQL 路径不加这条谓词,读全量。选项 × 真实代价
single⇒ 引擎不给驱动下发tenantId(buildDriverOptions读姿态;或 Layer 0none时清空tenantId)/data从 0 变回 12/30/40/14,与内存驱动、analytics 一致;group/isolated姿态不变(墙照旧)。推断:单租户部署里「管理员看不到自己数据」(ats#39)消失。代价:single部署里带脏organization_id的行全部可见——本就该可见single部署里管理员继续读 0业务含义直译:A = 一个店铺的老板能看见自己店里所有货;B = 老板要先证明每件货是他的,证不出来就当没有;C = 不同柜台各自发挥。
四轴
POST /analytics/dataset/querywithout the object-level grant or the tenant wall — a user with NO read grant on an object gets its row count (200) whereGET /data/OBJECTanswers 403; the memory driver's path refuses both #16645 表、ats#39)。这是「今天谁撞上」的直接答案。tenancyblock — its guard only refuses an explicitenabled: true, so memory-driver runs show cross-organization rows a SQL driver refuses #16589 裁决冲突。推荐 + 回退 + 置信缺口
推荐 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(读路径接受集变化),changesetminor+ BREAKING(single部署可见行集变宽)。B ⇒ 先撤 #16589 裁决再立卡。C ⇒ 关本卡 not planned 并在 ADR-0105 记录已知分歧。POST /analytics/dataset/querywithout the object-level grant or the tenant wall — a user with NO read grant on an object gets its row count (200) whereGET /data/OBJECTanswers 403; the memory driver's path refuses both #16645、ats#39)。tenancyblock — its guard only refuses an explicitenabled: true, so memory-driver runs show cross-organization rows a SQL driver refuses #16589 相悖。推荐 A(回退 C)。置信缺口:A 对
group/isolated姿态的无损性需 dev 实测,isTenancyDisabled与姿态的关系未逐对象核过。