From dda826405745d599d8d875a2ff6a72e0377da34f Mon Sep 17 00:00:00 2001 From: 1445043649 <> Date: Tue, 12 May 2026 21:18:19 +0800 Subject: [PATCH] docs(impl): PR3 implementation note + acceptance checklist MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 记录 Stage 0 PR3 的实施落点、LOCK 决策、跟进项与 6 commit 列表。 验收:3134 passed + 25 skipped + 0 failed(PR2 末 3087 + 47 新 ws_context/ ws_repo/membership_repo 测试 + 2 PG-only skipped)。RDS live 验证通过: workspaces / workspace_memberships 表 + partial unique on owner 都建出来。 T3.6 (membership ORM) 提前到 T3.4-T3.5 (workspace repo) 之前,因为 WorkspaceRepository.get/list_by_user 会 JOIN memberships。 Stage 0 PR3 T3.10. PR3 完成。 Co-Authored-By: Claude Opus 4.7 (1M context) --- .../03-impl/pr3-workspaces.md | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 docs/multi-tenant-redesign/03-impl/pr3-workspaces.md diff --git a/docs/multi-tenant-redesign/03-impl/pr3-workspaces.md b/docs/multi-tenant-redesign/03-impl/pr3-workspaces.md new file mode 100644 index 00000000..87feaa24 --- /dev/null +++ b/docs/multi-tenant-redesign/03-impl/pr3-workspaces.md @@ -0,0 +1,87 @@ +# PR3 · workspaces + workspace_memberships 表 + 仓储 + +> 实现笔记。对应 [docs/superpowers/plans/2026-05-10-stage-0-multi-tenant-foundation.md](../../superpowers/plans/2026-05-10-stage-0-multi-tenant-foundation.md) PR3(T3.1-T3.10)。 +> +> 状态:**已落地**,9 commits 提交在 branch `feat/stage-0-pr3-workspaces`。 + +## 范围 + +新建 `workspaces` + `workspace_memberships` 两张表 + 仓储 + ContextVar + 47 个单测。这是 Stage 0 schema 基础设施的**核心**——后续 PR4 注册改造、PR5 ALTER 现有表、PR6 入口校验都依赖这层。 + +不在范围(后续 PR): +- 注册流程改造 / JWT 加 wid+role(PR4) +- alembic revision(PR4 起,因 ALTER 现有表才需要) +- workspace_id 列加到 threads_meta 等(PR5) +- 入口路由 `(wid, tid)` 校验(PR6) + +## 验收 + +- [x] 47 个新单测全过(16 workspace_context + 23 workspace_repo + 8 membership_repo) +- [x] 全套 `pytest tests/` 3134 passed + 25 skipped + 0 failed(PR2 末 3087 + 47 新;+2 PG-only skip) +- [x] partial unique on owner 在 SQLite ephemeral DB(test_cannot_add_second_owner)+ 远程 RDS PG 17.9(live 验证)双驱动通过 +- [x] CASCADE 在 SQLite 上验过(test_delete_cascades_to_memberships / test_cascade_delete_user_removes_memberships) +- [x] RDS 上 `workspaces` + `workspace_memberships` 表 + 索引(含 partial unique)live create_all 出来: + ``` + idx_one_owner_per_workspace + UNIQUE INDEX ... (workspace_id) WHERE ((role)::text = 'owner'::text) + idx_workspace_memberships_user (user_id, workspace_id) + workspace_memberships_pkey UNIQUE (workspace_id, user_id) + ``` +- [ ] CI workflow 上跑 @pytest.mark.postgres → 实跑 partial-unique PG twin — **待 PR push 后 GH Actions 验** + +## 关键决策(LOCK 项 — 与 plan §横切 + workspace-schema-design §5 对齐) + +| 项 | 选择 | 理由 | +|---|---|---| +| 表名 | `workspaces` + `workspace_memberships` | workspace-schema-design 锁定 | +| ID 类型 | `String(36)` UUID v4 字符串 | 与 `users.id` 类型对齐,跨 SQLite/PG 可移植 | +| Membership PK | 复合 `(workspace_id, user_id)` | 工业实践默认;surrogate id 没必要 | +| Role 类型 | `String(16)` + 应用层校验 | 不用 PG enum(ALTER TYPE 加值痛苦);Stage 0 写 owner,Stage 2 开 admin/member | +| Partial unique on owner | `Index(..., sqlite_where=text(...), postgresql_where=text(...))` 双 where 并存 | SQLite + PG 都支持;T3.8 PG twin 验语义一致 | +| Owner FK 删除策略 | `ON DELETE RESTRICT` | 删 user 时阻拦(必须先转让 owner);transfer 流程放 PR4 | +| Membership FK 删除策略 | `ON DELETE CASCADE` (workspace_id + user_id) | 删 workspace / 删 user 时自动清成员;逻辑上正确 | +| `invited_by` 删除策略 | `ON DELETE SET NULL` | 邀请人离开不影响成员;只清字段 | +| Slug 校验位置 | 应用层 `_validate_slug` + DB UNIQUE | DB 不知道 regex / 黑名单;应用层规则可演进 | +| Slug 黑名单 | 25 个保留字(admin/api/auth/...)frozenset 在 workspace/sql.py | workspace-schema-design §2.1 完整列表 | +| WorkspaceRepository.create | 不自动建 owner 成员 | 单一职责;注册流程在事务内同时写 workspace + membership | +| WorkspaceRepository.get | JOIN memberships 做 access 检查 | 默认安全;显式 `user_id=None` 跳过用于迁移 | +| WorkspaceRepository.get_by_slug | **不**做 access 检查 | path-based routing 用:先 slug→wid 再 route handler 查成员 | +| Owner transfer | 不在仓储层实现 | 需要两行事务原子 swap,应在 auth router 内带权限检查 | + +## 跟进项(不在 PR3 范围) + +- PR4 注册流程:`/auth/initialize` + `/auth/register` 用 `WorkspaceRepository.create + WorkspaceMembershipRepository.add(role='owner')` 在一个事务里建 +- PR4 alembic 0001:加 `users.default_workspace_id` 列(也 FK 到 workspaces) +- PR4 `_auto_workspace_context` autouse fixture:现在 tests/test_workspace_repo 测试自己 set/reset 而不依赖 autouse;PR4 引入后简化 +- backend/CLAUDE.md:等 PR4 完成时一并补 Database + Workspace 段(PR2 follow-up + 现在的 schema) + +## 涉及文件 + +| 类别 | 文件 | +|---|---| +| ContextVar | `runtime/workspace_context.py` | +| ORM | `persistence/workspace/{__init__.py, model.py, sql.py}` | +| ORM | `persistence/workspace_membership/{__init__.py, model.py, sql.py}` | +| 注册 | `persistence/models/__init__.py`(加 2 个 import) | +| 测试 | `tests/test_workspace_context.py`(16 test) | +| 测试 | `tests/test_workspace_repo.py`(23 test) | +| 测试 | `tests/test_workspace_membership_repo.py`(8 test) | +| 测试 | `tests/test_workspace_partial_unique_postgres.py`(2 PG-only) | + +## 提交记录 + +9 commits(含 T3.9 live verification 不发 commit): + +``` +f63089ae feat(runtime): add workspace_context module + tests T3.1 + T3.2 +8323bf68 feat(persistence): add WorkspaceRow ORM model T3.3 +d2d2d29c feat(persistence): add WorkspaceMembershipRow ORM model T3.6 +a40df035 feat(persistence): WorkspaceRepository + 23 unit tests T3.4 + T3.5 +36ffe271 feat(persistence): WorkspaceMembershipRepository + 8 unit tests T3.7 +3313047f test(workspace): partial unique on owner — Postgres twin test T3.8 +``` + +T3.6 比 T3.4/T3.5 早提(plan 中 T3.4-T3.5 写仓储要 JOIN memberships,所以 T3.6 必须先)。 + +T3.9 是 live verification,不产 commit;记在本文档的"验收"段。 +T3.10(impl doc)即本文件。