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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions