diff --git a/.omo/run-continuation/ses_0705f8afbffePWIq7GrFfwfFwV.json b/.omo/run-continuation/ses_0705f8afbffePWIq7GrFfwfFwV.json deleted file mode 100644 index 35dd765..0000000 --- a/.omo/run-continuation/ses_0705f8afbffePWIq7GrFfwfFwV.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "sessionID": "ses_0705f8afbffePWIq7GrFfwfFwV", - "updatedAt": "2026-07-23T15:40:15.630Z", - "sources": { - "background-task": { - "state": "idle", - "updatedAt": "2026-07-23T15:40:15.630Z" - } - } -} \ No newline at end of file diff --git a/.omo/run-continuation/ses_07252990dffeRFmXnvdyynty2z.json b/.omo/run-continuation/ses_07252990dffeRFmXnvdyynty2z.json deleted file mode 100644 index 13afc86..0000000 --- a/.omo/run-continuation/ses_07252990dffeRFmXnvdyynty2z.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "sessionID": "ses_07252990dffeRFmXnvdyynty2z", - "updatedAt": "2026-07-23T06:33:33.086Z", - "sources": { - "background-task": { - "state": "idle", - "updatedAt": "2026-07-23T06:33:33.086Z" - } - } -} \ No newline at end of file diff --git a/.omo/run-continuation/ses_075d214a0ffe6mU8VNgukUfH5L.json b/.omo/run-continuation/ses_075d214a0ffe6mU8VNgukUfH5L.json deleted file mode 100644 index 7c08344..0000000 --- a/.omo/run-continuation/ses_075d214a0ffe6mU8VNgukUfH5L.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "sessionID": "ses_075d214a0ffe6mU8VNgukUfH5L", - "updatedAt": "2026-07-22T14:15:01.741Z", - "sources": { - "background-task": { - "state": "idle", - "updatedAt": "2026-07-22T14:15:01.741Z" - } - } -} \ No newline at end of file diff --git a/.omo/run-continuation/ses_075d3282bffejz0HFE00taOaGM.json b/.omo/run-continuation/ses_075d3282bffejz0HFE00taOaGM.json deleted file mode 100644 index 1ab24cd..0000000 --- a/.omo/run-continuation/ses_075d3282bffejz0HFE00taOaGM.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "sessionID": "ses_075d3282bffejz0HFE00taOaGM", - "updatedAt": "2026-07-22T14:13:54.785Z", - "sources": { - "background-task": { - "state": "idle", - "updatedAt": "2026-07-22T14:13:54.785Z" - } - } -} \ No newline at end of file diff --git a/.omo/run-continuation/ses_0781484caffeKJHXVKiD4BqEJw.json b/.omo/run-continuation/ses_0781484caffeKJHXVKiD4BqEJw.json deleted file mode 100644 index 5220743..0000000 --- a/.omo/run-continuation/ses_0781484caffeKJHXVKiD4BqEJw.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "sessionID": "ses_0781484caffeKJHXVKiD4BqEJw", - "updatedAt": "2026-07-22T06:17:18.238Z", - "sources": { - "background-task": { - "state": "idle", - "updatedAt": "2026-07-22T06:17:18.238Z" - } - } -} \ No newline at end of file diff --git a/.omo/run-continuation/ses_07b163c13ffeudULxSfIfK986T.json b/.omo/run-continuation/ses_07b163c13ffeudULxSfIfK986T.json deleted file mode 100644 index a20e325..0000000 --- a/.omo/run-continuation/ses_07b163c13ffeudULxSfIfK986T.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "sessionID": "ses_07b163c13ffeudULxSfIfK986T", - "updatedAt": "2026-07-21T13:55:58.878Z", - "sources": { - "background-task": { - "state": "idle", - "updatedAt": "2026-07-21T13:55:58.878Z" - } - } -} \ No newline at end of file diff --git a/.omo/run-continuation/ses_07b1e6c8effeq7bdmj6NgO0LqC.json b/.omo/run-continuation/ses_07b1e6c8effeq7bdmj6NgO0LqC.json deleted file mode 100644 index e11862c..0000000 --- a/.omo/run-continuation/ses_07b1e6c8effeq7bdmj6NgO0LqC.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "sessionID": "ses_07b1e6c8effeq7bdmj6NgO0LqC", - "updatedAt": "2026-07-21T13:38:01.132Z", - "sources": { - "background-task": { - "state": "idle", - "updatedAt": "2026-07-21T13:38:01.132Z" - } - } -} \ No newline at end of file diff --git a/.omo/run-continuation/ses_07b932dddffecSipFLwlJOvoJ9.json b/.omo/run-continuation/ses_07b932dddffecSipFLwlJOvoJ9.json deleted file mode 100644 index c0350d6..0000000 --- a/.omo/run-continuation/ses_07b932dddffecSipFLwlJOvoJ9.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "sessionID": "ses_07b932dddffecSipFLwlJOvoJ9", - "updatedAt": "2026-07-21T11:27:10.420Z", - "sources": { - "background-task": { - "state": "idle", - "updatedAt": "2026-07-21T11:27:10.420Z" - } - } -} \ No newline at end of file diff --git a/.omo/run-continuation/ses_07b9c4268ffeic4mMXCPUt225j.json b/.omo/run-continuation/ses_07b9c4268ffeic4mMXCPUt225j.json deleted file mode 100644 index 6e90994..0000000 --- a/.omo/run-continuation/ses_07b9c4268ffeic4mMXCPUt225j.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "sessionID": "ses_07b9c4268ffeic4mMXCPUt225j", - "updatedAt": "2026-07-21T11:18:53.449Z", - "sources": { - "background-task": { - "state": "idle", - "updatedAt": "2026-07-21T11:18:53.449Z" - } - } -} \ No newline at end of file diff --git a/.omo/run-continuation/ses_07b9c4346ffeVBdV5pf4h6s9PW.json b/.omo/run-continuation/ses_07b9c4346ffeVBdV5pf4h6s9PW.json deleted file mode 100644 index 15ecee9..0000000 --- a/.omo/run-continuation/ses_07b9c4346ffeVBdV5pf4h6s9PW.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "sessionID": "ses_07b9c4346ffeVBdV5pf4h6s9PW", - "updatedAt": "2026-07-21T11:18:29.979Z", - "sources": { - "background-task": { - "state": "idle", - "updatedAt": "2026-07-21T11:18:29.979Z" - } - } -} \ No newline at end of file diff --git a/.omo/run-continuation/ses_07b9c9a75ffeOyJ4ewp3DBvXhz.json b/.omo/run-continuation/ses_07b9c9a75ffeOyJ4ewp3DBvXhz.json deleted file mode 100644 index 49b51ec..0000000 --- a/.omo/run-continuation/ses_07b9c9a75ffeOyJ4ewp3DBvXhz.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "sessionID": "ses_07b9c9a75ffeOyJ4ewp3DBvXhz", - "updatedAt": "2026-07-21T11:21:09.776Z", - "sources": { - "background-task": { - "state": "idle", - "updatedAt": "2026-07-21T11:21:09.776Z" - } - } -} \ No newline at end of file diff --git a/.omo/run-continuation/ses_07cdbac0effeiBrdcHLUUUfwuF.json b/.omo/run-continuation/ses_07cdbac0effeiBrdcHLUUUfwuF.json deleted file mode 100644 index 9a0ffe1..0000000 --- a/.omo/run-continuation/ses_07cdbac0effeiBrdcHLUUUfwuF.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "sessionID": "ses_07cdbac0effeiBrdcHLUUUfwuF", - "updatedAt": "2026-07-23T15:37:59.918Z", - "sources": { - "background-task": { - "state": "idle", - "updatedAt": "2026-07-23T15:37:59.918Z" - } - } -} \ No newline at end of file diff --git a/.omo/文件库-开工计划.md b/.omo/文件库-开工计划.md deleted file mode 100644 index 945d2d0..0000000 --- a/.omo/文件库-开工计划.md +++ /dev/null @@ -1,114 +0,0 @@ -# 文件库 · 开工计划(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 节 diff --git a/hub/src/database/README.md b/hub/src/database/README.md index 9d18952..75fac3a 100644 --- a/hub/src/database/README.md +++ b/hub/src/database/README.md @@ -96,9 +96,11 @@ allowDevLoginBypass = (NODE_ENV !== "production") && HUB_DEV_LOGIN_BYPASS 为真 ## 文件库(filelib/) -独立文件库模块,语义由仓库根《文件库-接口契约.md》(C/D 编号)与 -`.omo/文件库-开工计划.md`(D11–D19)锚定。**与 hub 自己的 Folder/Project -(ADR-0021 explorer)是两套体系,不复用。** +独立文件库模块,语义由仓库根《文件库-接口契约.md》(C/D 编号)锚定。 +代码注释里的 D11–D19 出自已删除的 `.omo/文件库-开工计划.md`(内容可从 +git 历史取回);其中 D19(网站管理员 = silo org OWNER/ADMIN)另见 +ADR-0028。**与 hub 自己的 Folder/Project(ADR-0021 explorer)是两套 +体系,不复用。** | 文件 | 职责 | |------|------|