📚 离职知识库

01 · SDD 规格规程(规格先行)

P0 阶段的产物就是这一节里的所有规格文件。规格没落盘,不许写一行业务代码。


#1. 为什么要 SDD

无人值守的一夜里,没有人能纠正你的理解偏差。规格是你唯一的"甲方"。 先把「做什么」冻结成可验证的条目,后面每一行代码都在回答某一条 AC。这样明早人类可以只看 AC 矩阵就知道你做到哪,而不用读代码猜。


#2. 目录结构

在项目根建:

specs/
  _index.md                  # 总览表:编号 / 名称 / 状态 / AC 数 / 对应阶段
  001-identity/
    spec.md                  # 规格正文(唯一真源,AC 冻结)
    CHANGELOG.md             # 本 spec 的变更日志
  002-ingest/
  003-retrieval/
  004-memory/
  005-handover/
  006-cross-agent/
  007-console-ui/
  008-agent-layer/
docs/night/                  # 夜间产物(见 07-验收包模板)
  • 编号三位,全夜不复用、不改号。
  • spec.md 进入 TDD 循环后,AC 的内容冻结。要改必须走 §5 变更日志流程。
  • 禁止写规格之外的功能。想加东西 → 先加 spec 条目,再写代码。

#3. AC 编号规则

格式:AC-<spec_id>.<场景号>.<准则号>,例:AC-5.2.1 = 交接规格、第 2 个场景、第 1 条准则。

双向可追溯(硬要求)

  • spec.md 里每条 AC 带编号
  • 测试标题必须以编号开头:
    it('AC-5.2.1: 未勾选的记忆条目在接手人视角查不到', async () => { ... })
  • docs/night/progress.log 每条记录带编号
  • docs/night/ac-matrix.md 按编号汇总

没有编号的测试 = 不算数。没有测试覆盖的 AC = 未完成。 明早按这个口径验收。


#4. spec.md 模板

写 AC 一律用 EARS 句式(每条必须能被一个自动化测试直接验证):

句式 模板
普遍 THE SYSTEM SHALL <行为>
事件驱动 WHEN <触发> THE SYSTEM SHALL <行为>
状态驱动 WHILE <状态> THE SYSTEM SHALL <行为>
异常 IF <异常条件> THEN THE SYSTEM SHALL <行为>
可选 WHERE <条件成立> THE SYSTEM SHALL <行为>

⛔ 禁止写「系统应表现良好」「界面应该好看」这类无法断言的话。视觉类要求写成可断言的形式(如"页面根元素背景色为 #f6f7f9")。

# Spec 00X: <功能名称>

- 状态:DRAFT | ACTIVE | DONE | BLOCKED
- 所属阶段:P<n>
- 依赖:<其他 spec 编号,无则填「无」>
- 影响模块:<文件路径列表>

## 1. 目标(一句话)

## 2. 用户故事
作为 <角色>,我想要 <能力>,以便 <价值>。

## 3. 场景与验收准则

### 场景 X.1:<场景名>
- **AC-X.1.1**:WHEN ... THE SYSTEM SHALL ...
- **AC-X.1.2**:IF ... THEN THE SYSTEM SHALL ...

## 4. 非功能要求
- 性能 / 安全 / 可观测性(无则明写「无」,不许留空)

## 5. 明确不做(Out of Scope)

## 6. 数据契约
- 涉及表 / API 路由 / zod schema

## 7. 测试映射表(先填这张表,再写测试代码)

| AC 编号 | 层级 | 测试文件 | 备注 |
|---|---|---|---|
| AC-X.1.1 | unit / component / integration / e2e | tests/... | |

#5. 变更日志(CHANGELOG.md

规格发现有误要调整时,⛔ 不许直接改字覆盖原文,必须追加一条:

## [2026-07-31 03:14] AC-2.3.2 调整
- 原文:THE SYSTEM SHALL 在 5 秒内完成 50 页 PDF 的解析入库
- 改为:THE SYSTEM SHALL 分步处理,前端每 2 秒轮询一次状态直至完成
- 原因:Vercel Hobby 函数超时 10s,实测 50 页解析 + embedding 约 40s,原 AC 物理上不可达
- 影响测试:tests/integration/ingest.test.ts 改为断言状态机流转而非总耗时
- 性质:工程约束导致的必要调整(非为了让测试变绿而放水)

