You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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_permissionseditable 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 onlyobjects (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.)
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.
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 AssignedUsersSectionout 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.
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.tsxDATA_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
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.
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.
Tab-visibility source (P3): which apps/tabs are enumerated; package vs environment scope; reconciliation when apps change.
Summary renderers (P1): how much per-facet summary detail (counts vs first-N chips vs a compact matrix preview) before the deep-link.
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.
system_permissions visibility — RESOLVED: capabilities are designed in Studio like every other facet; Setup displays them read-only (no Setup-editable exception).
Notes
All phases are objectui UI work. Framework touchpoints are limited to how the sys_permission_set form renders and reading spec shapes; the storage columns are unchanged.
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_capabilityregistry), ADR-0049 (no unenforced security properties), ADR-0084 (app-builder IA).Problem
A permission set (
sys_permission_set) is authored across two surfaces over the same record, and the Setup surface is actively broken UX:sys_permission_setobject with its six authorization columns as raw values:object_permissionsandfield_permissionsshow as[Object], andsystem_permissions/row_level_security/tab_permissions/admin_scopeshow as raw JSONField.textareas. An admin on.../sys_permission_set/record/<id>sees对象权限 [Object],字段权限 [Object], and hand-editable JSON blobs.studio.access) Access pillar has a structuredPermissionMatrixEditor, but it edits onlyobjects(CRUD/VAMA/lifecycle) andfields(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.
sys_permission_setrecord 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_permissionsis 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>/accesshome) → the environment-scope structured editor via the metadata-admin permission route:/apps/com.objectstack.setup/metadata/permission/<setName>(packageIdoptional ⇒ 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) + itsPermissionMatrixEditPageenv-scope route;sys_capabilityregistry object + seeded platform caps (setup.access/studio.access/manage_*);AdminScopeSchema(ADR-0090 D12);RowLevelSecurityPolicySchema;AccessExplainPanel(D6);AssignedUsersSection(add/removesys_user_permission_set, showssys_position_permission_set).Target end-state (facet by facet)
sys_permission_setcolumnobject_permissionsPermissionMatrixEditor(exists)field_permissionsPermissionMatrixEditor(exists)system_permissionssys_capabilitymulti-select (new — from #2400)admin_scoperow_level_securitytab_permissionsAssignedUsersSection(exists)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_setrecord 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 surfaceAssignedUsersSection(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 (singlegetObjectSchemawidget-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 theCapabilityMultiSelectFieldalready built on the#2400branch (feat/perm-setup-capability-picker): the livesys_capabilityregistry (active = true) as scope-grouped, labelled chips withdescriptiontooltips; editssystemPermissionsin the metadata draft; round-trips byte-equivalent to the storedstring[]of capability names; unknown/legacy names preserved.Acceptance: granting/revoking
studio.accessand 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)
visible | hidden | default_on | default_off, enumerating installed apps/tabs.businessUnit+includeSubtree),manageAssignments/manageBindings/authorEnvironmentSetstoggles,assignablePermissionSets[]allowlist (ADR-0090 D12).Acceptance:
row_level_security,tab_permissions, andadmin_scopeare authorable structurally in Studio; editors read the canonical spec shapes (RowLevelSecurityPolicySchema,tabPermissionsenum,AdminScopeSchema); malformed legacy JSON degrades to a guarded raw view rather than data loss; Studio-only (behindstudio.access); ADR-0054 proofs.P4 — Move assignment fully into Setup; retire residual JSON; explain/lint alignment. (repo: objectui; possible framework companion)
Move
AssignedUsersSectionout of Studio'sPermissionMatrixEditor(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-securitychangeset 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).ObjectStackAdapter.getObjectSchemastamps a fieldwidget, honored by bothObjectFormandDetailView/DetailSection→ reused by P1 to stamp the six permission fields with a summary+deep-link renderer (instead of the capability picker).form.tsxDATA_SOURCE_FIELD_TYPESforwarding +DetailSectionwidget 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
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.contextVariables(onPermissionSetSchema, not asys_permission_setcolumn) is edited.— RESOLVED: capabilities are designed in Studio like every other facet; Setup displays them read-only (no Setup-editable exception).system_permissionsvisibilityNotes
sys_permission_setform renders and reading spec shapes; the storage columns are unchanged.[Object]/JSON UX the admin sees today) and is independent of P2/P3. P2 reuses feat(fields,plugin-form,plugin-detail): Setup system_permissions → capability multi-select (ADR-0056 P2) #2400 directly. P4 depends on P1–P3 homing every facet.