- 0.1–0.9 全部标为完成,但不是"代码写完"就算完成: 33 条测试全绿、ruff/format 通过、真机 uvicorn 冒烟(/health /version 登录 建工作区 列表 /auth/me)通过 之后才敢打勾;没跑过的(downgrade、mypy、CI 在 Gitea 实跑、Web/Android 联调)保持未勾并写明原因。 - 记录 5 处"计划与实际不符"的偏差及理由: JWT 30min/14d、refresh 无状态不可撤销、权限用 ROLE_RANK 而非 PERMISSIONS 字典、 app_user 故意不加 RLS(登录发生在租户绑定之前)、minio 与 pgvector 推迟到 M3。 - 新增「环境现状与阻塞」一节:Docker 起不来用便携 PG、外网 TLS 被拦走 HTTP 镜像、 pip 必须 --no-cache-dir(否则写缓存卡死)、pg_ctl restricted token 报错、fnsvr-01 只有 8418/3306。 - 验收用例表加"现状"列,区分已验证 / 部分 / 未做。 顺带修: - alembic downgrade 之前直接失败:workspace 的 RLS 策略引用了 member, 不先 DROP POLICY 就 drop table,报 "cannot drop table member because other objects depend on it"。 现已按策略先删后删表,downgrade base → upgrade head 全通。 - npm 依赖必须 --ignore-scripts 才能装(沙箱禁止 lifecycle 脚本派生进程), 已写进 DEVELOPMENT.md;web 首次构建成功(tsc -b + vite,177KB/gzip 58KB)。 验收:pytest 33 passed / ruff 干净 / alembic 升降级往返通过 / web build 通过。
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
## 改了什么
|
||||
|
||||
<!-- 一句话说清这个 PR 让系统多了/少了什么能力 -->
|
||||
|
||||
## 为什么
|
||||
|
||||
<!-- 关联的 issue / 设计文档章节 / 线上现象 -->
|
||||
|
||||
## 自检(merge 前逐条确认,不填不 merge)
|
||||
|
||||
- [ ] `pytest` 全绿(`cd backend && ENV=test .venv/Scripts/python -m pytest -q`)
|
||||
- [ ] `ruff check . && ruff format --check .` 无告警
|
||||
- [ ] 改了表结构:`workspace_id NOT NULL` + RLS 策略 + 跨租户测试,三者齐全
|
||||
- [ ] 改了接口:`2.Design/API.md` 已同步
|
||||
- [ ] 改了部署方式:`2.Design/DEPLOY.md` 已同步
|
||||
- [ ] 迁移文件人工复核过(没有盲信 autogenerate)
|
||||
- [ ] 已 push(本地有未推送提交 = 未完成)
|
||||
|
||||
## 风险 / 回滚
|
||||
|
||||
<!-- 影响面、灰度方式、出问题怎么退回去。没有就写"无" -->
|
||||
@@ -88,6 +88,24 @@ ruff check . && ruff format --check .
|
||||
| `pip` 报 `UnicodeDecodeError: 'gbk' codec` | requirements.txt 里有非 ASCII | 该文件保持纯 ASCII |
|
||||
| `docker compose` 报 `unknown command` | 只装了 compose v1 | 用 `docker-compose` |
|
||||
| Docker 起不来 | Docker Desktop 未启动 / 需要管理员 | 先启动 Desktop,`docker info` 有输出再继续 |
|
||||
| 迁移报 `cannot insert multiple commands into a prepared statement` | asyncpg 走预备语句,一次只能一条 SQL | 多语句 DDL 拆成每语句一次 `op.execute()` |
|
||||
|
||||
### 本机特例(2026-10 实测,换机器可跳过)
|
||||
|
||||
这台开发机的 Docker Desktop 起不来,且外网 TLS 被拦,实际用的是:
|
||||
|
||||
- **便携 PostgreSQL 16.4** 放在 `.local/pg`(已在 `.gitignore`),`initdb` 后用**后台作业**前台跑:
|
||||
```powershell
|
||||
.\.local\pg\pgsql\bin\postgres.exe -D .\.local\pg\pgdata -p 5433 `
|
||||
-c listen_addresses=127.0.0.1 -c fsync=off -c synchronous_commit=off
|
||||
```
|
||||
不要用 `Start-Process`:父进程一退出,子进程就被带走;`pg_ctl` 在本机会报
|
||||
`could not create restricted token: error code 87`。
|
||||
- **装包只能走 HTTP 镜像**:PyPI `http://mirrors.aliyun.com/pypi/simple/`,
|
||||
npm `http://mirrors.huaweicloud.com/repository/npm/`。
|
||||
- **pip 必须加 `--no-cache-dir`**:不加时写缓存会卡死(无输出、CPU 空转,实测 25 分钟无进展)。
|
||||
- **安装类命令一律写日志文件再 tail**,不要用管道截断输出,否则看不到进度也不知道卡在哪。
|
||||
- `npm install` 要加 `--ignore-scripts`:沙箱不允许 npm 的 lifecycle 脚本派生进程(`EPERM spawn`)。
|
||||
|
||||
## 8. 提交前自检清单
|
||||
|
||||
|
||||
+106
-61
@@ -3,6 +3,9 @@
|
||||
> 来源:`2.Design/PRODUCT-PLAN.md` v1.0 第 12 节
|
||||
> 粒度:每项 0.3–1.5 人日,2 人并行 ≈ 2 周一个里程碑
|
||||
> 状态标记:`[ ]` 未开始 `[~]` 进行中 `[x]` 完成
|
||||
>
|
||||
> **最后一次核对:M0 骨架已于本机跑通(33 条测试全绿、ruff 通过、真机 HTTP 冒烟通过)。**
|
||||
> 标 `[x]` 的都是**真跑过**的,不是"代码写完了"。写代码不等于完成,代码没有义务在第一次运行时正确。
|
||||
|
||||
---
|
||||
|
||||
@@ -11,79 +14,98 @@
|
||||
**里程碑目标**:一个能注册、能建团队、能证明"两个租户互相看不见"的空壳,部署在服务器上,CI 全绿。
|
||||
|
||||
### 0.1 仓库与约定(0.5d)
|
||||
- [ ] 目录结构:`backend/ web/ android/ deploy/ 2.Design/`
|
||||
- [ ] `AGENTS.md`:提交纪律(改完必 push)、前端产物只能由 HEAD 构建、文档落点
|
||||
- [ ] `.gitignore` / `.editorconfig` / `README.md`
|
||||
- [ ] 分支策略:`main` 受保护,功能走 `feat/*`;提交信息 `feat(module): 中文描述`
|
||||
- **验收**:Gitea 上能看到仓库,PR 模板可用
|
||||
- [x] 目录结构:`backend/ web/ android/ deploy/ 2.Design/`
|
||||
- [x] `AGENTS.md`:提交纪律(改完必 push)、前端产物只能由 HEAD 构建、文档落点
|
||||
- [x] `.gitignore` / `.editorconfig` / `README.md`
|
||||
- [x] 分支策略:`main` 受保护,功能走 `feat/*`;提交信息 `feat(module): 中文描述`
|
||||
- [ ] PR 模板(`.gitea/PULL_REQUEST_TEMPLATE.md`)
|
||||
- **验收**:Gitea 上能看到仓库 ✅(`http://192.168.8.160:8418/Tatta/DadaoTask.git`),PR 模板待补
|
||||
|
||||
### 0.2 本地环境(1d)
|
||||
- [ ] `deploy/docker-compose.dev.yml`:postgres:16(含 pgvector)+ redis:7 + minio
|
||||
- [ ] `postgres` 初始化脚本:`CREATE EXTENSION vector, pg_trgm, pgcrypto`
|
||||
- [ ] `.env.example` 全量注释
|
||||
- **验收**:`docker compose up -d` 后 `psql` 能连、扩展已装
|
||||
- [x] `deploy/docker-compose.dev.yml`:`pgvector/pgvector:pg16` + `redis:7`(**未含 minio**,M0 不用对象存储)
|
||||
- [x] `postgres` 初始化脚本:建 `dadao_owner` / `dadao_app` 两个角色 + 库 owner + `REVOKE CREATE`
|
||||
- [x] `.env.example` 全量注释
|
||||
- [ ] `CREATE EXTENSION vector, pg_trgm, pgcrypto`(**推迟到 M3 知识库**:M0 的迁移不依赖任何扩展)
|
||||
- **偏差(重要)**:本机 Docker Desktop 起不来(`com.docker.service` 未运行、需管理员、WSL 报 `E_ACCESSDENIED`),
|
||||
M0 是用**便携版 PostgreSQL 16.4** 跑通的:解压到 `.local/pg`,`initdb` 后由后台作业前台运行 `postgres.exe -p 5433`。
|
||||
步骤见 §「环境现状与阻塞」。换机器/换人仍以 docker-compose 为准。
|
||||
|
||||
### 0.3 配置与日志(0.5d)
|
||||
- [ ] `pydantic-settings` 分层配置(dev/prod),启动即校验必填项
|
||||
- [ ] 结构化日志(JSON)+ `request_id` 贯穿 + 敏感字段脱敏中间件(`api_key`/`token`/`password`)
|
||||
- **验收**:一个请求在日志里能用 request_id 串起来,密钥不会出现在日志里
|
||||
- [x] `pydantic-settings` 分层配置(dev/test/prod),`validate_runtime()` 启动即校验
|
||||
- [x] 结构化日志(JSON)+ `request_id` 贯穿 + 敏感字段脱敏(`password`/`token`/`api_key`/`secret`)
|
||||
- **验收**:✅ request_id 出现在响应头与所有日志行;`ENV=prod` 且密钥仍是默认值 → 启动直接失败
|
||||
|
||||
### 0.4 数据库基座(1d)
|
||||
- [ ] `alembic init`,autogenerate 接入,`env.py` 读应用配置
|
||||
- [ ] 基础表迁移:`workspace` `user` `member` `invitation` `audit_log`
|
||||
- [ ] 通用列约定:`id uuid`、`created_at`、`updated_at`、`version`、`deleted_at`
|
||||
- **验收**:空库 `alembic upgrade head` 一次成功;`downgrade` 可回滚
|
||||
- [x] `alembic init`,`env.py` 读 `MIGRATION_DATABASE_URL`(owner 角色)
|
||||
- [x] 基础表迁移:`workspace` `app_user` `member` `invitation` `audit_log`
|
||||
- [x] 通用列约定:`id uuid`、`created_at`、`updated_at`、`version`;`deleted_at` 按表按需(`workspace` 有)
|
||||
- [x] 空库 `alembic upgrade head` 一次成功
|
||||
- [ ] `downgrade` 回滚实测(代码写了,没跑过)
|
||||
- **踩坑记录**:asyncpg 走预备语句,**一条 `execute` 只能一条 SQL**。多语句 DDL 必须拆开逐条执行,否则报
|
||||
`cannot insert multiple commands into a prepared statement`。
|
||||
|
||||
### 0.5 租户上下文 + RLS(1.5d)★ 核心
|
||||
- [ ] 请求依赖:解析 JWT → 校验成员身份 → `SET LOCAL app.ws_id`
|
||||
- [ ] 全部租户表启用 RLS,策略 `workspace_id = current_setting('app.ws_id')::uuid`
|
||||
- [ ] 仓储基类强制 `ws_id` 为必填构造参数(不给默认值)
|
||||
- [ ] 测试:租户 A 的 token 读租户 B 的资源 → 0 行 / 403
|
||||
- **验收**:`pytest -k tenant_isolation` 全绿;故意删掉一条 where 条件时测试必须变红(用变异测试验证一次)
|
||||
- [x] 请求依赖:解析 JWT → 校验成员身份 → `set_config('app.ws_id', …, true)`(事务级,自动随事务失效)
|
||||
- [x] 租户表启用 RLS:`workspace` `member` `invitation` `audit_log`(策略见 `backend/alembic/versions/0001_init.py`)
|
||||
- [x] 仓储基类 `Repo(session, ws_id)` 强制 `ws_id` 必填、无默认值
|
||||
- [x] 应用角色 `dadao_app` **非 owner、非 superuser、无 BYPASSRLS**,且无 DDL 权限
|
||||
- [x] 测试:跨租户读 → 报 `3003 NOT_A_MEMBER`(不泄露对方工作区是否存在);DB 层未绑上下文 → 0 行
|
||||
- [ ] 变异测试:手工删掉一条 where 条件,确认测试会变红(验证测试真的有牙)
|
||||
- **已知取舍**:`app_user` 表不加 RLS——登录时按手机号查用户发生在任何租户绑定之前,加了策略会直接查不到。
|
||||
代价是"手机号是否已注册"在应用层可探测;缓解手段:错误信息统一、加登录限流。**这条记在 D4 风险里,M4 前必须处理。**
|
||||
|
||||
### 0.6 认证(1.5d)
|
||||
- [ ] 手机号 + 验证码注册/登录(开发环境固定码 `123456`,短信通道留接口)
|
||||
- [ ] 邮箱 + 密码(argon2)作为备选
|
||||
- [ ] JWT:access 15min / refresh 30d 轮换,refresh 落库可撤销
|
||||
- [ ] 登录限流(IP + 账号维度)
|
||||
- **验收**:注册 → 登录 → 刷新 → 登出 全链路可用;错误码清晰(不是 500)
|
||||
- [x] 手机号 + 验证码注册/登录(dev 固定码 `123456` 并回显;`ENV=prod` 禁止 `SMS_PROVIDER=dev`)
|
||||
- [x] JWT:**access 30min / refresh 14d**(计划里写的 15min/30d,实际取宽了一点,理由是移动端弱网刷新成本高)
|
||||
- [x] 发码节流(`SMS_THROTTLE_SECONDS`,默认 60s,测试可置 0)
|
||||
- [ ] 邮箱 + 密码(argon2)(**未做**,M1 有空再说;手机号登录先行)
|
||||
- [ ] refresh 令牌落库可撤销(**未做**,当前是无状态 JWT;被移出团队的成员靠"每次请求查 member 表"兜底,refresh 本身不可撤销)
|
||||
- **验收**:✅ 注册 → 登录 → 刷新 → `/auth/me` 全链路通;错误码 2010/2011/2012 语义清晰
|
||||
|
||||
### 0.7 权限守卫(0.5d)
|
||||
- [ ] `PERMISSIONS` 字典:`(资源, 动作) → 最低角色`,FastAPI 依赖注入统一执行
|
||||
- [ ] 四个角色:owner / admin / member / guest
|
||||
- **验收**:guest 调管理接口 403;member 调 admin 接口 403
|
||||
- [x] 四个角色:owner / admin / member / guest,`ROLE_RANK = {owner:4, admin:3, member:2, guest:1}`
|
||||
- [x] 依赖注入统一执行:`require_role(min)`、`AdminDep`、`OwnerDep`
|
||||
- [x] 细则:只有 owner 能邀请 admin;guest 不能邀请;非成员一律 `3003`
|
||||
- **偏差**:没做 `(资源, 动作) → 最低角色` 的 `PERMISSIONS` 字典,M0 只有"工作区"一个资源域,
|
||||
字典是过度设计。M1 引入 item 时再上。
|
||||
- **验收**:✅ member 调邀请接口 → 3004;跨租户 → 3003
|
||||
|
||||
### 0.8 接口规范(0.5d)
|
||||
- [ ] 统一响应体 `{code, message, data, request_id}`
|
||||
- [ ] 错误码表(`2.Design/API.md` 里维护)
|
||||
- [ ] 写接口支持 `Idempotency-Key`(Redis 落 24h)
|
||||
- [ ] 分页规范:cursor 优先,`limit` 上限 100
|
||||
- [x] 统一响应体 `{code, message, data, request_id}`
|
||||
- [x] 错误码表(**暂存在 `backend/app/errors.py`**,M1 抽到 `2.Design/API.md`;已按 1xxx 通用 / 2xxx 认证 / 3xxx 租户 / 4xxx 并发 分段)
|
||||
- [x] 写接口支持 `Idempotency-Key`(存储 TTL 24h;只缓存 <400,5xx 与 4xx 都不缓存)
|
||||
- [x] 分页:cursor(`(created_at, id)` 复合游标)+ `limit ≤ 100`
|
||||
- **已知限制**:同 key 并发请求有竞态窗口,靠后续唯一键约束兜底;M0 不解决。
|
||||
|
||||
### 0.9 健康与版本(0.3d)
|
||||
- [ ] `/health`(DB/Redis 探活)、`/version`(返回 commit + 构建时间)
|
||||
- **验收**:部署后 `curl /version` 与 `git rev-parse --short HEAD` 一致
|
||||
- [x] `/health`(DB 探活)、`/version`(commit / build_time / db_role 自检)
|
||||
- **偏差**:`commit` 来自 `BUILD_COMMIT` 环境变量(部署时注入),本机默认 `unknown`;Redis 未探活(dev 不用 Redis)
|
||||
- **验收**:真机 `curl /health` → `{"status":"ok","db":true}`;`/version` 显示 `db_role` 为 `dadao_app` 且三项全 false ✅
|
||||
|
||||
### 0.10 CI(0.7d)
|
||||
- [ ] `ruff` + `mypy`(宽松起步)+ `pytest`
|
||||
- [ ] 迁移检查:`alembic upgrade head` 后再 `alembic check` 无差异
|
||||
- [ ] 镜像构建 + 推送到私有 registry(或直接在服务器构建)
|
||||
- **验收**:坏代码推上去 CI 变红,前端产物不入库
|
||||
- [x] `ruff` + `pytest`(`.github/workflows/ci.yml`,起 pgvector 服务 + 建角色 + 跑迁移 + 跑测试)
|
||||
- [ ] `mypy`(宽松起步)——未接
|
||||
- [ ] `alembic check` 迁移一致性
|
||||
- [ ] **CI 在 Gitea 上真正跑过一次**(当前只写了配置;Gitea Actions runner 是否启用未确认)
|
||||
- **验收**:坏代码推上去 CI 变红 ❌ 未验证
|
||||
|
||||
### 0.11 部署(1d)
|
||||
- [ ] `deploy/docker-compose.prod.yml` + nginx 反代 + TLS
|
||||
- [ ] 端口分配登记到 `2.Design/DEPLOY.md`(避开 PosSync 的 5888 / 5176)
|
||||
- [ ] 备份脚本接入现有每日备份(pg_dump + 保留 14 天)
|
||||
- **验收**:从零一台机器 30 分钟内起完;重启后数据在;备份可恢复到临时库
|
||||
- [ ] 备份脚本(pg_dump + 保留 14 天)
|
||||
- **验收**:从零一台机器 30 分钟内起完;重启后数据在;备份可恢复
|
||||
|
||||
### 0.12 三端契约(0.5d)
|
||||
- [ ] 导出 `openapi.json`,生成 TS 客户端与 Kotlin 客户端
|
||||
- [ ] 导出 `openapi.json`,生成 TS 与 Kotlin 客户端
|
||||
- [ ] `make sdk` 一键重生成
|
||||
- **验收**:改一个后端字段,两端编译报错(说明契约真的在生效)
|
||||
- **验收**:改一个后端字段,两端编译报错
|
||||
|
||||
### 0.13 客户端壳(1d)
|
||||
- [ ] Web:Vite 起项目、路由、登录页、空白任务页、请求封装(含 refresh 重试)
|
||||
- [ ] Android:Compose 工程、登录页、空列表页、Retrofit+OkHttp 封装
|
||||
- **验收**:两端都能真实登录并拿到空列表
|
||||
- [x] Web:Vite + React18 + TS + Tailwind,路由(`/login` `/` `/w/:wsId` `/join/:wsId/:code`)、请求封装(信封解包 / 幂等键 / 401 清理)
|
||||
- [x] Android:Compose 工程(AGP 8.7.3 / Kotlin 2.0.21 / minSdk 26)、登录页、工作区页、明文流量只对 `10.0.2.2` 放行
|
||||
- [ ] Web 真实构建验证(`npm run build`)——依赖正在通过镜像安装,待跑
|
||||
- [ ] Android 真实编译(本机无 Android SDK;`gradle-wrapper.jar` 未入库,需 Studio 补)
|
||||
- [ ] 两端联调"登录 → 建工作区 → 列表"
|
||||
|
||||
### 0.14 微信 iLink 通道 spike(0.5d)★ 决策前置
|
||||
> 不阻塞 M0 交付,但必须在写 `integration` 表之前做完,否则接法选错要返工。详见 `PRODUCT-PLAN.md` §10.3。
|
||||
@@ -91,11 +113,34 @@
|
||||
- [ ] 用第二个微信号私聊该 bot,确认多用户共用一号时消息是否互不串
|
||||
- [ ] 压一下:连续 20 条消息的延迟与是否触发风控;断网 5 分钟后 `syncBuf` 能否续上
|
||||
- [ ] 记录 `bot_token` 失效表现(多久失效、失效后是报错还是静默)
|
||||
- **产出**:`2.Design/SPIKE-WECHAT-ILINK.md`,明确回答"用户自带号"还是"平台统一号"
|
||||
- **产出**:`2.Design/SPIKE-WECHAT-ILINK.md`
|
||||
- **验收**:文档里三个问题都有实测结论,不是推测
|
||||
|
||||
---
|
||||
|
||||
## 环境现状与阻塞(2026-10-28 实测)
|
||||
|
||||
换机器前先读这一节,能省半天。
|
||||
|
||||
| 项 | 结论 | 绕过办法 |
|
||||
|---|---|---|
|
||||
| Docker Desktop | 起不来:服务未运行需管理员、WSL `E_ACCESSDENIED` | 用便携 PG 16.4(`.local/pg`,见下) |
|
||||
| 外网 TLS | 被拦:`schannel SEC_E_NO_CREDENTIALS`、`基础连接已经关闭` | 走 HTTP 镜像:PyPI 用 `http://mirrors.aliyun.com/pypi/simple/`,npm 用 `http://mirrors.huaweicloud.com/repository/npm/` |
|
||||
| pip 缓存 | 装包时写缓存会**卡死**(无输出、CPU 空转) | 必须加 `--no-cache-dir`;装包命令输出要重定向到文件再 tail,不要管道截断 |
|
||||
| `pg_ctl` | `could not create restricted token: error code 87` | 直接跑 `postgres.exe`,且必须放在**后台作业**里(父进程退出会连带杀死) |
|
||||
| fnsvr-01 (192.168.8.160) | 只开放 8418(Gitea) / 3306(MySQL);5432/5433/22 全关 | 该机没有可用的 PostgreSQL 对外端口;SSH 也不通 |
|
||||
| Android | 本机无 Android SDK、无 gradle-wrapper.jar | 交给 Android Studio 打开 `android/` 自动补全 |
|
||||
|
||||
便携 PG 起法(本机专用):
|
||||
|
||||
```powershell
|
||||
# 后台作业里跑(id 会变),别用 Start-Process —— 父进程退出会把它带走
|
||||
.\.local\pg\pgsql\bin\postgres.exe -D .\.local\pg\pgdata -p 5433 `
|
||||
-c listen_addresses=127.0.0.1 -c fsync=off -c synchronous_commit=off
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## M1 · 任务域(第 3–4 周,10 人日)
|
||||
|
||||
**里程碑目标**:自己开始每天用它记待办,连续 3 天不别扭。
|
||||
@@ -143,24 +188,24 @@
|
||||
|
||||
## 关键验收用例(M0–M1 必须通过)
|
||||
|
||||
| # | 用例 | 期望 |
|
||||
|---|---|---|
|
||||
| 1 | 租户 A 用户请求租户 B 的 item id | 404(不是 403,避免泄露存在性) |
|
||||
| 2 | 同一手机号注册两次 | 第二次提示已存在并引导登录 |
|
||||
| 3 | 被移出团队的成员拿旧 token 请求 | 401,且 refresh 失效 |
|
||||
| 4 | 并发两次提交同一任务的不同状态 | 一次成功,一次 409 并可重试 |
|
||||
| 5 | 网络中断后重新提交 | 幂等键保证不产生两条 |
|
||||
| 6 | 删除任务后全局搜索 | 搜不到,且回收站可恢复 |
|
||||
| 7 | 停掉 Redis | `/health` 变 503,业务接口给明确错误而非 500 |
|
||||
| 8 | 部署后 `/version` | commit 与仓库 HEAD 一致 |
|
||||
| # | 用例 | 期望 | 现状 |
|
||||
|---|---|---|---|
|
||||
| 1 | 租户 A 用户请求租户 B 的 workspace id | 3003 未加入(不泄露存在性) | ✅ 已覆盖 |
|
||||
| 2 | 同一手机号登录两次 | 同一个 user_id,不重复建号 | ✅ 已覆盖 |
|
||||
| 3 | 被移出团队的成员拿旧 token 请求 | 3003;refresh 也失效 | [~] access 已拒,refresh 不可撤销(见 0.6) |
|
||||
| 4 | 并发两次提交同一任务的不同状态 | 一次成功,一次 409 并可重试 | [ ] M1 |
|
||||
| 5 | 网络中断后重新提交 | 幂等键保证不产生两条 | ✅ 已覆盖 |
|
||||
| 6 | 删除任务后全局搜索 | 搜不到,且回收站可恢复 | [ ] M3 |
|
||||
| 7 | 停掉 Redis | `/health` 变 503,业务接口给明确错误而非 500 | [ ] dev 用内存实现,未做 |
|
||||
| 8 | 部署后 `/version` | commit 与仓库 HEAD 一致 | [~] 机制已有,等部署验证 |
|
||||
|
||||
---
|
||||
|
||||
## 下一步排期建议
|
||||
|
||||
```
|
||||
第1周 0.1–0.7(另一个人并行做 0.13 客户端壳)
|
||||
第2周 0.8–0.12(另一个人并行做 Web 登录与请求封装)
|
||||
第3周 1.1–1.3 + 1.4 开工
|
||||
第4周 1.4–1.7 + 自己开始 dogfooding
|
||||
已完成 0.1–0.9(骨架、认证、租户隔离、接口规范)+ 0.13 两端壳
|
||||
待补 PR 模板 / CI 在 Gitea 实跑 / 0.14 微信 spike / Web 构建验证
|
||||
第3周 0.11 部署 + 0.12 契约 + 1.1–1.3 任务域后端
|
||||
第4周 1.4 Web 四视图 + 自己开始 dogfooding
|
||||
```
|
||||
|
||||
@@ -208,6 +208,15 @@ def upgrade() -> None:
|
||||
|
||||
def downgrade() -> None:
|
||||
op.execute(f"REVOKE ALL ON workspace, app_user, member, invitation, audit_log FROM {APP_ROLE};")
|
||||
# 必须先删策略:workspace 的策略里引用了 member,直接 drop 表会报
|
||||
# "cannot drop table member because other objects depend on it"。
|
||||
for table, policy in (
|
||||
("workspace", "workspace_isolation"),
|
||||
("member", "member_isolation"),
|
||||
("invitation", "invitation_isolation"),
|
||||
("audit_log", "audit_log_isolation"),
|
||||
):
|
||||
op.execute(f"DROP POLICY IF EXISTS {policy} ON {table};")
|
||||
op.drop_table("audit_log")
|
||||
op.drop_table("invitation")
|
||||
op.drop_table("member")
|
||||
|
||||
Generated
+2869
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
||||
{"root":["./src/app.tsx","./src/main.tsx","./src/components/layout.tsx","./src/lib/client.ts","./src/lib/types.ts","./src/pages/home.tsx","./src/pages/join.tsx","./src/pages/login.tsx","./src/pages/placeholder.tsx","./src/pages/workspace.tsx"],"version":"5.9.3"}
|
||||
Reference in New Issue
Block a user