Skip to content

fix(app-shell): the field-permission editor shows and writes exactly what the runtime enforces for a field with no explicit rule - #27006

Merged
objectstack-fleet[bot] merged 8 commits into
mainfrom
claude/issue-26597-field-perm-editor-display
Oct 28, 2026
Merged

objectstack-fleet[bot] merged 8 commits into
mainfrom
claude/issue-26597-field-perm-editor-display

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #26597

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:权限集编辑器(Studio 访问、Setup 权限集)中,没有字段规则的字段行改为显示对象本身的“读 / 改”,灰色虚线框并注明“跟随对象”;有规则的字段照常显示,规则授予而对象未授予的格子加琥珀色圈并说明原因。单击和批量按钮只写入屏幕上真正变化的格子;“全部可读”改名“全部只读”;权限集预览列出全部字段规则。
  • 为什么改:编辑器对“无字段规则”字段的显示与运行时实际执行不一致,管理员按屏幕判断会得到相反的结果(复现细节按维护者指示不公开)。
  • 风险与回滚:只改界面(app-shell、i18n 文案、两篇文档);运行时与 spec 均未改动,运行时一侧只新增一个固定现有行为的测试。回滚即还原本 PR。可见变化:未设规则的字段框变为灰色。
  • 先看:permission-slice.ts 的 fieldRuleCell / applyFieldBulk(显示与批量写入的唯一来源);PermissionMatrixEditor.tsx 的 FieldRuleBox 与 FieldsSubTable;previews/PermissionPreview.tsx 的字段列表。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/app-shell/src/views/metadata-admin/permission-slice.fieldRule-26597.test.ts,预期 16 passed。
  • 需要你做的:无,席位落地。

What changes

The field-permission editor now shows and writes exactly what the runtime enforces for a field with no explicit rule.

  • One drawing, one source (ui/packages/app-shell/src/views/metadata-admin/permission-slice.ts):
    • fieldRuleCell(entry, objectPerm) is what a field row draws. With no entry, the row draws the object row's own Read and Edit, read through the spec's objectPermissionGrants (the fold the evaluator answers from, super-user bits included), and is marked inherited. With an entry, the row draws the entry's bits, read as the runtime merge reads them (=== true, as fieldPermissionVote does). A bit the entry grants and the object row does not is marked capped.
    • fieldRuleSeed is what a click starts from: the entry, or on a row without one exactly the bits the row draws, so a click changes only the box it lands on.
    • applyFieldBulk drives the three field shortcuts. Each moves the visible rows to its target and leaves a row that already draws the target as it is, so a shortcut writes only the rows whose boxes change. Clear deletes the entries, and those rows draw the object grant again. withFieldBits (keeps an authored when) moved here from the editor unchanged.
  • The editor (PermissionMatrixEditor.tsx): FieldsSubTable receives the object row's permission and draws each box through FieldRuleBox. An inherited box is grey with a dashed edge and carries data-field-rule="inherited" and a tooltip. A capped box carries an amber ring, data-capped and a tooltip saying a holder of this set alone cannot use it. A legend line explains the grey boxes. updateFieldPerm and updateFieldWhen seed from fieldRuleSeed, and bulkSetFields calls applyFieldBulk. Both object layouts (the page table and the Studio matrix) render the same sub-table.
  • The preview (previews/PermissionPreview.tsx, also shown per set under a position) lists every field entry the set declares, tagged hidden, read-only or editable, with a when named in the chip's title.
  • Strings (@object-ui/i18n, studio namespace): perm.field.bulk.readable reads "Read-only all" (zh 「全部只读」) because the shortcut switches Edit off; four new keys explain the grey and amber boxes; engine.permissionPreview.fls.readOnly (en + zh) tags a read-only rule in the preview.
  • Docs: content/docs/permissions/field-level-security.mdx (the Studio bullet) and access-recipes.mdx (step 2 now says to set Edit off).
  • Runtime: no change. The editor now follows the contract the spec, ADR-0066 ⑧ and the field-level-security docs already state. The runtime half is pinned by a new test, packages/plugins/plugin-security/src/field-rule-object-grant.test.ts.

Same-default sweep across ui/: the field sub-table's render, the click seed (updateFieldPerm), the condition seed (updateFieldWhen) and the bulk shortcuts are all in the one editor and are all fixed here. The preview's field list filtered entries the same way and is fixed too. MePermissionsProvider.checkField already defers a field without an entry to the object grant. The explain panel draws no field rows. apps/console/src/preview-samples.ts is sample data.

