Skip to content

Epic: Permission sets — Studio designs every facet, Setup assigns users (ADR-0056) #2398

Description

@os-zhuang

Epic: Permission sets — Studio designs every facet, Setup assigns users (ADR-0056)

ADR: objectui ADR-0056 — Permission editing belongs in Studio; Setup keeps user management and assignment (#2399being revised from "Option B" to the pure model below).
Relates to (framework): ADR-0090 (Permission Model v2), ADR-0092 (identity write guard), ADR-0086 (authz metadata↔config boundary), ADR-0066 (sys_capability registry), ADR-0049 (no unenforced security properties), ADR-0084 (app-builder IA).

⚠️ Direction reset (2026-07-12). This epic previously encoded Option B, which kept system_permissions editable in Setup as an exception. That exception is dropped. The authoritative model is now pure separation:

  • Studio (/studio/<pkg>/access, studio.access) = where an app developer DESIGNS the permission set — every facet (object, field, system capabilities, RLS, tab visibility, admin scope) via structured editors.
  • Setup (com.objectstack.setup, setup.access) = where a system admin ASSIGNS users to a set and sees each facet as a read-only summary with a "Design in Studio →" deep-link.
  • Setup shows NO JSON and NO [Object]. Nothing permission-shaped is free-text-editable in Setup.

Problem

A permission set (sys_permission_set) is authored across two surfaces over the same record, and the Setup surface is actively broken UX:

  • Setup renders the sys_permission_set object with its six authorization columns as raw values: object_permissions and field_permissions show as [Object], and system_permissions / row_level_security / tab_permissions / admin_scope show as raw JSON Field.textareas. An admin on .../sys_permission_set/record/<id> sees 对象权限 [Object], 字段权限 [Object], and hand-editable JSON blobs.
  • Studio (studio.access) Access pillar has a structured PermissionMatrixEditor, but it edits only objects (CRUD/VAMA/lifecycle) and fields (FLS). The other four concerns are authorable only as raw JSON in Setup.

Raw/[Object] authorization metadata in Setup is: unreadable to an admin, invisible to the ADR-0090 explain engine (D6) and publish linter (D7), unvalidated at author time, duplicated (objects/fields editable in two surfaces), and a silent-incident footgun (ADR-0049).

Decision (pure model)

Design → Studio. Assign → Setup. Setup is read-only for every permission facet.

  • Studio gets structured editors for all six facets (objects + fields already exist; capabilities, RLS, tab, admin_scope are new).
  • Setup's sys_permission_set record page becomes: identity fields + user assignment + a read-only summary card per facet, each with a deep-link into the structured (Studio-grade) editor. No JSON. No [Object].
  • system_permissions is designed in Studio like every other facet (this is the change from the old Option B — there is no longer a "capabilities stay editable in Setup" carve-out). Setup only displays the granted capabilities read-only + deep-links to Studio.

Deep-link target. Package-owned sets → the package's Studio Access pillar. Environment-authored sets (created in Setup, not owned by a package — so they have no /studio/<pkg>/access home) → the environment-scope structured editor via the metadata-admin permission route: /apps/com.objectstack.setup/metadata/permission/<setName> (packageId optional ⇒ env scope). Either target renders the structured editor, never JSON. (Reconciling these two entry points is open Q1.)

Already exists (reuse, don't rebuild): PermissionMatrixEditor (objects+fields) + its PermissionMatrixEditPage env-scope route; sys_capability registry object + seeded platform caps (setup.access/studio.access/manage_*); AdminScopeSchema (ADR-0090 D12); RowLevelSecurityPolicySchema; AccessExplainPanel (D6); AssignedUsersSection (add/remove sys_user_permission_set, shows sys_position_permission_set).

Target end-state (facet by facet)

sys_permission_set column Structured editor (design) Design home Setup shows
object_permissions PermissionMatrixEditor (exists) Studio read-only summary + deep-link
field_permissions PermissionMatrixEditor (exists) Studio read-only summary + deep-link
system_permissions sys_capability multi-select (new — from #2400) Studio read-only capability chips + deep-link
admin_scope Delegated-admin editor (new) Studio read-only summary + deep-link
row_level_security Structured RLS editor (new) Studio read-only summary + deep-link
tab_permissions Tab-visibility editor (new) Studio read-only summary + deep-link
(assignment) AssignedUsersSection (exists) Setup the assignment UI itself

Phases (supersedes the prior P1–P5 scoping)

  • P1 — Setup: kill [Object]/JSON; render read-only summary + deep-link for all six facets; surface user assignment. (repo: objectui)
    On Setup's sys_permission_set record page, replace the raw display of all six permission columns with a read-only summary card per facet (e.g. "5 objects granted", capability chips, "3 RLS policies") + a "Design in Studio →" deep-link to the structured editor for that set; and surface AssignedUsersSection (add/remove users) on the same page. Reuses the feat(fields,plugin-form,plugin-detail): Setup system_permissions → capability multi-select (ADR-0056 P2) #2400 groundwork (single getObjectSchema widget-override choke point + form/detail widget-honoring), but points the fields at a summary+deep-link renderer instead of an editable widget.
    Acceptance: Setup shows no [Object] and no editable JSON for any permission facet; each facet has a read-only summary + a deep-link that opens the correct set in the structured editor at the right scope and returns to the Setup row; admins can add/remove users from the set in Setup; package-managed sets render read-only per ADR-0086; ADR-0054 browser proof.

  • P2 — Studio: System Capabilities editor. (repo: objectui)
    Add a System Capabilities editor to Studio's Access pillar (and the env-scope PermissionMatrixEditPage) using the CapabilityMultiSelectField already built on the #2400 branch (feat/perm-setup-capability-picker): the live sys_capability registry (active = true) as scope-grouped, labelled chips with description tooltips; edits systemPermissions in the metadata draft; round-trips byte-equivalent to the stored string[] of capability names; unknown/legacy names preserved.
    Acceptance: granting/revoking studio.access and other capabilities is done in Studio via the picker, never JSON; the value persists byte-equivalent; Setup only reflects it read-only (P1); ADR-0054 proof.

  • P3 — Studio: structured editors for RLS, tab visibility, and admin scope. (repo: objectui)

    • RLS: per-policy rows (object · operation · USING · CHECK) with a predicate field that round-trips CEL (simple-rule builder + raw-CEL escape hatch — open Q2); validated against the RLS predicate surface.
    • Tab visibility: per-app/tab selector → visible | hidden | default_on | default_off, enumerating installed apps/tabs.
    • Admin scope: BU-subtree picker (businessUnit + includeSubtree), manageAssignments/manageBindings/authorEnvironmentSets toggles, assignablePermissionSets[] allowlist (ADR-0090 D12).
      Acceptance: row_level_security, tab_permissions, and admin_scope are authorable structurally in Studio; editors read the canonical spec shapes (RowLevelSecurityPolicySchema, tabPermissions enum, AdminScopeSchema); malformed legacy JSON degrades to a guarded raw view rather than data loss; Studio-only (behind studio.access); ADR-0054 proofs.
  • P4 — Move assignment fully into Setup; retire residual JSON; explain/lint alignment. (repo: objectui; possible framework companion)
    Move AssignedUsersSection out of Studio's PermissionMatrixEditor (currently embedded there) so assignment lives only in Setup (per the pure model); confirm every facet is now homed in a structured editor + summarized read-only in Setup, and remove any remaining raw-JSON rendering from Setup; verify each facet is legible to the ADR-0090 explain engine + publish linter.
    Acceptance: no permission concern is editable as free-text JSON anywhere in Setup; assignment UI appears in Setup, not Studio; Setup permission-set page = identity + assignment + read-only summaries with deep-links; legacy rows with irrecoverable JSON keep a guarded read-only escape (open Q5); if any field-widget narrowing is done framework-side, a companion plugin-security changeset cites ADR-0056; ADR-0054 proof.

Reusable groundwork from #2400 (do not rebuild)

The drafted P2 PR (#2400, branch feat/perm-setup-capability-picker) built plumbing that this epic reuses, even though its original placement (capability picker in Setup) is superseded by the pure model:

  • CapabilityMultiSelectField (field:capability-multiselect, @object-ui/fields) + its 9 passing unit tests → moves to Studio (P2).
  • Single widget-override choke pointObjectStackAdapter.getObjectSchema stamps a field widget, honored by both ObjectForm and DetailView/DetailSection → reused by P1 to stamp the six permission fields with a summary+deep-link renderer (instead of the capability picker).
  • form.tsx DATA_SOURCE_FIELD_TYPES forwarding + DetailSection widget enrichment → reused for the P1 renderer.

Disposition: #2400 is parked (draft) as superseded-in-placement; its code is preserved on-branch and re-homed by P1/P2 above. ADR #2399 is being revised from Option B to this pure model.

Open questions

  1. Deep-link target/scope (P1): package-owned sets → package Studio Access pillar; env-authored sets → env-scope PermissionMatrixEditPage (/apps/com.objectstack.setup/metadata/permission/<setName>). Confirm the two entry points, package-managed read-only presentation, and back-navigation to the Setup row.
  2. RLS editor depth (P3): simple-rule builder vs raw-CEL hybrid in v1; where contextVariables (on PermissionSetSchema, not a sys_permission_set column) is edited.
  3. Tab-visibility source (P3): which apps/tabs are enumerated; package vs environment scope; reconciliation when apps change.
  4. Summary renderers (P1): how much per-facet summary detail (counts vs first-N chips vs a compact matrix preview) before the deep-link.
  5. Backward-compat (P3/P4): parse-and-render legacy JSON; guarded read-only + raw escape for malformed/unrepresentable values; whether a one-time normalization/lint pass stamps legacy rows.
  6. system_permissions visibility — RESOLVED: capabilities are designed in Studio like every other facet; Setup displays them read-only (no Setup-editable exception).

Notes

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions