Skip to content

feat(app-shell,plugin-detail): permission sets — Studio designs, Setup assigns (ADR-0056) - #2403

Merged
os-zhuang merged 7 commits into
mainfrom
feat/perm-pure-model
Jul 13, 2026
Merged

feat(app-shell,plugin-detail): permission sets — Studio designs, Setup assigns (ADR-0056)#2403
os-zhuang merged 7 commits into
mainfrom
feat/perm-pure-model

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

What

Implements the pure separation model for permission-set editing (ADR-0056 / epic #2398): an app developer designs every facet in Studio; a system admin assigns users in Setup, where every facet is read-only — no [Object], no JSON. Supersedes the earlier "capability picker in Setup" approach (#2400).

Fixes the reported bug: the Setup sys_permission_set record page showed 对象权限 [Object], 字段权限 [Object], and raw-JSON textareas for the other four facets.

Phases (all browser-verified against the live app-showcase backend)

P1 — Setup: kill [Object]/JSON → summary + Studio deep-link. The six facets (object_permissions, field_permissions, system_permissions, row_level_security, tab_permissions, admin_scope) render read-only as a compact summary (counts, or capability chips) + a "Design in Studio →" deep-link to /apps/:appName/metadata/permission/:setName (env scope). New permission-facet-link widget, stamped onto the six fields via the single ObjectStackAdapter.getObjectSchema choke point and honored by DetailSection (read + inline-edit) and the record form.

  • Verified:sales_rep shows "10 objects" / "4 field rules" + deep-links; the link opens the env-scope matrix (10 objects / 4 field overrides — matching).

P1b — Setup: surface user assignment. The add/remove-users panel (AssignedUsersSection, via sys_user_permission_set) now renders directly on the Setup record page.

  • Verified:sales_rep record page shows the "已分配用户" panel with the real component.

P2 — Studio: System Capabilities editor. The permission matrix editor gains a capability multi-select over the live sys_capability registry (scope-grouped, labelled chips), wired to PermissionSetDraft.systemPermissions.

  • Verified: toggling Studio Access + Save persists across editor reload.

P3 — Studio: RLS / tab-visibility / admin-scope editors. Structured editors for the three JSON-only facets, as collapsed-by-default sections below the object matrix: RLS per-policy rows with CEL USING/CHECK; tab visibility visible|hidden|default_on|default_off; delegated admin scope (business-unit + subtree, manage toggles, assignable-sets allowlist).

  • Verified:organization_admin renders its 18 RLS policies as structured rows with CEL predicates; showcase_field_ops_delegate shows businessUnit "Field Operations", correct toggles, and assignable sets showcase_contributor + showcase_manager; a manageBindings toggle + Save persisted across reload (reverted).

P4 — move assignment out of the design editor. With assignment on the Setup record page (P1b), it's removed from the matrix editor — which is now purely a design surface.

  • Verified: the editor shows matrix + capabilities + advanced facets and no assignment; the Setup create form shows identity fields + read-only "Design in Studio" facet hints (no JSON textareas).

Storage / compatibility

Storage columns and field types are unchanged. Editors read/write the draft's existing parsed fields (systemPermissions, rowLevelSecurity, tabPermissions, adminScope) and tolerate a JSON string on load so legacy rows survive.

Known follow-up (framework)

Env-scope metadata saves of these facets do not yet project onto the queryable sys_permission_setdata record that the Setup read-only summary reads, so a fresh Studio edit isn't reflected in Setup's summary until the projection refreshes. Enforcement reads the authoritative metadata (ADR-0090), so this is display-freshness only — tracked as a framework companion in #2398.

Notes

🤖 Generated with Claude Code

os-zhuangand others added 7 commits July 12, 2026 20:54
…pability multi-select (ADR-0056 P2)
Replace the raw JSON textarea for sys_permission_set.system_permissions with a
structured capability picker over the live sys_capability registry — scope-grouped,
labelled chips with the capability description on hover.
- New CapabilityMultiSelectField, registered field:capability-multiselect
(@object-ui/fields). Value round-trips BYTE-EQUIVALENT to the JSON-string-array
storage (parse on load, JSON.stringify(names) on save); unknown/legacy names
are preserved.
- Stamped onto the field at the single getObjectSchema choke point
(ObjectStackAdapter) so BOTH the record form (ObjectForm) and the detail-page
inline edit (DetailView/DetailSection) show the picker. Field storage type
unchanged. ObjectForm auto-gen now carries field.widget; DetailSection enriches
+ honors widget in its inline-edit switch.
ADR-0056 P2 / epic #2398. First step toward retiring Setup's permission JSON
textareas for structured Studio editors.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ry + Studio deep-link (ADR-0056 P1)
The six sys_permission_set authorization facets (object/field/system/RLS/tab/
admin_scope) rendered in Setup as raw [Object]/JSON textareas. In the pure
model they are *designed* in Studio's structured editors and only *assigned*
(to users) in Setup, so Setup must show them read-only.
New `permission-facet-link` widget renders each facet as a compact summary
(counts, or capability chips for system_permissions) plus a "Design in Studio
→" deep-link to /apps/:appName/metadata/permission/:setName (env scope; the
existing PermissionMatrixEditPage). The widget is stamped onto all six fields
via the single ObjectStackAdapter.getObjectSchema choke point; DetailSection's
read + inline-edit branches honor it (mirroring the capability-multiselect
path), and it is registered as field:permission-facet-link so the record form
resolves it too. Storage types are untouched.
Verified live: sales_rep shows '10 objects' / '4 field rules' / capability
chips + deep-links (no [Object]/JSON); the link opens the env-scope matrix
editor (10 objects / 4 field overrides, matching the summary).
ADR-0056 epic #2398 P1.
…record page (ADR-0056 P1b)
In the pure model, assigning users to a permission set is a Setup (admin) act.
The set's facets now render read-only as summary + Studio deep-link (P1); this
adds the add/remove-users panel directly to the Setup sys_permission_set record
page so admins manage membership without leaving Setup.
New RecordPermissionAssignmentsRenderer wraps the existing AssignedUsersSection
(add/remove via sys_user_permission_set, shows position-held grants), reading
the set's api-name from the record context. Rendered directly in
RecordDetailView (mirroring RecordAttachmentsPanel) inside RecordContextProvider,
gated on objectName === 'sys_permission_set' — not via a page slot, because the
render-time page rebuild (renderedPage) only forwards assignedSlots.
Verified live: sales_rep record page shows '已分配用户 0 人' + 添加用户 with the
real assignment component (matches the matrix editor's panel).
ADR-0056 epic #2398 P1b.
…matrix (ADR-0056 P2)
Adds a 'System Capabilities' section to the Studio / env-scope permission matrix
editor (PermissionMatrixEditPage), reusing CapabilityMultiSelectField: the live
sys_capability registry as scope-grouped, labelled chips (studio.access,
manage_users, …). In the pure model, capabilities are *designed* here like every
other facet; Setup shows them read-only (PermissionFacetLink, P1).
Wired to PermissionSetDraft.systemPermissions (string[]) — the picker round-trips
via a JSON string, so we parse back into the array the draft uses. Persisted by
the editor's existing whole-record Save at environment scope; respects the
read-only (allowOrgOverride) gate. Capability rows are read via useAdapter (data),
distinct from the metadata client that owns the draft. i18n keys added (en/zh).
Verified live: sales_rep editor shows the 6 platform capabilities; toggling
Studio Access on + Save persists (survives editor reload); toggled back off.
KNOWN GAP (framework companion): env-scope metadata saves of systemPermissions do
not project onto the sys_permission_set *data record* the Setup detail page reads,
so a Studio capability edit isn't reflected in Setup's read-only summary until the
projection refreshes. Enforcement reads the authoritative metadata (ADR-0090);
this is a display-freshness concern tracked for a framework follow-up.
ADR-0056 epic #2398 P2.
…s in Studio (ADR-0056 P3)
The three remaining permission facets — row_level_security, tab_permissions,
admin_scope — were authorable only as raw JSON in Setup. New
PermissionAdvancedFacets adds structured editors for them to the Studio /
env-scope permission matrix, as collapsed-by-default sections below the object
matrix so they don't crowd it:
- Row-Level Security: per-policy rows (name · object · operation · enabled) with
CEL USING/CHECK textareas; add/remove.
- Tab Visibility: per-tab key → visible | hidden | default_on | default_off.
- Delegated Admin Scope: business-unit + include-subtree, manage-assignments /
manage-bindings / author-env-sets toggles, and an assignable-permission-sets
allowlist (multi-select over all sets, loaded via client.list('permission')).
Each reads/writes the draft's parsed camelCase field (rowLevelSecurity /
tabPermissions / adminScope), tolerating a JSON string on load so legacy rows
survive, and is persisted by the editor's existing whole-record Save. Shapes
mirror the framework spec (sampled from live data). i18n en/zh added.
Verified live: organization_admin renders its 18 RLS policies as structured rows
with CEL predicates; showcase_field_ops_delegate shows businessUnit 'Field
Operations', the correct toggles, and assignable sets showcase_contributor +
showcase_manager; toggling manageBindings + Save persists across reload (then
reverted). Same env-scope metadata store as P2 (display-in-Setup freshness is the
tracked framework follow-up).
ADR-0056 epic #2398 P3.
…itor (ADR-0056 P4)
Completes the pure-model separation: the permission matrix editor is now purely
a *design* surface (object/field matrix + system capabilities + RLS/tab/admin
facets), and *assigning* users is a Setup act handled on the sys_permission_set
record page (RecordPermissionAssignmentsRenderer, P1b). Removes the embedded
AssignedUsersSection from PermissionMatrixEditor; the component itself is
unchanged and still used by the Setup record page.
With P1 rendering all six facets read-only (summary + Studio deep-link) in
Setup, no permission concern is editable as free-text JSON anywhere in Setup —
verified: the Setup create form shows identity fields + read-only 'Design in
Studio' facet hints (no JSON textareas), the record page shows summaries +
deep-links + the assignment panel, and the editor shows the matrix +
capabilities + advanced facets with no assignment.
ADR-0056 epic #2398 P4.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercelBot commented Jul 12, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectuiIgnoredIgnoredJul 12, 2026 3:18pm

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Main entry (gzip)60.5 KB350 KB
Entry fileindex-D5pRTJs4.js
StatusPASS

