diff --git a/README.md b/README.md index 51bf32b..4448037 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,8 @@ slash 命令:`/hot` `/ai` `/reports` `/oppdiag`。 1. **按项目 owner 过滤**(`list_visible_reports` 单一事实源): - 挂项目的报告:当前用户是项目 owner(`sd_projects.created_by==uid`)才可见; - agent 创建的项目(`created_by LIKE 'agent.%'`)降级为同机构成员可见 + agent 创建的项目(`created_by LIKE 'agent.%'`,模式经 `${kw}$` 参数化—— + SQL 字面量裸 `%` 会被驱动当格式化符炸 ValueError,实测踩过)降级为同机构成员可见 (对齐 `check_project_owner` 既有降级语义,不重复发明) - 未挂项目的报告(平台级调研,存量数据大多是这类):登录用户可见 - 项目已删的孤儿报告:owner 无法验证 → 不可见(宁缺勿漏,LEFT JOIN 自然排除) diff --git a/pipeline_opportunity/opp_report_capability.py b/pipeline_opportunity/opp_report_capability.py index 3c735e6..3f25460 100644 --- a/pipeline_opportunity/opp_report_capability.py +++ b/pipeline_opportunity/opp_report_capability.py @@ -110,6 +110,9 @@ async def list_visible_reports(sor, user_id): if not user_id: return [] uorg = await _user_org(sor, user_id) + # ⚠️ LIKE 模式必须走 ${kw}$ 参数化——SQL 字面量里的裸 % 会被驱动当格式化符 + # (实测 ValueError: unsupported format character;rag_client.py 的 + # NOT LIKE 'agent.%' 是同一颗潜伏雷)。参数值里的 % 安全。 sql = ( "SELECT r.id, r.project_id, r.software, r.title, r.status, r.ppt_path, " "r.created_by, r.created_at, r.updated_at, " @@ -117,10 +120,11 @@ async def list_visible_reports(sor, user_id): "FROM opp_reports r LEFT JOIN sd_projects p ON r.project_id = p.id " "WHERE (r.project_id IS NULL OR r.project_id = '') " " OR p.created_by = ${uid}$ " - + (" OR (p.created_by LIKE 'agent.%' AND p.org_id = ${uorg}$ AND ${uorg}$ <> '') " - if uorg else "") - + "ORDER BY r.created_at DESC LIMIT 200") - recs = await sor.sqlExe(sql, {"uid": user_id, "uorg": uorg}) + " OR (p.created_by LIKE ${agent_pat}$ " + " AND p.org_id = ${uorg}$ AND ${uorg}$ <> '') " + "ORDER BY r.created_at DESC LIMIT 200") + recs = await sor.sqlExe(sql, {"uid": user_id, "uorg": uorg, + "agent_pat": "agent.%"}) await sor.sqlExe("COMMIT", {}) return rows_to_dicts(recs)