# 文件库 · 开工计划（v1.0）

> 配套文档：《文件库-接口契约.md》(v0.1，仓库根目录)。本计划已吸收规划顾问评审意见，包含开工前必须先冻结的**架构收口决策 D11–D19**（回写契约 v0.2 时并入）。
> 工期按 1 人全栈估算；前端从 Phase 2 后可并行。

## 开工判定

**可以开工。** 四个外部依赖（版本包 / Group / 审计 / 平台身份）全部有契约可依、可 mock 并行开发；无阻塞项。唯一前置：本文件的 D11–D19 决策在写第一行 schema 前过一遍（半天，自查即可，有异议再升级）。

## 架构收口决策（新增，先冻结再动工）

| 编号 | 决策 | 理由 |
|---|---|---|
| D11 | `creator` 是 Node 的**不可变列**，创建时同时落一条 Manage grant；独立权限开关关闭时，**creator 的 Manage 仍生效**，其余项目级 grant 冻结不参与计算 | 否则 creator 可能被自己关掉的开关锁死 |
| D12 | 移动节点授权 = **本节点 Manage + 目标父 Edit+**；移动在事务 + Postgres 咨询锁内完成，防并发成环 | 预检环检测在并发下不安全 |
| D13 | Group resolve 失败返回 **503**（不伪装 404/403）；每次 HTTP 请求 fresh resolve，不跨请求缓存；单请求内多次校验合并为一次调用（此点与 Group 团队确认，OPEN-9） | 依赖故障 ≠ 无权限；契约 G4 要求实时 |
| D14 | 命名规则：NFC 归一化、trim、≤128 字符、禁 `/` 与控制字符；**活跃兄弟节点大小写不敏感唯一**；根节点全局唯一 | 缺命名规则必有脏数据 |
| D15 | 删除 = 只给被删节点打标，后代**不递归打标**，靠"任一祖先已删"过滤；所有查询链路强制此过滤（含递归 SQL，不只靠 Prisma middleware） | 递归打标的写放大与恢复复杂度不可控 |
| D16 | `baseVersion` 为**文件级版本**（GET file 返回的 version）；无关文件的提交不产生冲突 | 与 C1 `head(dir, filePath)` 语义一致 |
| D17 | breadcrumb 只暴露用户有 View 的祖先名称；无权限祖先显示占位符 `…`，不暴露名字 | 404 不泄露语义（D8）延伸到面包屑 |
| D18 | v1 **单副本部署**（git 仓库在本地磁盘）；多副本需共享存储或分片，后置 | 不解决不存在的扩展问题 |
| D19 | 网站管理员**不隐式读内容**；force_adjust 凭 node id 操作（id 从审计或用户上报获得） | C4 已定的最小权限原则 |

## 阶段计划

### Phase 0 · 骨架与身份先行（1–2 天）

- [ ] 过 D11–D19，回写契约 v0.2
- [ ] repo 骨架 `server/` + `web/`，Postgres docker-compose，CI（lint / typecheck / vitest / 集成测试用真实 Postgres service container）
- [ ] **JWT 中间件 + 本地 JWKS 测试服务 + 签名 token fixtures**（有效/过期/错 issuer/错 audience/错算法/未知 kid/轮换）——身份先行，不做"宽松 mock 身份"
- [ ] 4 个 port 接口定义 + mock：`VersionStore`、`GroupResolver`、`AuditSink`（直写 outbox 表的实现就是真的，mock 的是对端服务）、`ExportAdapter`
- [ ] fastify-swagger 接入，OpenAPI 骨架——**OpenAPI 随每个端点同步产出，不留到最后**
- **验收**：骨架可 `pnpm dev` 起服务；本地 JWKS 六类 token fixture（有效/过期/错 issuer/错 audience/错算法/未知 kid）中间件行为全对；CI 流水线绿

### Phase 1 · 数据模型与权限引擎（2–3 天）⚑ 心脏

- [ ] Prisma schema：`Node`（parentId **权威** + id 编码的 materialized path 派生列，name 不入 path）、`Grant`、`ProjectIndependentSettings`、`OutboxEvent`（含 attempts/nextAttemptAt/lease/lastError，relay 字段一次到位）、`ExportJob`
- [ ] 数据库约束：kind 枚举、项目无子节点、活跃兄弟名唯一（部分唯一索引，root 的 NULL parent 特殊处理）、活跃 grant 按 (nodeId, principalType, principalId) 唯一、独立设置仅项目
- [ ] **纯权限 reducer**：输入已解析的 grants + 祖先链 + 用户组集合，输出 role|null；不碰 DB/网络。fast-check 属性测试：max 单调、加 grant 只升不降、祖先继承、toggle 行为、顺序无关、软删过滤
- [ ] 数据获取层 + `effective(user, node)` 组装
- [ ] 树操作服务：create（creator 自动 Manage、root 仅管理员且必带 ≥1 Manage）、rename、move（D12 事务+咨询锁）、soft delete（D15）、breadcrumb（D17）
- **验收**：并发对移（A→B 与 B→A）、移动+并发建子、property tests、删除祖先过滤，真实 Postgres 全绿

### Phase 2 · 树与授权 API（1–2 天）