📦 Bundle Size Report

PackageSizeGzipped
app-shell (index.js)7.84KB2.85KB
app-shell (runtime-config.js)7.42KB2.32KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)7.57KB2.97KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)1.17KB0.53KB
auth (AuthProvider.js)21.08KB4.19KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.12KB3.41KB
auth (LoginForm.js)14.70KB4.38KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.63KB2.15KB
auth (SocialSignInButtons.js)8.89KB3.61KB
auth (UserMenu.js)3.40KB1.22KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)28.83KB7.01KB
auth (createAuthenticatedFetch.js)3.93KB1.55KB
auth (index.js)1.75KB0.76KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)4.74KB0.85KB
auth (useIsWorkspaceAdmin.js)1.61KB0.85KB
collaboration (CommentThread.js)18.38KB4.49KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)3.65KB1.42KB
collaboration (PresenceProvider.js)2.42KB0.96KB
collaboration (index.js)1.25KB0.53KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)441.51KB95.26KB
core (index.js)1.65KB0.59KB
create-plugin (index.js)9.28KB2.98KB
data-objectstack (index.js)112.33KB27.89KB
fields (index.js)190.04KB46.40KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (i18n.js)4.32KB1.77KB
i18n (index.js)2.46KB0.96KB
i18n (pickLocalized.js)1.31KB0.67KB
i18n (provider.js)5.37KB1.72KB
i18n (useObjectLabel.js)21.15KB4.68KB
i18n (useSafeTranslation.js)2.68KB0.98KB
layout (index.js)38.41KB10.65KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.74KB
mobile (index.js)1.50KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)4.42KB1.27KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.71KB0.42KB
mobile (useResponsiveConfig.js)1.36KB0.63KB
mobile (useSpecGesture.js)1.77KB0.77KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)5.09KB1.84KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)3.46KB1.03KB
permissions (evaluator.js)4.00KB1.23KB
permissions (index.js)0.91KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.52KB
permissions (usePermissions.js)1.50KB0.70KB
plugin-ai (index.js)15.71KB3.79KB
plugin-calendar (index.js)45.12KB12.34KB
plugin-charts (index.js)46.31KB13.06KB
plugin-chatbot (index.js)173.88KB41.49KB
plugin-dashboard (index.js)108.35KB26.87KB
plugin-designer (index.js)213.95KB43.04KB
plugin-detail (index.js)208.27KB50.27KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)104.27KB25.25KB
plugin-gantt (index.js)136.81KB33.91KB
plugin-grid (index.js)171.87KB45.39KB
plugin-kanban (index.js)48.16KB12.94KB
plugin-list (index.js)98.18KB23.16KB
plugin-map (index.js)16.80KB5.24KB
plugin-markdown (index.js)13.65KB4.67KB
plugin-report (index.js)37.83KB9.97KB
plugin-timeline (index.js)25.37KB7.20KB
plugin-tree (index.js)8.21KB2.76KB
plugin-view (index.js)85.47KB20.80KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.55KB0.67KB
providers (UploadProvider.js)11.71KB3.53KB
providers (index.js)0.44KB0.22KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)3.19KB1.38KB
react (LazyPluginLoader.js)3.77KB1.33KB
react (SchemaRenderer.js)18.23KB5.97KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)0.79KB0.43KB
sdui-parser (codegen.js)4.09KB1.74KB
sdui-parser (index.js)2.16KB0.94KB
sdui-parser (parse.js)10.04KB2.82KB
sdui-parser (types.js)0.29KB0.24KB
sdui-parser (validate.js)4.69KB1.48KB
tenant (TenantContext.js)0.31KB0.25KB
tenant (TenantGuard.js)1.04KB0.43KB
tenant (TenantProvider.js)2.76KB0.98KB
tenant (TenantScopedQuery.js)0.77KB0.44KB
tenant (index.js)0.75KB0.38KB
tenant (resolver.js)2.64KB0.76KB
tenant (useTenant.js)0.50KB0.32KB
tenant (useTenantBranding.js)0.62KB0.39KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)0.20KB0.18KB
types (crud.js)0.20KB0.18KB
types (data-display.js)0.20KB0.18KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)0.77KB0.41KB
types (disclosure.js)0.20KB0.18KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (index.js)1.54KB0.68KB
types (layout.js)0.20KB0.18KB
types (mobile.js)0.20KB0.18KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.26KB1.96KB
types (tenant.js)0.20KB0.18KB
types (theme.js)0.20KB0.18KB
types (ui-action.js)0.75KB0.46KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-zhuang
os-zhuang merged commit e492b9d into mainJul 13, 2026
10 checks passed
@os-zhuang
os-zhuang deleted the feat/perm-pure-model branch July 13, 2026 00:21
os-zhuang added a commit that referenced this pull request Jul 13, 2026
Drops the Option-B `system_permissions`-in-Setup exception: all six facets are
designed in the structured permission editor (reached from both Studio and
Setup's env-scope metadata route), and Setup renders every facet read-only
(summary + Studio deep-link) plus user assignment. Updates the decision, the
facet table, the phase list (P1/P1b/P2/P3/P4 as shipped), resolves open Q6,
adopts alternative A2 (bootstrap knot resolved via the Setup-side entry point),
and adds the metadata↔data-record projection-freshness gap as open Q7 / a
framework follow-up.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
os-zhuang added a commit that referenced this pull request Jul 13, 2026
…ps assignment (#2399)
* docs(adr): ADR-0056 — permission editing belongs in Studio, Setup keeps assignment
A permission set (sys_permission_set) has two editing surfaces over the SAME
record: Setup's six raw-JSON textareas and Studio's structured
PermissionMatrixEditor (objects + fields only). Raw JSON authz metadata is
invisible to the ADR-0090 explain engine + publish linter, unvalidated,
duplicated, and a silent-incident footgun.
Decision (Option B): permission editing → Studio (structured editors); Setup
keeps user management + permission-set assignment; system_permissions (incl.
studio.access) stays in Setup as a sys_capability picker. Phased P1–P5,
tracked in #2398.
Design-review PR — Status: Proposed. Open questions (RLS editor depth,
deep-link UX/scope, legacy-JSON backward-compat) are called out for reviewers.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(adr): revise ADR-0056 to the pure model (implemented in #2403)
Drops the Option-B `system_permissions`-in-Setup exception: all six facets are
designed in the structured permission editor (reached from both Studio and
Setup's env-scope metadata route), and Setup renders every facet read-only
(summary + Studio deep-link) plus user assignment. Updates the decision, the
facet table, the phase list (P1/P1b/P2/P3/P4 as shipped), resolves open Q6,
adopts alternative A2 (bootstrap knot resolved via the Setup-side entry point),
and adds the metadata↔data-record projection-freshness gap as open Q7 / a
framework follow-up.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@os-zhuang