Repository navigation
docs(adr): ADR-0147 — membership-scoped RLS lowers to an engine-internal org-member semi-join leaf; the visible set stays exactly what it is (Proposed) - #29501
Conversation
…nal org-member semi-join leaf (Proposed) Proposes that `FIELD in current_user.org_user_ids` keep its spelling and lower to a branded sibling of ReadableIds that the SQL family compiles to `IN (SELECT user_id FROM sys_member WHERE organization_id = :org ...)`, with the caller OR-ed in from the resolver's own (org, caller) anchor and the exact completion-read array as the floor for every evaluator that does not compile it. A same-set invariant (D-0) holds the visible set to exactly what the resolver answers today; options that change it are listed as rejected. ADR-0057-erp D3 gains an amendment note: the `org_user_ids` cap it cites no longer exists. Index regenerated. Claude-Session: https://claude.ai/code/session_01ADHkmSNasUm3e1dfbV5aaD Co-authored-by: Claude <noreply@anthropic.com>
…S predicate Claude-Session: https://claude.ai/code/session_01ADHkmSNasUm3e1dfbV5aaD Co-authored-by: Claude <noreply@anthropic.com>
|
domain:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut Contract-review-claim: session_01ADHkmSduNGC2SZXszyrNut · head f3a117f · 2026-11-02T16:41Z The lane seat reviews this head itself at |
|
domain:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut Contract reviewServed-tier: Inputs read: card #29360 (body, triage ① Derived judgments
② Semver level
③ Boundary flagsDev deviations (os-dev-report
Open questions (judged, not stood in for):
Security findings: withheld by the dev; none of my own beyond ④. ④ Design judgmentThe bar (triage Two places the record's own invariant is falsified by the live tree, both repairable in the record: Must-fix 1 — ADR-0147 reverses ADR-0139 D1's "⛔ No second leaf" without saying so, and ADR-0139 carries no pointer. Must-fix 2 — D-d's evaluator census misses the analytics raw-SQL read-scope compiler, which today compiles the array and would refuse the leaf. Should-fix (none changes S; each is a sentence or a row):
Implemented-by: VERDICT: FAIL |
|
domain:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut 待裁:ADR-0147 的新公开名字与一处有意保留的不对称(#29360)
|
… pointer notes; the analytics read-scope compiler takes the floor Contract review 6583043471 on PR #29501: - must-fix 1: ADR-0147 names ADR-0139 D1 ("No second leaf") and D3 ("No DriverCapabilities bit returns") as revisited for its one leaf, and ADR-0139 gains a dated append-only pointer note at each; Amends and Acceptance criteria list all three notes. - must-fix 2: D-d gains the service-analytics compileScopedFilterToSql row (floor, chosen up front), the analytics lane in Consumers and V1, and an analytics pin in D-g. - should-fix: the explainer publishes the array as readFilter; the Consequences say the organization-membership read goes (Leg 1's own sys_member read stays) and drop the failure-surface claim; the floor substitution descends into composed ReadableIds scopes, with a nested pin; D-e cites engine.ts ScopedContext[SEED_USER_GRANTS]; O4 is not a set change. Claude-Session: https://claude.ai/code/session_01ADHkmSNasUm3e1dfbV5aaD Co-authored-by: Claude <noreply@anthropic.com>
…0057-erp gains its Status-line Amended entry Seat rulings on PR #29501 round 2: naming is the lane seat's call, so O1 names OrgMemberIds as proposed and leaves the driver-declaration name to the S1 spec card, with no maintainer gate; ADR-0057-erp's Status line gains one inline Amended entry pointing at its D3 note (after the ADR-0131 entry; the indexer keys on the earliest keyword, Accepted). Claude-Session: https://claude.ai/code/session_01ADHkmSNasUm3e1dfbV5aaD Co-authored-by: Claude <noreply@anthropic.com>
|
domain:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut Withdrawn: the decision request 6583078458 on this PR. Under pm-dispatch SKILL.md 〈升级与决策〉 both of its questions are the seat's call, not a maintainer fork. (1) The leaf name Prompted by the maintainer's question at 2026-11-02T17:03Z. |
|
domain:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut Contract reviewServed-tier: ① The two must-fixes — both closedMust-fix 1 (ADR-0139 D1/D3 revisited without saying so, no pointer) — closed. ADR-0147's header now says it: "Builds on" names the two prohibitions it revisits for this one leaf, with anchors into D-a and D-d; "Amends" lists three places (ADR-0139 D1, ADR-0139 D3, ADR-0057-erp D3), "each by a dated, append-only pointer note and nothing else", and says which Status lines move (0057-erp: one inline entry) and which do not (0139). D-a gains the bullet "A second leaf, so ADR-0139 D1 is revisited for it", with D1's reason (one brand, one meaning) and why it is kept (the second meaning gets its own brand, never a mode of Must-fix 2 (the analytics raw-SQL read-scope compiler missing from D-d) — closed. D-d's table gains the row for ② The five should-fixes — all closed
O1 reworded — consistent with the record's own history: "The name." bullet in D-a ( ③ The same-set invariant, re-tried where the delta touched it
Nothing in the delta changes S on any path; D-0 still holds against the code the record cites. ④ Index and gates
⑤ RemainingNo must-fix remains. One should-fix, new and small (a sentence; no set change):
Implemented-by: VERDICT: PASS |
|
domain:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut Head-sha: 维护者速读
席位意见:可批。契约复审在 f3a117f 判 FAIL(6583043471,两处遗漏),在现 head 的 delta 判 PASS(6583780256),两个 must-fix 与五个 should-fix 都已关;CI 全绿。 席位核对 — ACCEPT ·
|
|
domain:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut Hand-off: seat 2 is going idle (the maintainer's wind-down for the spec lane), and this PR is the only seat-2 item left.
|
Requested by Steve · project thread
Fixes #29360
Clause-②: no
Metadata-protocol: none
维护者速读
id in current_user.org_user_ids这类成员范围 RLS 不再每次请求预读整个组织成员列表,而是下推为数据库子查询;另给 ADR-0139 D1/D3 与 ADR-0057 D3 各加一条带日期的指针注记。sys_user读取直接 500。docs/adr/0147-…md的 D-0(不变式)、D-d(不编译的驱动走精确数组兜底,绝不拒绝)、D-e(读取方清单)。node scripts/check-adr-symbol-anchors.mjs && node scripts/check-adr-links.mjs,预期两行 ✅,ADR-0147 的每个代码锚点都能解析。OrgMemberIds(驱动编译声明的名字由 spec 卡定),无需你另行确认。What changes
docs/adr/0147-membership-scoped-rls-lowers-to-an-engine-internal-org-member-semi-join-leaf.md, StatusProposed. It decides D-a to D-g as ADR card: membership-scoped RLS (id in current_user.org_user_ids) lowers to an engine-internal semi-join leaf instead of a pre-resolved id array, removing the per-request completion read and the bind-parameter ceiling #29360 lists them (the core/security: org_user_ids is built from a sys_member read capped at 1000 rows, so in an organization with more than 1000 members the rest of the members' sys_user rows are invisible to every non-admin user #29281 design, os-dev-report6577634380; seat ruling6577690137item 4), plus D-0, the same-set invariant triage6580364238asked for as a decision of its own: the visible set isS(org, caller) = {caller} ∪ {non-blank sys_member.user_id of the active org},{caller}with no active org, and every lowering answers exactly S on every driver, posture, polarity and size.FIELD in current_user.org_user_idslowers to a branded sibling ofReadableIds(proposed nameOrgMemberIds; the S1 spec card names the driver declaration; naming is the lane seat's call), carrying the resolver's own(organizationId, callerId)anchor. A context that carries an array still lowers the array.!(… in …)NULL-safe).userId(the delegated explainer carries the principal's list under the delegator's id).DriverCapabilitiesbit returns" (a consulted declaration, because a refusal is excluded here).service-analyticscompileScopedFilterToSql). The floor substitution descends into composedReadableIdsscopes. The explainer publishes the array asreadFilter, today's wire shape.cafe8ee6, corrected from the card: the current-user endpoint and the annotation pins read nothing; the automation, sharing and transport carriers and the presence probes were missing. It says which sites move to the anchor and which keep the array.rlsMembershipleaves as out of scope, with the reason.user_idnegation, caller-arm, no-active-org, delegation, floor-parity, analytics, nested and explain-readFilterpins.docs/adr/0139-engine-internal-semi-join-leaf-readable-parent-ids.md: two dated, append-only pointer notes, under D1's "No second leaf" and under D3's capability-bit bullet.ReadableIdskeeps both as written, and the Status line is untouched.docs/adr/0057-erp-authorization-core-business-units-and-scope-depth.md: one dated amendment note under D3's "mirroring theorg_user_idscap" bullet, and one inline· **Amended**entry on its Status line pointing at it (after the ADR-0131 entry; the index still keys onAccepted). The cap no longer exists after PR fix(core)!: org_user_ids holds every member of an organization with more than 1,000 members #29356. D3's own bound requirement is unchanged.docs/adr/README.md: regenerated bynode scripts/adr-index.mjs(one row and the counts).No code, no schema, no test.
skip-changeset:docs/adr/**publishes nothing.Tests
None: an ADR-only diff. No forced test edits.
Acceptance notes
Round 2 answers contract review
6583043471(FAIL onf3a117fb): both must-fixes and should-fixes a, b, c and e land in the records. Round 3 applies the seat's two rulings on round 2: should-fix d (ADR-0057-erp's Status line gains its inlineAmendedentry), and naming is the seat's call, so O1 carries no maintainer gate.ADR number. 0147 is the next free number. The highest on
maincafe8ee6is 0146, and none of the 43 open PRs adds adocs/adr/0*.md(each head fetched,git diff --name-status origin/main HEAD -- docs/adr, zeroArows). 0136 and 0140 are unused gaps; they are not taken, since numbers are never reassigned.Out of scope, carried: the
packages/plugins/plugin-auth/src/admin-set-user-manager.tscomment quotes ADR-0057 D3's "org_user_idscap". The quotation stays accurate as a quotation, and correcting the comment is a server-lane code edit, so V3 carries it (ADR-0147 O6).New public names: the leaf is
OrgMemberIds; S1 names the driver's compile declaration. Naming is the lane seat's call (ADR-0147 O1).Seat's mechanism assumptions, measured: the spec sites are
rls.zod.ts's module header ("Subquery-shaped needs are pre-resolved…"), its "does not lower" paragraph and "Dynamic membership", andexecution-context.zod.ts'sorg_user_idsandrlsMembershipdocs. That matches the card; the line numbers have drifted. GeneralisingReadableIdswould make S depend on the caller's grants onsys_member, so the sibling leaf is chosen (Alternatives).Verification
Derived gates on `723e27a6`: 21 of 21 exit 0, --ran reconciled
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 21 commands (4 paths, +412 / -4). The lint closure was built first (atd55c14e4;formulaandlintare untouched since), becausecheck:doc-formula-expressionsreads@objectstack/formulaand@objectstack/lintdist:os-verify-lock.sh -c 'pnpm exec turbo run build --filter=@objectstack/formula --filter=@objectstack/lint --concurrency=2', VERDICT command-exit 0. Every command ran with stdin from/dev/null, its exit code captured before any pipe:node scripts/check-adr-links.mjsnode scripts/check-adr-links.mjs --self-testnode scripts/check-closing-keyword-parity.mjsnode scripts/check-closing-keyword-parity.mjs --self-testnode scripts/check-comment-mask-corpus.mjsnode scripts/ci/plan-test-core-shards.mjs --self-testpnpm --filter @objectstack/lint run check:doc-formula-expressionspnpm check:adr-anchorspnpm check:adr-indexpnpm check:adr-symbol-anchorspnpm check:ci-filter-paritypnpm check:closed-ceremony-textpnpm check:cross-package-test-inputspnpm check:doc-authoringpnpm check:driver-memory-censuspnpm check:gitlink-declaredpnpm check:nul-bytespnpm check:pm-governed-mergespnpm check:pm-prior-rulingspnpm check:refd-timer-probepnpm check:watch-hint-literalnode scripts/pm/dispatch-gates.mjs --ran FILE --repo objectstack-ai/objectstack(exit 0): "21 derived famil(ies) accounted for — 21 run, 0 NOT-MEASURED (a DERIVED zero — all 21 recorded an exit code and none of them is 3)".check-adr-symbol-anchors: 3037 anchors across 149 records resolve.check-adr-links: 979 relative destinations resolve.adr-index --report: README current.Human Minimum
packages/specholds no runtime logic. (n/a: no schema)OrgMemberIds, PascalCase likeReadableIds)pnpm lintpasses. (no lintable file in the diff)main, one pull request.skip-changeset)packages/spectouched: n/a.content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.Governed surface (
docs/adr/**, Tier H): this PR stays DRAFT and waits for aGOVERNED_APPROVERSapproval.🤖 Generated with Claude Code
https://claude.ai/code/session_01ADHkmSNasUm3e1dfbV5aaD
Generated by Claude Code