每条变更必须早于对应代码 commit。 明早人类会先扫这个文件,判断改动是工程判断还是作弊式放水。


#6. 八份规格的 AC 全清单

下面是本项目全部规格的 AC。P0 阶段的工作就是把它们按 §4 模板抄进各自的 spec.md,补齐数据契约和测试映射表。 AC 内容可以补充细化,但不许删减。确实做不到的,走 §5 变更日志。


#SPEC-001 身份与访问(P1)

场景 1.1 · 密码门

  • AC-1.1.1:IF 请求未携带有效会话 Cookie THEN THE SYSTEM SHALL 将除 /login/api/login/api/health、静态资源以外的所有路径 302/307 重定向到 /login,并把原路径写进 ?next= 参数。
  • AC-1.1.2:WHEN 用户在 /login 提交正确密码 THE SYSTEM SHALL 返回 200 并下发 httpOnly、secure、domain=.saveme505.help 的会话 Cookie。
  • AC-1.1.3:IF 密码错误 THEN THE SYSTEM SHALL 返回 401 且响应体不包含正确密码的任何片段。
  • AC-1.1.4:IF 服务端未配置 HUB_SITE_PASSWORDHUB_AUTH_SECRET THEN THE SYSTEM SHALL 拦截全部请求(绝不裸奔),/api/login 返回 503。

场景 1.2 · 员工身份

  • AC-1.2.1:THE SYSTEM SHALL 在侧边栏提供身份切换器,可在种子员工(王销售 / 李销售 / 赵采购)之间切换。
  • AC-1.2.2:WHEN 当前身份切换 THE SYSTEM SHALL 使后续所有页面与 API 请求都以新身份的 employee_id 为作用域。
  • AC-1.2.3:THE SYSTEM SHALL 把当前身份持久化(Cookie 或 localStorage),刷新页面后保持不变。

