Skip to content

fix(security): round-64 批1——edge 人工审批门属主化(非属主 404 同形 / pending 按属主过滤) + hub health 兜底不再撒谎(nil handler → 503+ready:false) (#2241) - #2242

Merged
DeliciousBuding merged 6 commits into
masterfrom
chore/round-64-wave
Sep 2, 2026

Conversation

@DeliciousBuding

Copy link
Copy Markdown
Collaborator

这是什么

round-64 wave 的集成分支(tracker #2241)。为压缩 CI 等待(本仓 required checks strict: true + runner 账号级串行 + 单次 checks ≈8min),本轮把已验收的 lane 堆到一个分支、只跑一次 CI,而不是每 lane 一个 PR 各跑一轮、且每合一个就让后面的分支变陈旧重跑。

含 2 条 lane / 6 个 commit,均带 (#2241)

lane A — edge 人工审批门属主化(安全 P1)

  • PostPermissionDecide / PostPlanDecide:在任何副作用(broker decide / registry consume / event publish)之前加属主门,非属主一律 404。
  • 404 用域内码 ErrPermissionRequestNotFound / ErrPlanNotFound,与「本来就找不到」路径逐字节同形 ⇒ 不成为 runId 存在性 oracle。
  • GetPlansPending过滤而非 404(同 GetArtifacts / GetPreviews / GetDeliveryJournal 房型),新增 filterPendingPlansByOwner;单租户哨兵看全部,空身份 fail-closed(AH-SR-045)。
  • 门形态取同包 handlers_artifacts.go:77 的属主-only 写法,照抄 diff_apply.go!ok || 存在性分支——否则既有断言 RejectsUnknownRequest/RejectsWrongRun/RejectsSecondDecision/RejectsExpiredRequest 的 errcode 会被改写(禁止改既有断言)。
  • api/openapi.yaml 三段 description 同步(措辞照抄 /v1/delivery-journal)。

lane C — hub health 兜底分支诚实化(可观测性)

  • SetupRouteshealthHandler == nil 兜底原先对 /health/health/ready 都答 200 + ready:true,而该分支下没有任何依赖探测。改为两者均 503 + status:"unavailable" + ready:false,镜像已接线 HealthHandler.Ready 的降级信封。
  • /health/live 保持 200 + live:true:能应答探针的进程本身就是存活证据,503 会让编排器重启一个只是缺 health 接线的进程。
  • 该兜底在真实部署中不可达:App.Run 在监听前无条件接线 HealthHandlerinternal/app/wiring.go);已接线的 /health 即使依赖降级仍答 200(body 带 status/ready 供调用方区分)。
  • api/openapi.yamlhubHealth503 响应 + 说明 200/503 的分界。

证据(本机实测,非推断)

lane A(.worktrees/approval-owner):

  • 红→绿:实现未改时新测试 7 红(含 LEAK: user-b sees user-a's pending plan);上门后 internal/api 全绿,测试数 274 → 287(只增不减)。
  • go test ./internal/api/ -race -count=2 → ok 26.5s;gofmt -l 空;golangci-lint run ./internal/api/... → 0 issues。
  • 反向验证:门改 if false && …、过滤器还原 broker.ListPending() → 8 红;还原后 grep -c REVERSE-VERIFY = 0。
  • 属主路径改前/改后同一组守护测试均绿(200 + {"code":"ok","data":{"status":"ok"}} + run.agent.permission_decided 事件 + 二次决定 404 单次消费语义)⇒ desktop 活体调用方不受影响。

lane C(.worktrees/health-version):先落红测试 router_health_fallback_test.go(120 行)再修实现;已接线 /health/ready 在依赖坏时仍 503。

集成分支(本 PR 树,cherry-pick 后复跑):

verify-openapi-contract.py  → OpenAPI Hub routes 153 / Router 156 / Allowlisted 3 / 2xx violations 0 (new: 0)   EXIT=0
verify-doc-ssot.py          → verifier-map ok (66 scripts, 58 CI files) / AGENTS.md 96 paths / doc SSOT ok     EXIT=0
verify-commit-messages.sh   → commit message check ok (6 commits)                                              EXIT=0
verify-ci-gates / -devserver-contract / -oidc-code-ssot / -action-runtimes / -project-skills                    全 OK
go test ./internal/api/ (edge)          → ok 2.226s
go test ./internal/router ./internal/app (hub) → ok 0.140s / ok 1.799s
git diff origin/master..HEAD --check    → clean
api/openapi.yaml YAML parse             → 221 paths

api/openapi.yaml 被两条 lane 同时改(A 在 /v1/permissions|plans/* 段、C 在 /health 段),cherry-pick 自动合并且契约校验逐项回到干净树基线。

不在本 PR 内(明确边界)

  • App.Version 注入app.go:58 注释声称 "settable via -ldflags / Defaults to dev",实测全仓 -ldflags 只有 -s -w、无 -X,且 Version 是结构体字段 ⇒ 语言层面不可能被 -X 赋值,生产实际值是 ""):lane C 的第二半,涉及 release.yml 7 处 build 行 + 新增包级 var,属发布管线风险面,另开切片
  • lane B(desktop 菜单尾巴:toast.actionUnavailable i18n key + forward 接线):仍在途,若在本 PR CI 结束前验收完会 cherry-pick 追加(concurrency: cancel-in-progress 按 PR 号分组 ⇒ 追加只会取消在跑的那次,不额外付一轮完整 CI)。
  • lane A 登记的 4 个无属主门文件(handlers_agents.go ×6 / handlers_runtime_sessions.go ×1 / handlers_settings.go ×4 / model_catalog.go ×1):其中 settings/health/runners/model-catalog 是 Edge 节点级配置面而非 run-scoped,是否属主化需产品裁决;agent-profiles 与 runtime-sessions 更像真缺口 → 另开 issue。

验收口径

L0(单测/vet/gofmt/lint)+ L1(契约/文档 SSOT 校验)本机已跑;L2/L3 未跑(本 PR 不含需要真实 Edge→Hub 回调或浏览器登录的改动)。不声称生产就绪、不声称真实 E2E。

DeliciousBuding and others added 6 commits September 3, 2026 03:39
…份、单租户三类用例 (#2241)

Co-authored-by: Cursor <cursor@vectorcontrol.tech>
…按属主过滤 (#2241)

Co-authored-by: Cursor <cursor@vectorcontrol.tech>
…/pending 按属主过滤 (#2241)

Co-authored-by: Cursor <cursor@vectorcontrol.tech>
…y 均 503+ready:false,/health/live 保持 200,openapi 补 503 (#2241)

Co-authored-by: Cursor <cursor@vectorcontrol.tech>
@coderabbitai

coderabbitai Bot commented Sep 2, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Team

Run ID: 3c61d695-1a75-4be5-ad40-cfee38443016

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@DeliciousBuding
DeliciousBuding merged commit 4a7af8c into master Sep 2, 2026
39 checks passed
@DeliciousBuding
DeliciousBuding deleted the chore/round-64-wave branch September 2, 2026 19:52
DeliciousBuding added a commit that referenced this pull request Sep 2, 2026
…tionUnavailable zh/en 键落地(未接入动作不再谎称「请重试」) + 消息动作 port 假注释订正 (#2241)

round-64 wave 批 2(tracker #2241),lane B。批 1(#2242)已合入 master `4a7af8c6`;本分支已 rebase 到该 master,无冲突(批 1 只碰 Go 与 `api/openapi.yaml`,本批只碰 `app/**`)。

原 commit 粒度(均带 (#2241)):
- fix(i18n): 补 toast.actionUnavailable zh/en 键,未接入动作的提示不再说"请重试"
- docs(workbench): 订正消息动作 port 的 doc 注释——#2238 之后它已是假话
- feat(desktop): 接上 Hub 转发 mutation,desktop 右键"转发"条目重新渲染并真派发

三件事的因果链:`workbenchTranscriptChromeActionMappers.ts:545` 早已导出 `UNAVAILABLE_ACTION_TOAST_KEY = 'toast.actionUnavailable'` 并在 7 个调用点使用,但 chatview bundle 里没有这个键 ⇒ i18next 回落判据 `resolved !== KEY` 不成立 ⇒ `announceUnavailableAction` 落到 `effect.failureMessage`,用户看到「…失败,请重试」这句对「这个端根本没接线」的错误指导(#2238 登记的尾巴 ①)。补键后,其中 desktop 的 forward 不再属于「未接线」——因为本批同时补上了 `useHubForwardMessage` port 并接线(#2238 当初是按 handler 存在性 fail-closed 关掉的,缺的正是这个 port,尾巴 ③)。`AgentHubWorkbenchTypes.ts:165-171` 的 doc 注释在 #2238 之后已是假话(尾巴 ②),一并订正。

**`regenerate` 明确不接线**:`regenerateAgentTask` 在 Desktop 的 `DesktopHubTaskBridge` / agent task 语义下未经验证,接了等于把未证语义推给用户 ⇒ 保持 fail-closed,`onRegenerate` / `onAddMessageReaction` 的 undefined 断言原样保留。

两处需要 reviewer 知情的取舍:
1. 删了一条既有断言 `expect(props.onForwardMessage).toBeUndefined()`——它的前提写在它自己的注释里("forward needs a `useHubForwardMessage` port"),本批交付物正是补上该 hook,接线后该断言与事实相反。只删这一条;同 test 内 `onRegenerate`/`onAddMessageReaction` 两条 fail-closed 断言原样保留,另在「chatActions 缺失」test 里新增 `expect(props.onForwardMessage).toBeUndefined()` 保住 #2154 不变量。测试数只增不减(3 → 4)。
2. `App.v4.test.tsx` 加了 1 行 `vi.mock` 夹具导出(白名单式 mock 不提供新 hook 会直接红)——**该文件任何断言一字未改**;抽掉这 1 行实测 12+ 用例全红,补回 14 passed。

证据与未验证项详见 PR #2249 描述(含红→绿双向证据、主机侧 rebase 后独立复跑 desktop 18 passed / workbench 5 passed、3 个 i18n 门禁数值、tsc×4 = 0 error、eslint 0 新增 problem 且 2 条 pre-existing 经 master blob 探针确认、以及 `real_tested=false` 的明确标注)。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant