- I-1 站点停用全链接线: Site 数据类加 status 字段; DB 源 _load_one 与 文件源 read_tenant 均只输出 active 站点(停用站点域名不进 registry 索引 → 该域名 404;全停用=合法运营状态输出空 sites;文件源无 status 键默认 active 兼容)。E2E: PATCH 站点 disabled → 其域名 404、复启用 恢复;源级直测两源过滤。 - I-3 prefix 跨租户查重补两口: db.other_push_prefixes 共用助手 (MAX(version) 逐租户当前前缀,排除自身);创建向导(出厂档案前缀被占 → 400 提示改 blank/改占用方)与 reset push(重置回出厂撞车 → 400 提示 走 PUT)接入;PUT 原有查重改用同一助手。rollback 维持豁免(历史不可变 语义优先,取舍清单记档)。既有测试第二租户改 blank profile 避撞。 - I-4 默认凭据警示: lifespan 启动检测 ADMIN_PASSWORD 仍为 Settings 字段 默认值(非硬编码第二份,防双源漂移)→ logger.warning 醒目提示; CLAUDE.md Phase 4 部署记档补 ADMIN_PASSWORD 生产要求。 - I-2/M-2 取舍补录: 密钥掩码未实现(GET config/settings 明文返回 api_key/smtp 密码,Phase 3 前须定 __SET__ 哨兵方案);站点管理权限现为 仅超管(Phase 3 对接时决策放开或改 spec);取舍清单第 2 条同步刷新 (rollback 现为唯一豁免口)。 全量回归: 571 passed(基线 566 + 新增 5)。 Co-Authored-By: Claude Code <noreply@anthropic.com>
15 KiB
CLAUDE.md — all-kefu 工作约定
项目定位
本项目 all-kefu 是统一多租户 AI 客服平台:一套引擎 + 租户配置包,消除"每客户复制一套代码"的旧模式。项目由 ai-kefu / ai-kefu-jc 两个复制分叉项目合并而来,jc 为主线(代码以 ai-kefu-jc 为基础迁移,再做租户化改造)。
硬约束
- uvicorn 必须以
--workers 1运行。内存中的 token 缓存、限流状态、配置缓存、每租户 engine 缓存均假设单进程,多 worker 会导致状态割裂。任何启动脚本/部署配置不得改动此项。 - Windows 环境:跑 python / pytest 前设置
PYTHONUTF8=1(GBK 控制台遇 emoji / 中文 payload 会直接崩,血泪教训)。
本地开发访问方式
- 路由按 Host 解析租户:
/与/static也需要已知租户域名(未知 Host 一律 404,唯一豁免/health,供部署健康检查 curl 用)。 - 本地访问二选一:改 hosts 把
chat.agent.hnxinyouhe.com/chat2.agent.hnxinyouhe.com指到127.0.0.1;或 curl 显式带-H "Host: chat2.agent.hnxinyouhe.com"。 - uvicorn 起在 8018:
PYTHONUTF8=1 .venv/Scripts/python -m uvicorn app.main:app --port 8018 --workers 1。
开发流程
- 改动
seed_profiles后必须跑全量 pytest(出厂配置影响面大)。 - 全量回归命令:
PYTHONUTF8=1 .venv/Scripts/python -m pytest -q(根目录与 tests/ 子目录均可跑,pytest.ini 已锚定根路径)。
Phase 1 完成状态(2026-10-12 收尾核对)
测试基线:459 passed(根目录与 tests/ 子目录各跑一遍,数字一致;唯一 warning 为 starlette TestClient 对 httpx 的弃用提示,无害)。此为 Phase 1 时点数字;现行回归基线以 Phase 2 完成状态段为准(566)。
零同步 IO 审计(grep -rn "requests\.\|urllib\|time\.sleep" app/):
- 命中数 0——app/ 内无 requests 调用、无 urllib、无 time.sleep。
- 全库唯一 HTTP 出站点为
app/engine/services/lead_push_service.py_do_post,使用httpx.AsyncClient(async with 上下文),异步路径成立。 app/engine/providers/glm.py的同步 token 生成与兜底同步调用分别经loop.run_in_executor/asyncio.to_thread下卸,事件循环不被阻塞。app/engine/services/rate_limit.py的import time仅用time.time()读时钟(纯 CPU,非 IO);app/ 源码内无open()文件读取。
spec §6.2 合并清单核对(逐项,全部在位):
- 取自 jc:cc_completion 完整判定树+状态机+extractor
cc_reply+e164 出站渲染(app/engine/services/cc_completion.py,conversation.py 双门控消费);多语言墙(€/£、西/葡/俄/阿词表在seed_profiles/jiancai/wall.json、ES 迂回池 boundary_filter+wall.json、建材防误杀白名单为 pattern 级 comment+后顾断言 DN800/SDR11/PN16/"euro pallets" 排除);dual-contact 分步双要(cc_settled 串行门控邮箱轻补)+_email_asked;CC 强制墙(conversation.py cc_wall 裁决段)+零号码墙回显豁免收窄(boundary_filter.py:旧豁免移除,改 allow_number 回显确认轮白名单+防自锁线);XFF 服务端优先(chat_rest_server_ip/chat.py 握手期提取,均取第一段)、阿语数字归一(contact_pipeline 捕获源头 translate)、resolve_region_code(ip_service.py)。 - 上游吸收:input-placeholder 可配置化(品牌配置组,public.py 下发,widget/chat data-placeholder 管道);7 个测试文件(test_channel_hygiene / test_chat_rest_ip / test_lead_push_cc / test_cc_integration / test_ip_service_region / test_prompt_static / test_placeholder_static,commit d1aed8e)。
- 三不带:blog.html(static/ 无此文件)、Redis(app+scripts 零命中)、旧版 whatsapp-cc-completion(零命中,jc 版 cc_completion.py 取代)。
当前能力一览:多租户引擎(TenantRuntime 隔离边界,engine 不感知 platform);站点模型(tenant_config.Site,一租户多站点覆盖);词表/提示词/品牌全配置化(seed_profiles 出厂包 + 文件级 registry_reload 热加载,Phase 2 升级为 platform.db);Host 路由(host_middleware,未知 Host 404);WS 防串库(租户化 client key + 会话站点标);WS 每 IP 连接上限(conn_limiter,租户级 ws_conn_limit 配置)。
Phase 2 完成状态(2026-10-13 收尾核对)
测试基线:566 passed(根目录与 tests/ 子目录各跑一遍,数字一致;唯一 warning 同 Phase 1——starlette TestClient 对 httpx 的弃用提示,无害)。后续任务回归以此为准。
零同步 IO 审计复查(grep requests / urllib / time.sleep / open 于 app/):命中数 0——无 requests/urllib 导入、无 time.sleep、无 open() 调用;import time 仅 platform/auth.py 与 engine/services/rate_limit.py 两处,均只读 time.time() 时钟。scrypt 登录路径 CPU 开销注记在案(auth.py 模块 docstring:单次 ~50ms,只在登录/改密路径,不在每请求热路径——依赖只查内存 token 表)。
安全自查(读码 + 定向测试 28 条全绿:test_auth / test_admin_mounting / test_avatar_upload / test_host_routing):
- 密码/token 不进日志:app/platform/ 全层零 logger 调用(干净由构造保证);main.py 启动日志仅静态串 + slug/db 路径;引擎层(Phase 1 既有)日志参数为会话内容摘录/session_id/notify_emails,无凭据字段。
- gitignore 覆盖复核(git check-ignore 实证全命中):
tenants/*.db{,-wal,-shm}(租户库实际落tenants/<slug>.db)、platform.db{,-wal,-shm}(仓库根)、data/(含data/uploads/上传卷)。 - 登录限流在 /api/platform 豁免路径仍生效:host_middleware 对
/api/platform/前缀的豁免只跳过租户解析,认证守护在路由依赖;登录限流先于密码验证(锁定期内正确密码也 429)。测试钉位:test_auth.py::test_login_lockout_after_5_fails + test_admin_mounting.py::test_platform_api_bypasses_tenant_resolution / test_platform_prefix_not_open / test_prefix_match_precise。 - 上传目录不可执行:上传只认魔数(png/jpg/webp)+ uuid 重命名落盘——.py 文件无进无出;avatar 下发(public.py)取 PurePosixPath.name 并显式拒绝
/\与目录点(防 Windows 反斜杠穿透),FileResponse content-type 由扩展名映射(只可能 image/*)。
平台层能力一览(Phase 2 交付;engine 仍不感知 platform):
- 认证:scrypt 版本化哈希(stdlib 零新依赖,偏离 spec bcrypt/argon2 有注记)、内存 token 8h TTL、登录限流(username+IP 滑动窗 5 次/15 分钟)、改密(must_change_password 引导)、角色两级(超管 / 租户管理员)+ 409 语义。
- 租户管理:创建向导(消费出厂档案,blank=jc 墙提示词)、站点与域名子资源 CRUD、停用(聊天面 503、后台仍可见)、slug 不可变。
- 配置版本管线:分组校验 → 整份版本快照(MAX+1)→ 回滚 / 重置出厂 / 试金石(wall/test)→ registry_reload 热生效。
- 上传:头像 multipart(魔数校验 / 2MB 上限 / uuid 重命名),写配置版本 + 三级解析链下发(uploads → seed 资产 → 静态兜底)。
- 数据视图:会话列表(筛选/分页)/ 详情 / 删除 + 推送日志——租户 scope 强制(跨租户 404)。
- 统计:卡片 / 漏斗 / 趋势 / 分布 / 推送健康——逐租户库查询 + 应用层合并(禁 ATTACH,spec §7.6);指标口径表定死在 stats.py docstring。
- DB 源生产装配:TENANT_SOURCE=db 默认(DbTenantSource),FileTenantSource 保留为测试/档案/冷备形态;bootstrap_platform.py 一键引导。
admin API 速览(全部 /api/platform 前缀;Host 中间件豁免该前缀的租户解析,认证由路由依赖守护——require_super 超管 / _tenant_guard 租户管理员):
- 认证:
POST /auth/login、POST /auth/logout、POST /auth/change-password - 租户:
GET|POST /tenants、GET|PATCH /tenants/{slug}、POST /tenants/{slug}/sites、PATCH /tenants/{slug}/sites/{site_slug}、POST|DELETE /tenants/{slug}/sites/{site_slug}/domains[/{domain}] - 配置:
GET /tenants/{slug}/config、PUT /tenants/{slug}/config/{group}、POST /tenants/{slug}/config/{group}/reset、GET /tenants/{slug}/config/versions[/{version}]、POST /tenants/{slug}/config/rollback、POST /tenants/{slug}/wall/test - 上传:
POST /tenants/{slug}/brand/avatar - 数据:
GET /tenants/{slug}/conversations[/{conv_id}]、DELETE /tenants/{slug}/conversations/{conv_id}、GET /tenants/{slug}/push-logs - 统计:
GET /stats(超管,?tenant=单租户下钻)、GET /tenants/{slug}/stats - 平台设置:
GET|PUT /settings
Phase 2 沉淀的取舍与 Phase 3 清理单
实现取舍(API 冻结 / Phase 3 前端对接前须知):
- PUT config 整组替换语义——前端必须整组读-改-写(Task 6;做部分更新需先 GET 全组再改再 PUT)。
- rollback 仍绕过 prefix 跨租户查重(终审 I-3 已收口 PUT/创建向导/reset 三口;rollback 单独保留豁免 = 历史不可变语义优先——回滚到含已占前缀的历史版本属数据修复场景;如仍需收口,在 rollback 前补跑查重)。
- 并发保存同租户撞 UNIQUE → 500(单管理员场景可接受;收口 = 捕 IntegrityError 转 409)。
- 统计 API 无
?site=筛选(spec §7.6 提及;Phase 3 前端需要时补)。 - 登录限流反代形态退化为按用户名(限流键 username + request.client.host,无 XFF 解析;可信代理解析归部署批次)。
- avatar 旧文件不清理(换头像后旧文件留盘;孤儿累积无害)。
- 停用租户管理员存量 token 最长存活 8h(TTL 限窗)——即时锁死需在 validate 时回查租户状态。
- 400(预检撞车)vs 409(竞态 IntegrityError)并存——API 冻结前统一。
- limiter 全量清扫(token/登录限流的内存键永不回访,长跑进程缓慢累积;收口 = 定期清扫)。
- lead_push_service 的 settings re-export 锚(app.engine.config 的 settings 在 lead_push_service 顶部 re-export,22 处测试 monkeypatch 依赖此锚——改动前先迁测试)。
- 密钥掩码未实现——GET config/settings 返回明文 api_key/smtp 密码;Phase 3 前端对接前必须定掩码哨兵方案(GET 吐
__SET__+ PUT 收哨兵保留旧值)(出处:终审审查 I-2)。 - 站点管理权限现为仅超管(spec §7.3 写租户管理员同左)——Phase 3 对接时决策放开或改 spec(出处:终审审查 M-2)。
Phase 3 清理单(Phase 1/2 审查挂账滚动合并;删除线 项已在 Phase 2 完成):
FakeAI 三份拷贝收敛进 conftest(Task 10 完成:conftest.py 单一定义,tests/ 零拷贝)。rate_limit 与 ws_conn_limit 的 0 语义统一 + 配置类型校验(Task 10 完成)。name_to_region_extras / context_seed_map 等散装 resolver 收拢为 config_views 单入口(Task 10 完成:app/platform/config_views.py)。avatar 从 seed_profiles 迁 uploads 卷(Task 7 完成:上传 + 三级解析链)。- AIService.from_runtime 类方法——组装正典化(出处:引擎改造审查)。
- boundary_filter 视图常量退役——admin 重写后旧测试迁移时(出处:引擎改造审查)。
- push enabled 布尔空槽语义——admin 配置显式化时解决(出处:push 审查)。
- contact.template_languages 运行期接线——后台批次(出处:配置 schema 审查)。
- 话术含引号/换行的转义——编辑器批次(
_format_deflection_pooldocstring 有注)。 - 产品决策项:国名纠正 e164 变化时是否补发更正邮件(上游行为更优);REST/WS 服务端 IP 口径是否统一(出处:上游吸收审查)。
- /health 分级健康——Phase 4 部署批次(进程存活之外按依赖分级上报;出处:终审审查 M-3)。
- PromptProvider.from_dir 的 FileNotFoundError 带路径上下文——提示词目录缺失时异常信息含实际路径(出处:终审审查 M-4)。
Phase 3 计划编写注意
- conftest 三坑纪律延续:
async_db夹具用 NullPool;TestClient 必须走 portal 桥接事件循环;禁止asyncio.run直开租户 session_factory(三个坑都在 tests/conftest.py 有既成解法,照抄勿发明)。 build_app/app_client/TenantTestClient夹具已就绪,新测试优先复用而非自建 app 实例;平台层测试用db_source_app/super_client夹具(conftest.py:DB 源装配 + 超管登录态客户端,Phase 2 全部平台路由测试的既成姿势)。- 引擎侧读配置一律经 config_views 单入口(app/platform/config_views.py:resolve_email/push/ai_settings、resolve_contact_flags、context_seed_map、name_to_region_extras)——新消费点不再自写 resolver。
- 统计前端照 stats.py docstring 的指标口径表展示(会话数不要求有消息、线索=三选一非空、推送成功率分母口径、北京日界趋势等,歧义处已定死)。
Phase 4 部署记档(提前锚定,部署批次展开成 docs/deploy-production.md)
pip install -r requirements.txt必须执行:python-multipart(>=0.0.20)为 Phase 2 新依赖——Task 7 实证缺失即头像上传 500(FastAPI multipart 解析依赖它)。- bootstrap_platform.py 用法:
PYTHONUTF8=1 python scripts/bootstrap_platform.py [--db PATH] [--profiles SLUG ...] [--seed DIR]。两步全幂等:init(schema + 超管引导,凭据取 env/.env 的 ADMIN_USERNAME/ADMIN_PASSWORD)+ 导入出厂租户(默认 auto jiancai 两租户,已有 slug 跳过);不建租户管理员(走后台创建向导补)。 - TENANT_SOURCE=db 默认(生产装配形态);
file为冷备/本地调试回退。 - admin 域名反代区分:
/api/platform/*前缀豁免租户 Host 解析——平台后台 API 可与租户聊天域名同源反代(按路径分流)或独立域名,两形态皆可;租户聊天面(/、/static、WS、/api/public、/api/chat*)仍严格按 Host 解析租户,未知 Host 404。 - ADMIN_PASSWORD 默认值警示(终审 I-4):lifespan 启动检测到 ADMIN_PASSWORD 仍是出厂默认(admin123)时打 WARNING 日志——生产部署必须设 ADMIN_PASSWORD 环境变量换掉,且超管首登立即改密(must_change_password 门强制)。
文档索引
- 设计文档(spec):
docs/superpowers/specs/2026-10-12-unified-kefu-platform-design.md - Phase 1 实施计划:
docs/superpowers/plans/2026-10-12-phase1-engine-merge-tenantization.md - Phase 2 实施计划:
docs/superpowers/plans/2026-10-13-phase2-platform-layer.md - 部署坑手册:Phase 4 补
docs/deploy-production.md(尚未存在;Phase 4 部署记档段已锚定要点)