场景 1.3 · 数据隔离(安全底线)

  • AC-1.3.1:WHEN 以员工 A 的身份调用任意知识类 API THE SYSTEM SHALL 只返回 A 拥有的、或已通过完成态交接授予给 A 的数据。
  • AC-1.3.2:IF 请求中显式传入他人的 employee_id 且当前身份无权访问 THEN THE SYSTEM SHALL 返回 403 而非返回数据。
  • AC-1.3.3:THE SYSTEM SHALL 保证浏览器端构建产物中不包含任何 Supabase 写权限密钥(构建后 grep .next/static 命中数为 0)。
  • AC-1.3.4:THE SYSTEM SHALL 使所有 bt_ 表的数据访问都经由 lib/db.ts 导出的函数,源码中除该文件外不出现 .from('bt_ 字样(可用 grep 断言)。

#SPEC-002 知识库摄取(P2)

场景 2.1 · 上传

  • AC-2.1.1:WHEN 用户把文件拖入上传区 THE SYSTEM SHALL 创建一条 bt_files 记录,parse_status='pending',并把原文件存入对象存储。
  • AC-2.1.2:IF 文件类型不在 pdf/docx/xlsx/txt/md 之内 THEN THE SYSTEM SHALL 拒绝上传并在 UI 显示可读的中文错误。
  • AC-2.1.3:IF 单文件超过 20MB THEN THE SYSTEM SHALL 拒绝并提示上限。
  • AC-2.1.4:THE SYSTEM SHALL 在上传过程中显示进度,并在状态变化时更新状态文案:待处理 → 解析中 → 切片 N 片 → 向量化中 → 已入库

场景 2.2 · 解析与切片

  • AC-2.2.1:WHEN 解析一份 PDF THE SYSTEM SHALL 为每个 chunk 记录真实 page_no,且 chunk 原则上不跨页。
  • AC-2.2.2:WHEN 解析一份 docx THE SYSTEM SHALL 记录 heading_path(章节路径)作为出处,page_no 允许为空但 page_label 必须有值。
  • AC-2.2.3:WHEN 解析一份 xlsx THE SYSTEM SHALL 以 Sheet名!行区间 作为 page_label,且每个 chunk 内包含表头行。
  • AC-2.2.4:THE SYSTEM SHALL 使每个 chunk 的字符数落在 300–1000 之间(末片可短),相邻 chunk 重叠约 15%。
  • AC-2.2.5:IF PDF 解析后提取到的文本总字符数 < 50 THEN THE SYSTEM SHALL 判定为扫描件,把 parse_status 置为 failedparse_error 写明「疑似扫描件,暂不支持 OCR」,并在 UI 上以警告态展示(⛔ 不许静默成功)。

场景 2.3 · 向量化与状态机

  • AC-2.3.1:THE SYSTEM SHALL 使文件状态严格按 pending → parsing → chunking → embedding → done 单向流转,失败时转入 failed 并保留 parse_error
  • AC-2.3.2:IF embedding 接口调用失败 THEN THE SYSTEM SHALL 保留该 chunk 的 embedding IS NULL 并允许后续补跑,⛔ 不许丢弃 chunk。
  • AC-2.3.3:WHEN 同一份文件被重复处理 THE SYSTEM SHALL 不产生重复 chunk(靠 (file_id, chunk_index) 唯一约束保证)。
  • AC-2.3.4:THE SYSTEM SHALL 使单次 API 调用的处理量可控(分批),单次调用不超过 Vercel 函数超时限制。

#SPEC-003 检索与出处(P2)

场景 3.1 · 混合检索

  • AC-3.1.1:WHEN 用户在自己的知识库搜索一个中文客户名 THE SYSTEM SHALL 返回命中该客户名的 chunk,即使该词未被向量召回(即模糊匹配这一路必须真的生效)。
  • AC-3.1.2:THE SYSTEM SHALL 用 RRF 融合向量与模糊两路结果,返回结果按融合分降序。
  • AC-3.1.3:IF 查询词长度 < 3 个字符 THEN THE SYSTEM SHALL 降低模糊匹配阈值或并入子串匹配兜底,保证短查询不会返回空。
  • AC-3.1.4:THE SYSTEM SHALL 使每条检索结果都带出处:文件名 + page_label,且可点击定位到该 chunk 原文。

场景 3.2 · 隔离与授权可见

  • AC-3.2.1:WHEN 员工 A 检索 THE SYSTEM SHALL ⛔ 不返回员工 B 的任何 chunk(除非该文件已通过完成态交接授予 A)。
  • AC-3.2.2:WHEN 一笔交接完成后 THE SYSTEM SHALL 使接手人能检索到被交接文件的 chunk,且结果上标注「来源:<前任> 交接,<日期>」。
  • AC-3.2.3:IF 交接单状态不是 completed THEN THE SYSTEM SHALL ⛔ 不向接手人开放任何被勾选内容。

场景 3.3 · 问答

  • AC-3.3.1:WHEN 调用 /api/ask THE SYSTEM SHALL 先检索再生成,且回答中必须包含至少一条出处引用。
  • AC-3.3.2:IF 检索结果为空 THEN THE SYSTEM SHALL 回答「我的资料里没有」,⛔ 禁止凭模型记忆编造。
  • AC-3.3.3:THE SYSTEM SHALL 把每次问答落一条 bt_agent_queries 记录(含命中的 chunk/memory id 与耗时)。

#SPEC-004 记忆条目(P3)

场景 4.1 · 抽取

  • AC-4.1.1:WHEN 对一份已入库文件执行「抽取」 THE SYSTEM SHALL 产出若干 bt_memories 记录,每条带 source_file_idsource_chunk_id
  • AC-4.1.2:THE SYSTEM SHALL 把每条记忆归入五类之一:客户约定 / 报价底线 / 供应商渠道 / 人际雷区 / 流程习惯
  • AC-4.1.3:IF LLM 返回的结构不符合预期 schema THEN THE SYSTEM SHALL 拒绝写库并记录错误,⛔ 不许写入半成品数据。
  • AC-4.1.4:THE SYSTEM SHALL 使重复抽取同一文件不产生重复条目(按 source_chunk_id + 标题去重)。

场景 4.2 · 编辑与开关

  • AC-4.2.1:WHEN 用户就地编辑一条记忆的正文并保存 THE SYSTEM SHALL 持久化新内容并更新 updated_at
  • AC-4.2.2:THE SYSTEM SHALL 为每条记忆提供三个独立开关:is_editablevisible_to_colleaguesinclude_in_handover_default,切换后立即持久化。
  • AC-4.2.3:IF is_editable = false THEN THE SYSTEM SHALL 使该条目在 UI 上不可编辑,且服务端拒绝其更新请求(前后端都要拦)。
  • AC-4.2.4:THE SYSTEM SHALL 在记忆条目页支持按类型筛选。

#SPEC-005 交接(P4 · 项目的心脏)

场景 5.1 · 发起

  • AC-5.1.1:WHEN 用户选择「从 A 交给 B」并选择原因(离职/换岗/日常同步) THE SYSTEM SHALL 创建一张 status='draft' 的交接单。
  • AC-5.1.2:IF A 与 B 是同一人 THEN THE SYSTEM SHALL 拒绝创建。
  • AC-5.1.3:THE SYSTEM SHALL 默认勾选 A 名下所有 include_in_handover_default = true 的记忆条目,用户可逐条增删。
  • AC-5.1.4:THE SYSTEM SHALL 允许在同一张交接单里勾选原始文件(item_type='file')。
  • AC-5.1.5:WHEN 用户点击「交接预览」 THE SYSTEM SHALL 生成一份「接手人会看到什么」的摘要,列出条目数、文件数、按类型分组。

场景 5.2 · 确认与生效

  • AC-5.2.1:WHEN 发起方提交 THE SYSTEM SHALL 把交接单置为 submitted 并记录 submitted_at,此时接手人尚不能访问任何内容。
  • AC-5.2.2:WHEN 接手人确认 THE SYSTEM SHALL 把交接单置为 completed、回填 completed_at 与每条明细的 granted_at,此后接手人可访问被勾选内容。
  • AC-5.2.3:THE SYSTEM SHALL ⛔ 不修改被交接内容的 owner_employee_id(交接是授予可见权,不是搬走数据,原始归属必须留痕)。
  • AC-5.2.4:WHEN 交接完成 THE SYSTEM SHALL 使未被勾选的内容对接手人依然不可见(这条要有专门的负向测试)。
  • AC-5.2.5:THE SYSTEM SHALL 在交接页以三步进度展示状态:已发起 → 对方已查看 → 对方已确认。

场景 5.3 · 记录与封存

  • AC-5.3.1:THE SYSTEM SHALL 在记录页展示每笔交接:谁、何时、交了哪些、交给谁、对方何时确认。
  • AC-5.3.2:WHEN 一名员工 status 变为 offboarded THE SYSTEM SHALL 把其未交接内容标记为「已随账号封存」并在记录页展示,⛔ 不删除数据。
  • AC-5.3.3:THE SYSTEM SHALL 使交接记录不可编辑(只增不改)。

#SPEC-006 跨 Agent 提问(P4)

场景 6.1 · 问同事

  • AC-6.1.1:WHEN 员工 A 的问答在自己库中检索为空 THE SYSTEM SHALL 允许向指定同事 B 发起一次跨人提问。
  • AC-6.1.2:THE SYSTEM SHALL 只在 B 的 visible_to_colleagues = true 的记忆范围内作答,⛔ 不得触及 B 的其他内容。
  • AC-6.1.3:THE SYSTEM SHALL 只允许一跳:由跨人提问触发的回答,⛔ 不得再次触发对第三人的提问(防无限套娃)。
  • AC-6.1.4:WHEN 跨人提问返回结果 THE SYSTEM SHALL 在答案中明确标注「以下来自 <同事> 的 Agent」。
  • AC-6.1.5:THE SYSTEM SHALL 为每次跨人提问落一条 bt_agent_queries 记录,was_cross_employee = true

场景 6.2 · 记录

  • AC-6.2.1:THE SYSTEM SHALL 在记录页的「跨人提问」tab 展示:谁问了谁、什么时候、问了什么、拿到了什么、依据是哪条。

#SPEC-007 后台界面(P1,浅色干净风)

场景 7.1 · 布局

  • AC-7.1.1:THE SYSTEM SHALL 提供左侧栏 + 右内容区布局,侧边栏含公司名、身份切换器、五个菜单项。
  • AC-7.1.2:THE SYSTEM SHALL 使页面背景为浅灰(#f6f7f9 或等价 token)、卡片为纯白,二者不相同(可用 E2E 读取 computed style 断言)。
  • AC-7.1.3:THE SYSTEM SHALL 在 1280px 宽度下不出现横向滚动条。

场景 7.2 · 五个页面

  • AC-7.2.1:总览页 THE SYSTEM SHALL 展示四个数字卡片(员工数 / 文件总数 / 记忆条目总数 / 本月交接次数)、员工卡片墙、右侧动态时间线。
  • AC-7.2.2:知识库页 THE SYSTEM SHALL 上半为拖拽上传区、下半为文件表格;点击一行在抽屉中展开该文件的全部 chunk,每片标注出处。
  • AC-7.2.3:记忆条目页 THE SYSTEM SHALL 按五类分组展示卡片,每卡含结论、出处小字、三个开关。
  • AC-7.2.4:交接页 THE SYSTEM SHALL 左栏选人与原因、右栏可折叠的勾选清单、底部预览与发起按钮。
  • AC-7.2.5:记录页 THE SYSTEM SHALL 提供两个 tab:交接记录、跨人提问记录。
  • AC-7.2.6:THE SYSTEM SHALL 使五个页面在数据为空时都有明确的空态提示(⛔ 不许白屏)。

场景 7.3 · 可靠性

  • AC-7.3.1:IF 任一 API 返回错误 THEN THE SYSTEM SHALL 以 Sonner toast 展示可读的中文错误,⛔ 不许静默失败。
  • AC-7.3.2:THE SYSTEM SHALL 使所有列表页在加载中显示骨架屏或加载态。

#SPEC-008 服务器 Agent 层(P6 · 纯增量,可整体降级)

场景 8.1 · 增量安全(最高优先级)

  • AC-8.1.1:THE SYSTEM SHALL 在施工前后各做一次现有服务存活快照,且施工后 feishu-bothermes-gatewayashare-auto-git 等原有服务状态与施工前一致。
  • AC-8.1.2:THE SYSTEM SHALL ⛔ 不执行任何 rm / systemctl stop|disable / pip uninstall / 覆盖已有 .env 的操作。
  • AC-8.1.3:THE SYSTEM SHALL 在任何新增操作前,先备份将被读取或影响的配置文件到带日期的副本。

场景 8.2 · 多 profile

  • AC-8.2.1:THE SYSTEM SHALL 新建三个 Hermes profile(emp_wang / emp_li / emp_zhao),各自家目录独立。
  • AC-8.2.2:WHEN 分别向三个 profile 提同一个问题 THE SYSTEM SHALL 使回答互不串台(各答各的资料)。
  • AC-8.2.3:THE SYSTEM SHALL 为每个 profile 写入 SOUL 铁律:涉及客户/报价/合同/供应商/交接的问题必须先检索、回答必须带出处、检索不到就说「我的资料里没有」、⛔ 绝对禁止凭记忆编造。

场景 8.3 · 瘦服务与飞书

  • AC-8.3.1:THE SYSTEM SHALL 提供一个只绑 127.0.0.1 的转发服务,按员工分队列(不同人并行、同一人串行)。
  • AC-8.3.2:WHERE 飞书新应用凭据可用 THE SYSTEM SHALL 以长连接接入,且使用独立的 App ID(⛔ 不复用拉斐尔的应用)。
  • AC-8.3.3:IF 飞书凭据不可用 THEN THE SYSTEM SHALL 跳过飞书接入,仅完成命令行验证,并在 blockers 中记录。

#7. specs/_index.md 模板

# 规格总览

| 编号 | 名称 | 阶段 | AC 数 | 状态 |
|---|---|---|---|---|
| 001 | 身份与访问 | P1 | 10 | ACTIVE |
| 002 | 知识库摄取 | P2 | 12 | DRAFT |
| ... |

P0 结束时这张表必须填满,且每个 spec 目录下都有 spec.md + 空的 CHANGELOG.md


#8. P0 的闸门

# 全部为真才算 P0 完成
test -f specs/_index.md
find specs -name spec.md | wc -l   # 应为 8
! find specs -name spec.md | xargs grep -L "AC-" | grep -q .   # 每份都含 AC 编号
find specs -name CHANGELOG.md | wc -l   # 应为 8

来源:沉淀/03-项目方案与交接/接棒-通宵施工包-20260731/01-SDD规格规程.md(整理于 2026-08-18)