fix(migrations): enforce schema ownership safety

This commit is contained in:
caoxiaozhu
2026-07-14 09:23:34 +08:00
parent 1347366b95
commit 11275e4ba6
18 changed files with 755 additions and 57 deletions

View File

@@ -1,6 +1,6 @@
# AI 费用闭环与价值证明 概念文档
更新时间2026-07-13
更新时间2026-07-14
文档路径document/development/2026-07-13/feature/ai-expense-closed-loop-and-value-proof/CONCEPT.md
@@ -246,6 +246,7 @@
-`ReimbursementRequest` 进入只读兼容和迁移状态,停止新增第二套业务编排。
- 现有 `risk_flags_json` 保持读取兼容,新审批、付款、归档和关系事件写入结构化表。
- API 新字段优先追加,不在同一阶段破坏现有前端契约。
- 迁移桥接期集中维护 migration-owned 表集合;所有 legacy bootstrap 只能创建集合之外的表。标准启动在 `alembic upgrade head` 前只读核对 revision 与自有表集合发现漂移时拒绝启动不自动猜测、stamp 或修复。
- 所有表结构通过 Alembic 迁移,不继续在请求路径执行 DDL。
#### 版本与审计
@@ -405,7 +406,7 @@ customer_contribution_margin
```bash
docker exec -w /app -e SERVER_VENV_DIR=/tmp/x-financial-server-venv \
local-x-financial-linux \
x-financial-local-linux \
/tmp/x-financial-server-venv/bin/pytest -q server/tests/test_expense_case_service.py
```
@@ -500,3 +501,6 @@ docker exec -w /app -e SERVER_VENV_DIR=/tmp/x-financial-server-venv \
- 2026-07-13AI 申请身份边界):申请预览快速入口不再接受请求体中的 `user_id`、管理员标记、角色、员工编号或其他身份字段作为授权事实,全部强制绑定服务端会话;伪造管理员身份编辑他人退回申请会返回 400且原申请与费用事件不发生变化。
- 2026-07-13安全与事务验证容器内受影响后端定向回归 36 项、Expense Case 前端兼容测试 9 项和 Python `ruff --select F,I` 通过;覆盖本人、当前审批人、财务、管理员、无权限、跨租户、整 Case 安全摘要、草稿新建/更新、失败回滚、HTTP 创建重放、相同快照去重、不同快照留痕、Steward 重放及伪造身份越权。未修改数据库结构,未执行持久化开发数据库迁移。
- 2026-07-13联调边界当前持久化开发数据库尚无 `auth_sessions``expense_cases``expense_case_links``business_events` 表,浏览器登录无法获得有效认证凭证,因此本轮未声称完成真实页面端到端联调,也未擅自执行数据库迁移。计划、消费/票据、入账、对账和复盘事件仍待后续补齐。
- 2026-07-14迁移所有权加固新增统一 `schema_ownership.py`,七个运行时初始化入口只创建 legacy 表;标准启动在 Alembic upgrade 前执行只读漂移预检revision 与 migration-owned 表集合不一致时 fail-fast且不会自动 stamp 或修改数据库。
- 2026-07-14真实迁移验证在主应用容器连接的一次性 tmpfs PostgreSQL 17 中完成空库升级、重复升级、关键约束/索引、真实外键级联、降级到 base、legacy 哨兵保留、无版本自有表漂移拒绝和再次升级,`test_alembic_migrations.py` 4 项通过,最终 revision 为 `20260713_0002`;临时容器已自动清理,持久化开发数据库复查仍未迁移。
- 2026-07-14剩余边界当前两条 revision 只覆盖 Expense Case、Business Event 和 Auth Session完整 legacy schema baseline 及停止其余运行时 DDL 仍未完成;本轮受影响服务回归 46 项通过,既有员工目录历史部门归一化用例仍单独失败,未混入本次迁移安全范围。

View File

