From f5d4c74f533fe1453c6198bfc2db37b979aab6af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=85=E5=BF=B5?= Date: Tue, 13 Oct 2026 07:41:53 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20Phase=202=20=E8=AE=A1=E5=88=92=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E4=BF=AE=E5=A4=8D=EF=BC=88=E6=8C=82=E8=BD=BD=E6=8C=AA?= =?UTF-8?q?Task2/Site=E8=A1=A5brand=5Foverlay=E5=88=97/reload=20upsert?= =?UTF-8?q?=E8=AF=AD=E4=B9=89/=E9=87=8D=E7=BD=AE=E5=87=BA=E5=8E=82+?= =?UTF-8?q?=E5=B9=B3=E5=8F=B0=E8=AE=BE=E7=BD=AECRUD=E5=BD=92=E4=BD=8D?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Code --- .../plans/2026-10-13-phase2-platform-layer.md | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/docs/superpowers/plans/2026-10-13-phase2-platform-layer.md b/docs/superpowers/plans/2026-10-13-phase2-platform-layer.md index 297a742..570ff05 100644 --- a/docs/superpowers/plans/2026-10-13-phase2-platform-layer.md +++ b/docs/superpowers/plans/2026-10-13-phase2-platform-layer.md @@ -44,7 +44,8 @@ async def test_platform_db_wal(tmp_path): ```python # 模型(PlatformBase 独立 MetaData,与租户库 Base 无关) class Tenant(id, slug UNIQUE, name, status, created_at) -class Site(id, tenant_id FK, slug, name, status) # UNIQUE(tenant_id, slug) +class Site(id, tenant_id FK, slug, name, status, brand_overlay JSON NULL) # UNIQUE(tenant_id, slug) + # brand_overlay = 站点门面覆盖层(spec §5.2;DbTenantSource 映射进站点 brand 合并链) class SiteDomain(id, domain UNIQUE, site_id FK) class TenantConfigVersion(id, tenant_id FK, version, config_json TEXT, updated_by, comment, created_at) # 当前生效 = MAX(version);UNIQUE(tenant_id, version) @@ -66,6 +67,7 @@ async def bootstrap_superuser(engine, username, password): ... # 空表才建 **Files:** - Create: `app/platform/auth.py`、`app/platform/routes/__init__.py`、`app/platform/routes/auth_routes.py`、`tests/test_auth.py` +- Modify: `app/main.py`(挂 auth_routes——**本任务就挂**,jiancai Host 下 /api/platform/auth/login 可经中间件直达路由,TDD 循环才能闭环;Task 3 只管豁免与守护) - [ ] **Step 1: 失败测试**(核心 8 项) @@ -84,14 +86,16 @@ def test_logout_invalidates_token(app_client) - `auth.py`:`hash_password/verify_password`(Task 1 已有则上移共用);`TokenStore`(内存 dict:token(secrets.token_urlsafe 32B)→{user_id, role, tenant_slug, expires_at};TTL 默认 8h;prune 过期;logout 删);`LoginLimiter`((username, ip) 键滑动窗 5 次/15 分钟,照抄 rate_limit 风格) - `auth_routes.py`:`POST /api/platform/auth/login`(限流→验密→发 token);`POST /api/platform/auth/logout`;`POST /api/platform/auth/change-password`(验旧密,must_change 清零) - 依赖项:`get_current_user(request) -> UserContext`(Authorization: Bearer 解析→TokenStore 查验→role/tenant);`require_super`/`require_tenant(slug)` 两个依赖工厂(403 语义) +- **must_change_password 强制语义**:UserContext 带该标志;除 `/auth/change-password` 外的所有 `/api/platform/*` 受保护端点在标志为真时返回 409(超管引导密码不得只当摆设) - 密码永不明文进日志;token 走 Authorization 头(无 Cookie,免 CSRF——spec §7.2) +- 偏差注记:spec §7.2 写 bcrypt/argon2,本实现用 **hashlib.scrypt**(stdlib 零新依赖,抗 GPU 性能同级)——记录为有意取舍 - [ ] **Step 3: 全量回归 + 提交** `feat: 平台认证(scrypt/内存token 8h/登录限流/改密/角色依赖)` ### Task 3: 中间件豁免与 admin 挂载骨架 **Files:** -- Modify: `app/platform/host_middleware.py`(豁免 `/api/platform/`)、`app/main.py`(挂 auth_routes) +- Modify: `app/platform/host_middleware.py`(豁免 `/api/platform/`) - Test: `tests/test_admin_mounting.py` - [ ] **Step 1: 失败测试** @@ -129,7 +133,8 @@ async def test_registry_reload_via_db_source(...) # DB 改版 ``` - [ ] **Step 2: 实现** -- `tenant_source.py`:`LoadedTenant` dataclass(slug/name/status/sites raw/snapshot dict);`TenantSource` Protocol(`load_all() / reload(slug)`);`FileTenantSource(seed_dir)`(把 registry 现有 `_load_tenant` 逻辑搬入,**行为逐字等价**);`DbTenantSource(engine)`(JOIN tenants/sites/domains + 当前版本 config_json;prompts 嵌在 config_json 的 `prompts` 键) +- `tenant_source.py`:`LoadedTenant` dataclass(slug/name/status/sites raw/snapshot dict);`TenantSource` Protocol(`load_all() / reload(slug)`);`FileTenantSource(seed_dir)`(把 registry 现有 `_load_tenant` 逻辑搬入,**行为逐字等价**);`DbTenantSource(engine)`(JOIN tenants/sites/domains——**sites.brand_overlay 进站点覆盖层**——+ 当前版本 config_json;prompts 嵌在 config_json 的 `prompts` 键) +- **registry_reload 补 upsert 语义**:现行实现在 slug 未加载时抛 UnknownTenantError——改为"source 已知但未加载 → 载入并插入"(Task 5 创建租户后 reload 依赖此语义);补测试:新租户(未 resolve 过)创建后直接 reload 不炸且可 resolve - registry:`__init__(source)`;`load_from_dir(seed_dir, data_dir)` = `TenantRegistry(FileTenantSource(seed_dir), data_dir)` 便捷函数(**既有 conftest/test 调用零改动**);reload 走 source - `PromptProvider.from_config(md: dict)`(四件套字符串构造;`from_dir` 改为读文件后委托它);DbTenantSource 路径的 TenantRuntime.prompts 用 from_config(seed_dir 字段对 DB 源为 None——avatar 解析链 Task 7 更新) @@ -187,8 +192,9 @@ def test_tenant_admin_scoped_config(...) # jiancai 管理员能改自己、 ``` - [ ] **Step 2: 实现校验管线 `config_validation.py`** -- `validate_group(group, payload, context) -> list[str]`(错误清单):类型检查(engine 组 int/bool;brand 字符串列表)、wall → `compile_wall`(复用全部既有校验)、prompts 大小上限(company_facts ≤50KB、其余 ≤100KB)、email notify_emails 格式、push/ai 数值范围、`context.other_prefixes` 查重 -- `config_routes.py`:`GET /api/platform/tenants/{slug}/config`(当前 merged+version);`PUT .../config/{group}`(校验→整份快照写 MAX(version)+1【updated_by=当前用户, comment 可选】→ registry_reload);`GET .../config/versions`(列表:版本/时间/作者/备注);`POST .../config/rollback` `{version}`(复制为新版本);`POST .../wall/test`(试金石:compile 请求体里的 wall【或当前+覆盖】→ 逐句调 boundary_filter 纯函数给判定) +- `validate_group(group, payload, context) -> list[str]`(错误清单):类型检查(engine 组 int/bool;brand 字符串/字符串列表;contact 开关与 template_languages、misc 映射也纳入类型校验)、wall → `compile_wall`(复用全部既有校验)、prompts 大小上限(company_facts ≤50KB、其余 ≤100KB)、email notify_emails 格式、push/ai 数值范围、`context.other_prefixes` 查重 +- `config_routes.py`:`GET /api/platform/tenants/{slug}/config`(当前 merged+version);`PUT .../config/{group}`(校验→整份快照写 MAX(version)+1【updated_by=当前用户, comment 可选】→ registry_reload);`POST .../config/{group}/reset`(**重置出厂**:拷出厂档案该组为新版本——auto/jiancai 档案取 seed_profiles,blank/无档案租户取 DEFAULT_CONFIG 组;spec §5.4);`GET .../config/versions`(列表:版本/时间/作者/备注);`POST .../config/rollback` `{version}`(复制为新版本);`POST .../wall/test`(试金石:compile 请求体里的 wall【或当前+覆盖】→ 逐句调 boundary_filter 纯函数给判定) +- **平台设置 CRUD**(spec §7.3 平台配置页后端):`GET/PUT /api/platform/settings`(仅超管;PlatformSetting 表读写——集团默认 AI 凭据/默认 SMTP;**resolver 回退链接入 platform_settings 归 Phase 3**——本阶段先落存储与接口) - 鉴权:超管任意租户;租户管理员限自己(require_tenant) - [ ] **Step 3: 提交** `feat: 配置API(分组校验管线/整份版本/回滚/试金石端点/前缀唯一)` @@ -226,7 +232,7 @@ def test_push_logs_payload_lazy(...) # 列表不含 request_payload def test_tenant_scope_isolation(...) # jiancai 管理员 GET auto 的会话 → 403;自己的 → 200 ``` -- [ ] **Step 2: 实现**(查询逻辑从 jc 原 admin.py 的查询语义移植,跑在 `runtime.session_factory()` 上;分页 page/page_size 上限;筛选:intent/site/日期区间;IP 归国沿用 ip_service) +- [ ] **Step 2: 实现**(查询逻辑从 jc 原 admin.py 移植查询语义——源文件在 `D:\refactor\ai-kefu-jc\app\api\routes\admin.py`,实施时可读;跑在 `runtime.session_factory()` 上;分页 page/page_size 上限;筛选:intent/site/日期区间;IP 归国沿用 ip_service) - [ ] **Step 3: 提交** `feat: 数据视图API(会话/详情/推送日志/租户scope强制)` ### Task 9: 统计 API @@ -272,14 +278,14 @@ def test_cross_tenant_super_view(...) # 超管无 tenant 参数=全部租户合 - [ ] **Step 1: 全量回归(根+tests/ 双跑)** - [ ] **Step 2: 零同步 IO 审计复查**(grep requests/urllib/time.sleep/open 于 app/——新增代码延续纪律;scrypt 在登录路径是 CPU 非阻塞 ~100ms 可接受,注释声明) - [ ] **Step 3: 安全自查**:token 不进日志、密码不进日志、platform.db 进 gitignore 复核、登录限流在豁免路径上仍生效、上传目录不可执行 -- [ ] **Step 4: CLAUDE.md/README 更新**(Phase 2 完成状态、Phase 3 清理单刷新、bootstrap 用法、admin API 速览) +- [ ] **Step 4: CLAUDE.md/README 更新**(Phase 2 完成状态、Phase 3 清理单刷新、bootstrap 用法、admin API 速览、基线数字刷新 463→当前) - [ ] **Step 5: 提交** `chore: Phase 2 收尾——平台层完成` --- ## 显式不做(Phase 2 范围外) -art-design-pro SPA 全部页面(Phase 3);嵌入代码复制块的前端展示(Phase 3,API 已含 brand);Dockerfile/部署/存量数据导入(Phase 4);/health 分级健康(Phase 4 批次,清理单在案);推送失败告警(二期)。 +art-design-pro SPA 全部页面(Phase 3);嵌入代码复制块的前端展示(Phase 3,API 已含 brand);**resolver 回退链接入 platform_settings**(Phase 3 平台配置页联动;本阶段 PlatformSetting 表与 CRUD 已落);Dockerfile/部署/存量数据导入(Phase 4);/health 分级健康(Phase 4 批次,清理单在案);推送失败告警(二期)。 ## 执行注意(继承 Phase 1 血泪)