Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .changeset/28578-setup-metadata-into-studio.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
'@object-ui/app-shell': patch
'@object-ui/console': patch
'@object-ui/i18n': patch
'@objectstack/platform-objects': patch
'@objectstack/plugin-security': patch
'@objectstack/plugin-sharing': patch
---

feat(setup): Setup's metadata pages open Studio's All metadata area, the catalog's Active switch moves to each item's page, and Studio stops linking into Setup

Clause-②: no
Metadata-protocol: none

Setup no longer has a metadata editor or a metadata catalog (ADR-0146 D1). Studio's "All metadata" area serves those pages now.

- **Setup's metadata URLs redirect.** Each `/apps/<app>/metadata/...` route opens the same page in Studio's "All metadata" area: the directory, `_diagnostics`, a type's list, an item, its history and its create page. The legacy aliases redirect too: `component/metadata/{directory,resource}` in the shell, and `system/objects`, `system/metadata` and `system/{roles,positions,permissions}` in the console. `?package=`, `?scope=environment` and `?review=1` carry over, and each URL lands in one hop on the leg its query names.
- **The datasource stays in Setup.** Its routes render Setup's pages as before. It is runtime configuration, not metadata authoring, and the area sends datasource URLs back to Setup.
- **A bookmark of the retired Setup › Packaged automation page lands in Studio.** `/apps/<app>/component/automation/packaged?package=<id>` opens that package's Automations pillar, where a packaged flow's switch and New similar are, with the query kept. With no package named, it opens the area's cross-package list of flows.
- **The catalog entries leave Setup.** Positions, Capabilities, Permission Sets and Declared Sharing Rules leave Setup's navigation, and their labels are retired in all four locales. Home loses the "Manage Objects" quick action, and the sidebar loses its Objects entry.
- **The Active switch moves to the item's page.** A permission set, a position or a declared sharing rule is now switched on and off on its own page in the area. It is the same switch the catalog list had: switching off asks first and names how many users lose the access, and switching on writes at once. The area's type lists keep their switch column. **An admin with `setup.access` but no `studio.access` now switches these items in Studio.** Grant `studio.access` to anyone who managed the catalog from Setup.
- **Studio stops linking into Setup.** The landing page's environment-overlay link is gone. The read-only item link and the chat's "Review changes" now open the item in Studio's area.
2 changes: 1 addition & 1 deletion docs/adr/0126-packaged-metadata-customization-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,7 @@ callers first, or don't), and preserves "packaged code calls what it names".

