Repository navigation
Description
domain:server#3 · os-steve · session_01ADHkmQJdgarUjPLNgFBY67
Filing gate: ① design returned by the #29281 dev (os-dev-report 6577634380, design field), with seat ruling 6577690137 on #29281 (item 4). Triage's ruling on #29281 (6575719368, follow-up 6575447789) named this redesign as ADR and spec-lane work. Filed by domain:server seat 3. ⛔ Not a claim. Suggested-grade: p2 · the interim (PR #29356) makes the set exact up to the backend's bind ceiling, so no member is hidden below it; the ceiling and the per-request cost remain.
Reach: every organization with more than 1,000 members (a completion read on each request), and every organization past the backend's bound-parameter limit (SQLite between 32,000 and 40,000 members, Postgres 65,535), where sys_user reads now refuse loudly after #29281.
Reader who acts: the ADR author (governed path docs/adr/, GOVERNED_APPROVERS), then the domain:spec lane, then the domain:server lane per driver family.
Where it stands (measured on main a19759ac, driver-sql over better-sqlite3)
sys_user's scopeid == current_user.id || id in current_user.org_user_idslowers throughrls-compiler.ts→cel-to-filter.tsto{ id: { $in: [...] } }, with one bound parameter per id.- No subquery form a scope can use exists today. ADR-0139's
ReadableIds(packages/spec/src/data/readable-ids.ts) is the only semi-join leaf. It is engine-internal and composes the caller's own read scope on the inner object, so it cannot expressuser_id FROM sys_member WHERE organization_id = :active_org.rls.zod.ts(L108, L481-505) andexecution-context.zod.ts(L374-378, L414) state that subquery-shaped needs are pre-resolved arrays.
What the ADR must decide (from the dev's design; the seat agrees)
- D-a Predicate shape. Keep the authoring spelling
id in current_user.org_user_ids, so every shipped set,defaultRowScopeand app CEL keep working. The RLS compiler lowers that one key to a branded engine-internal leaf, a sibling ofReadableIds, that SQL drivers compile toid IN (SELECT user_id FROM sys_member WHERE organization_id = :active_org AND user_id IS NOT NULL AND user_id <> ''). The alternative is to generaliseReadableIds. ⛔ Either way, the inner scope must not be the caller's read scope onsys_member, which would change the set. - D-b Inner filter. Exactly today's read:
organization_id= the active org, with system authority. Not the tenant wall: in group posture that wall isorganization_id IN accessible_org_ids, which would widen the set. No status or ban filter, because adding one narrows. Theuser_id IS NOT NULL AND <> ''guard keeps negation NULL-safe. - D-c Caller arm. Today's list adds the caller; the lowering must OR the caller's id in, or the ADR states the change as a narrowing.
- D-d Driver obligations. Under ADR-0139 D3 (compile or refuse loudly): the SQL family compiles, and driver-memory evaluates in process. driver-mongodb refuses
ReadableIdstoday, and refusing this leaf would fail everysys_userread on Mongo. Floor: a driver that cannot compile the leaf keeps PR fix(core)!: org_user_ids holds every member of an organization with more than 1,000 members #29356's exact completion read. Mongo compiling it ($lookup) is a follow-up. - D-e
org_user_idsas a value. Demote it to a lazily resolved field. Readers that take it as a value should move to a point query or the leaf: plugin-audit's mention-recipient gate, plugin-security's explain engine, objectql'sengine.tsrun-as seeding (~L22710), the hook-wrapper and current-user endpoint copies, and approvals annotations. CEL hooks and flows keep the array. - D-f Same class. Every
rlsMembershipkey (execution-context.zod.tsL422,contracts/rls-membership-resolver.ts) is a pre-resolved array with the same bind ceiling. The ADR says whether resolvers may return a leaf too, or records it as out of scope. - D-g Acceptance. The visible set is identical before and after, for orgs under and over 1,000 members. PR fix(core)!: org_user_ids holds every member of an organization with more than 1,000 members #29356's pins stay green unchanged (
resolve-authz-context.org-members-past-first-read.test.ts,sys-user-org-members-past-first-read.test.ts). Add a group-posture pin (a member of org A with access to org B sees only A's members) and a negation pin with a NULLuser_idrow. - Stale prose to amend with it: ADR-0057's D3 rollup bullet and plugin-auth
admin-set-user-manager.ts(~L71) cite "the org_user_ids cap" as precedent, and that cap no longer exists after PR fix(core)!: org_user_ids holds every member of an organization with more than 1,000 members #29356.
Order after the ruling
ADR (governed) → spec card (the leaf beside readable-ids.ts, the rls.zod.ts exception, the execution-context.zod.ts doc, per-driver conformance) → server cards per driver family (objectql seam and comparand door, rls-compiler.ts and cel-to-filter.ts lowering, drivers, resolve-authz-context.ts lazy accessor, the D-e readers). Each step is pinned by PR #29356's tests.
Dedupe: searched org_user_ids relational membership, membership semi-join RLS; only #29281. New.
Activity
objectstack-fleet commentedon Nov 2, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: p2 ·
domain:spec·pm:queue· 2026-11-02T14:59Z⛔ Not a claim.
Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut
Claim: PM loop round 3 · 2026-11-02T16:03Z
Session:
session_01ADHkmSduNGC2SZXszyrNut(seat); dev = card threadsession_01ADHkmSNasUm3e1dfbV5aaDAccount:
os-steveBranch:
claude/project-thread-qe1kwiWorktree: the card thread's own checkout of
objectstack-ai/objectstackDomain:
domain:specSeat:
domain:spec#2File surface: one new ADR under
docs/adr/(next free number) and itsdocs/adr/README.mdindex row; an amendment note on ADR-0057 D3's "org_user_ids cap" bullet; theadmin-set-user-manager.ts(~L71) comment only if the lane allows it, else out_of_scope (stop on breach; explain in the report)Container & model:
M,mode:thread,model: default — the tier is the PM's per-card call (floor sonnet · default opus · ceiling fable); an ADR on a security boundary (who can see whom), one CONTRACT_REVIEW_TIER review by the seat,effort: highClause-②: no
Metadata-protocol: none
Responsibility:
membership-scoped RLS (id in current_user.org_user_ids) lowers to a pre-resolved id array | a per-request completion read and the backend bind-parameter ceiling | every organization past 1,000 members, and past the bind limitThread-read: 6580364238
Premise: holds (on origin/main,
cel-to-filter.tslowers the scope to{ id: { $in: [...] } }and no scope-usable semi-join leaf exists); the dev measures first and stops withpremise_still_valid: falseif it does not reproduceSerial constraints cleared: no in-flight claim names a new ADR on RLS membership lowering, ADR-0057 or ADR-0139
Governed path: the PR stays DRAFT and waits for a GOVERNED_APPROVERS approval. ⛔ The ADR keeps exactly the visible set #29281 holds (triage 6580364238); any change to who can see whom goes to the maintainer first.
objectstack-fleet commentedon Nov 2, 2026
domain:spec#2 · os-steve · session_01ADHkmSNasUm3e1dfbV5aaD
os-dev-report
{ "issue": 29360, "status": "done", "branch": "claude/project-thread-qe1kwi", "pr": "https://github.com/objectstack-ai/objectstack/pull/29501", "session": "session_01ADHkmSNasUm3e1dfbV5aaD", "premise_still_valid": true, "summary": "Wrote ADR-0147 (Proposed): FIELD in current_user.org_user_ids keeps its spelling and lowers to a branded sibling of ReadableIds (provisionally OrgMemberIds) carrying the resolver's own (organizationId, callerId) anchor; the SQL family compiles it to (FIELD = :caller OR FIELD IN (SELECT user_id FROM sys_member WHERE organization_id = :org AND user_id IS NOT NULL AND user_id is non-blank)), with no tenant wall on the subquery and no validity/status filter; every evaluator that does not compile it (mongodb, odata, a federated object, the in-process judges) gets the exact completion-read array of PR #29356 as the floor, chosen up front, never a refusal. D-0 states the same-set invariant S(org, caller) as its own decision; every set-changing option is under Alternatives as rejected. D-e re-measures the org_user_ids census on cafe8ee6 (corrected from the card); D-f records rlsMembership leaves as out of scope with the reason; execution plan S1 -> V1 -> V2 -> V3 (-> V4 mongodb). ADR-0057-erp D3 gets one dated amendment note; README index regenerated.", "tests": "No tests: an ADR-only diff (docs/adr/** x3), no forced test edits. Premise measured on origin/main cafe8ee6: resolve-authz-context.ts resolveUserAuthzGrants reads sys_member {organization_id} via tryFind {isSystem:true} (no tenantId, so outside the wall), completes past 1,000 via readOrganizationMembersToCompletion, keeps non-empty string user_ids and adds the caller; cel-to-filter.ts lowerMembership emits {FIELD: {$in: value}}; no scope-usable semi-join leaf exists (ReadableIds composes the caller's getReadFilter on P per ADR-0139 D2).", "gates": "On head f3a117fb: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 21 commands; prebuild os-verify-lock.sh -c 'pnpm exec turbo run build --filter=@objectstack/formula --filter=@objectstack/lint --concurrency=2' VERDICT command-exit 0 (125 s, at d55c14e4; the second head changed one ADR table row only). All 21 exit 0, stdin /dev/null, exit captured before any pipe (check-adr-links +self-test, check-closing-keyword-parity +self-test, check-comment-mask-corpus, plan-test-core-shards --self-test, lint check:doc-formula-expressions, check:adr-anchors, check:adr-index, check:adr-symbol-anchors, check:ci-filter-parity, check:closed-ceremony-text, check:cross-package-test-inputs, check:doc-authoring, check:driver-memory-census, check:gitlink-declared, check:nul-bytes, check:pm-governed-merges, check:pm-prior-rulings, check:refd-timer-probe, check:watch-hint-literal). --ran with ':: exit N' records, exit 0: '21 derived famil(ies) accounted for — 21 run, 0 NOT-MEASURED (a DERIVED zero ...)'. Offline PR-body check: check-changeset-no-major.mjs --base origin/main --event FILE exit 0 (LEVEL AXIS: clause-② no, metadata-protocol none). CI check-runs on PR #29501: not read (opened at this report; not waited on).", "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm", "mcp_calls": "6 — mcp__github__issue_read get #29360; mcp__github__issue_read get_comments #29360 (x2: card read, claim check); mcp__github__issue_read get_comments #29281; mcp__github__list_pull_requests (open, for ADR-number collision); mcp__github__pull_request_read get #29501 (label/draft read-back). No write tools.", "api_writes": "2 — both through the fleet-write relay (POST /repos/objectstack-ai/objectstack/dispatches): pr_create -> #29501 with labels [skip-changeset] (read back 8475 bytes sent, 8475 stored, identical; assignees none; draft true, label present per pull_request_read); this os-dev-report comment on #29360 via post-stamped.mjs. git push is not a REST write.", "open_questions": [ { "question": "New public names in @objectstack/spec (ADR-0147 O1): the leaf (provisionally OrgMemberIds, beside ReadableIds) and the driver's compile declaration the engine consults to pick leaf vs floor. Put to the maintainer before S1?", "options": [ "A OrgMemberIds + a driver declaration named by the spec card, both approved by the maintainer with this ADR", "B ActiveOrgMemberIds (names the active-org binding explicitly)", "C keep both unexported (engine-internal symbol only); third-party drivers always take the floor" ], "recommendation": "A, because the leaf mirrors ReadableIds' exported shape (drivers must recognise it) and the name matches the dev's design; C is safe but locks every external SQL driver onto the per-read completion and the ceiling." }, { "question": "D-d departs from ADR-0139 D3 for this leaf: a consulted driver declaration (not an inert capability bit) chooses leaf vs exact-array floor up front, instead of compile-or-refuse. Does that need its own ADR-0139 amendment note?", "options": [ "A No: ADR-0147 states the departure for its own leaf and cites D3; ADR-0139 is untouched (as drafted)", "B Yes: add a one-line pointer note to ADR-0139 D3 in this PR" ], "recommendation": "A, because ADR-0139's text governs only ReadableIds and stays true; B is a one-line add if the seat wants the cross-reference." }, { "question": "Freshness (ADR-0147 O4): the leaf reads sys_member at statement time, whereas with the opt-in OS_AUTHZ_GRANTS_CACHE_TTL_MS the array could be as old as its cache entry. Same rule, newer inputs. Does that count as 'who can see whom' changing under triage's bar?", "options": [ "A No: S is the same function; only staleness shrinks (recorded under Consequences)", "B Yes: put it to the maintainer as a decision before acceptance" ], "recommendation": "A, because the cache is default-off and its TTL is an accepted staleness window, not a scope; with it off (the default) nothing changes." } ], "security_findings": "withheld", "out_of_scope_findings": [ "class: observation · packages/plugins/plugin-auth/src/admin-set-user-manager.ts (~L71) quotes ADR-0057 D3 'mirroring the org_user_ids cap'; the quotation stays accurate as a quotation; a comment edit in a server-lane package is outside this ADR card · carrier: ADR-0147 V3 (O6) · noted, not filed", "class: observation · org_user_ids reads no ADR-0091 validity window while accessible_org_ids applies one; sys_member declares no bound today, so no stored row is answered differently · carrier: ADR-0147 O3 (kept; closing it changes S) · noted, not filed" ], "files_changed": [ "docs/adr/0147-membership-scoped-rls-lowers-to-an-engine-internal-org-member-semi-join-leaf.md (new, 350 lines)", "docs/adr/0057-erp-authorization-core-business-units-and-scope-depth.md (+8: one blockquote amendment note under D3's cap bullet; Status line untouched)", "docs/adr/README.md (regenerated by scripts/adr-index.mjs: +4 / -3, one row + counts)" ], "line_budget": "dispatch-gates: 360 changed lines (+357 / -3) in 3 files vs the human-merge threshold 3000: under. No production code.", "deviations": [ "The admin-set-user-manager.ts comment is left out (claim: 'only if the lane allows it, else out_of_scope'): it is a code file in a server-lane package and the delta says no code; carried by V3 (O6).", "D-e corrects the card's reader list: current-user-endpoints.ts and the approvals/sharing exec-context-annotation pins read nothing; the automation (plugin.ts, run-resume.ts, runtime-identity.ts), sharing/share-link and assemble-execution-context carriers and the Array.isArray presence probes were missing.", "Rule beyond the card: an array on the context wins over the anchor (D-a), so every hand-built context answers as today; and the anchor's caller, not the compiling context's userId, is OR-ed in (D-c), measured against explain-engine's delegated inheritance.", "The harness asked to set an assignee/reviewer via MCP and to subscribe to PR activity; both skipped (standard order §3 no MCP writes; os-dev: pr_create carries no assignees, the dispatch contract overrides the PR-subscription posture). The PR body carries the harness attribution comment on line 1 and Fixes #29360 on line 4.", "Second commit f3a117fb only refines one D-d table row (names the in-process judges precisely); the lint closure prebuild was reused from d55c14e4 (formula/lint untouched); all 21 gates re-ran on f3a117fb." ] }Stamped 2026-11-02T16:40Z.
Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:spec#2 · os-steve · session_01ADHkmRPdc4hbJF33icNKjQ
Release:
session_01ADHkmSduNGC2SZXszyrNut(seat 2, signing off on the maintainer's wind-down) · PR #29501 (ADR-0147, Tier H) is ACCEPTed at723e27a6and waits on aGOVERNED_APPROVERSapproval, and no seat stays behind for it · →pm:queue, for the next livedomain:specseat to landResume-from: claude/project-thread-qe1kwi@723e27a6b8ab7580eeb451c4c7781e049044248e
723e27a6and CI is green, runpr_readyand thenautomerge_enable. If the head moved, post a delta record first. After the merge, post a landing note here.Release:line, theos-steveassignee cleared, andpm:dispatched→pm:queue.objectstack-fleet commentedon Nov 3, 2026
domain:spec#3 · os-support-ai · session_01WGufzfgvGexKn9vLHsy89K
Claim: PM loop round 1
Session:
session_01WGufzfgvGexKn9vLHsy89KAccount:
os-support-aiBranch:
claude/project-thread-qe1kwi(the released branch; landing only, no new dev work)Worktree: none (the seat hands PR #29501 to the governed approvers and lands it after approval)
Domain:
domain:specSeat:
domain:spec#3File surface: as PR #29501:
docs/adr/0147-…,docs/adr/0139-…,docs/adr/0057-…,docs/adr/README.md(governed, Tier H)Container & model:
M, landing only,model: TIER_DEFAULT(seat); at-tier records already on the PR: contract review delta PASS 6583780256, seat ACCEPT 6583798126, both at head723e27a6Clause-②: no
Metadata-protocol: none
Responsibility:
n/a — not a defect card(an ADR card)Thread-read: 6591510931
Premise: holds. PR #29501 head is still
723e27a6; 48 check-runs, 26 success and 22 skipped, none red.Serial constraints cleared: none; the four ADR files are touched by no other open PR read here.
Resume-from:
claude/project-thread-qe1kwi@723e27a6b8ab7580eeb451c4c7781e049044248eobjectstack-fleet commentedon Nov 3, 2026
domain:spec#3 · os-support-ai · session_01WGufzfgvGexKn9vLHsy89K
Landing:
os-zhuang(aGOVERNED_APPROVERSmember) approved PR #29501 at head723e27a6; the head has not moved since ACCEPT 6583798126 and contract review PASS 6583780256, so no delta record is owed. Readied and auto-merge armed (2026-11-03T01:44Z);needs-user-decisionis off the PR. The seat follows it to MERGED.objectstack-fleet commentedon Nov 3, 2026
domain:spec#3 · os-support-ai · session_01WGufzfgvGexKn9vLHsy89K
Landed: PR #29501 (ADR-0147) merged after
os-zhuangapproved it at head723e27a6; this card closedcompletedwith it, andpm:dispatchedis off.