Files
ephron-ren-prd/prd-app-factory-config-unification-batch2-followup.md
2026-05-16 15:06:53 +08:00

405 lines
12 KiB
Markdown
Raw Permalink 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.
# PRD — App Factory / Config Unification Batch 2 Follow-up
## 背景
基于 `origin/main` 最新代码的完整复审Batch 2 已经完成了两项重要统一:
1. 五个服务入口 `main.py` 已统一切到 `shared.app_factory.create_service_app(...)`
2. 五个服务配置 `config.py` 已基本统一切到 `shared.config_base`
对应远端提交主线为:
- `14484ca` Add shared modules: app factory, config base, error handlers with tests
- `6d94b08` Refactor all apps to use shared config and app factory
- `378a4e5` Update home pages route and add test templates
这说明 Batch 2 的方向是正确的,且公共启动逻辑已经完成第一轮收敛。
但完整复审后,当前状态还不能定义为“重构完成”。现有实现仍存在三类结构性残留:
- 入口层和业务层仍大量依赖 `sys.path.insert(...)` 做导入兜底
- `shared.app_factory` 已把 `slowapi` 变成统一启动依赖,但测试/安装契约没有同步补齐
- home/admin 的认证守卫仍是服务内私有实现,没有进一步抽象为 shared guard helper后续容易再次漂移
因此需要补一轮 follow-up 修正,目标不是继续大规模抽象,而是把 Batch 2 从“表面统一”推进到“结构上站稳”。
---
## 本轮目标
1. 消除本轮重构后最显著的导入技术债,减少 `sys.path.insert(...)` 残留
2. 补齐 `slowapi` 的依赖契约与测试契约,确保 shared factory 在开发、测试、部署三条链路上一致
3. 为 home/admin 当前的路由守卫模式定义统一抽象边界,避免未来各服务重复复制 `_require_auth(...)`
4. 补齐回归验证标准,让“通过”不再只表示 shared 单测通过,而是关键服务入口也能被实际导入/启动
---
## 非目标
本轮不做以下内容:
- 不重写整站认证体系
- 不把所有服务的 auth/token 校验一次性全量抽到 shared
- 不强行改成 Python package 完整安装式布局(如一次性引入 pyproject + editable install 作为唯一导入方案)
- 不改动业务权限模型本身
- 不为所有历史测试做全面 fixture 重构
本轮只处理 Batch 2 复审中已经暴露、且会持续影响后续维护的最小必要问题。
---
## 发现的问题
### P0. shared factory 统一后,`slowapi` 变成强依赖,但测试/环境契约未同步
#### 现象
复审中执行:
```bash
python -m pytest tests/test_security_hardening.py tests/test_frontend_backend_reuse_contract.py -q
```
结果通过:
```text
13 passed
```
但执行 auth 侧 smoke tests
```bash
python -m pytest auth/tests/test_security_hardening.py auth/tests/test_login_redirect_flow.py -q
```
结果在 collection 阶段失败:
```text
ModuleNotFoundError: No module named 'slowapi'
```
#### 根因
`shared.app_factory.py` 直接导入:
- `from slowapi import _rate_limit_exceeded_handler`
- `from slowapi.errors import RateLimitExceeded`
这意味着一旦服务入口统一切到 shared factory`slowapi` 就不再是某些服务“可选依赖”,而是所有服务公共启动路径的硬依赖。
但是当前测试环境或依赖清单没有保证这一点,导致:
- shared 层合同测试可以过
- 真实服务入口测试却无法 import app
这会制造“重构似乎完成,但服务级 smoke 根本没跑”的假阳性。
#### 需要修改
1. 检查并统一项目依赖声明,确保 `slowapi` 出现在实际安装入口中
-`requirements*.txt` / 服务依赖清单 / 部署安装脚本中必须一致
2. 如果项目采用多入口安装方式,至少保证:
- 本地开发安装能拿到 `slowapi`
- CI / 测试安装能拿到 `slowapi`
- 生产部署安装能拿到 `slowapi`
3. 补一条最小 smoke 验证:
- 能成功 `import auth/src/main.py` 对应 app
- 能成功 `import home/src/main.py` 对应 app
#### 验收标准
以下命令在标准开发环境中必须通过:
```bash
python -m pytest tests/test_security_hardening.py tests/test_frontend_backend_reuse_contract.py -q
python -m pytest auth/tests/test_security_hardening.py auth/tests/test_login_redirect_flow.py -q
```
不允许再出现 `ModuleNotFoundError: slowapi`
---
### P1. `sys.path.insert(...)` 残留过多,说明 shared 化尚未形成稳定导入边界
#### 现象
本轮复审中,目标文件本身仍保留路径注入:
- `auth/src/config.py`
- `blog/src/config.py`
- `canvas/src/config.py`
- `prompt/src/config.py`
- `home/src/config.py`
- `auth/src/main.py`
- `blog/src/main.py`
- `canvas/src/main.py`
- `prompt/src/main.py`
- `home/src/main.py`
此外,搜索结果表明更多 route / service / test 文件中也仍在重复同一模式。
#### 问题
这说明当前 shared 重构虽然抽走了公共逻辑,但**没有真正建立清晰的模块导入边界**。直接后果有三个:
1. 各服务仍默认依赖“脚本式运行 + 手动改 sys.path”
2. 测试文件需要反复插 path 才能 import `src.*`
3. 后续任何目录结构变化,都会在多个服务同时引发脆弱导入问题
#### 本轮建议策略
不要一口气清理全仓所有 `sys.path.insert(...)`,而是先做“核心路径收缩”:
##### 第一阶段:只处理 Batch 2 涉及的入口与配置
目标:
- 让五个 `main.py`
- 五个 `config.py`
尽量不再各自复制路径注入模板。
推荐方案二选一:
**方案 A抽一个 shared import bootstrap helper**
- 新建如 `shared/imports.py` 或等价模块
- 提供统一函数,例如:
- `ensure_project_root(__file__, levels=3)`
- 所有入口/配置文件统一调用这一 helper而不是手写 `Path(...); if str(...) not in sys.path: sys.path.insert(...)`
**方案 B明确项目运行约定减少入口内 path hack**
- 如果现有运行方式允许,以仓库根目录为 working directory 运行 uvicorn / pytest
- 配合测试配置修正 import path
- 让入口文件不再自己改 `sys.path`
本轮更推荐 **方案 A**,原因:
- 改动小
- 风险低
- 不要求一次性重建整个包结构
- 能先消掉最显眼的重复模板
##### 第二阶段:只在 follow-up 里列出,不要求本轮全做
- route 层残留 `sys.path.insert(...)`
- service 层残留 `sys.path.insert(...)`
- test 层残留 `sys.path.insert(...)`
这部分可以作为下一轮技术债治理,不在本轮强制清仓。
#### 验收标准
至少以下 10 个文件不再各自手写重复的 `sys.path.insert(...)` 模板,或改为统一 helper
- `auth/src/config.py`
- `blog/src/config.py`
- `canvas/src/config.py`
- `prompt/src/config.py`
- `home/src/config.py`
- `auth/src/main.py`
- `blog/src/main.py`
- `canvas/src/main.py`
- `prompt/src/main.py`
- `home/src/main.py`
并补一条测试/检查,验证这些入口仍可被正常导入。
---
### P1. home/admin 路由守卫真实存在,但仍是私有实现,后续易漂移
#### 现象
`home/src/routes/admin.py` 目前已经具备明确的服务端守卫逻辑:
- `_require_auth(...)` 负责 cookie 用户校验
- 未登录跳转到 auth 登录页,并携带 redirect
- 已登录但无权限时拒绝或回首页
- service token 明确禁止访问 home admin
这说明 ephron.ren 现状是:
- 前台公开页无全局 guard
- 管理后台路由有服务端 guard
这一方向本身是合理的。
#### 问题
现在的 guard 逻辑仍被实现为 `home` 服务内部私有函数,风险在于:
1. 如果 blog/canvas/prompt 后续继续补 admin 规则,很容易各自再复制一份 `_require_auth(...)`
2. 登录跳转、redirect 处理、service token 禁入策略可能在不同服务间漂移
3. 审计时很难快速确认“所有 admin 路由是否遵循同一守卫契约”
#### 本轮要求
本轮不要求把所有服务的鉴权都重构完,但至少要把“守卫抽象边界”定下来。
推荐最小改法:
1.`shared/` 新增 admin guard helper命名可调整
- 负责未登录重定向的通用拼装
- 负责 service token 是否允许访问 admin 的统一策略
- 负责权限失败的统一响应约定
2. `home/src/routes/admin.py` 改为消费 shared helper而不是继续把完整策略内嵌在文件内部
3. 先以 home 为落点,其他服务后续增量迁移
#### 范围边界
本轮 shared guard helper 不要求覆盖:
- 所有业务权限判断细节
- 各服务特有的资源级校验
- 所有服务 token 行为差异
只需要先统一“admin 入口守卫”的公共骨架。
#### 验收标准
- `home/src/routes/admin.py` 中不再保留大段内联守卫模板代码
- 登录跳转、redirect、service token 禁止访问 admin 的公共逻辑进入 shared helper
- 现有 home admin 行为保持不变
---
### P2. 当前回归验证偏 shared 层,缺少“服务入口可导入”级别的合同测试
#### 现象
当前已经有的 shared/contract 测试能证明:
- 安全头策略存在
- reuse contract 存在
但它们不能证明:
- 五个服务入口都还能成功 import
- shared app factory 的依赖在真实入口层可用
- 各服务在统一 factory 下仍保持自己的 startup 责任
#### 需要补齐
新增一组轻量合同测试,目标不是测业务,而是测“入口仍然站得住”:
建议至少覆盖:
1. 五个服务入口可导入
- `auth/src/main.py`
- `blog/src/main.py`
- `canvas/src/main.py`
- `prompt/src/main.py`
- `home/src/main.py`
2. 导入后存在 `app`
3. `app.routes` 中至少包含 `/health`
4. docs 开关、static mount、router 注入保留各自行为(可做轻量断言)
#### 验收标准
新增测试在标准环境下通过,并能在 shared factory 依赖缺失、入口装配损坏、health route 丢失时给出明确失败信号。
---
## 建议实施顺序
### Task 1 — 补齐 `slowapi` 依赖契约
- 检查根依赖声明
- 检查测试环境安装入口
- 补齐缺失项
- 跑 auth smoke tests 验证
### Task 2 — 收缩 Batch 2 目标文件中的 path hack
- 先只处理五个 `main.py`
- 再处理五个 `config.py`
- 如果采用 shared helper保证 helper 本身足够轻量,不引入新的循环依赖
### Task 3 — 提取 home admin guard 公共骨架
- 保留当前行为不变
- 只抽公共流程,不重写权限模型
- 先让 `home` 成为第一个 shared guard 使用者
### Task 4 — 补轻量入口合同测试
- 测入口 import
-`/health`
- 测 shared factory 关键装配结果
---
## 建议涉及文件
### 必改
- `shared/app_factory.py`
- `shared/config_base.py`(如需辅助 import/helper可一并评估
- `auth/src/main.py`
- `blog/src/main.py`
- `canvas/src/main.py`
- `prompt/src/main.py`
- `home/src/main.py`
- `auth/src/config.py`
- `blog/src/config.py`
- `canvas/src/config.py`
- `prompt/src/config.py`
- `home/src/config.py`
- 依赖声明文件(按实际项目结构)
### 可能新增
- `shared/imports.py` 或等价 helper
- `shared/admin_guard.py` 或等价 helper
- 新的入口合同测试文件
### 本轮只观察,不强制修改
- `home/src/services/auth.py`
- 其他 route/service/test 中的历史 `sys.path.insert(...)`
---
## 验证命令
至少执行并记录以下命令:
```bash
cd /home/ubuntu/ephron.ren
git fetch origin
python -m pytest tests/test_security_hardening.py tests/test_frontend_backend_reuse_contract.py -q
python -m pytest auth/tests/test_security_hardening.py auth/tests/test_login_redirect_flow.py -q
```
如果新增了入口合同测试,再补:
```bash
python -m pytest tests/test_service_entrypoints.py -q
```
如有 home/admin 相关抽象变动,补对应最小验证:
```bash
python -m pytest home/tests -q
```
---
## 完成定义
满足以下条件才算本轮完成:
1. `slowapi` 不再导致服务入口测试 import 失败
2. Batch 2 涉及的 10 个入口/配置文件不再各自复制同一段 path 注入模板,或至少统一为同一个 helper
3. home/admin 的公共守卫骨架进入 shared 层,当前行为无回归
4. 新增轻量入口合同测试,能证明五个服务入口仍可正常装配
5. 所有新增/修改测试通过
---
## 备注
本 PRD 的定位不是重新定义 Batch 2而是对 Batch 2 做“完整复审后的结构补强”。
换句话说,前一轮已经解决“多个服务启动方式不统一”的问题;这一轮要解决的是:
- 统一之后是否真的可维护
- 统一之后测试是否真能覆盖到入口层
- 统一之后 guard/导入边界是否足够稳定
这轮做完,才能把 Batch 2 从“完成了一次重构”提升为“建立了可持续的基础设施约束”。