@@ -1,6 +1,6 @@
# AI 费用闭环与价值证明 开发 TODO
更新时间2026-07-13
更新时间2026-07-14
文档路径document/development/2026-07-13/feature/ai-expense-closed-loop-and-value-proof/TODO.md
@@ -58,9 +58,12 @@
证据:`expense_case.py``test_expense_case_endpoints.py`;用户态响应只保留安全流程摘要,关联资源 ID 和未知嵌套 payload 被递归过滤,本人、当前审批人、财务、管理员、无权限及跨租户边界测试通过。
- [ ] [CONCEPT: 权限与安全] 为 Qdrant collection/namespace、对象存储前缀和缓存键补齐租户隔离回归测试。
- [ ] [CONCEPT: 数据与契约] 建立 Alembic baseline 和正式迁移链,停止请求路径运行 DDL。
- [x] [CONCEPT: 兼容策略] 集中 migration-owned 表所有权并在标准启动迁移前执行只读漂移预检,禁止 legacy bootstrap 越权建表。
证据:`schema_ownership.py``migration_preflight.py``server_start.sh``test_migration_preflight.py``test_schema_ownership.py`;无版本自有表、缺表、多表、未知/多 revision 均 fail-fast不自动 stamp 或修改数据库。
- [x] [CONCEPT: 费用领域与编排] 新增 `ExpenseCaseService` 和费用事件查询接口,保持编排与具体职责分离。
证据:`server/src/app/services/expense_cases.py``server/src/app/api/v1/endpoints/expense_cases.py``GET /api/v1/expense-cases/by-claim/{claim_id}`;容器 OpenAPI 校验通过。
- [ ] [CONCEPT: 数据与契约] 新增 `expense_cases``expense_case_links``business_events` 表及迁移。
- [x] [CONCEPT: 数据与契约] 新增 `expense_cases``expense_case_links``business_events` 表及迁移。
证据:`20260713_0001_expense_case_business_events.py`;一次性 PostgreSQL 17 已验证表、唯一约束、复合索引、外键级联、降级和再次升级。
- [ ] [CONCEPT: 业务事件与 AI 决策] 新增 `ai_decisions``ai_decision_feedback``workflow_outcomes` 表及迁移。
- [ ] [CONCEPT: 数据与契约] 为申请、票据、草稿、提交、退回、审批、付款和归档接入统一 correlation ID。
- [ ] [CONCEPT: 业务事件与 AI 决策] 建立事务 Outbox申请、提交、退回、审批、支付和入账状态与事件同事务提交消费端按事件 ID 幂等处理。
@@ -73,7 +76,7 @@
- [x] [CONCEPT: 权限与安全] 将 AI 申请预览快速入口的用户、租户、角色与管理员身份强制绑定到服务端会话,拒绝请求体伪造身份编辑他人申请。
证据:`reimbursements.py``test_reimbursement_endpoints.py`;对抗用例修复前返回 200修复后返回 400且目标申请和费用事件保持不变。
- [x] [CONCEPT: 兼容策略] 建立迁移桥接:服务启动先执行 Alembic旧 metadata bootstrap 排除 migration-owned 表。
证据:`server_start.sh``agent_foundation.py``20260713_0001_expense_case_business_events.py`;容器 `sh -n`、离线 upgrade/downgrade SQL 和 legacy bootstrap 排除测试通过。
证据:`server_start.sh``schema_ownership.py``migration_preflight.py``20260713_0001_expense_case_business_events.py``20260713_0002_auth_sessions.py`;容器 Shell/静态检查及一次性 PostgreSQL 完整 upgrade/downgrade/re-upgrade 通过。
- [ ] [CONCEPT: 兼容策略] 正式切换前以 shadow 事件校验现有 `ExpenseClaim` 映射;切换后禁止关键事件可丢弃写入。
- [ ] [CONCEPT: 兼容策略] 制定旧 `ReimbursementRequest` 只读兼容、迁移和停止新增编排的计划。
- [ ] [CONCEPT: 兼容策略] 把新增审批、付款、归档和关系事件移出 `risk_flags_json`,保留旧数据读取兼容。
@@ -155,6 +158,8 @@
- [x] [CONCEPT: 测试方案] 为 Expense Case GET 接口补充 owner、审批人、财务、管理员、无权限用户和整 Case 关联事件可见范围的 HTTP 权限测试。
证据:`test_expense_case_endpoints.py` 容器内 8 项通过,覆盖跨租户、无 Case、申请与报销关联摘要以及内部字段递归过滤。
- [ ] [CONCEPT: 测试方案] 为 Alembic baseline、升级、旧数据迁移和回滚边界补充 Postgres 集成测试。
- [x] [CONCEPT: 测试方案] 为当前 migration-owned schema 切片补充一次性 PostgreSQL 集成测试和危险 URL 防误连门禁。
证据:`test_alembic_migrations.py` 默认无显式 URL 时跳过,主机和库名必须带 disposable 标记tmpfs PostgreSQL 17 中 4 项通过,覆盖空库升级、重复升级、关键约束/索引、外键级联、base 降级、legacy 哨兵保留、漂移拒绝和再次升级;持久化开发库未被修改。完整 legacy baseline 仍保留在上一条未完成项中。
- [ ] [CONCEPT: 测试方案] 为连接器幂等、重试、回执、失败恢复、重复付款和对账补充测试。
- [ ] [CONCEPT: 测试方案] 跑通申请 → 票据 → 报销 → 预审 → 审批 → 付款 → 入账 → 归档端到端。
- [ ] [CONCEPT: 测试方案] 跑通 AI 建议 → 用户修改 → 工作流结果 → 记忆激活 → 下次建议变化闭环。

