犀牛鸟 2026 研究笔记 estelledc.github.io

案例 #1772:移除 Viewer 误拦 Agent 对话

PR:#1772 | 4 文件 +66/-109 | 2026-06-23 合入 | lyingbug

结论先行

CustomAgent.RunnableByViewer类型定义、DB 列、Go SDK 中存在,但 运行时从未完整实现;更糟的是 session/qa.go 的 Agent QA gate 仍检查该字段 — builtin agent 内存中未填充该字段,导致 viewer 调用 /api/v1/agent-chat 恒 403,尽管 DB 默认 runnable_by_viewer=true

修复:删除死字段 + 移除 gate(净删 43 行)— 当目标是 unblock 用户时,删错误逻辑优于补 half-baked RBAC

适合谁读:改 Agent QA 入口、租户 RBAC、Go handler 权限的贡献者。

Ch45 Agent/Token · Ch12 Agentic RAG · 对比 case-1774(删前引用检查)。


1. 问题现象(What)

复现(PR Test plan)

  1. 以租户 Viewer 角色登录
  2. POST /api/v1/agent-chat/{session_id}agent_id=builtin-smart-reasoning
  3. 修复前:403 Forbidden
  4. 修复后:200,流式返回正常

产品预期 vs 实际

角色 预期 修复前
Viewer 只读使用已发布 agent 被 RunnableByViewer gate 拦截
Contributor/Owner 正常对话 正常
knowledge-chat viewer 可用 未改(本 PR 不动)

1.3 修复前 / 修复后

角色 修复前 修复后
Viewer + builtin agent 403(RunnableByViewer zero value) 200 流式
Contributor/Owner 200 200(不变)
knowledge-chat viewer 未改 PR scope 未改

2. 定位步骤

rg "RunnableByViewer|agent-chat|AgentQA" internal/handler/session/
rg "RunnableByViewer" internal/types client/

阅读顺序qa.go agent-chat 分支 → custom_agent.goclient/agent_manage.go


3. 根因分析(Why)

半实现 flag 的危险

flowchart LR
    A[DB 列 runnable_by_viewer<br/>default true] --> B[CustomAgent struct 有字段]
    B --> C[Go SDK JSON 暴露]
    C --> D[qa.go gate 检查字段]
    D --> E{builtin agent 内存对象}
    E -->|字段未赋值| F[Go zero value = false]
    F --> G[403 ❌]

    style G fill:#f96

三层不一致

  1. DB 默认 true — 运维/前端以为 viewer 可跑
  2. builtin agent 构造时不 populate — 内存为 false
  3. handler 仍信任内存字段 — 误拦

这是典型的 「有 schema 无行为」 — 比完全没有字段更误导排查。

关键文件

文件 改动
internal/types/custom_agent.go 移除 RunnableByViewer
client/agent_manage.go SDK 类型同步
internal/handler/session/qa.go 删除 AgentQA RBAC gate
logger.go minor 清理

PR Summary 强调:built-in agents never populated this field in memory — 这是 403 的直接触发条件。

PR 四段式

What Why How Test
Viewer 403 on agent-chat dead flag + zero value 删字段与 gate Viewer POST builtin agent → 200

4. 解决方案(How)

为何「删」而不是「补全 RBAC」?

方案 成本 风险
补全:load DB → populate → 前端配置 高(全链路 RBAC) 新 bug 面
删除死字段 + gate 低(-109 行) viewer 与 contributor 在 agent-chat 暂同等

Issue 目标是 unblock viewer — 维护者选择 删错误逻辑。完整 viewer 只读 RBAC(含 #1248 feedback 写权限)应 另开设计,不在 dead flag 上补丁。

定位 QA 入口

rg "agent-chat|AgentQA|RunnableByViewer" internal/handler/session/
rg "RunnableByViewer" internal/types client/

qa.goagent-chatknowledge-chat 分支 — 本 PR 只动前者。


5. 与 #1248 / 竞赛

关联 说明
谁能写 feedback #1248 需区分 viewer(只读?)vs editor — 不能复制本 PR「删 gate」模式,需 正面设计
dead flag 排查 grep RunnableByViewer 类字段 — 配置存在但行为 absent
handler 读路径 session/qa.go 是 QA 入口地图 — 与 case-1774 handler 层同级

6. 可复用模式


7. 常见误区

错误认知:DB default true 就能保证运行时 true。
正确理解:handler 读的是 内存 struct — 构造链必须赋值。

错误认知:403 一定是 JWT 角色问题。
正确理解:先看 agent 级 gate,再查 tenant RBAC。

错误认知:删 RBAC gate 等于「不做权限」。
正确理解:本 PR 是 移除错误 gate;正确 RBAC 应基于 资源 + 动作 表设计,而非单个未维护 bool。

错误认知:builtin agent 与 custom agent 走同一构造路径,字段一定一致。
正确理解:builtin 在内存中 不 populate RunnableByViewer,Go zero value 为 false — 集成测必须覆盖 builtin-smart-reasoning,不能只看 DB 默认。


8. 思考点

  1. 若要做「viewer 可读 agent 但不可改配置」,应在 哪一层 校验?(路由 / middleware / service)
  2. #1248 点赞是否算 写操作?viewer 该不该允许?设计 API 返回码。
  3. 写一条 回归测试:viewer + builtin agent → 200;如何 mock session?
  4. 对比 case-1774「删模型前查引用」— 何时该 加检查,何时该 删检查

章末自检


延伸阅读