You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit ef2ad31
Browse filesBrowse the repository at this point in the historyBrowse files
fix(app-shell): reconsider a dismissed or revoked default permission set in the suggested-bindings panel (#30266)
<!-- claude-projects-attribution: {"github_login":"os-support-ai"} -->
_Requested by **Yibo Wang** · [project
thread](https://claude.ai/code/project/chan_01BVUyyPie8gHwoPM18iABwB?thread=cmsg_01BVUyyPie8gHwoPM18iABwBBxSPB3for8PaPgERfFkDCM)_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 #30192 (the `/security` doors
reading the environment kernel). That is outside this PR.
## Verification
<details><summary>Live: dismiss, reconsider, accept in Studio's Access
pillar; an invited member then reads the object</summary>
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`.
</details>
<details><summary>Suites, type-check, lint, gates</summary>
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).
</details>
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
(No spec change.)
- [x] 2. Every name I added uses the casing for its kind.
- [x] 3. No formatter ran; `pnpm lint` passes. (Touched files linted;
the repo-wide run is CI's.)
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. A change that publishes carries a changeset, and both
declaration lines are filled in here and in the changeset.
- [x] 6. `packages/spec` touched: its generated artifacts are
regenerated and committed. (Not touched.)
- [x] 7. No hand edits to `content/docs/references/`,
`packages/*/CHANGELOG.md` or `content/docs/releases/`.
- [x] 8. Title, body and comments are in English. (The 维护者速读 block is
the dispatch's required Chinese summary.)
- [x] The *Before Submitting* list in CONTRIBUTING.md is done.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01WGufzezVANVfXzi7zxbUT8)_
Co-authored-by: Claude <noreply@anthropic.com>
fix(app-shell): a dismissed or revoked default permission set in the suggested-bindings panel can be reconsidered (#30199)
7
+
8
+
Clause-②: no
9
+
Metadata-protocol: none
10
+
11
+
The suggested-bindings panel (the install dialog and Marketplace package page, and Studio's Access pillar) drew a dismissed suggestion, or a revoked acceptance, as a state line with no action. An administrator who chose Dismiss could only take it back by calling `POST /api/v1/security/suggested-bindings/:id/reopen` by hand, although the server has offered that door all along.
12
+
13
+
Such a row now offers "Reconsider". It calls the re-open door, and the row comes back as a pending suggestion with Accept and Dismiss (a Guest row with Dismiss only, as before). Re-opening grants nothing: the set reaches the Everyone position only when it is accepted again, under the same checks. The button is shown to the same callers who see Accept and Dismiss: a caller without `manage_metadata` is refused the list and sees no panel. A refusal (the row is gone, the set is no longer suggested, or the deployment's security service has no re-open door) is shown with the server's own message, and the panel re-reads the list.
14
+
15
+
New client call: `reopenSuggestedBinding(id)`, beside `dismissSuggestedBinding`. New strings: `marketplace.suggestedBindings.reopen`, `.reopening` and `.reopenedToast` in all ten locale packs, and `engine.studio.access.suggestReopen`, `.suggestReopening` and `.suggestReopenedToast` (en, zh).
0 commit comments