Tests

  • permission-slice.fieldRule-26597.test.ts (16): the six cells (no entry / read-only / editable × object Edit on / off) against the write gate's verdict, the super-user fold, the capped marks, the seed, and every shortcut's writes, including that it writes nothing for rows already at its target.
  • PermissionMatrixEditor.fieldRuleInherited-26597.test.tsx (8): the rendered boxes and their data-field-rule / data-capped marks, and the saved payload after a click, a shortcut and Clear.
  • previews/PermissionPreview.fieldRules-26597.test.tsx (3): every declared entry is listed and tagged.
  • field-rule-object-grant.test.ts in plugin-security (7): the same six cells through a real ObjectQL over SQLite with the real SecurityPlugin middleware, refusals asserted on code + status (and details.forbiddenFields for the field gate), plus the read of a field without an entry.
  • Updated: PermissionMatrixEditor.fieldBulk.test.tsx and PermissionMatrixEditor.fieldWhen-25866.test.tsx (the renamed shortcut; the Clear case now asserts the inherited mark), previews/adminPreviews.i18n-10862-s2.test.tsx (the new preview row in en and zh).
  • Ablation (one-time, not kept, run at ab5961d4): with fieldRuleCell's no-entry branch mutated so it no longer reads the object grant, 12 of the 24 cases in the two new editor-side files went red. The mutation went through scripts/ablation-replace.mjs (anchor hit 1 → 0, blob changed) and was restored to the HEAD blob (db141c2b).
  • One-time HTTP measurement (not kept): the six cells driven through bootStack's real HTTP door by a member holding only the fixture set agreed with both pins in every cell.

Acceptance notes

  • Not in this PR (observation, reported to the coordinator). The object matrix draws an object row from the set's explicit row only and does not model the '*' wildcard, so for a set whose grant on an object comes only from '*' the object row and the field rows under it do not show that grant. This predates this change.
  • Dispatch vs role file. The dispatch asked for dispatch-gates.mjs --commands … CHANGED_PATHS; the role file says to derive without paths so the script reads the change set from the merge base. The role file was followed.
  • @object-ui/app-shell is private; the changeset names it with @object-ui/console (which ships it) and @object-ui/i18n, as the other app-shell changesets in .changeset/ do.

Verification

All readings at a723bac9 (this branch merged with origin/main c6853b12), unless a line says otherwise.

Builds and type-checks
  • pnpm turbo run build --concurrency=2 --filter='@object-ui/app-shell^...' --filter='@objectstack/plugin-security^...': exit 0
  • pnpm --filter @object-ui/app-shell run type-check (tsc --noEmit, then tsc -p tsconfig.test.json): exit 0
  • pnpm --filter @object-ui/i18n run type-check: exit 0
  • pnpm --filter @objectstack/plugin-security run typecheck: exit 0
Tests
  • pnpm --dir ui exec vitest run --maxWorkers=2 over metadata-admin PermissionMatrixEditor*, permission-slice*, previews/PermissionPreview*, previews/adminPreviews.i18n-10862-s2.test.tsx, PositionPermissionSetsSection.test.tsx, studio-design/StudioDesignSurface.accessTabs-12456.test.tsx and the two @object-ui/i18n studio-namespace tests: Test Files 42 passed (42), Tests 318 passed (318), exit 0.
  • pnpm --filter @objectstack/plugin-security exec vitest run --maxWorkers=2 src/field-rule-object-grant.test.ts: Test Files 1 passed (1), Tests 7 passed (7), exit 0.
  • Declared narrowing: the rest of the app-shell suite is CI's (Test UI).
Gates
  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths) derived 96 families for the 15-path change set. All 96 ran locally, each exit 0, pnpm --dir ui exec vitest run scripts/ included (Test Files 178 passed, 2 skipped). --ran: "96 derived, 96 run, 0 NOT-MEASURED, 0 UNRUN".
  • ui/package.json gates: check:i18n-keys, check:i18n-drift, check:i18n-dead-keys, check:i18n-designer-parity, check:designer-field-key-parity and check:new-line-citations (0 new citations): exit 0 each.

Generated by Claude Code

…eld-perm-editor-display

# Conflicts:
#	ui/packages/app-shell/src/views/metadata-admin/i18n.ts
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs.

What this run could not see

Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 3271a0834cae34b42de4685ac54bddd081150ac7 → packageMentionDocs.

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Oct 28, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

ACCEPT — PM review on head a723bac9 (session session_01J8VVAkZns5rNnK2rtszzbQ)

  • File surface: 15 files. Inside the claim: the field-permission editor, permission-slice and the preview under ui/packages/app-shell/src/views/metadata-admin/**, plus their tests and the changeset. Outside the claim, accepted:
    • studio i18n strings (ui/packages/i18n/src/namespaces/studio/{en,zh}.ts), required by the renamed and new labels;
    • two doc lines (content/docs/permissions/{access-recipes,field-level-security}.mdx) that state the new editor behaviour;
    • a runtime pin, packages/plugins/plugin-security/src/field-rule-object-grant.test.ts, which is test-only and adds no runtime change.
  • Disclosure: the commits, changeset, docs and body state only the new contract (a field with no rule follows the object grant), which the docs already document for the runtime.
  • Governed check: NOT governed (check-governed-merges.mjs --pr 27006).
  • CI on a723bac9: 63 check-runs, 0 failure, 0 pending.
  • Follow-up: the implementer's out-of-scope security finding is filed as Studio Access pillar: the permission matrix draws some object grants differently from what the runtime enforces (detail withheld pending maintainer) #27010 (detail withheld). It is held pm:blocked until this PR merges, because both touch PermissionMatrixEditor.tsx.
  • Landing authority: maintainer, this session, 2026-10-28, verbatim: 「按照pm-dispatch规范审核和合并pr」

Landing: ready + auto-merge.


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 28, 2026 01:29
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 28, 2026 01:29
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 28, 2026
Merged via the queue into main with commit a90163d Oct 28, 2026
64 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-26597-field-perm-editor-display branch October 28, 2026 02:12
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/l tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

2 participants