- **Leaning D2 — a Setup page for packaged automation** (final call rides this ADR's merge):
packaged flows with their activation state and the enable/disable and clone actions,
contributed the way `nav_permission_sets` is (`packages/plugins/plugin-security/src/security-plugin.ts#nav_permission_sets`). The maintainer
contributed the way "nav_permission_sets" is (`packages/plugins/plugin-security/src/security-plugin.ts`; 2026-11-02 note: that entry is retired under N-28, #28578, and §7.4's surface moved per ADR-0146 D4). The maintainer
corrected the record here: automation UI is **Studio-only today** (`packages/platform-objects/src/apps/studio.app.ts`),
so this page is new work, and the Setup permission-set page is the precedent shape. Studio
keeps the editing; Setup gets the operational state. Minimum honest content per packaged flow:
Expand Down
4 changes: 0 additions & 4 deletions packages/platform-objects/src/apps/translations/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,11 +95,7 @@ export const en: TranslationData = {
nav_permission_set_assignment: { label: 'Permission Set Assignment' },

// Access Control
nav_positions: { label: 'Positions' },
nav_capabilities: { label: 'Capabilities' },
nav_permission_sets: { label: 'Permission Sets' },
nav_sharing_rules: { label: 'Sharing Rules' },
nav_declared_sharing_rules: { label: 'Declared Sharing Rules' },
nav_record_shares: { label: 'Record Shares' },
nav_queues: { label: 'Queues' },
nav_queue_members: { label: 'Queue Members' },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,7 @@ export const esESSourceHashes: Readonly<Record<string, string>> = {
'apps.setup.navigation.nav_invitations.label': '4ebb9fd13de7a546',
'apps.setup.navigation.nav_position_holders.label': '627cacb50fd1b0d8',
'apps.setup.navigation.nav_permission_set_assignment.label': 'fcf7d1b69ee9c298',
'apps.setup.navigation.nav_positions.label': '517a2743f949b5f0',
'apps.setup.navigation.nav_capabilities.label': '0b59005e10add41f',
'apps.setup.navigation.nav_permission_sets.label': '4addfe2b8b28b78a',
'apps.setup.navigation.nav_sharing_rules.label': 'ceaa66fa5f1df4ad',
'apps.setup.navigation.nav_declared_sharing_rules.label': 'a3371ec9975d5b0f',
'apps.setup.navigation.nav_record_shares.label': 'e261d340ceb25b9a',
'apps.setup.navigation.nav_sso_providers.label': '7b06b6a6abf53ba4',
'apps.setup.navigation.nav_api_keys.label': '8cda9851248b0490',
Expand Down
4 changes: 0 additions & 4 deletions packages/platform-objects/src/apps/translations/es-ES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,7 @@ export const esES: TranslationData = {
nav_position_holders: { label: 'Titulares de posiciones' },
nav_permission_set_assignment: { label: 'Asignación de conjuntos de permisos' },

nav_positions: { label: 'Posiciones' },
nav_capabilities: { label: 'Capacidades' },
nav_permission_sets: { label: 'Conjuntos de Permisos' },
nav_sharing_rules: { label: 'Reglas de Compartición' },
nav_declared_sharing_rules: { label: 'Reglas de Compartición Declaradas' },
nav_record_shares: { label: 'Registros Compartidos' },
nav_queues: { label: 'Colas' },
nav_queue_members: { label: 'Miembros de Colas' },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,7 @@ export const jaJPSourceHashes: Readonly<Record<string, string>> = {
'apps.setup.navigation.nav_invitations.label': '4ebb9fd13de7a546',
'apps.setup.navigation.nav_position_holders.label': '627cacb50fd1b0d8',
'apps.setup.navigation.nav_permission_set_assignment.label': 'fcf7d1b69ee9c298',
'apps.setup.navigation.nav_positions.label': '517a2743f949b5f0',
'apps.setup.navigation.nav_capabilities.label': '0b59005e10add41f',
'apps.setup.navigation.nav_permission_sets.label': '4addfe2b8b28b78a',
'apps.setup.navigation.nav_sharing_rules.label': 'ceaa66fa5f1df4ad',
'apps.setup.navigation.nav_declared_sharing_rules.label': 'a3371ec9975d5b0f',
'apps.setup.navigation.nav_record_shares.label': 'e261d340ceb25b9a',
'apps.setup.navigation.nav_sso_providers.label': '7b06b6a6abf53ba4',
'apps.setup.navigation.nav_api_keys.label': '8cda9851248b0490',
Expand Down
4 changes: 0 additions & 4 deletions packages/platform-objects/src/apps/translations/ja-JP.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,7 @@ export const jaJP: TranslationData = {
nav_position_holders: { label: 'ポジション保持者' },
nav_permission_set_assignment: { label: '権限セットの割り当て' },

nav_positions: { label: 'ポジション' },
nav_capabilities: { label: 'ケイパビリティ' },
nav_permission_sets: { label: '権限セット' },
nav_sharing_rules: { label: '共有ルール' },
nav_declared_sharing_rules: { label: '宣言済みの共有ルール' },
nav_record_shares: { label: 'レコード共有' },
nav_queues: { label: 'キュー' },
nav_queue_members: { label: 'キューメンバー' },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,20 @@
// entry id (example apps, docs, test fixtures) — so
// for that one the grep is no evidence, and the
// assertions below are the whole claim.
// nav_positions the Setup metadata catalog (ADR-0146 D1, M-1
// nav_capabilities ruling A): Setup carries no metadata catalog.
// nav_permission_sets The four entries opened the registry types'
// nav_declared_sharing_ environment-scope lists, which Studio's "All
// rules metadata" area serves now, with the on/off switch
// on each item's page; Setup's URLs redirect there.
// The first three were plugin-security's, the last
// plugin-sharing's; both depend on this package, so
// the declaring side is pinned in their own tests.
// Measured differently: the grep answers zero for
// `nav_capabilities` and `nav_declared_sharing_rules`
// but not for the other two, which fixtures and an
// ADR still spell, so for those the assertions below
// are the whole claim.
// nav_packaged_automation Setup › Packaged automation (ADR-0146 D11): its
// flows half moved into Studio (D4), and its actions
// half is its own entry, `nav_packaged_actions`.
Expand Down Expand Up @@ -100,10 +114,14 @@ const DEAD_SETUP_NAV_IDS = [
'nav_accounts',
'nav_approval_processes',
'nav_audit_log_browser',
'nav_capabilities',
'nav_declared_sharing_rules',
'nav_device_codes',
'nav_jwks',
'nav_metadata',
'nav_packaged_automation',
'nav_permission_sets',
'nav_positions',
'nav_user_preferences',
'nav_verifications',
] as const;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,7 @@ export const zhCNSourceHashes: Readonly<Record<string, string>> = {
'apps.setup.navigation.nav_invitations.label': '4ebb9fd13de7a546',
'apps.setup.navigation.nav_position_holders.label': '627cacb50fd1b0d8',
'apps.setup.navigation.nav_permission_set_assignment.label': 'fcf7d1b69ee9c298',
'apps.setup.navigation.nav_positions.label': '517a2743f949b5f0',
'apps.setup.navigation.nav_capabilities.label': '0b59005e10add41f',
'apps.setup.navigation.nav_permission_sets.label': '4addfe2b8b28b78a',
'apps.setup.navigation.nav_sharing_rules.label': 'ceaa66fa5f1df4ad',
'apps.setup.navigation.nav_declared_sharing_rules.label': 'a3371ec9975d5b0f',
'apps.setup.navigation.nav_record_shares.label': 'e261d340ceb25b9a',
'apps.setup.navigation.nav_sso_providers.label': '7b06b6a6abf53ba4',
'apps.setup.navigation.nav_api_keys.label': '8cda9851248b0490',
Expand Down
4 changes: 0 additions & 4 deletions packages/platform-objects/src/apps/translations/zh-CN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,7 @@ export const zhCN: TranslationData = {
nav_position_holders: { label: '岗位持有人' },
nav_permission_set_assignment: { label: '权限集分配' },

nav_positions: { label: '岗位' },
nav_capabilities: { label: '能力' },
nav_permission_sets: { label: '权限集' },
nav_sharing_rules: { label: '共享规则' },
nav_declared_sharing_rules: { label: '已声明的共享规则' },
nav_record_shares: { label: '记录共享' },
nav_queues: { label: '队列' },
nav_queue_members: { label: '队列成员' },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,21 +114,22 @@ describe('SecurityPlugin — Setup People & Organization assignment entries (ADR
}
});

it('carries the same gate as the Positions / Permission Sets entries: none of its own', async () => {
it('carries no gate of its own, and the catalog entries are gone (ADR-0146 D1)', async () => {
const manifest = await registeredManifest();
const items = (manifest.navigationContributions as Contribution[]).flatMap((c) => c.items);
const contributions = manifest.navigationContributions as Contribution[];
const items = contributions.flatMap((c) => c.items);
const byId = new Map(items.map((i) => [i.id, i]));
const positions = byId.get('nav_positions');
const permissionSets = byId.get('nav_permission_sets');
// Control: the catalog entries really are in this manifest, so the
// comparison below is against a real gate, not two undefined lookups.
expect(positions).toBeDefined();
expect(permissionSets).toBeDefined();
// Setup carries no metadata catalog: positions, capabilities and permission
// sets are Studio's "All metadata" area's, so this plugin contributes no
// entry into Access Control.
for (const id of ['nav_positions', 'nav_capabilities', 'nav_permission_sets']) {
expect(byId.has(id), id).toBe(false);
}
expect(contributions.filter((c) => c.group === 'group_access_control')).toEqual([]);
for (const id of [POSITION_HOLDERS.id, PERMISSION_SET_ASSIGNMENT.id]) {
const entry = byId.get(id);
expect(entry, id).toBeDefined();
expect(gateOf(entry!), id).toEqual(gateOf(positions!));
expect(gateOf(entry!), id).toEqual(gateOf(permissionSets!));
expect(Object.values(gateOf(entry!)).filter((v) => v !== undefined), id).toEqual([]);
}
});

Expand All @@ -149,13 +150,11 @@ describe('SecurityPlugin — Setup People & Organization assignment entries (ADR
expect(all.filter((i) => i.componentRef === PERMISSION_SET_ASSIGNMENT.componentRef)).toHaveLength(1);
});

it('without this plugin, neither entry appears — hidden exactly where the catalog entries are', async () => {
it('without this plugin, neither entry appears', async () => {
const nav = await renderedSetup(false);
const ids = nav.flatMap((n) => n.children ?? []).map((i) => i.id);
// Control: the fold still serves platform-objects' entries.
expect(ids).toContain('nav_users');
expect(ids).not.toContain('nav_positions');
expect(ids).not.toContain('nav_permission_sets');
expect(ids).not.toContain(POSITION_HOLDERS.id);
expect(ids).not.toContain(PERMISSION_SET_ASSIGNMENT.id);
});
Expand Down
33 changes: 11 additions & 22 deletions packages/plugins/plugin-security/src/security-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1629,32 +1629,21 @@ export class SecurityPlugin implements Plugin {
// can resolve them by name when SecurityPlugin middleware queries
// `metadata.list('permissions')`.
permissions: this.bootstrapPermissionSets,
// ADR-0029 D7 — contribute the RBAC entries into the Setup app's
// `group_access_control` slot. This plugin owns the security catalog, so
// it ships its menu too; when the plugin is absent the entries don't
// appear. [ADR-0131 D3/D7] Each entry opens Setup's list of the registry
// type in its environment scope — the catalog has no object behind it.
// [ADR-0146 D1] No catalog entries: Setup carries no metadata catalog.
// Positions, capabilities and permission sets are listed in Studio's
// "All metadata" area (`/studio/~all/metadata`), where a position or a
// permission set is switched on and off on its own page (a capability
// has no switch), and an old Setup link to them redirects there. Setup
// keeps the people pages below (D7).
navigationContributions: [
{
app: 'setup',
group: 'group_access_control',
priority: 100,
items: [
{ id: 'nav_positions', type: 'url', label: 'Positions', url: '/apps/setup/metadata/position?scope=environment', icon: 'shield-check' },
{ id: 'nav_capabilities', type: 'url', label: 'Capabilities', url: '/apps/setup/metadata/capability?scope=environment', icon: 'badge-check' },
{ id: 'nav_permission_sets', type: 'url', label: 'Permission Sets', url: '/apps/setup/metadata/permission?scope=environment', icon: 'lock' },
],
},
{
// [ADR-0146 D7] The two ASSIGNMENT pages — who holds a position, and
// which users carry a permission set. They sit with the people they
// assign (People & Organization), not with the catalog above. The
// console binds each `componentRef` to its page (app-shell
// `builtinComponents`), and the pages read and write this plugin's
// assignment rows, so the entries live and die with this plugin like
// the catalog entries. Gate: none of their own, exactly like
// Positions / Permission Sets — the Setup app's `setup.access` and the
// ungated group anchor decide who sees them (ADR-0146 D7: the scope of
// assign (People & Organization). The console binds each
// `componentRef` to its page (app-shell `builtinComponents`), and the
// pages read and write this plugin's assignment rows, so the entries
// live and die with this plugin. Gate: none of their own — the Setup
// app's `setup.access` and the ungated group anchor decide who sees them (ADR-0146 D7: the scope of
// `setup.access` and the delegated-administrator gate), and the writes
// the pages make keep their own server-side gates (the ADR-0090 D12
// delegated-admin gate among them). Priority 200 keeps them after
Expand Down
17 changes: 8 additions & 9 deletions packages/plugins/plugin-sharing/src/sharing-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -653,23 +653,22 @@ export class SharingServicePlugin implements Plugin {
SysAssignmentCursor,
],
// ADR-0029 D7 — contribute the sharing entries into the Setup app's
// `group_access_control` slot (priority 200 so they sit after plugin-
// security's Roles / Permission Sets). This plugin owns these objects (K2).
// `group_access_control` slot (priority 200). This plugin owns these
// objects (K2).
//
// [ADR-0131 D6, regime C] A sharing rule has two sources, and Setup lists
// each where it lives: `nav_sharing_rules` opens the organization's own
// rules (`sys_sharing_rule` rows), `nav_declared_sharing_rules` the rules
// packages declare — registry items with no object behind them, so the
// entry is a `url` to Setup's list of the registry type in its environment
// scope, as plugin-security's `nav_permission_sets` is.
// [ADR-0131 D6, regime C] A sharing rule has two sources: `nav_sharing_rules`
// opens the organization's own rules (`sys_sharing_rule` rows). The rules
// packages declare are registry items with no object behind them, and
// [ADR-0146 D1] Setup carries no metadata catalog: they are listed and
// switched on and off in Studio's "All metadata" area, so no entry here
// opens them.
navigationContributions: [
{
app: 'setup',
group: 'group_access_control',
priority: 200,
items: [
{ id: 'nav_sharing_rules', type: 'object', label: 'Sharing Rules', objectName: 'sys_sharing_rule', icon: 'share-2', requiresObject: 'sys_sharing_rule', requiredPermissions: ['manage_platform_settings'] },
{ id: 'nav_declared_sharing_rules', type: 'url', label: 'Declared Sharing Rules', url: '/apps/setup/metadata/sharing_rule?scope=environment', icon: 'package', requiredPermissions: ['manage_platform_settings'] },
{ id: 'nav_record_shares', type: 'object', label: 'Record Shares', objectName: 'sys_record_share', viewName: 'all_shares', icon: 'link', requiresObject: 'sys_record_share', requiredPermissions: ['manage_platform_settings'] },
{ id: 'nav_queues', type: 'object', label: 'Queues', objectName: 'sys_queue', icon: 'inbox', requiresObject: 'sys_queue', requiredPermissions: ['manage_platform_settings'] },
{ id: 'nav_queue_members', type: 'object', label: 'Queue Members', objectName: 'sys_queue_member', icon: 'users', requiresObject: 'sys_queue_member', requiredPermissions: ['manage_platform_settings'] },
Expand Down
Loading
Loading