- [ ] nodes 端点组 + grants 端点组 + independent-permission 开关 + effective-permission 自查
- [ ] 授权矩阵校验（契约 8.1：creator-only 授 Manage、Manage 不可动 creator、force_adjust 独立通道）
- [ ] 全部写操作落 outbox 事件（C3 词汇表）
- **验收**：契约 8.1 矩阵逐格 API 测试；D8 的 404/403/503 三分语义测试

### Phase 3 · 仓库生命周期与文件 API（2 天）

- [ ] 项目 provisioning 状态机 `PROVISIONING → READY | FAILED`：先建 DB 行（PROVISIONING）→ 调 `VersionStore.init`（幂等）→ 置 READY；失败可重试；孤儿仓库对账 job
- [ ] 每项目写锁（Postgres 咨询锁，跨进程安全）——不假设版本包能跨进程串行
- [ ] 路径安全：canonical 后必须落在项目根内、拒绝 `.git`、禁 symlink 逃逸、路径长度上限
- [ ] 上传限额（单文件 10MB / 单项目配额，数值 OPEN-5）+ 流式接收
- [ ] files 端点组全量（list/read/upload/commits→409/diff/history/delete）+ 双客户端冲突 e2e
- **验收**：DB/git 故障注入（init 失败、提交后崩溃）→ 对账能收敛；路径穿越语料库测试全拒

### Phase 4 · 外部集成（1–2 天）

- [ ] `GroupResolver` HTTP client：超时/5xx/429/畸形 JSON/万级 group 的故障注入测试；失败 → 503（D13）
- [ ] Audit relay worker：租约领取、指数退避、eventId 幂等、重启恢复、积压告警
- [ ] 导出 job：状态机 + 轮询 + 下载权限校验；`ExportAdapter` 桩（参数 OPEN-6 未定前**不计入完成标准**）
- **验收**：relay 重启不丢不重（对端幂等）；group 故障时写操作 503、读操作按 D13

### Phase 5 · 前端（3–5 天，Phase 2 后并行启动）

- [ ] 登录（平台 SSO 占位 + 本地 JWKS 直通开关）
- [ ] 树浏览（懒加载 + 分页）、breadcrumb（D17 占位符）、创建对话框（类型 + 批量授权选择器，Group search 接 C2）
- [ ] 权限管理面板（按 8.1 矩阵控制可选项，creator 标识）
- [ ] 文件页：查看/上传/下载/历史列表（编辑 UI 不归我方，留对接位）
- [ ] 导出按钮 + job 轮询
- **验收**：无权限节点全链路不可见；授权面板不会送出矩阵禁止的组合

### Phase 6 · 硬化与交付（1–2 天）

- [ ] 404/403/503 全端点核对；并发与重试幂等抽查
- [ ] 备份脚本（DB + git 仓库一致性快照）、健康检查（DB/JWKS/磁盘/Group）、磁盘水位告警
- [ ] OpenAPI 终稿 + 部署脚本 + 一页运维手册

## 里程碑

| 里程碑 | 内容 | 累计工期 |
|---|---|---|
| M1 | Phase 0–1：骨架 + 权限引擎 + 树操作可跑 | ~4 天 |
| M2 | Phase 2–3：后端 API 全量（含文件冲突流） | ~7 天 |
| M3 | Phase 4：外部集成（导出不计） | ~9 天 |
| M4 | Phase 5–6：前端 + 硬化，可交付 | ~14–18 天 |

## Mock 保真红线（mock 可以顶，但不许骗）

| Mock | 不许掩盖的事 | 真身到位后的契约测试 |
|---|---|---|
| VersionStore | 磁盘延迟、半初始化、锁残留、跨进程并发 | 真包跑临时仓库：并发 commit/init 重试/遍历与 symlink 语料 |
| GroupResolver | 超时、5xx、畸形响应、大规模组集 | 故障注入 HTTP 桩全过；我方绝不自己推祖先 |
| GroupSearch（前端选择器） | 与 resolve 是**两个独立端点**，不可用 resolve mock 顶替 | C2 `/groups/search` 真身到位后跑分页/空结果/超时 |
| Audit 对端 | 重复投递、乱序、长时间不可用 | relay 重启恢复 + 对端幂等 |
| JWT | issuer/audience/算法/轮换 | 本地 JWKS 全用例 |
| ExportAdapter | 参数、幂等、耗时 | 参数定了再写，之前不算完成 |

## Top 5 风险

1. **DB/git 分裂**（崩溃导致元数据、仓库、审计三边不一致）→ provisioning 状态机 + 对账 job，Phase 3 前置
2. **移动子树改变整树权限** → D12 事务+锁+源目标双授权，并发测试
3. **creator/独立权限开关交互** → D11 先冻结
4. **外部契约缺口**（导出参数、请求内 resolve 合并）→ OPEN 清单跟踪，mock 保真红线不许掩盖
5. **存储滥用/路径逃逸** → 路径安全 + 配额进 Phase 3 验收，不拖到硬化

## OPEN 清单（在契约文档基础上增补）

- OPEN-9：单请求内合并多次 resolve 是否符合 G4"实时"语义（找 Group 团队确认）
- OPEN-10：恶意软件扫描归属（我方/平台/不做）
- 其余 OPEN-1~8 见《文件库-接口契约.md》第 10 节
