dda8264057
记录 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) <noreply@anthropic.com>
5.4 KiB
5.4 KiB
PR3 · workspaces + workspace_memberships 表 + 仓储
实现笔记。对应 docs/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)
验收
- 47 个新单测全过(16 workspace_context + 23 workspace_repo + 8 membership_repo)
- 全套
pytest tests/3134 passed + 25 skipped + 0 failed(PR2 末 3087 + 47 新;+2 PG-only skip) - partial unique on owner 在 SQLite ephemeral DB(test_cannot_add_second_owner)+ 远程 RDS PG 17.9(live 验证)双驱动通过
- CASCADE 在 SQLite 上验过(test_delete_cascades_to_memberships / test_cascade_delete_user_removes_memberships)
- 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_contextautouse 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)即本文件。