View File

@@ -0,0 +1,12 @@
# 迁移自有表被运行时 bootstrap 越权创建
日期2026-07-14
文档路径document/development/2026-07-14/dev-logs/bugs/migration-owned-table-bootstrap-drift.md
## 修复记录
- 09:21记录 bug 修复:迁移自有表被运行时 bootstrap 越权创建。bug-log:1347366b
- Git 提交检查fetch 成功upstream `origin/main`upstream 新提交:未发现;本地 ahead 提交1347366b (HEAD -> main) feat(expenses): secure timeline and draft events22669a90 feat(expenses): show unified expense event timelinea616b30c fix(expenses): unify AI application submission transaction653eda05 feat(auth): add opaque bearer sessions661990b2 feat(expenses): add transactional expense case events。
- 修改:新增 `schema_ownership.py` 统一四张 migration-owned 表的所有权边界,把 Agent Foundation、员工、预算、设置、系统看板、数字员工看板和演示数据初始化中的全量 `create_all` 改为只创建 legacy 表;新增 `migration_preflight.py`,在启动迁移前只读核对 Alembic revision 与自有表集合,遇到无版本表、缺表、多表、未知版本或多版本时直接拒绝,不自动 stamp 或修复。
- 操作:调整 `server_start.sh` 的启动顺序和 `alembic.ini` 的脚本绝对定位方式在无端口、无持久卷、tmpfs 数据目录的一次性 PostgreSQL 17 容器中执行空库升级、重复升级、降级到 base、漂移拦截和再次升级结束后自动删除临时容器没有执行持久化开发数据库迁移。
- 验证:迁移与预检定向测试 `19 passed, 1 skipped`;一次性 PostgreSQL 真实迁移测试 `4 passed`,最终 revision 为 `20260713_0002`关键唯一约束、复合索引和外键级联均通过legacy 哨兵跨降级/再升级保留;受影响服务回归 `46 passed, 1 failed`,唯一失败为既有员工目录历史部门归一化用例,单独运行同样失败,与本次建表 helper 无关;新增文件 Ruff 全规则、旧服务 F/I/B/UP、Shell 语法、Alembic heads 和 `git diff --check` 均通过。
- 影响:运行时 bootstrap 不再可能越权重建 Alembic 管理的表,启动会在可能破坏数据前暴露版本/表漂移;完整 legacy schema baseline 仍未建立,后续迁移旧表时必须继续小步验证。