Skip to content

fix(app-shell): the Setup position page previews each permission set it names, read-only (#26187) - #26360

Merged
objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/issue-26187-position-permission-preview
Oct 27, 2026
Merged

objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/issue-26187-position-permission-preview

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #26187

Clause-②: no
Metadata-protocol: none

维护者速读

改了什么:Setup → 岗位页面上,岗位列出的每个权限集各显示一个只读的权限预览(对象 × 增删改查、读写记录范围、字段、行级策略),按岗位列出的顺序排列,标题是权限集名并链接到该权限集页面;找不到的权限集只显示一行「未找到」,岗位没列权限集时显示空状态。
为什么改:#26187 的裁决(Q1)要求岗位页能看到权限集授予的范围;之前岗位页只列权限集名字,管理员必须逐个打开权限集才知道岗位到底给了什么。权限集页面本身已由 PR 1(#26284)的矩阵选择器显示范围,席位裁定不再加第二份只读副本。
风险与回滚:只改 app-shell 的岗位页面(新增一个只读区块 + 环境区块从单个改为列表)和 i18n 新键,不改任何写入、规范或接口;最坏情况是岗位页多出一个显示不对的区块,不影响保存。回滚 = revert 本 PR。
先看:ui/packages/app-shell/src/views/metadata-admin/PositionPermissionSetsSection.tsx(风险 hunk:按名读取权限集的 effect,404 → 未找到、其它错误 → 拒绝原文)→ ResourceEditPage.tsx(ENVIRONMENT_SECTIONS 改为数组并把页面草稿传入)→ i18n.ts(en + zh 各 7 个新键)。
两分钟验证:在 ui/ 下跑 pnpm exec vitest run packages/app-shell/src/views/metadata-admin/PositionPermissionSetsSection.test.tsx packages/app-shell/src/views/metadata-admin/ResourceEditPage.positionPermissionSets-26187.test.tsx --maxWorkers=2,预期 Test Files 2 passed (2) / Tests 9 passed (9);或打开 /apps/setup/metadata/position/POSITION_NAME?scope=environment,持有人区块下方出现「Permission sets」区块。
需要你做的:无,席位落地

What changes

PR 2 of 2 (PR 1, the matrix scope pickers, merged as #26284). This PR touches the position page only: the seat ruled that the permission-set page needs no second, read-only preview, because its matrix already shows each object's read and write scope since PR 1.

  • New PositionPermissionSetsSection (metadata-admin/PositionPermissionSetsSection.tsx). On the Setup position page (environment scope, not create mode, not embedded), under the definition and after the holders, it renders one PermissionPreview per permission set the position names in permissionSets:
    • in declared order, each under an h3 with the set's name, linking to ../../permission/NAME (path-relative, keeping ?scope=environment), so the link lands on the set's own page in the same catalog scope;
    • each preview is the set's own body, read with client.get('permission', name) through the console metadata client the page already uses. No union of sets is computed and nothing is editable (the preview gets no onPatch; the section holds no control);
    • a named set the server does not serve (get answers null on 404) shows one line, Permission set "NAME" was not found., for that set only; the other previews still render;
    • a refused or failed read shows Permission set "NAME" could not be read. plus the server's sentence, not "not found": a refused read is not an absent set;
    • a position that names no sets shows This position names no permission sets. and reads nothing.
    • The names come from the page's draft (as PositionPreview above it reads them), and the read effect keys on the list's content, so editing another field does not re-read the sets.
  • ResourceEditPage.tsx: ENVIRONMENT_SECTIONS maps a type to an ordered list of sections, each handed { name, draft }; position is [PositionHoldersSection, PositionPermissionSetsSection]. PositionHoldersSection is unchanged and still reads only name.
  • i18n.ts: 7 new engine.position.sets.* keys, en and zh, beside the existing engine.position.* block. No existing key changes.
  • PermissionPreview.tsx: unchanged. Without onPatch it is already read-only, so no prop was needed.
  • Changeset: .changeset/26187-position-permission-preview.md, @object-ui/app-shell patch.

Tests

  • New PositionPermissionSetsSection.test.tsx (6 tests): two named sets give two previews in declared order, each with its own objects and scopes (no union), each headed by the set name with the right href; read-only (no checkbox, combobox or button); a missing set gives one not-found line only for that set; a refused read is said as a refusal, not "not found"; no sets, and no permissionSets key, give the empty state and no read.
  • New ResourceEditPage.positionPermissionSets-26187.test.tsx (3 tests): the Setup position page mounts the previews in order beside the holders; Studio (no scope) and another type in the Setup scope do not.
  • Red first, ablation, green, the importer suite and the type-check are in Verification below.

Acceptance notes

  • Declared sets only. The section shows exactly the sets the definition names. The position-name fold (a set sharing the position's name) is retired on main (.changeset/24757-retire-position-name-fold.md), so the declared list is the whole grant; the list page's served _grantedPermissionSets column remains the server's own answer.
  • Draft, not saved state. The names are read from the definition on screen, so an unsaved edit to permissionSets previews the edited list. The Setup position page renders read-only for a caller without manage_metadata, where the draft equals the stored definition.
  • Layout. Under the designer, the environment sections share the existing bounded strip (max-h-[35vh], scrolling), so several previews scroll inside it rather than squeezing the canvas.
  • Card relation is declared once, in this body.

Verification

Red first — the two new files against `origin/main` 46255b1's implementation (tree only), then restored

ResourceEditPage.tsx and i18n.ts restored to 46255b19 in the working tree only (blob equal to the main blob; 0 engine.position.sets keys) and the new component removed, under scripts/pm/os-verify-lock.sh:

  • ResourceEditPage.positionPermissionSets-26187.test.tsx: Tests 1 failed | 2 passed (3). The failing test is the positive pin (renders one preview per named set, in declared order, beside the holders); the two passing ones are its absence controls (Studio, another type).
  • PositionPermissionSetsSection.test.tsx: fails to resolve ./PositionPermissionSetsSection (the module does not exist on main).
  • Restore proven: every restored blob equals its HEAD blob, git diff HEAD empty.
Ablation, once — a refused read reported as missing

node scripts/ablation-replace.mjs replaced the failed-read arm (err: unknown): SetRead => ({ state: 'failed', ... }) with (): SetRead => ({ state: 'missing' }) (anchor x1 -> x0, blob 83ca6312 -> 60eba45d). PositionPermissionSetsSection.test.tsx: Tests 1 failed | 5 passed (6), the failing test being a refused read is said as a refusal, not as "not found". Restored: blob == HEAD (83ca6312), git diff HEAD empty. No dist is involved: app-shell tests import src.

Green on the final head 771f48f (after merging `main` with #26340), each under the verify lock
  • Closure pnpm exec turbo run build --filter='@object-ui/app-shell^...' --concurrency=2: exit 0.
  • The two new files: Test Files 2 passed (2) / Tests 9 passed (9).
  • Every app-shell test file that imports ResourceEditPage, PermissionPreview, PositionHoldersSection, the new section or metadata-admin/i18n (resolved by real import path, 291 files), plus Setup permission set, Delegated Admin Scope: no hint or one-click grant for the companion grants a delegate needs to enter Setup and pick people #26191's PermissionAdvancedFacets.companionGrants-26191.test.tsx, which shares i18n.ts: Test Files 292 passed (292) / Tests 2726 passed (2726), exit 0.
  • pnpm --filter @object-ui/app-shell type-check (both tsc programs, tsconfig.test.json included): exit 0.
  • The full app-shell suite was not run locally (it holds the shared lock for 50+ minutes); CI's Test UI shards run it.
Derived gates — `dispatch-gates --commands --repo objectstack-ai/objectstack` at 771f48f: 35 commands, all exit 0; `--ran`: 35 derived, 35 run, 0 NOT-MEASURED

Derivation header: gate list derived from the tree of 'objectstack-ai/objectstack' at commit 771f48f4; change set 6 paths, 495 changed lines (+489 / -6), under the 3000 human-merge threshold. Each command via bash -c, exit code captured before any pipe.

command exit wall
node scripts/check-adr-0087-registration.mjs --base origin/main 0 4s
node scripts/check-adr-0087-registration.mjs --self-test 0 48s
node scripts/check-changeset-no-major.mjs --base origin/main 0 0s
node scripts/check-changeset-no-major.mjs --self-test 0 8s
node scripts/check-closing-keyword-parity.mjs 0 2s
node scripts/check-closing-keyword-parity.mjs --self-test 0 4s
node scripts/check-comment-mask-corpus.mjs 0 190s
node scripts/check-empty-changeset.mjs --base origin/main 0 0s
node scripts/check-empty-changeset.mjs --self-test 0 2s
node scripts/check-sdui-manifest.mjs 0 0s
node scripts/check-sdui-manifest.mjs --self-test 0 3s
node scripts/check-undeclared-dep-imports.mjs 0 5s
node scripts/check-undeclared-dep-imports.mjs --self-test 0 5s
node scripts/pm/release-rehearsal-clone.mjs --self-test 0 2s
node scripts/release-pending-publish.mjs --self-test 0 5s
pnpm --filter @objectstack/spec run check:duration-unit-keys 0 8s
pnpm check:changeset-gate-self-tests 0 61s
pnpm check:closed-ceremony-text 0 3s
pnpm check:cross-package-test-inputs 0 28s
pnpm check:dispatcher-error-vocabulary 0 6s
pnpm check:driver-memory-census 0 5s
pnpm check:engine-double-contract 0 53s
pnpm check:gitlink-declared 0 1s
pnpm check:nul-bytes 0 3s
pnpm check:pm-changeset-deadline-census 0 1s
pnpm check:published-files 0 1s
pnpm check:query-options-erasure 0 299s
pnpm check:refd-timer-probe 0 19s
pnpm check:skill-identifier-liveness 0 9s
pnpm check:test-source-alias 0 26s
pnpm check:tier-file-adoption 0 0s
pnpm check:type-check-coverage 0 5s
pnpm check:watch-hint-literal 0 6s
pnpm check:where-matcher 0 11s
pnpm check:type-check-debt (run under the verify lock) 0 110s re-measure

check-sdui-manifest: sdui.manifest.json is present, intact (sha256 1f6e1ccb925a…, 106 components); app-shell has no sdui record and scripts/sdui-manifest.* is untouched.


Generated by Claude Code

…views (#26187)

Red first: the Setup position page names its permission sets but shows
nothing of what they grant.

Claude-Session: https://claude.ai/code/session_01SMps7wq7rBTk53e8KTSEup
Co-authored-by: Claude <noreply@anthropic.com>
…it names, read-only (#26187)

One PermissionPreview per named set, in declared order, under the set's
name and a link to its page. No client-side union, no editing. A set the
server does not serve shows one not-found line; a refused read says so.

Claude-Session: https://claude.ai/code/session_01SMps7wq7rBTk53e8KTSEup
Co-authored-by: Claude <noreply@anthropic.com>
…#26187)

A path-relative link resolves against the matched route's pathname, so
the href pin needs the route the console mounts the page under.

Claude-Session: https://claude.ai/code/session_01SMps7wq7rBTk53e8KTSEup
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

维护者速读

改了什么:Setup → 岗位页面上,岗位列出的每个权限集各显示一个只读的权限预览(对象 × 增删改查、读写记录范围、字段、行级策略),按岗位列出的顺序排列,标题是权限集名并链接到该权限集页面;找不到的权限集只显示一行「未找到」,岗位没列权限集时显示空状态。
为什么改:#26187 的裁决(Q1)要求岗位页能看到权限集授予的范围;之前岗位页只列权限集名字,管理员必须逐个打开权限集才知道岗位到底给了什么。权限集页面本身已由 PR 1(#26284)的矩阵选择器显示范围,席位裁定不再加第二份只读副本。
风险与回滚:只改 app-shell 的岗位页面(新增一个只读区块 + 环境区块从单个改为列表)和 i18n 新键,不改任何写入、规范或接口;最坏情况是岗位页多出一个显示不对的区块,不影响保存。回滚 = revert 本 PR。
先看:ui/packages/app-shell/src/views/metadata-admin/PositionPermissionSetsSection.tsx(风险 hunk:按名读取权限集的 effect,404 → 未找到、其它错误 → 拒绝原文)→ ResourceEditPage.tsx(ENVIRONMENT_SECTIONS 改为数组并把页面草稿传入)→ i18n.ts(en + zh 各 7 个新键)。
两分钟验证:在 ui/ 下跑 pnpm exec vitest run packages/app-shell/src/views/metadata-admin/PositionPermissionSetsSection.test.tsx packages/app-shell/src/views/metadata-admin/ResourceEditPage.positionPermissionSets-26187.test.tsx --maxWorkers=2,预期 Test Files 2 passed (2) / Tests 9 passed (9);或打开 /apps/setup/metadata/position/POSITION_NAME?scope=environment,持有人区块下方出现「Permission sets」区块。
需要你做的:无,席位落地
席位意见:ACCEPT。只读区块、只在 app-shell,两个开放问题都按开发建议保留(读屏上草稿、持有人在前)。

席位核对

seat 2 · 2026-10-26T23:45Z · head 771f48f4 (#26187 PR 2)

item reading
form 6 files, +489/−6: new PositionPermissionSetsSection.tsx (+6-case test), ResourceEditPage.tsx (Setup-section map holds an ordered list per type and passes the draft; position = holders, then permission sets), new page pin (3 cases), metadata-admin/i18n.ts (7 engine.position.sets.* keys, en + zh), root .changeset/ patch on app-shell
premise measured #26284 (PR 1) MERGED; PermissionPreview is read-only without an edit callback, so it is unchanged; the set page gets no second preview (seat ruling)
gates (check-runs on 771f48f4) 54 runs: 46 success, 8 skipped, 0 failed. Local: red first (page pin 1 failed / component module missing), one ablation red; 292 importer files / 2726 tests green; app-shell type-check exit 0; 35 derived gates all exit 0
accepted deviations (1) a refused or failed read shows the server's sentence, separate from "not found", because a refused read is not a missing set; (2) the component test mounts under the console's metadata/:type/:name route (test-only)
open questions (1) previews follow the draft on screen, like the position preview above: keep; (2) holders first, then permission sets: keep
landing NOT governed (check-governed-merges --pr 26360: 495 lines, no hit path); git merge-tree against origin/main clean; app-shell has no sdui record, so no line; already merged main after #26340 on the shared i18n.ts

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 26, 2026 23:47
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 26, 2026 23:47
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 26, 2026
Merged via the queue into main with commit 77a52cf Oct 27, 2026
55 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-26187-position-permission-preview branch October 27, 2026 00:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Permission matrix (Setup / Studio Access): readScope and writeScope cannot be set or seen, though ADR-0084 declares the record-scope column

2 participants