Skip to content

fix(app-shell): reconsider a dismissed or revoked default permission set in the suggested-bindings panel - #30266

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-tsy0zz
Nov 5, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-tsy0zz

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Yibo Wang · project thread

Fixes #30199

维护者速读

  • 改了什么:权限建议面板里,被「忽略」或被「撤销」的默认权限集现在多一个「重新考虑」按钮。点了以后这一行回到待决定状态,重新出现「授予」「忽略」;重新打开本身不授予任何权限,要再点「授予」才生效。10 种语言和 Studio 中英文案都已补齐。
  • 为什么改:服务端早就有 POST /security/suggested-bindings/:id/reopen,但界面从没调用它,管理员点了「暂不」之后只能找开发者调接口才能反悔。
  • 风险与回滚:只动面板、它的客户端函数、两处调用方的文案表和语言包;没有服务端、REST 或 spec 改动。按钮只出现在已忽略/已撤销的行上,其他状态的行一字不变(有对照测试钉住)。回滚即 revert。
  • 先看:① SuggestedBindingsPanel.tsx 里 state === 'dismissed' 的新按钮和 act() 的 reopen 分支;② suggestedBindingsApi.ts 的 reopenSuggestedBinding;③ de-quote-pairing-3876.test.ts 的计数从 102 调到 103(新的德语提示引用了权限集名)。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/app-shell/src/components/SuggestedBindingsPanel.test.tsx,预期 23 个用例全部通过。
  • 需要你做的:无,席位落地。

Clause-②: no
Metadata-protocol: none

What changes

  • SuggestedBindingsPanel: a dismissed row (a declined suggestion or a revoked acceptance; the server records both as dismissed) drew only strings.dismissedState(s). It now offers one action, Reconsider (suggested-binding-reopen-SET), which calls the re-open door. The server answers the row as pending, the panel puts that row in place, and it redraws with Accept and Dismiss (a re-opened guest row redraws as guest: Dismiss, still no Accept). A success toast says the suggestion is open again and that nothing is granted until it is accepted. A refusal goes through the same catch as Accept and Dismiss: the server's message as an error toast, then a re-read of the list.
  • Who sees it. The same callers as Accept and Dismiss. The panel has no client-side gate of its own: a caller without manage_metadata is refused the list (403) and the panel renders nothing. The re-open door enforces the same authority (assertDeploymentAuthority).
  • suggestedBindingsApi.reopenSuggestedBinding(id), beside dismissSuggestedBinding: POST …/security/suggested-bindings/:id/reopen, through the same parseOrThrow, so a 404 (SUGGESTION_NOT_FOUND), 409 (SUGGESTION_STATE) or 501 (NOT_IMPLEMENTED, a security service without the optional member) is thrown with the server's code, status and message.
  • Strings. SuggestedBindingsStrings gains reopen, reopening and reopenedToast. The panel has two string tables, and each gets the keys in the locales it already carries: marketplace.suggestedBindings.* in all ten locale packs (ar, de, en, es, fr, ja, ko, pt, ru, zh), and engine.studio.access.suggestReopen / suggestReopening / suggestReopenedToast in the studio namespace (en, zh, the only two it has). zh: 重新考虑 / 重新打开中… .
  • de-quote-pairing-3876.test.ts: the German toast quotes the set („{{set}}“), one more matched pair, so the two counts move 102 → 103 with a comment line each, as earlier keys did.

Why

Issue #30199. The door has existed since the re-open work on the server side (reopenAudienceBindingSuggestion, mounted in packages/rest/src/security-routes.ts and the runtime dispatcher), and its contract says a dismissed or revoked row returns to pending and grants nothing until accepted. The Console never called it.

