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_PASSWORD或HUB_AUTH_SECRETTHEN 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置为failed、parse_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 交接单状态不是
completedTHEN THE SYSTEM SHALL ⛔ 不向接手人开放任何被勾选内容。
场景 3.3 · 问答
- AC-3.3.1:WHEN 调用
/api/askTHE 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_id与source_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_editable、visible_to_colleagues、include_in_handover_default,切换后立即持久化。 - AC-4.2.3:IF
is_editable = falseTHEN 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变为offboardedTHE 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-bot、hermes-gateway、ashare-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)