feat(console): 补齐控制台工作区归档入口并对齐 Anthropic 级联吊销语义 - #96
Conversation
Soft-delete a console workspace by setting archived_at and, in the same idempotent transaction, cascade the archive to every console API key scoped to it, mirroring Anthropic workspace semantics. Refuse to archive the default workspace and the caller's current workspace (409) to avoid self-lockout; unknown or cross-org workspaces return 404. Mount the route on the existing RegisterConsoleOrganizationWorkspaceRoutes. The front-end adds an archive action with a confirmation dialog and disables it for the default and active workspaces.
|
Warning Review limit reached
Next review available in: 29 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthrough新增 Console workspace 归档能力:后端提供事务级联归档接口并保护默认/当前工作区,前端加入确认对话框和操作菜单,测试覆盖错误响应、API key 吊销、幂等性与组织隔离。 Changes工作区归档
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant WorkspacesSettingsPage
participant ArchiveWorkspaceDialog
participant archiveConsoleWorkspace
participant ConsoleWorkspaceAPI
participant Database
User->>WorkspacesSettingsPage: Select archive action
WorkspacesSettingsPage->>ArchiveWorkspaceDialog: Open confirmation
User->>ArchiveWorkspaceDialog: Confirm archive
ArchiveWorkspaceDialog->>archiveConsoleWorkspace: Submit workspace ID
archiveConsoleWorkspace->>ConsoleWorkspaceAPI: POST archive endpoint
ConsoleWorkspaceAPI->>Database: Archive workspace and related keys
Database-->>ConsoleWorkspaceAPI: Archived workspace
ConsoleWorkspaceAPI-->>archiveConsoleWorkspace: Workspace response
archiveConsoleWorkspace-->>ArchiveWorkspaceDialog: Resolve
ArchiveWorkspaceDialog-->>WorkspacesSettingsPage: Close dialog
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (2)
docs/design/be/console-workspace-archive.md (1)
79-81: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueSpecify a language for the fenced code block.
To satisfy markdown lint rules (MD040) and improve syntax highlighting, specify a language like
httportextfor this code block.♻️ Proposed fix
-``` +```http POST /api/console/organizations/{orgUuid}/workspaces/{workspaceId}/archive</details> <details> <summary>🤖 Prompt for AI Agents</summary>Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.In
@docs/design/be/console-workspace-archive.mdaround lines 79 - 81, Update the
fenced code block containing the archive POST endpoint to specify an appropriate
language such as http, while preserving the endpoint content unchanged.</details> <!-- cr-comment:v1:dcfd7ef8f192cac41def58d8 --> _Source: Linters/SAST tools_ </blockquote></details> <details> <summary>tests/console_workspace_archive_api_test.go (1)</summary><blockquote> `90-99`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _💤 Low value_ **Move the failure scenario to precede the success scenarios.** As per coding guidelines, "测试组织顺序应先写失败场景,再写成功场景" (test organization order should place failure scenarios first, followed by success scenarios). The "isolated by organization" test is a failure scenario (expects a 404) and should be positioned before the "archive succeeds and cascades to api keys" test. <details> <summary>♻️ Proposed fix</summary> Move this test block to line 44, right before the `archive succeeds and cascades to api keys` test. ```go t.Run("isolated by organization", func(t *testing.T) { otherOrgID := seedArchiveOrganization(t, app, "org_archive_isolation_"+uniqueAdminSuffix()) otherWS := seedArchiveTargetWorkspace(t, app, otherOrgID, "Other Org WS") resp := app.doPlatformConsole(t, http.MethodPost, base+"/workspaces/"+otherWS+"/archive", nil, cookies) defer resp.Body.Close() if resp.StatusCode != http.StatusNotFound { t.Fatalf("status = %d, want 404 (org isolation): %s", resp.StatusCode, readAll(t, resp.Body)) } })🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/console_workspace_archive_api_test.go` around lines 90 - 99, Move the “isolated by organization” subtest within the relevant test function so it runs before “archive succeeds and cascades to api keys.” Keep the test body and its 404 assertion unchanged, preserving failure scenarios before success scenarios.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@internal/db/console_workspaces.go`:
- Around line 56-57: Update the workspace cascade query in the surrounding
function to filter console_api_keys.workspace_id using the workspace external
ID, reusing the function’s workspaceID argument or workspace.ExternalID rather
than workspace.UUID; keep the org_uuid filter unchanged.
- Around line 35-46: Update the RETURNING list used by scanConsoleWorkspace so
the second color slot returns w.color instead of duplicating w.display_color;
preserve w.display_color in the first slot and keep the remaining returned
columns unchanged.
In `@web/src/features/settings/WorkspacesSettingsPage.tsx`:
- Around line 178-181: Update
web/src/features/settings/WorkspacesSettingsPage.tsx:178-181 to pass an archive
restriction reason (`default`, `current`, or `undefined`) to WorkspaceRowActions
instead of a boolean, distinguishing the permanently non-archivable default
workspace from the active workspace. Update
web/src/features/settings/WorkspacesSettingsPage.tsx:251-261 to render the
corresponding accessible explanation without relying on title or pointer events.
Add the distinct default-workspace message in
web/src/shared/i18n/messages/en.json:1217 and its Chinese translation in
web/src/shared/i18n/messages/zh-CN.json:1217.
In `@web/src/shared/workspaces/WorkspaceProvider.tsx`:
- Around line 80-85: The archiveWorkspace mutation must forward the bootstrap
CSRF token for cookie-authenticated requests. In
web/src/shared/workspaces/WorkspaceProvider.tsx lines 80-85, obtain the token
from the authentication context and pass it to archiveConsoleWorkspace; in
web/src/shared/workspaces/api.ts lines 75-79, update archiveConsoleWorkspace to
accept the token and include it in the consoleApi options so X-CSRF-Token is
sent.
---
Nitpick comments:
In `@docs/design/be/console-workspace-archive.md`:
- Around line 79-81: Update the fenced code block containing the archive POST
endpoint to specify an appropriate language such as http, while preserving the
endpoint content unchanged.
In `@tests/console_workspace_archive_api_test.go`:
- Around line 90-99: Move the “isolated by organization” subtest within the
relevant test function so it runs before “archive succeeds and cascades to api
keys.” Keep the test body and its 404 assertion unchanged, preserving failure
scenarios before success scenarios.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: af678cc1-9f48-4213-864d-45e76dd43b7f
📒 Files selected for processing (12)
docs/design/be/console-workspace-archive.mdinternal/db/console_workspaces.gointernal/platformapi/console_workspaces.gointernal/platformapi/platform_backend_routes.gotests/console_workspace_archive_api_test.goweb/src/features/settings/WorkspacesSettingsPage.tsxweb/src/shared/i18n/messages/en.jsonweb/src/shared/i18n/messages/zh-CN.jsonweb/src/shared/workspaces/ArchiveWorkspaceDialog.tsxweb/src/shared/workspaces/WorkspaceProvider.tsxweb/src/shared/workspaces/api.tsweb/src/shared/workspaces/context.ts
There was a problem hiding this comment.
DuckPR reviewer: opencode
Model: anthropic/glm-5.2
Caution
当前实现尚未完成与 Anthropic 对齐的“级联吊销”语义:归档后 /v1 服务使用的 api_keys 仍保持 active,实际 API key 仍可调用服务;同时真正的默认工作区(数据库 external_id = workspace_default)可被归档。这两处会造成功能性和数据完整性风险,需要修复后再合并。
Reviewed changes — 审查了 Console 工作区归档的完整实现:后端单事务归档、防自锁与默认工作区保护、前端 DropdownMenu 操作入口与二次确认 Dialog、相关测试与设计文档。
- 新增
ArchiveConsoleWorkspace与handleArchiveConsoleWorkspace— 在internal/db与internal/platformapi中实现工作区软删除及级联console_api_keys归档。 - 新增归档路由 —
POST /api/console/organizations/{orgUuid}/workspaces/{workspaceId}/archive。 - 前端归档入口 —
WorkspacesSettingsPage改为DropdownMenu,ArchiveWorkspaceDialog做破坏性二次确认。 - 新增测试与设计文档 — 覆盖 200/404/409 default/级联/幂等/组织隔离。
ℹ️ Nitpicks
web/src/features/settings/WorkspacesSettingsPage.tsx:180前端用===比较工作区 ID,后端用strings.EqualFold,建议统一策略。web/src/shared/workspaces/WorkspaceProvider.tsx:86-88归档成功后仅过滤列表缓存,建议失效['console', ...]前缀的相关查询并清理localStorage中的preferredWorkspaceId。- 对 disabled 的
DropdownMenuItem使用title属性展示原因,可访问性弱于Tooltip或aria-describedby。
anthropic/glm-5.2 | 𝕏
curl 'https://platform.claude.com/api/console/organizations/xx/workspaces/wrkspc_0/archive' { |
|
default不能 archive |
- Extract principalWorkspace local in handleArchiveConsoleWorkspace to avoid duplicating TrimSpace and keep the self-lockout guard within style line limits. - Filter the API key cascade by the workspaceID argument instead of workspace.UUID. Both hold the workspace external_id (scanConsoleWorkspace maps w.external_id into ConsoleWorkspace.UUID), but the parameter is clearer and avoids the field-name trap.
There was a problem hiding this comment.
DuckPR reviewer: opencode
Model: anthropic/glm-5.2
Caution
新提交仅修正了 console_api_keys 级联查询的 workspace_id 参数,但上一轮 review 指出的关键风险仍未处理,PR 尚不可合并。
Reviewed changes — 重新审查了 a13d877 对归档级联查询的修正,以及当前会话工作区比较逻辑的整理。
- 修正
console_api_keys级联定位列 —internal/db/console_workspaces.go:57将级联更新中的workspace.UUID改为workspaceID,与console_api_keys.workspace_id存储外部 ID 的语义一致。 - 整理当前会话工作区比较 —
internal/platformapi/console_workspaces.go:47先对principal.WorkspaceExternalID做TrimSpace,再与请求的工作区 ID 比较。
🚨 关键风险仍未解决
上一轮指出的以下问题在本提交中未得到修复,仍会导致归档后 /v1 调用可继续成功、真正的默认工作区被归档、前端请求缺少 CSRF 防护等后果。
api_keys表未随归档更新。console_api_keys级联只写archived_at,未同步status = 'archived'。- 默认工作区保护仅拦截
"default",未拦截数据库真实默认workspace_default。 - 归档请求未携带
X-CSRF-Token。 - 测试未覆盖
workspace_default和「归档当前工作区」场景。
Technical details
# 关键风险仍未解决
## Affected sites
- 上一轮 review: https://github.com/superduck-ai/open-managed-agents/pull/96#pullrequestreview-4725311244
- `internal/db/console_workspaces.go` — 归档事务未同步吊销 `api_keys`
- `internal/db/console_workspaces.go` — 级联 `console_api_keys` 未设置 `status = 'archived'`
- `internal/platformapi/console_workspaces.go` — 默认工作区保护未拦截 `workspace_default`
- `tests/console_workspace_archive_api_test.go` — 未覆盖 `workspace_default` 与归档当前工作区
- `web/src/shared/workspaces/api.ts` 与 `WorkspaceProvider.tsx` — 归档请求未携带 CSRF token
## Required outcome
- 归档事务需要同时使 `/v1` 调用立即失败(通过更新 `api_keys` 或让 `GetAPIKey` 读取 `workspaces.archived_at`)。
- 已归档的 `console_api_keys` 在 API 响应中显示 `status: 'archived'`。
- 后端默认工作区保护应拦截真实默认 `external_id = 'workspace_default'`,并在测试中覆盖。
- 前端基于 cookie 的归档请求携带 `X-CSRF-Token`。anthropic/glm-5.2 | 𝕏
The handler only blocked the literal "default" alias, so a caller who knew the default workspace's real external_id could bypass the guard and archive it. Add `lower(coalesce(name, '')) <> 'default'` to the UPDATE WHERE clause so the "default workspace is never archivable" invariant holds at the write path regardless of which identifier the caller used; such a request now surfaces as ErrNotFound (404). The (organization_id, name) unique constraint guarantees one default workspace per org, so the guard is exact. Cover it with a direct DB-layer test and reorder tests so all failure cases precede the success cases.
|
@arthur-zhang 两点反馈都已处理,见最新两个 commit 1. default 不能 archive(已加固) 之前 handler 只拦截
handler 仍保留 2. 归档响应结构(已对齐你贴的官方 curl) 归档响应复用既有 3. 视觉对齐 shadcn 样式 归档链路本身已是标准 shadcn new-york:
你三张截图展示的官方列表页( 如果你说的"视觉对齐"特指归档入口 / 对话框的某个具体细节(间距、图标、色调、文案),麻烦再指一下,我直接在这改;列表页整体视觉我愿意单独跟进,建议另开 issue,避免在这个归档 PR 里混入不相关的列表重构。 |
There was a problem hiding this comment.
DuckPR reviewer: opencode
Model: anthropic/glm-5.2
Reviewed changes — 重新审查了 0b87aec 对默认工作区归档防护的补强。
- 在
internal/db/console_workspaces.go的归档 WHERE 子句中增加lower(coalesce(w.name, '')) <> 'default',使默认工作区无论通过别名还是真实 external_id 都无法被归档。 - 新增
tests/console_workspace_archive_api_test.go用例,直接验证 DB 层对真实 external_id 的默认工作区返回ErrNotFound且archived_at保持为空。 - 更新
docs/design/be/console-workspace-archive.md状态图与错误码表,将真实 external_id 默认工作区的归档结果表述为 404。
Caution
新提交在 DB 层为默认工作区补上了不可归档的兜底,但此前 review 中标记的关键风险仍未修复,PR 尚不可合并。仍未解决的上游问题包括:归档事务未同步吊销 /v1 服务使用的 api_keys;级联 console_api_keys 更新未写入 status = 'archived';前端归档请求未携带 X-CSRF-Token;测试未覆盖「归档当前会话工作区」的 409 场景;WorkspacesSettingsPage 对 default 与 current 的禁用原因使用了同一提示文案。
anthropic/glm-5.2 | 𝕏
The finally block ran setSubmitting(false) after onClose() on the success path, flipping the confirm button from "Archiving..." back to "Archive" for a frame before the dialog unmounted. Drop the finally: leave submitting set on success (the dialog is about to unmount anyway) and only reset it in the catch path so the action can be retried after a failure.
|
补充一个归档确认弹窗的体验修复( |
- ArchiveConsoleWorkspace 事务中同时归档 api_keys 表里该工作区的活跃 key,修复归档后 /v1 API key 仍可鉴权的漏洞 - console_api_keys 级联更新补充设置 status='archived' - WorkspaceApiKeysPage / WorkspaceProvider 变更类请求传入 csrfToken - WorkspacesSettingsPage 通过 archiveDisabledReason 区分 default/current 工作区禁用提示 - 新增 cannot_archive_current_workspace 子测试及 live API key 吊销断言 - 新增 workspace.archive.defaultDisabledHint i18n key(中/英)
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
web/src/shared/workspaces/api.ts (1)
3-14: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
Workspacetype omitsarchived_at.The archive endpoint returns the full workspace payload including
archived_at(performatConsoleWorkspaceand the backend test assertingarchived["archived_at"]), andWorkspaceApiKeyin this same file already modelsarchived_at?: string | null.Workspaceshould too, so downstream consumers (e.g., the archive dialog/confirmation UI) can read it without unsafe casts.🐛 Proposed fix
export type Workspace = { id: string; type: 'workspace'; name: string; display_color?: string; color?: string; data_residency?: { workspace_geo?: string; allowed_inference_geos?: string; default_inference_geo?: string; } | null; + archived_at?: string | null; };🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@web/src/shared/workspaces/api.ts` around lines 3 - 14, Update the Workspace type to include an optional nullable archived_at string property, matching WorkspaceApiKey and the archive endpoint payload so consumers can access it without casts.
🧹 Nitpick comments (2)
web/src/shared/workspaces/api.ts (1)
68-81: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win
csrfTokenreintroduced as optional across all four functions — same pattern that already caused a missed-CSRF bug once.Every current call site (
WorkspaceProvider.tsx,WorkspaceApiKeysPage.tsx) already passescsrfToken, so making it a required parameter costs nothing today but prevents a future call site from silently omitting it, per the guideline requiring CSRF on all cookie-authenticated mutating requests.Also applies to: 89-103, 105-120
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@web/src/shared/workspaces/api.ts` around lines 68 - 81, Make the csrfToken parameter required for all four workspace mutation functions, including createConsoleWorkspace and archiveConsoleWorkspace and the functions in the referenced ranges. Remove the optional marker while preserving the existing argument order and ensure every current call site continues passing the token.Source: Path instructions
tests/console_workspace_archive_api_test.go (1)
14-183: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
TestConsoleWorkspaceArchiveflagged as high complexity/length by static analysis.The function spans roughly 170 lines across 6 subtests plus shared fixture setup, exceeding the general Go function-length guidance (
**/*.go: 函数最多 163 行). Consider extracting the cascade/idempotency logic (129-182) and/or the current-workspace session-binding logic (82-123) into named helper functions to shrink the top-level body while keeping the subtests intact.♻️ Sketch of extraction
func testArchiveCascadesAndIsIdempotent(t *testing.T, app *testApp, base, orgUUID string, orgID int64, cookies []*http.Cookie) { // move lines 125-182 body here, called from within t.Run wrappers }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/console_workspace_archive_api_test.go` around lines 14 - 183, Reduce the complexity of TestConsoleWorkspaceArchive by extracting the current-workspace session-binding setup/restore and the archive cascade/idempotency assertions into focused named helpers. Keep the existing t.Run cases, fixtures, assertions, and test behavior unchanged, with helpers receiving the required app, organization, workspace, and cookie context.Source: Linters/SAST tools
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@tests/console_workspace_archive_api_test.go`:
- Around line 224-242: Update seedLiveAPIKeyForWorkspace to verify that the
INSERT ... SELECT affected exactly one row, capturing the command result and
checking its affected-row count after execution. Fail the test when the
workspace lookup inserts zero rows (or an unexpected number), so later GetAPIKey
assertions genuinely exercise revocation.
---
Outside diff comments:
In `@web/src/shared/workspaces/api.ts`:
- Around line 3-14: Update the Workspace type to include an optional nullable
archived_at string property, matching WorkspaceApiKey and the archive endpoint
payload so consumers can access it without casts.
---
Nitpick comments:
In `@tests/console_workspace_archive_api_test.go`:
- Around line 14-183: Reduce the complexity of TestConsoleWorkspaceArchive by
extracting the current-workspace session-binding setup/restore and the archive
cascade/idempotency assertions into focused named helpers. Keep the existing
t.Run cases, fixtures, assertions, and test behavior unchanged, with helpers
receiving the required app, organization, workspace, and cookie context.
In `@web/src/shared/workspaces/api.ts`:
- Around line 68-81: Make the csrfToken parameter required for all four
workspace mutation functions, including createConsoleWorkspace and
archiveConsoleWorkspace and the functions in the referenced ranges. Remove the
optional marker while preserving the existing argument order and ensure every
current call site continues passing the token.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: b4b568b8-dcd9-4d12-8211-c7470f042ce8
📒 Files selected for processing (10)
docs/design/be/console-workspace-archive.mdinternal/db/console_workspaces.gotests/console_workspace_archive_api_test.goweb/src/features/settings/WorkspaceApiKeysPage.tsxweb/src/features/settings/WorkspacesSettingsPage.tsxweb/src/shared/i18n/messages/en.jsonweb/src/shared/i18n/messages/zh-CN.jsonweb/src/shared/workspaces/ArchiveWorkspaceDialog.tsxweb/src/shared/workspaces/WorkspaceProvider.tsxweb/src/shared/workspaces/api.ts
🚧 Files skipped from review as they are similar to previous changes (7)
- web/src/shared/i18n/messages/zh-CN.json
- web/src/shared/i18n/messages/en.json
- web/src/shared/workspaces/WorkspaceProvider.tsx
- web/src/shared/workspaces/ArchiveWorkspaceDialog.tsx
- web/src/features/settings/WorkspacesSettingsPage.tsx
- docs/design/be/console-workspace-archive.md
- internal/db/console_workspaces.go
There was a problem hiding this comment.
DuckPR reviewer: opencode
Model: anthropic/glm-5.2
Important
新提交在合并 sqlx 迁移后修复了归档扫描回归,但 ArchiveConsoleWorkspace 仍使用 raw pgx 句柄,是 internal/db 中唯一未跟随迁移的函数,建议在合并前对齐。
Reviewed changes — 重新审查了 0b87aec 之后的增量改动:babdf1a 修复 sqlx 迁移合并引入的归档扫描回归,以及两个 shell 测试脚本的 cosmetic refactor。
- 重引入
scanConsoleWorkspace修复归档扫描 —internal/db/console_workspaces.go在0f9104e合并 sqlx 迁移后扫描逻辑丢失,babdf1a重新加入scanConsoleWorkspacehelper 并把pgx.ErrNoRows映射为platform.ErrNotFound,归档流程恢复可用。 - Shell 测试脚本 cosmetic refactor —
generate-code-session-jwt-key_test.sh与generate-upstream-proxy-ca-key_test.sh把 PEM header 比较中的"PRIVATE KEY"字面量提取为private_key_label变量,无行为变化。 - 此前 review 关注点已全部解决 —
557492e/2a5eac1/620ecaa已完成api_keys级联吊销、CSRF 传递、seed helper 行数校验、当前工作区 409 测试与 default/current 禁用文案拆分;全部 8 条 pullfrog 线程已由作者确认并 resolve。
Note: 2 inline comment(s) dropped because they did not anchor to lines inside the PR diff:
internal/db/console_workspaces.go:169(RIGHT) — line 169 (RIGHT) is not inside a diff hunkdocs/design/be/console-workspace-archive.md:125(RIGHT) — line 125 (RIGHT) is not inside a diff hunk
anthropic/glm-5.2 | 𝕏
There was a problem hiding this comment.
DuckPR reviewer: opencode
Model: anthropic/glm-5.2
ℹ️ 本轮无关键问题 —— 一处文档同步建议见内联。
Reviewed changes — 重新审查了 babdf1a 之后的增量改动 a2fbcdc:将 ArchiveConsoleWorkspace 从 raw pgx 全量迁移到 sqlx,并对齐 UUID 引用语义。
ArchiveConsoleWorkspace迁移到 sqlx —internal/db/console_workspaces.go由d.Pool.Begin+tx.QueryRow/tx.Exec改为d.sql.BeginTxx+getConsoleWorkspaceSQLX+namedExecContext,使用:organization_uuid/:workspace_uuid/:workspace_external_id命名参数与dbUUID/tryParseDBUUIDIdentifierhelper,消除internal/db中最后一个 raw-pgx 写入路径。WHERE 与 RETURNING 改为按organization_uuid/workspace_uuid等 UUID 列过滤,与 migration 00044 回填的 UUID 引用列及对应 v2 索引一致;上一轮指出的 raw-pgx 句柄问题由此关闭。- 归档自锁防护改用
WorkspaceScope—internal/platformapi/console_workspaces.go改为通过consoleWorkspaceScopeFromRequest解析出WorkspaceScope,同时按UUID与DisplayID匹配当前会话工作区,比上一版的单一 external_id 比较更稳健;ArchiveConsoleWorkspace现接收workspaceScope.UUID。 - 测试 seed helper 对齐 UUID 引用规则 —
tests/console_workspace_archive_api_test.go的seedArchiveOrganization/seedArchiveTargetWorkspace/seedLiveAPIKeyForWorkspace由organization_id(bigint)与workspace_id(text external id)改为organization_uuid/workspace_uuid;seedConsoleAPIKeyForWorkspace改用真实的db.CreateConsoleAPIKey,符合internal/db/AGENTS.md的标识符引用约定。 - Shell 测试脚本空白整理 —
generate-code-session-jwt-key_test.sh与generate-upstream-proxy-ca-key_test.sh删除一处多余空行,无行为变化。
anthropic/glm-5.2 | 𝕏
| - `workspaces.archived_at timestamptz`:非空即归档。 | ||
| - `console_api_keys.archived_at timestamptz`:随工作区归档一并置位。 | ||
|
|
||
| `console_api_keys.workspace_id` 存储工作区的 `external_id`(如 `default`),级联按 `org_uuid` + `workspace_id` 定位。归档写使用 `coalesce(archived_at, now())`,因此对已归档工作区重复归档是幂等的:`archived_at` 不会被改写。 |
There was a problem hiding this comment.
ℹ️ 这段数据模型描述与本提交的级联实现已经脱节。a2fbcdc 把 ArchiveConsoleWorkspace 的级联 WHERE 从 org_uuid + workspace_id(文本 external_id 列)改成了 organization_uuid + workspace_uuid(UUID 列,见 internal/db/console_workspaces.go:64-65),但本段与时序图第 48 行仍按旧的文本列描述。同一段还提到「只写两个时间戳列」,而实现现在还会把 console_api_keys.status 置为 'archived' 并级联吊销 api_keys(见 console_workspaces.go:60-61、73-79)。
Technical details
# 文档与实现脱节
## Affected sites
- `docs/design/be/console-workspace-archive.md:20` — 数据模型段称级联按 `org_uuid` + `workspace_id` 定位,且只写 `archived_at`;实现已改为 UUID 列并写入 `status`。
- `docs/design/be/console-workspace-archive.md:48` — 时序图标注 `WHERE org_uuid AND workspace_id`,同样过时。
- `docs/design/be/console-workspace-archive.md:71-73` — 包职责段称级联只更新 `console_api_keys.archived_at`,且「复用 `console_api_keys.go` 的 `scanConsoleWorkspace`」;本提交已删除本地 `scanConsoleWorkspace`、改用 `getConsoleWorkspaceSQLX`,且级联同时写 `status='archived'` 与 `api_keys`。
## Required outcome
- 数据模型段与时序图按 `organization_uuid` / `workspace_uuid`(UUID 列)描述级联定位。
- 级联写操作列出演齐 `console_api_keys`(`status` + `archived_at`)与 `api_keys`(`status`)。
- 包职责段的 scan 辅助函数引用更新为 `getConsoleWorkspaceSQLX`。
## Suggested approach
- 第 20 行改为「级联按 `organization_uuid` + `workspace_uuid` 定位」,并补充 `console_api_keys.status` 与 `api_keys.status` 的级联写入。
- 第 48 行改为 `WHERE organization_uuid AND workspace_uuid`。
- 第 71-73 行更新为 `getConsoleWorkspaceSQLX` 并补全 `console_api_keys.status` 与 `api_keys` 级联吊销。There was a problem hiding this comment.
已在 ff469c8 同步:数据模型段、时序图与包职责段均已按 organization_uuid / workspace_uuid、sqlx 事务、api_keys 级联吊销与 WorkspaceScope 更新。
anthropic/glm-5.2 | 𝕏






问题背景
Closes #95。
当前控制台(Console)工作区只支持创建,没有归档入口;且后端归档只盖
workspaces.archived_at,不级联吊销该工作区下的console_api_keys,归档后这些 key 仍可查询和使用,与 Anthropic 官方「归档立即吊销所有 API key」的语义不一致。#95 给出了代码证据与官方文档依据。方案
面向 Console 用户补一个归档端点,在单个事务内软删除工作区并级联吊销其 API key,并加防自锁与默认工作区保护:
internal/db/console_workspaces.go:新增ArchiveConsoleWorkspace(orgUUID, workspaceID),单事务内update workspaces set archived_at = coalesce(...)并级联update console_api_keys set archived_at = coalesce(...),组织隔离 + 幂等(coalesce保留原archived_at)。internal/platformapi/console_workspaces.go:handleArchiveConsoleWorkspace做禁归档校验——default工作区与当前会话绑定的工作区返回409(防自锁:归档当前工作区会立即吊销会话绑定的 API key);未知 / 跨组织工作区返回404。RegisterConsoleOrganizationWorkspaceRoutes(platform_backend_routes.go),与GET /workspaces同处。ArchiveWorkspaceDialog:破坏性二次确认;WorkspacesSettingsPageActions 列改为 DropdownMenu,default与当前激活工作区的归档项禁用并给出切换提示;归档成功后从工作区列表移除。改动文件
internal/db/console_workspaces.go(新增)internal/platformapi/console_workspaces.go(新增)internal/platformapi/platform_backend_routes.go(+1 行归档路由)tests/console_workspace_archive_api_test.go(新增,覆盖 200 / 404 / 409 default / 409 current / 级联吊销 / 幂等)web/src/shared/workspaces/ArchiveWorkspaceDialog.tsx(新增)web/src/shared/workspaces/{api,context,WorkspaceProvider}.ts(x)web/src/features/settings/WorkspacesSettingsPage.tsxweb/src/shared/i18n/messages/{en,zh-CN}.jsondocs/design/be/console-workspace-archive.md(状态机 / 级联时序 / 防自锁 / API 契约 / 测试)验证
go test ./... -count=1通过(含归档子测试)just lint/just dead-code/just duplicates/just complexity通过bun run build(tsc + vite)、just web-format-check、bun run lint:naming、lint:complexity通过说明
console_api_keys.go(db / platformapi 两个文件)与行预算文件未改动:归档功能不夹带与之无关的 select 去重或 handler 搬迁,此类重构另行处理。待维护者 review,谢谢。
Summary by CodeRabbit
New Features
409(default/current workspace) and404(not found/other organization) responses.Tests
409/404behavior.Documentation