Files
ai-kefu-admin/CLAUDE.md
T
2026-10-14 13:45:14 +08:00

181 lines
26 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 会直接崩,血泪教训)。
- **临时文件用完即删(用户要求)**:冒烟截图、调试日志、造数脚本、临时库/platform.db/tenants 运行产物、probe 脚本、`.playwright-mcp/` 产物等一切工作过程文件,任务收尾必须全部删除,以 `git status` 干净 + 仓库目录无残留为验收标准。不许堆积无用文件。
## 本地开发访问方式
- 路由按 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`)、`data/`(含 `data/uploads/` 上传卷)。platform.db 位置注记(Phase 4 修正):**现落 `data/platform.db`,由 `data/` 规则覆盖**(main.py PLATFORM_DB 与 compose bind mount 钉死,P4T1 审查修复);.gitignore 根级 `platform.db*` 三条为历史留存,无害。
- **登录限流在 /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 前端对接前须知)**:
1. **PUT config 整组替换语义**——前端必须整组读-改-写(Task 6;做部分更新需先 GET 全组再改再 PUT)。
2. **rollback 仍绕过 prefix 跨租户查重**(终审 I-3 已收口 PUT/创建向导/reset 三口;rollback 单独保留豁免 = 历史不可变语义优先——回滚到含已占前缀的历史版本属数据修复场景;如仍需收口,在 rollback 前补跑查重)。
3. **并发保存同租户撞 UNIQUE → 500**(单管理员场景可接受;收口 = 捕 IntegrityError 转 409)。
4. **统计 API 无 `?site=` 筛选**(spec §7.6 提及;Phase 3 前端需要时补)。
5. **登录限流反代形态退化为按用户名**(限流键 username + request.client.host,无 XFF 解析;可信代理解析归部署批次)。
6. **avatar 旧文件不清理**(换头像后旧文件留盘;孤儿累积无害)。
7. **停用租户管理员存量 token 最长存活 8h**(TTL 限窗)——即时锁死需在 validate 时回查租户状态。
8. **400(预检撞车)vs 409(竞态 IntegrityError)并存**——API 冻结前统一。
9. **limiter 全量清扫**(token/登录限流的内存键永不回访,长跑进程缓慢累积;收口 = 定期清扫)。
10. **lead_push_service 的 settings re-export 锚**(app.engine.config 的 settings 在 lead_push_service 顶部 re-export,22 处测试 monkeypatch 依赖此锚——改动前先迁测试)。
11. ~~密钥掩码未实现~~(Phase 3 收口,commit 0c25287:GET config/settings 四处响应密钥值掩码为哨兵 `__SET__`,PUT 收哨兵保留库值;前端 `admin/src/api/mask.ts` secretToSubmit/secretPlaceholder 同款契约)。
12. ~~站点管理权限现为仅超管~~(Phase 3 收口,commit 0c25287:放开租户管理员同权,与 spec §7.3 原文一致)。
**Phase 3 清理单**(Phase 1/2 审查挂账滚动合并;~~删除线~~ 项已结,结项时点见各条注记;未结项滚入 Phase 4 清理单增补):
1. ~~FakeAI 三份拷贝收敛进 conftest~~(Task 10 完成:conftest.py 单一定义,tests/ 零拷贝)。
2. ~~rate_limit 与 ws_conn_limit 的 0 语义统一 + 配置类型校验~~(Task 10 完成)。
3. ~~name_to_region_extras / context_seed_map 等散装 resolver 收拢为 config_views 单入口~~(Task 10 完成:app/platform/config_views.py)。
4. ~~avatar 从 seed_profiles 迁 uploads 卷~~(Task 7 完成:上传 + 三级解析链)。
5. **AIService.from_runtime 类方法**——组装正典化(出处:引擎改造审查)。
6. **boundary_filter 视图常量退役**——admin 重写后旧测试迁移时(出处:引擎改造审查)。
7. ~~push enabled 布尔空槽语义~~(Phase 3 收口:凭据页显式布尔开关 ElSwitch,normalize `enabled === true` 空槽归 false,提交永为显式布尔)。
8. **contact.template_languages 运行期接线**——后台批次(出处:配置 schema 审查)。
9. **话术含引号/换行的转义**——编辑器批次(`_format_deflection_pool` docstring 有注)。
10. **产品决策项**:国名纠正 e164 变化时是否补发更正邮件(上游行为更优);REST/WS 服务端 IP 口径是否统一(出处:上游吸收审查)。
11. ~~/health 分级健康~~(Phase 4 Task 1 完成:`/health` 返回 status/tenants(engine_ok 三态)/domains 分级 JSON——单租户库损坏 degraded 仍 200 不杀容器;compose healthcheck 消费同一端点,Phase 4 终验 docker 演练实测与手册 §2 解读一致)。
12. ~~PromptProvider.from_dir 的 FileNotFoundError 带路径上下文~~(现状即满足:`read_text` 原生 FileNotFoundError 自带完整路径——终验实测 `PromptProvider.from_dir` 指向缺失目录时异常信息为 `[Errno 2] No such file or directory: '<完整路径>/system.md'`;DB 源路径另有 tenant_source 显式包装注记)。
## Phase 3 完成状态(2026-10-13 收尾核对)
**测试基线:后端 pytest 595 passed / 前端 vitest 156 passed**(Phase 3 起步 139,Task 9 平台设置页 +14、终审修复通用 JSON 组 +3;两端数字均已实跑核对,后端唯一 warning 仍为 starlette TestClient 弃用提示,无害)。`pnpm build`(vue-tsc --noEmit && vite build)零错误。后续回归以此为准。
**交付形态**:`admin/` 子目录的 Vue3 + TS + Vite + Element Plus SPA(art-design-pro 模板,pnpm 管理),与后端同仓。本地起法:
- 后端:`PYTHONUTF8=1 .venv/Scripts/python -m uvicorn app.main:app --port 8018 --workers 1`(不变)。
- 前端:`cd admin && pnpm install && pnpm dev`(默认 3006,被占自动顺延);Vite 代理 `/api` → 8018,`/api/public` 与 `/static` 走 Host 改写代理(`VITE_TENANT_PREVIEW_HOST`,见 admin/.env.development 注释)。`pnpm test` 跑 vitest;`pnpm build` 出 dist。
- 登录:超管 / 租户管理员同一登录页(滑块验证为纯前端交互校验);must_change_password 由后端 409 门强制,前端跳 `#/auth/force-change-password`。
**admin 能力一览**(Phase 2 平台 API 的全量对接;菜单按角色过滤,后端 require_super / _tenant_guard 兜底):
- 认证流:登录 / 强制改密 / 退出;pinia + 内存 token,401 统一跳登录,改密 409 契约锁定。
- 仪表盘:平台总览 + 租户卡片墙(超管)/ 我的指标(租户管理员),时间窗切换。
- 租户管理(仅超管):列表 / 创建向导(消费出厂档案)/ 详情编辑 / 站点与域名子资源 / 停用复启。
- 会话与线索:列表(筛选 / 分页 / 租户切换)/ 详情消息流 / 推送日志 / 删除。
- 配置中心:品牌(表单 / 本地简化预览 / 嵌入代码 / 头像上传)、提示词四件套与结构化词表、试金石面板、凭据(邮件 / 推送 / AI 表单 + SMTP 测试 + 推送探测)、contact/engine/misc 三组通用 JSON 编辑(终审 I-1 补齐 spec §7.3 全组覆盖)、版本历史(两版对比 / 回滚 / 组级重置出厂)。
- 统计分析:卡片 / 转化漏斗 / 按日趋势 / 意向·来源·站点·国家分布(ECharts)/ 推送健康表;时间与租户筛选(口径同 stats.py docstring 口径表)。
- 平台设置(仅超管):自由 KV 表的推荐键表单(ai.* / smtp.*,键名是前端约定)+ 存储总览表格;保存 = 逐键 PUT(只提交脏键)。**Phase 4 Task 5A 起推荐键已接线 resolver 平台默认回退链(租户槽位 → 平台设置快照 → env),保存后即时生效**;密钥键掩码哨兵交互与凭据页同款(清除 / 恢复保持)。
**前端实现约定**(后续维护须知):
- 视图逻辑沉淀在各 `views/**/model.ts` 纯函数(掩码判定 / 归一 / 脏检测 / 提交值构造 / 校验),vitest 直接覆盖;.vue 只做装配。auto-import 生效(ref/reactive/ElMessage 等无需手写 import)。
- 密钥键判定前端镜像后端 `_SETTING_SECRET_MARKERS` 子串匹配('monkey' 含 'key' 也命中——与后端一致是契约,不"修正")。
- PUT config 整组替换语义:前端封装读-改-写(GET 全组 → 改 → PUT);平台设置不同,为逐键 upsert。
- `components.d.ts` 构建漂移:`pnpm build`/dev 会重写入库的 dts(仅行尾差异)——`git diff --ignore-cr-at-eol` 为空即 `git checkout --` 还原入库版(超集原则,勿提交行尾噪音)。
**滚动纪律**(自 Phase 3 计划注意延续):conftest 三坑(NullPool / portal 桥接 / 禁 asyncio.run)、平台层测试用 `db_source_app` / `super_client` 夹具、引擎读配置走 config_views 单入口、统计展示照 stats.py 口径表——均照抄勿发明。
## 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 门强制)。
- **Phase 4 清理单增补**(Phase 3 冒烟沉淀 + 终审记档;Phase 3 清理单未结项 5/6/8/9/10 一并滚入):
1. `components.d.ts` 构建行尾漂移(长期收口 = 生成物出库或 EOL 规范钩子,现阶段靠手工还原)。**收档(已知现状非缺陷,2026-10-14 终验)**:build/dev 重写入库 dts 仅行尾差异(`components.d.ts` 与 `auto-imports.d.ts` 两文件同性状,终验 pnpm build 再证实)——`git diff --ignore-cr-at-eol` 为空即 `git checkout --` 还原,勿提交行尾噪音。
2. 推送探测 env 回退真址:租户 push URL 空槽时 `resolve_push_settings` 回退 env `PUSH_API_URL`(默认即真 UniService 生产址)——后台点「探测」会对真实服务发空批量 POST(`{"messages": []}`,按契约文档应为无操作,但属真实出站)。**收档(已知现状非缺陷,2026-10-14 终验)**:处置 = 介意真实出站则生产 .env 将 PUSH_API_URL 指 stub 域后再点探测。
3. 生产反代需规划 admin 域的头像/品牌公开端点:`/api/public/*` 按租户 Host 解析,admin 域直挂必 404——开发期靠 Vite Host 改写代理,生产要么同源反代改 Host(单租户形态)要么前端改用租户域名绝对 URL。**收档(已知现状非缺陷,2026-10-14 终验)**:处置 = 生产选形态 B(租户域名路径分流)即规避;坚持形态 A 则接受预览缺失(页内本地简化预览兜底,手册 §3.3 已注记)。
4. `VITE_TENANT_PREVIEW_HOST` 单一预览域名:开发代理只指向一个租户 Host,切换租户时头像等公开资源预览不准(.env.development 已注记)。**收档(已知现状非缺陷,2026-10-14 终验)**:仅影响开发期预览,切租户时改 .env.development 预览 Host 重启 dev server 即可。
5. ~~resolver 平台默认回退链接入 platform_settings~~(Phase 4 Task 5A 完成:`app/platform/settings_store.py` 内存快照 + lifespan 装载 + PUT settings 后 refresh 热刷新;config_views 的 email/ai 两组改三级回退(租户槽位 → 平台设置快照 → env),push 无平台推荐键保持两级;平台设置页"仅存储"注记移除)。
6. ~~email/test 与 push/test 速率限制~~(Phase 4 Task 5B 完成:config_routes 内 `SlidingWindowCounter` 滑动窗限流(登录限流器同款风格),每租户每类端点 15 分钟 5 次真实出站,超限 429;校验失败/未配置 400 不消耗预算,超限后 429 优先于一切预检)。
7. ~~锁屏功能已摘除入口~~ + ~~通知组件摘除~~(锁屏 Phase 3 摘入口,Phase 4 Task 5F 删代码:art-screen-lock/art-notification 组件目录、component.ts/headerBar.ts 配置项、useHeaderBar.shouldShowNotification、类型字段、components.d.ts 条目、crypto-js 依赖(env.d.ts 声明/package.json/optimizeDeps/lockfile)、en/zh 孤儿语言键全部清除,grep 零残留)。
8. ~~租户切换器 composable 收拢~~(Phase 4 Task 5E 完成:`admin/src/composables/useTenantOptions.ts` 收拢会话/配置/统计三页租户下拉(默认形态选中首个 + bootstrapTenantContext 引导;stats 用 selectFirst:false 纯数据源);dashboard 卡片墙形态不在此列)。
9. ~~ECharts 按需收窄~~(Phase 4 Task 5D 完成:echarts.use 收窄为 Bar/Line/Pie + Title/Tooltip/Grid/Legend + CanvasRenderer——全仓 grep 证实存活消费方仅 stats 页与 useChart;echarts chunk 767,864 B → 559,737 B(-27%),dist 总体积 -273 KB)。
## Phase 4 完成状态(2026-10-14 收尾核对)
**测试基线:后端 pytest 630 passed + 2 skipped / 前端 vitest 164 passed**(后端根目录与 tests/ 子目录各跑一遍数字一致;唯一 warning 仍为 starlette TestClient 弃用提示,无害)。`pnpm build`(vue-tsc --noEmit && vite build)与 `docker compose build`(两阶段)复验零错误。**docker 演练终检实测**:`up -d` 后 healthcheck 约 20s 转 healthy → bootstrap(run --rm 形态)输出与手册 §2 逐字吻合 → 重启后 `/health` 分级 JSON 与手册示例一致(status=ok、两租户 active、domains=3、engine_ok null 属懒建正常态)→ `/admin/` 200 → down 后 data/ 演练产物删净、tenants/ 清空、无容器/网络残留。演练顺带核实:**容器运行中跑 bootstrap 不热刷新内存租户注册表**(/health 需重启才反映,手册 §6.6 已补注记;首次部署按 §2 顺序无此问题)。**项目四阶段至此全部完成,此为最终回归基线。**
**部署工件清单**(全部入库,上线路径自足):
- `Dockerfile`:两阶段构建(node:20-alpine 编译 admin dist → python:3.13-slim 运行时含 tzdata;服务器零 Python/node 依赖)。
- `docker-compose.yml`:`name: all-kefu`(防 1Panel 目录撞名互删);`./data` + `./tenants` 双 bind mount——**platform.db 在 data/ 卷内**(`data/platform.db`,容器重建不丢,P4T1 审查修复);healthcheck 用容器内 python urllib(slim 无 curl);json-file 日志封顶 10m×3;mem_limit 768m;`--workers 1` 钉死。
- `deploy/openresty-all-kefu.conf.template`:反代模板(多域名同 upstream、WS 升级头单套、1Panel include 行坑注记)。
- `docs/deploy-production.md`:runbook 工件(.env 逐项 / 首次部署 / 反代三件事 / 存量迁移对账 / 切换 runbook 四阶段 / 备份恢复演练 / 归档 / 踩坑实录 13 条;手册为 runbook 工件非实录,首次部署前按全局部署 SOP 与用户确认)。
- `scripts/` 四件:`bootstrap_platform.py`(幂等引导 schema+超管+出厂租户)、`import_legacy.py`(老库只读导入+四元组对账)、`archive.py`(push_logs 归档)、`breach_rate.py`(破防率观测,`--tenant` 多租户适配,Task 5 随迁)。
**上线前检查清单(浓缩版;命令与细节见手册)**:生产 .env 设 ADMIN_PASSWORD 等强凭据 → `docker compose build` → `docker compose run --rm app python scripts/bootstrap_platform.py` → `docker compose up -d` → `/health`(status=ok、domains=3)→ `/admin/` 超管首登强制改密、后台补租户管理员 → 1Panel 反代片段备好(Host 头 / WS 单套 / 5m 三件事,先不指流量)→ 老库导入对账(手册 §4)→ 隧道+hosts 全功能验证(清单见手册 §5 阶段一)→ 逐域名切流(先一切半观察再切二)→ 观察一周(日检清单 + breach_rate)→ 老项目停用归档(stop 不 rm,回滚预案在手册 §5)。
## 文档索引
- 设计文档(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 3 实施计划:`docs/superpowers/plans/2026-10-13-phase3-admin-spa.md`
- Phase 4 实施计划:`docs/superpowers/plans/2026-10-14-phase4-deploy-migration.md`
- 部署手册(含切换 runbook + 踩坑实录):`docs/deploy-production.md`;反代模板 `deploy/openresty-all-kefu.conf.template`
## 项目完成总结(2026-10-14,四阶段收官)
ai-kefu / ai-kefu-jc 两套复制分叉代码 → 统一多租户平台,四个阶段全部完成:Phase 1 引擎合并与租户化(2026-10-12)、Phase 2 平台层(10-13)、Phase 3 管理后台 SPA(10-13)、Phase 4 部署与迁移工件(10-14)。历程数字:**测试 315 → 630+2skip(后端)+ 164(前端 vitest)**;终审环节抓出并当场收口的关键缝隙三例——**墙的执行侧缺口**(词表配置齐但执行路径未全量消费)、**站点停用语义**(停用租户的存量管理员 token 存活窗口与聊天面 503 边界)、**密钥掩码旁路**(GET 掩码但 PUT/回滚路径可绕过哨兵契约)——审查纪律的价值注脚。**后续演进方向**(均为增量,不动四层架构):提示词沙箱试聊(后台配置中心直接对话验证词表/提示词效果)、推送失败告警(lead_push 连续失败主动通知,当前只有推送健康表被动展示)、单租户独立部署路径已预留(engine 包不感知 platform,可整体拎走单租户跑)。