Repository navigation
Commit 0df67f2
feat(app-shell,fields,console): Setup's positions and permission sets read the registry, through the metadata-admin pages' environment scope (part of objectui#7611) (#12089)
Part of #7611
Clause-②: yes
- The claim (6095693499) declared `no` provisionally and said it becomes
`yes` if the public surface widens. It widens by one export:
`@object-ui/app-shell` gains `ENVIRONMENT_SCOPE_QUERY`, a string
constant. No accept set narrows. The changeset is `minor`.
_Updated at round 2 (`bc7a86bb6f`): the sections below describe this
head. The round reports are 6104663917 and 6106225321; the contract
record is 6106517489._
**Draft by dispatch** (objectui AGENTS.md §9 ②). The epic PM of
objectstack#15194 moves this PR after objectstack#15204 stage 1 has
merged and been measured. Several halves of the card need server or spec
changes first; they are listed below with the gate each one waits on.
Size: 2,084 changed lines at round 2 (additions plus deletions, tests
included), 26 files, against `206505c`. One PR, under the 3,000-line
budget.
## What this PR does
The Setup catalog for positions and permission sets reads the registry.
It reuses the metadata-admin list and editors in an **environment
scope** (`?scope=environment`) instead of adding a page family, as
ruling 6094171670 asks ("Build it by reuse, not by new pages"):
- **The list** (`…/metadata/position?scope=environment`,
`…/metadata/permission?scope=environment`) shows every item `GET
/api/v1/meta/TYPE` serves, the platform's own sets included. The Studio
list still shows one project package's slice. Every link the list emits
keeps the scope, and so does the editor's breadcrumb.
- **Gated for Setup (#22621 → A).** A caller without `manage_metadata`
gets no create affordance and a read-only editor, and the page says why.
Under `single` the reason is that the platform administrator defines
these items and the caller's organization assigns them. Under a wall the
reason is that the operator defines them in Studio. Both editors
(generic and permission matrix) now apply this caller gate wherever they
render, because the metadata door refuses that caller's save in every
scope. The lock banner's "New" offer follows the same gate.
- **Holders.** In the scope, the permission-set editor shows the set's
holders (`AssignedUsersSection`) and the position editor shows the
position's holders (`PositionHoldersSection`, new). Both read and write
assignment rows by the item's name.
- **`AssignedUsersSection` reads by name.** Grants come from the
`permission_set` name column. The positions that distribute a set come
from the registry's position definitions (`permissionSets`, the shape on
stage 1's branch). The section no longer reads
`sys_position_permission_set` or `sys_position` rows.
- **The recipient picker** (sharing rules) lists the `position` registry
through the console's metadata store, and no longer reads `sys_position`
rows.
- **The console's `system/roles`, `system/positions` and
`system/permissions`** go to the catalog list instead of the object
pages.
- **The active switch.** The list has an active column and an
active/inactive filter, through one seam, `catalog-activation.ts`,
described next.
### The active switch writes the activation ledger
The switch shows the state of `sys_metadata_activation` (no row means
active), read through the data door the way Setup's packaged-actions
section already reads it. It writes through objectstack's stage-2c door,
`POST /api/v1/security/_activation/:type/:name` with `{ enabled }`. It
reads and writes no catalog row, and there is no row-flag fallback. The
door's refusals reach the page in the server's own words. The audience
anchors `everyone` and `guest` show a disabled switch with the reason,
read from the spec's `AUDIENCE_ANCHOR_POSITIONS`. Capabilities have no
switch.
On objectstack `main`, the door still refuses an item saved through the
metadata door (503, "Package is required"). The fix is objectstack#15204
stage 2d (finding F5). Until it lands, that refusal shows on the page.
## Measured against a running objectstack showcase (`main` `d85615dd`,
fresh database)
| Read | Result |
|:--|:--|
| `GET /api/v1/meta/permission` | 17 items. Equal to the 17
`sys_permission_set` rows. No item carries `active`, `managedBy` or
`_lock`. |
| `GET /api/v1/meta/position` | 16 items. Equal to the 16 `sys_position`
rows. No item carries `active` or `is_default`. |
| `GET /api/v1/meta/capability` | **2 items. There are 11
`sys_capability` rows.** The nine platform capabilities
(`manage_metadata`, `manage_users`, `setup.access` and the others) exist
only as rows. |
| Platform admin `PUT /meta/permission/NAME` | 200. The set is projected
to a row (`active: true`, `managed_by: admin`). |
| Platform admin `PUT /meta/position/NAME` | 200. **No row is created**
(the S7 write-through runs from row to definition only). |
| Organization admin `GET /meta/permission`, `/position`, `/capability`
| 200 for all three. |
| Organization admin `PUT /meta/permission/…`, `/meta/position/…` | 403
`FORBIDDEN`, "Saving a metadata item requires the `manage_metadata`
capability." |
| Organization admin, the activation door | 403 `PERMISSION_DENIED`
(round 2, on `490cb6d9`). The console no longer PATCHes the catalog row.
|
| Grant by name: `POST sys_user_permission_set` with only
`permission_set` | **400 `VALIDATION_FAILED`.** `permission_set_id` is
still required. |
| Assignment by name to a registry-only position | 400
`VALIDATION_FAILED` on `d85615dd`. **201 since objectstack stage 2a**
(round 2, on `490cb6d9`). |
The organization-admin persona was the showcase's auditor demo user,
promoted to org `admin` (`manage_org_users`, `setup.access`,
`setup.write`; no `manage_metadata`).
## Browser verification (objectui at this branch's head, Vite on a
private port, proxied to the showcase above)
Platform administrator, `single`:
- `system/permissions` lands on
`/apps/setup/metadata/permission?scope=environment`. The list has 18
rows; every link carries the scope, New is offered, and no read-only
reason shows.
- **Create:** catalog New, name and label, Save (a draft), then the
matrix editor's Save makes it live. `GET /meta/permission/c9_ui_set`
answers 200 and the row is projected. **Edit:** granting Read on
`showcase_account` and saving answers
`{"showcase_account":{"allowRead":true}}`. **Deactivate and reactivate**
(round 2, on `490cb6d9`): the switch posts to the activation door. One
ledger row reads off and the holder loses the set; switching on returns
it. The catalog row is untouched.
- A packaged set opens locked, with Clone offered and no Save.
- **Positions:** `system/positions` lands on the position catalog (17
rows). A position created from New (draft, then Publish) is live in the
registry. The `auditor` page lists its holders. **Assign by name:**
adding the phone persona landed `{"position":"auditor"}` in
`sys_user_position`.
- On a registry-only position (round 2, on `490cb6d9`), adding a holder
answers 201. The switch shows the door's F5 refusal until objectstack
stage 2d lands.
Organization administrator: the list states the reason, offers no New,
and its switches are disabled. The set editor has no Save and states the
reason, and so does the position editor.
Requests the catalog made: `/meta/permission`, `/meta/position`,
`/meta/*/NAME/layers`, and, for the switch,
`/data/sys_metadata_activation` and the activation door. No request
fetched a merged list. The matrix editor's capability picker still reads
`/data/sys_capability`; see the gates below.
**NOT MEASURED:** a walled posture (`group` / `isolated`). The showcase
runs `single`, and the walled reason text is covered only by a unit
test.
## What this PR cannot do yet: the server and spec halves, each with its
gate
1. **The active switch:** done. The door landed in objectstack stage 2c.
Its remainder, for items saved through the metadata door, is
objectstack#15204 stage 2d.
2. **Capabilities** (the Capabilities page and
`CapabilityMultiSelectField`) still read `sys_capability`. The registry
has served 11 of 11 since objectstack#22669, so the server half is met.
The capability move stays in part 2.
3. **Granting a permission set by name.** The section reads by name, but
a new grant still carries `permission_set_id`. That is one row lookup,
`resolveGrantRowId`, at add time. **Gate:** the grant door accepts
`permission_set` without `permission_set_id`.
4. **Assigning a registry-only position:** done on the server since
objectstack stage 2a (201, measured at round 2).
5. **Approver pickers and the approver directory** (`approverIdentity`,
`useApproverDirectory`, `FlowReferenceField`, `flow-node-config`) and
the decision-output position picker (`decisionOutputParams`). These
follow the **spec**: `APPROVER_VALUE_BINDINGS.position` is `{ source:
'record', object: 'sys_position', valueField: 'name' }`, and
`DecisionOutputDef` says a `position` output collects `sys_position`
record ids. Contract-first: the spec changes first, then these readers.
**Gate:** a registry binding in `@objectstack/spec`, plus a name-valued
`position` decision output.
6. **The Setup navigation** (`nav_positions`, `nav_permission_sets` and
`nav_capabilities`, all `type: 'object'` in plugin-security) still opens
the object pages until stage 8. The catalog URLs for stage 8 are
`/apps/setup/metadata/position?scope=environment` and
`/apps/setup/metadata/permission?scope=environment`. The object pages'
special cases stay until then, because those pages remain reachable: the
`RecordDetailView` assignments slot, `recordDelete`'s reset copy,
`data-objectstack`'s facet-widget stamp and `PermissionFacetLink`. They
become dead code at stage 8 and leave in a cleanup paired with it.
7. **Clone to customize** still runs the `clone_permission_set` row
action, which stage 8 deletes. Rebuilding it on the metadata door means
deciding which keys a clone carries. `adminScope` is ruled out.
`isDefault` would silently add the copy to the everyone baseline. That
decision is raised in the report rather than made here.
8. **The position → permission-set binding editor** needs a form field.
Stage 1 as landed declares `permissionSets` in `position.form.ts` as a
tags field. This PR builds the read side against that shape
(`positionsDistributing`); what remains for the editor is a registry
picker widget.
## Cloud: the `.objectui-sha` hold this PR needs
cloud consumes objectui `main` on framework `56bf27affb` (the v17 line).
I read that commit's source. It has no `security-catalog.ts`, no
`builtin-positions.ts` registration and no position write-through.
Positions are seeded and declared into rows only
(`bootstrap-builtin-positions`, `bootstrap-declared-positions`). So `GET
/api/v1/meta/position` there serves the declared positions only: no
built-in position and no organization-created position. The recipient
picker and the position catalog would show cloud's organizations fewer
positions than they see today. **Hold:** cloud's `.objectui-sha` must
not move past this PR's merge commit until cloud moves to v18 (after
C7).
## objectui#7205
This PR does not close it. The Setup catalog pages are no longer
`ObjectView`s, so a column-header click there no longer persists an
org-wide overlay. That was this re-route's share of the card. The
persistence in `ObjectView`'s sort handler is untouched, and it is still
live for every other object, so objectui#7205 remains open.
## objectstack#11753
That card answers 404 on REST and on the web, while its neighbours
answer 200, so it has been destroyed. What survives is its record in
objectstack: the CHANGELOG entry for `ActionParamSchema.carryOver` (spec
half, #11992) and ADR-0126 §7.1. objectui never rendered `carryOver`.
Its only producers are the five `clone_permission_set` facet params,
which stage 8 deletes with the action. A metadata-door clone copies the
whole definition and collects only a name and a label, so it needs no
carry-over param. Once stage 8 lands, `carryOver` has no producer; that
is reported for the PM.
## Reader census (37 files at base `023f00d4`, tests, docs and Markdown
excluded)
- **Changed (reads):** `AppContent.tsx` (three routes),
`AssignedUsersSection.tsx`, `RecipientPickerField.tsx`.
- **Read, unchanged, and gated:** `CapabilityMultiSelectField.tsx` (gate
2); `approverIdentity.ts`, `useApproverDirectory.ts`,
`decisionOutputParams.ts`, `FlowReferenceField.tsx` and
`flow-node-config.ts` (gate 5); `permission-set-clone-dispatch.ts`,
`PermissionMatrixEditor.tsx`'s clone path and its four `i18n.ts` clone
strings (gate 7).
- **Object-page special cases that stay until stage 8 (gate 6):**
`RecordDetailView.tsx`, `RecordPermissionAssignmentsRenderer.tsx`,
`recordDelete.ts`, `data-objectstack/src/index.ts` and
`PermissionFacetLink.tsx`.
- **Text only** (comments, docblocks and one component-input
description): `ObjectView.tsx`, `capabilityLint.ts`,
`fields/src/index.tsx`, `MetadataFieldsPage.tsx`,
`InlineFieldInput.tsx`, `RelatedList.tsx`,
`plugin-detail/src/index.tsx`, `record-related-list.tsx`,
`ObjectForm.tsx` and `field-types.ts`, plus all ten locale files (11
hits, every one a comment).
## Verification
Measured at the branch head named in the report.
- Type-check (`type-check`, hyphenated): `@object-ui/fields`,
`@object-ui/app-shell` and `@object-ui/console` all pass, each after
building its dependency closure.
- New and updated tests (vitest, run from the repo root):
- `ResourceListPage.environmentScope-7611` 9/9;
`ResourceEditPage.setupCatalog-7611` 5/5;
`PermissionMatrixEditor.setupCatalog-7611` 5/5;
- `PositionHoldersSection` 4/4; `catalog-activation` 5/5;
`AssignedUsersSection` and its envelope test 9/9;
- `RecipientPickerField` 11/11; `AppContent.systemHubRoutes` 22/22.
- Full suites of the three affected packages: see the report's `tests`
field.
- **Ablation** (each mutation committed first, landed on disk with its
anchor count 1 → 0, restored from `HEAD` and proven by blob hash and an
empty `git diff HEAD`):
- removing the environment-scope bypass in the list turned 5 of 9 list
pins red;
- removing `canAuthor` from the editor's `canWrite` turned exactly the
caller-gate pin red (1 failed, 4 passed).
- Gates passed: `check:new-line-citations` (0 new),
`check:control-bytes`, `check:i18n-keys`, `check:i18n-dead-keys`,
`check:side-effects-array`, `check:unreferenced-sources`,
`check:vi-mock-specifiers`, `check:vi-mock-inherit`,
`check:vi-mock-override-shape`, `check:test-path-roots`,
`check:doc-fences`, `check:doc-types` and `check:docs-route-closure`.
The changeset gates pass too: presence (21 published source files, 1
changeset) and no-major.
- **NOT MEASURED**, because a precondition was not met (unbuilt
`@object-ui/cli` and `@object-ui/plugin-ai`, outside this diff's
closure): `check:readme-exports` (the entries it could judge had 0
wrong-path and 0 fabricated) and `check:doc-snippets`. Also not run
locally: `pnpm lint` and the eager-closure budget, which needs a console
production build. Both run in CI.
## Acceptance notes
- In the Setup catalog, a position or permission set created with New is
saved as a draft first. The permission matrix's Save, or the position
editor's Publish, makes it live. Today's Setup object pages wrote a live
row in one step.
- The `permission` list registration dropped its `managedBy` "Source"
column. The registry serves no such key, so it read "Custom" for every
set, the platform's own included. The list's own Source badge (Artifact
or Runtime, from `_packageId` and `_provenance`) remains.
- Until stage 1 lands, a set's "via position" holders are empty on
`main`. On `main` the bindings are junction rows, which this section no
longer reads.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01Rerax7QTjKMPCUZxQUtPFR)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent a194b4e commit 0df67f2
26 files changed
Lines changed: 1948 additions & 136 deletions
File tree
- .changeset
- apps/console/src
- __tests__
- content/docs/guide
- packages
- app-shell
- src
- services
- views/metadata-admin
- fields/src/widgets
- plugin-form/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
13 | | - | |
| 13 | + | |
14 | 14 | | |
15 | 15 | | |
16 | 16 | | |
| |||
129 | 129 | | |
130 | 130 | | |
131 | 131 | | |
132 | | - | |
133 | | - | |
134 | | - | |
135 | | - | |
136 | | - | |
137 | | - | |
138 | | - | |
139 | | - | |
140 | | - | |
141 | | - | |
142 | | - | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
143 | 138 | | |
144 | 139 | | |
145 | 140 | | |
| |||
154 | 149 | | |
155 | 150 | | |
156 | 151 | | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
157 | 173 | | |
158 | 174 | | |
159 | 175 | | |
| |||
238 | 254 | | |
239 | 255 | | |
240 | 256 | | |
241 | | - | |
242 | | - | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
243 | 262 | | |
244 | 263 | | |
245 | 264 | | |
| |||
269 | 288 | | |
270 | 289 | | |
271 | 290 | | |
272 | | - | |
273 | | - | |
274 | | - | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
275 | 294 | | |
276 | 295 | | |
277 | 296 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
240 | 240 | | |
241 | 241 | | |
242 | 242 | | |
243 | | - | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
244 | 246 | | |
245 | 247 | | |
246 | 248 | | |
| |||
269 | 271 | | |
270 | 272 | | |
271 | 273 | | |
272 | | - | |
273 | | - | |
274 | | - | |
275 | | - | |
276 | | - | |
277 | | - | |
| 274 | + | |
| 275 | + | |
278 | 276 | | |
279 | 277 | | |
280 | 278 | | |
281 | 279 | | |
282 | | - | |
283 | | - | |
284 | | - | |
285 | 280 | | |
286 | 281 | | |
287 | 282 | | |
| |||
347 | 342 | | |
348 | 343 | | |
349 | 344 | | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
| 388 | + | |
| 389 | + | |
| 390 | + | |
| 391 | + | |
| 392 | + | |
350 | 393 | | |
351 | 394 | | |
352 | 395 | | |
| |||
369 | 412 | | |
370 | 413 | | |
371 | 414 | | |
372 | | - | |
373 | | - | |
374 | | - | |
375 | 415 | | |
376 | 416 | | |
377 | 417 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
98 | 98 | | |
99 | 99 | | |
100 | 100 | | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
101 | 109 | | |
102 | 110 | | |
103 | 111 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
478 | 478 | | |
479 | 479 | | |
480 | 480 | | |
| 481 | + | |
| 482 | + | |
| 483 | + | |
| 484 | + | |
| 485 | + | |
| 486 | + | |
| 487 | + | |
| 488 | + | |
| 489 | + | |
| 490 | + | |
| 491 | + | |
| 492 | + | |
| 493 | + | |
| 494 | + | |
| 495 | + | |
| 496 | + | |
| 497 | + | |
| 498 | + | |
| 499 | + | |
| 500 | + | |
| 501 | + | |
| 502 | + | |
| 503 | + | |
| 504 | + | |
| 505 | + | |
| 506 | + | |
| 507 | + | |
| 508 | + | |
| 509 | + | |
| 510 | + | |
| 511 | + | |
| 512 | + | |
| 513 | + | |
| 514 | + | |
| 515 | + | |
| 516 | + | |
| 517 | + | |
481 | 518 | | |
482 | 519 | | |
483 | 520 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
425 | 425 | | |
426 | 426 | | |
427 | 427 | | |
| 428 | + | |
| 429 | + | |
| 430 | + | |
| 431 | + | |
428 | 432 | | |
429 | 433 | | |
430 | 434 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
197 | 197 | | |
198 | 198 | | |
199 | 199 | | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
200 | 204 | | |
201 | 205 | | |
202 | 206 | | |
203 | | - | |
204 | | - | |
205 | | - | |
206 | | - | |
207 | | - | |
208 | | - | |
| 207 | + | |
209 | 208 | | |
210 | 209 | | |
211 | 210 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
47 | 47 | | |
48 | 48 | | |
49 | 49 | | |
50 | | - | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
51 | 56 | | |
52 | 57 | | |
53 | | - | |
54 | | - | |
55 | | - | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
56 | 61 | | |
57 | 62 | | |
58 | 63 | | |
| |||
0 commit comments