Files
Neo-ZQYY/docs/audit/changes/2026-03-25__tenant-users-soft-delete-upsert-fix.md
Neo 14a12342b5 chore(audit): 补追 96 份未入仓审计孤本 — 覆盖 2026-02-26 ~ 2026-04-08
这些审计记录原本堆积在 docs/audit/changes/changes/ 嵌套误产物目录下(由开发机迁移
79d3c2e 前后的不明批量操作产生)。由于同期 .gitignore 屏蔽了 docs/audit/ 全目录,
它们从未入过 git 任何分支 history。删除即永久丢失。

按 docs/specs/audit-gap-recovery/tasks.md 阶段 1 执行,将全部 96 份 D 类孤本
(主目录无同名、git history 亦无记录)复制到 docs/audit/changes/ 主目录入仓。

涵盖主题: P1-P18 全栈集成 / 多模块累积变更 / ETL bug 修复 / 业务日切 /
   召回与任务引擎改造 / 租户管理与审批 / 董事会财务 / 客户与助教详情 /
   DDL 基线合并 / Kiro 到 Claude Code 迁移

阶段 2(B 类内容漂移 1 份)和阶段 4(嵌套目录删除)独立推进。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 06:35:42 +08:00

58 lines
3.7 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.
# 变更审计记录:租户用户审核 — 软删除恢复 upsert 修复
| 字段 | 值 |
|------|-----|
| 日期 | 2026-03-25 01:54:19 |
| Prompt-ID | P20260325-000112 |
| Session-ID | 4627fd68 |
| Session 路径 | docs/audit/session_logs/2026-03/24/79_de2a1755_235713 |
## 操作摘要
修复租户管理后台审核通过流程中 `ON CONFLICT DO NOTHING` 被已软删除记录阻塞的 bug。当用户曾被移除`is_removed=true`)后重新申请并审核通过时,`user_site_roles` 写入被唯一约束上的已删除记录阻塞,导致 approved 用户无角色。改为 `DO UPDATE SET is_removed = false, removed_at = NULL` 恢复软删除记录。同步更新了前后端联调规范文档,将此模式记录为通用规则。
## 风险标签
`dir:backend``root-file``dir:admin-web``dir:etl``dir:miniprogram``dir:db``db-schema-change`
> 注:本次 session 实际只修改了 `tenant_users.py` 和联调文档。其余风险标签来自累积未审计的变更DEMO-miniprogram 迁移、admin-web 重构、miniprogram 更新、ETL 调整等),这些变更在更早的提交中完成,本次审计一并覆盖标记清除。
## 本次对话文件变更
### 新增文件
- `docs/audit/prompt_logs/prompt_log_20260325_000112.md`
- `docs/audit/session_logs/2026-03/24/78_ec0569d7_234840/main_01_a5bb4c10.md`
- `docs/audit/session_logs/2026-03/24/79_de2a1755_235713/main_01_fe71cc2d.md`
- `docs/audit/session_logs/2026-03/24/79_de2a1755_235713/sub_01_fe71cc2d.md`
### 删除文件
- `docs/audit/session_logs/2026-03/24/78_ec0569d7_234840/main_01_df41de21.md`(被替换为新版本)
## 合规检查
- ⚠️ 接口代码已变更但 OpenAPI spec 未同步(`api_changed: true`, `openapi_spec_stale: true`)— 需手动运行 `python scripts/ops/_export_openapi.py` 重新导出
- ⚠️ DDL 基线待合并(`has_ddl_baseline: false`)— 累积变更中含 db-schema-change
- ⚠️ 文档同步缺失:`apps/backend/app/routers/tenant_users.py` 应同步更新 `apps/backend/docs/API-REFERENCE.md``docs/contracts/openapi/backend-api.json`
- ✅ 迁移 SQL无新增迁移`new_migration_sql` 为空)
## 改动注解
### `apps/backend/app/routers/tenant_users.py`
- 变更类型:修改
- 原始原因:用户反馈审核通过时,后端用 `applied_role_text` 匹配 `auth.roles` 失败导致 `user_site_roles` 未写入。实际问题是 `ON CONFLICT DO NOTHING` 被已软删除的记录阻塞——唯一约束 `(user_id, site_id, role_id)` 上存在 `is_removed=true` 的旧记录INSERT 触发冲突但 DO NOTHING 跳过,导致用户审核通过后无角色。
- 思路分析:将 `approve_application``user_site_roles` 的 INSERT 语句从 `ON CONFLICT DO NOTHING` 改为 `ON CONFLICT (user_id, site_id, role_id) DO UPDATE SET is_removed = false, removed_at = NULL`。这是软删除 + 唯一约束的标准处理模式(已记录在前后端联调规范中)。不影响其他端点,不改变表结构。
- 修改结果:审核通过流程可正确恢复已软删除的角色记录。影响范围仅限 `POST /api/tenant/applications/{id}/approve` 端点。
### `docs/guides/FRONTEND-BACKEND-INTEGRATION.md`
- 变更类型:修改(简要)
- 同步更新联调规范,新增"软删除 + 唯一约束陷阱"通用规则条目。
### `.kiro/steering/frontend-backend-integration.md`
- 变更类型:修改(简要)
- steering 规则同步更新,引用联调规范中的新增条目。
## 回滚策略
代码回滚revert `approve_application` 中的 upsert 改动,恢复为 `ON CONFLICT DO NOTHING`
数据影响:若已有用户通过新逻辑恢复了角色,回滚后不影响已恢复的记录(`is_removed=false` 状态保持)。