Tests

  • SuggestedBindingsPanel.test.tsx (real panel, real client, stubbed fetch answering the server's envelopes):
    • a dismissed or revoked row shows Reconsider and nothing else;
    • one click sends exactly one POST …/sug_crm_reader/reopen, no confirm; the row redraws as pending with Accept and Dismiss, the toast shows, onResolved gets the re-opened row;
    • dismiss → reconsider → accept sends dismiss, reopen, confirm, in that order, and ends accepted;
    • a re-opened guest row redraws as guest;
    • 409 (the set no longer declares isDefault), 404 (row gone; the re-read drops it) and 501 (no door; the dismissal stands) each show the server's message;
    • a caller refused the list (403) gets no panel and no Reconsider;
    • CONTROL: pending, guest, accepted and not-in-force rows offer no Reconsider and keep their states.
  • Ablation (scripts/ablation-replace.mjs, {state === 'dismissed' && ( → {false && (): 7 failed / 16 passed, the 7 being every Reconsider pin; the controls stay green. Restored: blob equals HEAD, git diff HEAD empty.

Acceptance notes

  • Revoked rows get the action too (measured). reopenAudienceBindingSuggestion refuses only a row whose status is not dismissed, and dismissAudienceBindingSuggestion writes status: 'dismissed' for both a decline and a revoke. Live: a revoke answered dismissed, and re-opening it answered pending, deployment_decision: false.
  • Fixture for the live check. This repo ships no HotCRM. The one example with an isDefault set, examples/app-showcase's showcase_member_default, boots as a baseline row (confirmed, deployment_decision: false), which this panel does not draw and the server will not dismiss. So the live leg used the closest fixture: on the showcase dev boot, a writable package com.qa.reopen made over the API, holding an object reopen_note (public_read) and an isDefault permission set reopen_member_default that grants read on it. Its suggestion lists as pending.
  • Hosted environments still depend on rest: on the hosted multi-environment runtime the /security routes read the HOST kernel, so no owner can accept an installed app's default permission set and every invited member gets 403 on its data (sixth door of #29999) #30192 (the /security doors reading the environment kernel). That is outside this PR.

Verification

Live: dismiss, reconsider, accept in Studio's Access pillar; an invited member then reads the object

Showcase dev boot (pnpm dev -- --fresh), Console via Vite with DEV_PROXY_TARGET, Playwright as admin in zh-CN at /studio/com.qa.reopen/access. The served panel source was checked to contain the new test id before the run. Screenshots: /mnt/project-files/ui-orders/30199-shots/ (project files).

start: pending
after dismiss: dismissed · buttons: 重新考虑
after reconsider: pending · buttons: 授予,忽略
after accept: accepted · buttons: 撤销
200 POST /api/v1/security/suggested-bindings/sug_mw301423d3iszr6v/dismiss
200 POST /api/v1/security/suggested-bindings/sug_mw301423d3iszr6v/reopen
200 POST /api/v1/security/suggested-bindings/sug_mw301423d3iszr6v/confirm

Invited member (invite-member + sign-up/email): GET /data/reopen_note 403 before the accept, 200 after it. GET /security/suggested-bindings as the member: 403, so the member sees no panel.

API legs on the same boot: revoke → dismissed; reopen of the revoked row → pending, and the member still reads 403 (a re-open grants nothing); reopen of a pending row → 409 SUGGESTION_STATE; unknown id → 404 SUGGESTION_NOT_FOUND; the member calling reopen → 403 PERMISSION_DENIED.

Suites, type-check, lint, gates

All at head ca174078, each under os-verify-lock where it builds or tests.

  • Panel + de quote-pairing: pnpm --dir ui exec vitest run --maxWorkers=2 packages/app-shell/src/components/SuggestedBindingsPanel.test.tsx packages/i18n/src/__tests__/de-quote-pairing-3876.test.ts exit 0 (2 files, 32 passed).
  • @object-ui/i18n whole suite + the panel: exit 0 (96 files, 1,466 passed, 13 skipped).
  • Callers of the panel: the five MarketplacePackagePage.* test files that render it and StudioDesignSurface.accessSegment-12452.test.tsx: exit 0 (6 files, 36 passed).
  • pnpm --dir ui --filter @object-ui/app-shell --filter @object-ui/i18n run type-check (both scripts echoed, tsc --noEmit and tsconfig.test.json): exit 0.
  • npx eslint on the five touched app-shell files: exit 0.
  • Derived gates: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack → 35 commands, each run and recorded; all 35 exit 0. --ran reconciliation: "35 derived famil(ies) accounted for — 35 run, 0 NOT-MEASURED". The list is at /mnt/project-files/ui-orders/30199-gates-ran.list (project files).

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. (No spec change.)
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint passes. (Touched files linted; the repo-wide run is CI's.)
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset.
  • 6. packages/spec touched: its generated artifacts are regenerated and committed. (Not touched.)
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English. (The 维护者速读 block is the dispatch's required Chinese summary.)
  • The Before Submitting list in CONTRIBUTING.md is done.

Generated by Claude Code

A dismissed (or revoked) row in SuggestedBindingsPanel drew only its state
line. It now offers Reconsider, which calls the existing
POST /security/suggested-bindings/:id/reopen door through a new
reopenSuggestedBinding(id) client call; the row redraws as pending with
Accept and Dismiss. Refusals (404 / 409 / 501) surface the server's message
and re-read the list, as the other actions do.

Strings: marketplace.suggestedBindings.reopen/.reopening/.reopenedToast in
all ten locale packs; engine.studio.access.suggestReopen/.suggestReopening/
.suggestReopenedToast in en and zh. The de quote-pairing ratchet moves to
103 for the new quoted toast.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGufzezVANVfXzi7zxbUT8
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Nov 5, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#1 · os-support-ai · session_01WGufzfE4V9fzZgJLrLDmMC

维护者速读(席位终稿)

  • 改了什么:建议权限集面板(安装对话框、应用市场包页面、Studio 访问页)里,已忽略或已撤销的建议现在有「重新考虑」按钮,点后回到待决定状态,重新出现「授予 / 忽略」。
  • 为什么改:管理员点了「忽略」之后只能手工调接口才能反悔;服务端的重新打开接口早就有,界面一直没接。
  • 风险与回滚:低。只是前端接上已有接口;重新打开不授予任何权限,要再次接受才生效。不改服务端、接口和 spec。回滚即 revert 本 PR。
  • 先看:SuggestedBindingsPanel.tsx 的 state === 'dismissed' 分支与 suggestedBindingsApi.ts 的 reopenSuggestedBinding。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/app-shell/src/components/SuggestedBindingsPanel.test.tsx。
  • 需要你做的:无。不是受管面,席位直接落地。
  • 席位意见:ACCEPT。

席位核对 · ACCEPT · head ca174078 · 2026-11-05T04:18Z

  1. Shape: draft, base main, Fixes #30199. One changeset: @object-ui/app-shell + @object-ui/i18n patch, Clause-②: no / Metadata-protocol: none. 19 files, +254/−16. check-governed-merges --pr 30266: NOT governed. No server, REST or spec change.
  2. Read by the seat: reopenSuggestedBinding(id) mirrors dismissSuggestedBinding (same raw-fetch, parseOrThrow, returns suggestion). The panel's act gains a reopen arm that goes through the same catch → server-message toast → re-read path; the button shows only on state === 'dismissed', which covers revoked rows because the server records a revoke as dismissed (measured by the executor: reopen of a revoked row → pending, member still 403). SuggestedBindingsStrings gains three required fields, and all three panel callers fill them (type-check exit 0). Toast copy says the row is pending again, never that the set is granted. Shots 2-dismissed-panel.png (重新考虑 on the dismissed row) and 3-reconsidered-panel.png (授予 / 忽略 back, zh toast) match.
  3. Accepted deviations: live leg on an API-made writable package (com.qa.reopen, isDefault set) because no fixture here draws a dismissable suggestion; strings also land in the two callers' tables and the de quote-pairing ratchet (102 → 103), which the claim's "the panel's strings, their tests" covers. Teardown: the first kill list was built wrong and ended the executor's own shell; the boot's two node PIDs were then killed by explicit PID, health 000, and ps afterwards showed no other run named. Judged: no harm on record, noted for the os-dev teardown habit (build the list from the boot's own PID tree only).
  4. Evidence: ablation ({state === 'dismissed' && ( → {false && (): 7 Reconsider pins red, CONTROL green, restored to HEAD blob. Panel + i18n suites green (1466 passed), app-shell + i18n type-check exit 0, eslint 0. dispatch-gates 35/35 exit 0 with --ran reconciled. Live: dismiss → reopen → confirm, three 200s; member data read 403 before accept, 200 after; 409 SUGGESTION_STATE on a pending row, 404 on an unknown id. CI on this head running at review time; auto-merge waits on it.
  5. Out of scope: none. Skills-docs: n/a.
  6. Landing: ordinary queue, now: relay pr_ready + automerge_enable, follow to MERGED, landing record on app-shell: SuggestedBindingsPanel draws a dismissed default permission set with no Reopen button, though POST /security/suggested-bindings/:id/reopen exists — an owner who chose 「暂不」 cannot change their mind (found by cloud#3560) #30199.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 5, 2026 04:19
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge November 5, 2026 04:19
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 5, 2026
Merged via the queue into main with commit ef2ad31 Nov 5, 2026
56 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-tsy0zz branch November 5, 2026 05:08
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 tests tooling

Projects

None yet

1 participant