Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

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

Closed

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 (#2399 — being 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 point — ObjectStackAdapter.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

Activity

  1. changed the title [-]Epic: Consolidate permission-set editing into Studio; Setup keeps assignment (ADR-0056)[/-] [+]Epic: Permission sets — Studio designs every facet, Setup assigns users (ADR-0056)[/+] on Jul 12, 2026
  2. os-zhuang commented on Jul 12, 2026

    @os-zhuang
    ContributorAuthor

    All phases implemented + browser-verified in #2403 (pure model).

    Phase What Status
    P1 Setup: 6 facets → read-only summary + "Design in Studio →" deep-link (kills [Object]/JSON) ✅
    P1b Setup: user-assignment panel on the record page ✅
    P2 Studio: System Capabilities editor (sys_capability multi-select) ✅
    P3 Studio: structured RLS / tab-visibility / admin-scope editors ✅
    P4 Assignment moved out of the design editor → Setup only; no residual JSON ✅

    Verified live (app-showcase): sales_rep facet summaries + deep-link; organization_admin 18 RLS policies as structured rows; showcase_field_ops_delegate admin-scope form; capability + admin-scope save round-trips persist.

    Framework follow-up (open Q + new): env-scope metadata saves of these facets don't project onto the queryable sys_permission_set data record the Setup summary reads, so a fresh Studio edit isn't reflected in Setup until the projection refreshes (enforcement reads the authoritative metadata). This is the one remaining gap for full closed-loop coherence.

  3. os-zhuang commented on Jul 13, 2026

    @os-zhuang
    ContributorAuthor

    Shipped + follow-ups filed:

    The objectui UI work for the pure model is complete; the one remaining gap for full closed-loop coherence is the framework projection (#2857).

  4. os-zhuang commented on Jul 13, 2026

    @os-zhuang
    ContributorAuthor

    Framework projection fix is up: objectstack-ai/objectstack#2867 (closes framework#2857). Projects env-scope permission-set edits onto the queryable sys_permission_set record via the protocol's onMetadataMutation hook — closing the last gap so a Studio edit reflects in Setup's read-only summary. 21 unit tests + no regression; live E2E to be confirmed by framework CI (local isolated-backend repro was blocked by a worktree/pnpm resolution artifact).

  5. os-zhuang commented on Jul 13, 2026

    @os-zhuang
    ContributorAuthor

    Follow-ups surfaced during go-live UAT (post-merge)

    With the pure model shipped and browser-verified end-to-end (Setup read-only + Studio designs all facets), two durable follow-ups came out of the walkthrough:

    Both are non-blocking; the shipped model works. Filing here so they're tracked against this epic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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