Repository navigation
Description
Filing gate ②: a maintainer ruling, carried out as child 5 of the nine that #26059's ruling (Q3) names. ⛔ Not a claim.
An admin can configure a Delegated Admin Scope on a permission set and grant the set to a user. That user then opens Setup and gets "You don't have access to this app". To act, they also need:
- the capability
setup.access - read access to
sys_user,sys_business_unitandsys_business_unit_member, so the lookups can pick people and units - CRUD on
sys_user_position
Nothing in the scope block says so, and the docs page leaves out setup.access. The server is right to refuse. The gap is that the editor gives no hint and offers no fix.
Part of #26059
Ruling-ref: 6416233738 (#26059 (comment))
Ruling: batch #396 item 1, letter Q1 是 / Q2 不做 / Q3 分拆 / Q4 框架. This card is Q3 child 5 (ui + docs, p2): item 2. Brief 6415795834, item-by-item reading 6415971852 (item 2).
Filed by domain:ui seat 2 (domain:ui#2) · os-steve · session session_01SMps7wq7rBTk53e8KTSEup, first claimant of #26059. Domain, grade and route are triage's on first touch.
Business reason. Delegated administration lets a tenant hand one subtree's assignments to a regional manager. As shipped, the admin who sets it up gets a delegate who cannot open Setup and has to work out four further grants alone, by trial and error.
Reader who acts. The domain:ui execution seat, dispatch step: one os-dev, one PR on ui/packages/app-shell/src/views/metadata-admin/PermissionAdvancedFacets.tsx (with its i18n strings) and the docs page content/docs/permissions/delegated-administration.mdx in the same PR.
Spec contract. None changes and no key is new. The grants go into the set's existing systemPermissions (packages/spec/src/security/permission.zod.ts:1115) and objects (:1109).
Duplicate check. A local scan of the 1,962 newest issues (all states, #22168–#26181) for setup\.access.{0,200}(delegat|adminScope) and the reverse order found 1 hit, not this (#24135, ADR-0090 D12 wording). #23323 (closed) fixed another sentence on the same docs page.
Ruled scope (ruling Q3 item 5)
- The Delegated Admin Scope block lists the companion grants:
setup.accesssys_user/sys_business_unit/sys_business_unit_memberreadsys_user_positionCRUD
- It offers a one-click "grant these" that adds them to the same set: one save, no new key.
delegated-administration.mdxgainssetup.access.- Not a server change.
Anchors (re-read on origin/main 83a26c38)
packages/spec/src/security/capabilities.ts:49:{ name: 'setup.access', label: 'Setup Access', description: 'Enter the Setup app shell.' }. Holds.- Docs file path verified:
content/docs/permissions/delegated-administration.mdx.- The authoring example now spans
:20–40, and its object grants are:33–38(sys_user_positionCRUD,sys_business_unit/sys_business_unit_member/sys_userread). - The ruling cited
:26–36, which has drifted. Corrected. setup.accessappears nowhere on the page.
- The authoring example now spans
ui/packages/app-shell/src/views/metadata-admin/PermissionAdvancedFacets.tsx:- The scope state and the objectui#9464 business-unit gate are at
:404–433(the ruling's:405–420falls inside), withscopeBlocksSaveat:444–445. - The block renders at
:677("Delegated Admin Scope"FacetSection). Holds.
- The scope state and the objectui#9464 business-unit gate are at
Expected / acceptance
- The Delegated Admin Scope block shows the five companion grants. Each is marked present or missing on the set being edited.
- "Grant these" adds only the missing ones to the same draft (
systemPermissionsgainssetup.access;objectsgains the reads and thesys_user_positionCRUD), never removes or narrows an existing grant, and is saved with the set's normal Save. - The hint and the action are hidden or inert while no scope is configured, matching the block's existing gate.
delegated-administration.mdx's example carriessystemPermissions: ['setup.access'], with one sentence explaining why a delegate needs it.- New strings exist in every locale the block's existing strings are in. Tests cover the present/missing state and the merge.
Activity
objectstack-fleet commentedon Oct 26, 2026
Triage: first grade — as filed ·
pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-26T16:01Z⛔ Not a claim, ⛔ not a dispatch.
Generated by Claude Code
objectstack-fleet commentedon Oct 26, 2026
Claim: PM loop round 6 · 2026-10-26T19:22Z
Session:
session_01SMps7wq7rBTk53e8KTSEupAccount:
os-steveBranch:
claude/issue-26191-delegate-companion-grantsWorktree:
objectstack-issue-26191Domain:
domain:uiSeat:
domain:ui#2File surface:
ui/packages/app-shell/src/views/metadata-admin/PermissionAdvancedFacets.tsx(the Delegated Admin Scope block),ui/packages/app-shell/src/views/metadata-admin/i18n.ts(new strings, en + zh, after #26284 merges), tests beside them,content/docs/permissions/delegated-administration.mdx(addsetup.access), root.changeset/. ⛔PermissionMatrixEditor.tsx/permission-slice.ts(#26284), the server,packages/spec, locale packs underui/packages/i18n,ui/.github/**, rootci.yml.Container & model:
M,mode:subagent,model: default tier,effort: mediumClause-②: no
Metadata-protocol: none
Responsibility:
a delegate given an Admin Scope who still cannot enter Setup or pick people|the set's existing systemPermissions and objects keys (permission.zod.ts :1109, :1115) and capabilities.ts setup.access|every admin who configures delegated administrationThread-read: 6420718254
Premise: holds 2026-10-26T19:22Z at
origin/mainbe531f685e.PermissionAdvancedFacets.tsxeditsadminScopewith no hint about the companion grants, anddelegated-administration.mdxnever namessetup.access.Serial constraints cleared: #26187 PR 1 (#26284) shares
metadata-admin/i18n.tsand is in the merge queue. This card develops now, mergesmainonce #26284 has MERGED, and opens its PR only after that.Writes: PR only via the relay op
pr_create; comments only via the relay write tools; ⛔ no GitHub MCP writes.setup.access; read onsys_user,sys_business_unit,sys_business_unit_member; CRUD onsys_user_position. Each shows whether this set already grants it.systemPermissionsandobjects), with one save, no new key, and no downgrade of an existing grant.packageIdpresent), show the hint but not the action, because the grants belong to the environment set. Say so in the PR.setup.accessin its prose and its authoring example.Fixes #26191.objectstack-fleet commentedon Oct 26, 2026
os-dev-report
{ "issue": 26191, "status": "done", "branch": "claude/issue-26191-delegate-companion-grants", "pr": "https://github.com/objectstack-ai/objectstack/pull/26340", "head": "8ecd73449fa2a8b689f335d5c5a9b577cc774a2c", "session": "session_01SMps7wq7rBTk53e8KTSEup", "premise_still_valid": true, "summary": "PermissionAdvancedFacets' Delegated Admin Scope block now lists the five companion grants (setup.access; read on sys_user, sys_business_unit, sys_business_unit_member; CRUD on sys_user_position) once the scope names a business unit. Each is marked granted or not on the set being edited. A 'Grant the missing ones' button adds only the missing grants in one setDraft call (systemPermissions and objects, no new key, nothing narrowed). Under a package binding the list is shown unmarked, with a note and without the button. The binding comes from a new optional packageBound prop, which defaults to only !== undefined because PermissionMatrixEditor.tsx is off this claim's surface. The docs page gains setup.access in its example and a paragraph on the five grants, and nine en+zh strings were added. #26284 had MERGED (merged=true) before the PR opened, and main was merged in twice with merge commits (c3066c2b, 8ecd7344).", "files_changed": [ ".changeset/26191-delegate-companion-grants.md", "content/docs/permissions/delegated-administration.mdx", "ui/packages/app-shell/src/views/metadata-admin/PermissionAdvancedFacets.tsx", "ui/packages/app-shell/src/views/metadata-admin/PermissionAdvancedFacets.companionGrants-26191.test.tsx", "ui/packages/app-shell/src/views/metadata-admin/i18n.ts" ], "line_budget": "+469 / -1 over 5 files (under the 3000 human-merge threshold)", "tests": "RED at c3066c2b (test committed, component unchanged), lock VERDICT command-exit 1: new file 9 failed / 1 passed. The pass is 'hidden until a business unit is named', which is true before and after; the failures were Unable to find data-testid admin-companions and similar. GREEN at 8ecd7344, lock VERDICT command-exit 0: cd ui; pnpm exec vitest run (254 app-shell test files importing PermissionAdvancedFacets or views/metadata-admin/i18n, the new file included) --maxWorkers=2 gave Test Files 254 passed (254), Tests 2578 passed (2578). pnpm --filter @object-ui/app-shell type-check (tsc --noEmit plus tsc -p tsconfig.test.json, so tests are compiled too), after building the @object-ui/app-shell^... closure: CLOSURE_EXIT=0, TYPECHECK_EXIT=0, lock VERDICT command-exit 0. eslint on the 3 changed source files: exit 0, 0 errors, 5 warnings, of which 2 are new no-explicit-any in the test harness's setDraft updater, matching the prop's own any. Full app-shell suite not run locally, per the card; CI runs it. No ablation: the red run is the before/after proof and no tested behaviour was otherwise mutated.", "gates": "Derived at 8ecd7344 with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 68 commands. Each was run with its exit code captured before any pipe, and --ran reconciled them as 68 derived, 66 run (all exit 0), 2 NOT-MEASURED, 0 UNRUN. NOT MEASURED (exit 3, PREREQUISITE NOT MET): pnpm --filter @objectstack/spec run check:skill-examples, because packages/client-react/dist was not built; the edited docs block carries no type-check marker. Also NOT MEASURED: pnpm check:type-check-debt, whose closure build needs the shared lock and timed out in the queue. CI's Lint & Repo Gates runs both. The spec-family gates were re-run after a spec rebuild on the merged tree: 7 exit 0 plus skill-examples exit 3, as above. node scripts/check-sdui-manifest.mjs exit 0 (app-shell has no sdui record). node scripts/pm/check-governed-merges.mjs --test over the 5 paths exit 0, NOT governed (content/docs is not in the register). PR check-runs: in_progress at report time, not awaited per the contract.", "dogfood_battery": "n/a: no ! in the PR title and Clause-2 is no with no (narrowing) arm", "deviations": [ "The package-binding signal is read from the facet's own only prop (optional packageBound prop, default only !== undefined), not from packageId. PermissionMatrixEditor.tsx is on the claim's no-touch list, so packageBound={!!packageId} is not wired. Today the two coincide: packageId reaches the editor only from AccessPillar, always with studio chrome, which is what sets only.", "Under a package binding the five rows are listed WITHOUT a granted/missing mark (the seat's route said each shows whether the set grants it). The package door's draft is sliced to the package's own objects, so a missing sys_* row there says nothing about the stored set, and a mark would mislead.", "The target test list is broader than the facet's direct importers. 254 files import views/metadata-admin/i18n, about 14% of app-shell's 1746 test files, and all were run (about 18 minutes under the lock).", "Lock contention: several queue-timeout (exit 99) attempts before each locked run, all re-queued with OS_VERIFY_LOCK_SLOT=issue-26191-dev. None of them is counted as a result." ], "mcp_calls": "0", "api_writes": "2: (1) relay op pr_create through scripts/pm/fleet-write/dispatch.mjs, run 40407600102: POST /repos/objectstack-ai/objectstack/pulls (draft) plus POST /repos/objectstack-ai/objectstack/issues/26340/assignees [os-steve]; the body read back identical (6810 bytes). (2) this os-dev-report comment, via scripts/pm/post-stamped.mjs: POST /repos/objectstack-ai/objectstack/issues/26191/comments. git push is not counted.", "open_questions": [ { "question": "Wire packageBound explicitly from PermissionMatrixEditor.tsx (packageBound={!!packageId} at both facet mounts) and drop the only-based default?", "options": [ "A: a follow-up card, or a seat-authorized widening of this claim's surface, that adds the one-line prop pass and removes the default", "B: keep the only-based default (correct for today's composition: AccessPillar is the only packageId host)" ], "recommendation": "A, because explicit composition beats an inferred default, and #26284, the reason for the no-touch, has merged; the change is two props and one default removal." } ], "out_of_scope_findings": [] }objectstack-fleet commentedon Oct 26, 2026
Landed · seat 2 · 2026-10-26T23:22Z
e14d9ce8, head8ecd7344(+469/−1, seat record on #26340)origin/maingit merge-base --is-ancestorexit 0)gh-readonly-queue/main/pr-26340-*: none left (consumed)Fixes #26191on #26340Status: the Delegated Admin Scope block lists the five companion grants with granted/missing marks and a one-click grant of the missing ones; under a package binding it lists them unmarked. Explicit
packageBoundwiring is #26349.pm:dispatchedremoved.fix(app-shell): pass the package binding to the companion list explic…