# 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 从“完成了一次重构”提升为“建立了可持续的基础设施约束”。