第 5 周:从问题合同做出确定性垂直薄片
直接答案: 第一个工程交付不应该是“搭好 AI 架构”,而应该是一个窄但完整的确定性薄片:一个获授权角色提交结构化任务,服务端校验输入和身份,从关系数据中取得当前适用且可追溯的政策;缺字段、冲突或特殊任务进入明确的复核状态。先证明普通软件能正确表达任务和失败,再决定以后是否需要模型。
本周只增加一个难度:做出第一个代码闭环
标题“本周只增加一个难度:做出第一个代码闭环”第 4 周已经决定“为什么值得探索”;第 5 周只验证“能否用最简单、可解释的软件跑通目标任务”。
Discovery Brief v1 → 一个角色和触发 → 结构化输入 → 服务端身份与业务校验 → 关系数据查询 → 成功或明确异常 → 来源、版本、状态与测试证据所谓垂直薄片,不是只写数据库、只画界面或只建 API。它从用户触发一直走到可见结果,并穿过实现所需的最少层次。所谓确定性,是同一版本、同一身份和同一输入产生可预测结果;不是“永远不会失败”。
先看完成品:北辰政策确认薄片
标题“先看完成品:北辰政策确认薄片”以下公司、身份、政策、请求、状态和结果全部是固定合成教学示例。
使用的 Brief 版本
标题“使用的 Brief 版本”- Brief:northstar-discovery-brief-v1(课程模拟批准)- 角色:获授权普通客服测试身份- 任务:套餐升级后的差价政策确认- 正常结果:返回唯一当前适用政策及来源- 异常结果:缺字段、政策冲突或特殊任务进入 needs_review- 非目标:生成回复、发送回复、主管批准、真实数据和生产部署完整薄片合同
标题“完整薄片合同”| 项目 | 完成示例 |
|---|---|
| 触发 | 客服在计费任务中请求核对政策 |
| 可信身份 | 服务端测试会话中的 support_agent_fixture;不接受请求体自报角色 |
| 必需输入 | task_type、region、purchased_at |
| 业务查询 | 找到在购买时间和地区适用、当前有效且普通客服可见的政策版本 |
| 成功输出 | eligible、政策 ID、版本、生效期、来源 ID 和可核验说明 |
| 异常输出 | needs_review 加安全原因码;不猜测、不返回受限正文 |
| 数据变化 | 保存一次任务查询及终态,不发送客户回复 |
| 可验证证据 | 响应、数据库任务记录和测试对同一 request_id 一致 |
合成关系数据
标题“合成关系数据”| policy_id | version | task_type | region | effective_from | effective_to | access_level | source_id |
|---|---|---|---|---|---|---|---|
| POL-PLAN-01 | 3 | plan_change | east | 2026-08-01 | 2026-12-31 | support | KB-BILLING-2026-08 |
| POL-PLAN-01 | 2 | plan_change | east | 2026-01-01 | 2026-07-31 | support | KB-BILLING-2026-01 |
| POL-REFUND-09 | 4 | special_refund | east | 2026-07-01 | 2026-12-31 | supervisor | KB-REFUND-2026-07 |
它不代表真实政策。access_level 只是本周固定夹具中的最小字段;完整多角色权限矩阵留到第 7 周。
成功输入与预期结果
标题“成功输入与预期结果”下面是与 HTTP 接口相似的行为示例。如果你使用函数、CLI 或其他接口,字段语义和可见结果保持一致即可。
{ "request_id": "req-w05-001", "task_type": "plan_change", "region": "east", "purchased_at": "2026-08-10"}身份来自固定服务端测试会话,不出现在请求体。预期响应:
{ "request_id": "req-w05-001", "status": "eligible", "candidate": { "policy_id": "POL-PLAN-01", "version": 3, "effective_from": "2026-08-01", "effective_to": "2026-12-31", "source_id": "KB-BILLING-2026-08" }, "next_action": "review_source"}预期持久化状态:
| request_id | identity_fixture | task_type | result_status | policy_id | policy_version | source_id |
|---|---|---|---|---|---|---|
| req-w05-001 | support_agent_fixture | plan_change | eligible | POL-PLAN-01 | 3 | KB-BILLING-2026-08 |
返回政策候选不等于客服已经正确完成任务,更不等于客户得到了正确回复。本周只证明系统能够给出一个可追溯候选。
异常输入与预期结果
标题“异常输入与预期结果”缺少地区:
{ "request_id": "req-w05-002", "task_type": "plan_change", "purchased_at": "2026-08-10"}预期结果:
{ "request_id": "req-w05-002", "status": "needs_review", "reason_code": "MISSING_REGION", "candidate": null, "next_action": "collect_required_field"}数据库不得偷偷保存一个默认地区,也不能返回看似正确的政策。
特殊退款由普通客服查询时:
{ "request_id": "req-w05-003", "task_type": "special_refund", "region": "east", "purchased_at": "2026-08-10"}预期只返回安全状态:
{ "request_id": "req-w05-003", "status": "needs_review", "reason_code": "SPECIAL_TASK_REQUIRES_REVIEW", "candidate": null, "next_action": "escalate"}响应不能泄露主管政策正文,也不需要告诉普通客服“存在一份名为 POL-REFUND-09 的受限政策”。
最小决策过程
标题“最小决策过程”从可信测试会话取得身份 → 未知身份:denied → 校验必需字段 → 缺失:needs_review / MISSING_FIELD → 查询当前适用且允许的政策 → 恰好一条:eligible + 来源与版本 → 零条:needs_review / NO_APPLICABLE_POLICY → 多条冲突:needs_review / CONFLICTING_POLICIES → 保存 request_id、输入摘要、终态和来源这是行为规则,不是某种语言的代码。不要把完整政策正文放进普通日志。
完整测试矩阵
标题“完整测试矩阵”| 用例 | 身份/输入 | 预期终态 | 必须核对 |
|---|---|---|---|
| 正常 | 已知客服;plan_change/east/2026-08-10 | eligible |
只返回 v3,来源和数据库一致 |
| 缺字段 | 已知客服;缺 region | needs_review |
无默认地区、无候选 |
| 特殊任务 | 已知客服;special_refund | needs_review |
不返回受限正文或记录 ID |
| 未知身份 | 未知测试会话 | denied |
查询不执行,默认拒绝 |
| 版本边界 | plan_change/east/2026-07-31 | eligible |
返回 v2,不错误使用 v3 |
本周最低要求独立实现正常路径和一条异常;完整示例的其他用例作为检查答案。第 7 周再增加真实的两角色动作闭环。
最小心智模型:先薄切任务,再选择组件
标题“最小心智模型:先薄切任务,再选择组件”写代码前,用六个问题限制范围:
- 角色: 谁触发,身份从哪里可信取得?
- 输入: 做决定最少需要哪些字段?
- 规则: 哪些判断必须是确定性的?
- 结果: 用户能看到并核验什么?
- 异常: 什么情况下必须拒绝或升级?
- 非目标: 本周明确不做什么?
如果你需要十多个服务才能描述一条请求,先检查是不是把未来阶段塞进了本周。
为什么默认模块化单体
标题“为什么默认模块化单体”本周的风险是任务和状态表达错,不是服务规模。一个进程或部署单元中仍可以分开接口、业务规则、数据访问和审计模块;这比先拆微服务更容易运行、测试和修改。
完成的 ADR 示例:
# ADR-001:使用模块化单体建立确定性基线
- 状态:接受(课程模拟)- Brief:northstar-discovery-brief-v1- 决定:用一个部署单元实现身份校验、政策查询和任务记录- 原因:范围只有一个只读任务;需要快速验证业务状态和错误,而不是独立扩缩容- 未选:微服务——当前没有独立团队、独立发布或扩缩容证据- 未选:RAG/模型——输入和规则是结构化的,且尚未建立正式评测- 代价:模块边界需要靠代码和测试维持- 重新评审条件:出现独立发布、隔离、负载或组织所有权证据第 1 天:写垂直薄片合同
标题“第 1 天:写垂直薄片合同”从 discovery-brief-v1.md 逐项复制引用,不要重写问题:
# vertical-slice-contract
- 使用的 Brief 与版本:- 本次角色和可信身份来源:- 触发:- 必需输入及业务含义:- 成功终态与用户可见依据:- 一个必须处理的异常:- 允许的数据变化:- 禁止的效果:- 可观察测试:- 什么结果会让我们返回 Brief:北辰示例在发现“地区并非所有任务的可靠字段”时,必须回到 Brief 或记录新的未知,不能在代码中静默填默认值。
第 2 天:定义接口、关系和状态
标题“第 2 天:定义接口、关系和状态”接口版本至少要能区分以后不兼容的变化。关系模型应保存:
- 政策稳定 ID 与版本;
- 任务类型、地区、生效和失效时间;
- 来源 ID 与最小访问级别;
- 请求 ID、可信测试身份、输入摘要和终态;
- 创建时间和当前使用的 Brief/数据版本。
先写迁移,再插入固定合成夹具。约束应让缺少关键标识或非法日期关系尽早失败;不要只靠界面验证。
第 3 天:实现最小模块化单体
标题“第 3 天:实现最小模块化单体”使用你已经熟悉的技术栈,按顺序实现:
- 从固定服务端会话取得受控测试身份;
- 校验结构化输入;
- 执行适用期、任务类型、地区和访问级别查询;
- 将零条、一条和多条候选映射到明确状态;
- 返回来源与版本;
- 保存安全的任务记录。
把运行应用所需的实际步骤写进自己的 README。因为本页没有提供代码仓库,所以不要把示例 JSON 当成已经存在的接口。
第 4 天:先写一个成功测试和一个失败测试
标题“第 4 天:先写一个成功测试和一个失败测试”自动化测试必须同时核对:
- 用户可见响应;
- 数据库任务终态;
- 候选政策和版本;
- 缺字段或受限场景没有假成功;
- 普通日志没有政策正文、密钥或个人信息。
只断言 HTTP 200 或函数没有抛异常,不算任务测试。
第 5 天:改变一个条件并记录 ADR
标题“第 5 天:改变一个条件并记录 ADR”先演示 req-w05-001 完整路径,再任选一个变化独立完成:
- 将购买日期改到版本边界;
- 删除地区;
- 制造两个同时适用版本;
- 使用未知身份。
演示时按“输入 → 身份 → 查询 → 终态 → 数据库 → 测试”顺序,不只展示界面。
五天安排与可见产物
标题“五天安排与可见产物”| 学习日 | 建议时间 | 当天动作 | 离开前必须有的结果 |
|---|---|---|---|
| 第 1 天 | 1.5 小时 | 从 Brief 写角色、输入、输出、异常和非目标 | vertical-slice-contract.md |
| 第 2 天 | 2 小时 | 定义接口、关系状态和首个迁移 | 接口契约、schema 与夹具 |
| 第 3 天 | 2–3 小时 | 实现最小应用和确定性查询 | 可按自有 README 运行的薄片 |
| 第 4 天 | 2 小时 | 实现成功和一条异常测试 | 响应、数据库与测试证据 |
| 第 5 天 | 1 小时 | 改一个条件,完成 ADR 和三层解释 | 演示记录、ADR、迁移练习 |
建议目录,不要求与你的框架目录完全相同:
fde-course/└─ week-05/ ├─ vertical-slice-contract.md ├─ api-contract/ ├─ migrations/ ├─ application/ ├─ tests/ └─ adr-001-deterministic-baseline.md验收与失败恢复
标题“验收与失败恢复”本周通过需要同时满足:
- 你自己的 README 写明实际运行方式,示例输入产生确定结果;
- 接口、数据模型和测试都引用同一版 Brief;
- 缺失、冲突和未知身份不会返回假成功;
- 单一受控测试身份在服务端取得,未知身份默认拒绝;
- 输出包含来源、版本和处理路径;
- 自动化测试核对可见结果与数据库终态,而不只看状态码;
- 没有模型、向量检索、Agent、MCP 或微服务拆分;
- 没有把本地运行写成生产、采用或业务效果。
| 常见失败 | 诊断信号 | 恢复动作 |
|---|---|---|
| 先写大量代码再找任务 | 有很多模块,却说不清一个请求 | 删除旁支,退回薄片合同 |
| 一开始拆微服务 | 调试主要花在网络和部署 | 合并为模块化单体,保留重评条件 |
| 只有成功 Demo | 缺字段仍得到政策 | 增加明确的拒绝或复核终态 |
| 请求体自报身份 | 修改 role 字段即可越权 |
从服务端测试会话取得身份,默认拒绝 |
| 输出看似正确但无来源 | 只有政策文字或“可退款” | 返回来源 ID、版本、生效期和处理状态 |
| 为简化而填默认字段 | 缺地区被当作 east | 进入 needs_review,回到任务和数据证据 |
独立迁移:供应链订单状态核对
标题“独立迁移:供应链订单状态核对”不要复制政策字段。为“计划员核对订单当前状态”重新完成:
- 写明角色、触发、订单 ID、承运商和事件时间;
- 定义“已确认”“数据冲突”“来源过期”和“需要人工核对”状态;
- 提供一条成功输入、一条 ERP 与承运商状态冲突输入及预期结果;
- 说明为什么冲突时不能自动选择更新时间更晚的一方;
- 写一项会让你返回供应链 Brief 的新证据。
如果只是把 policy_id 改成 order_id,却没有改变时效、来源所有权和错误代价,迁移未通过。
用同一薄片向三类人解释
标题“用同一薄片向三类人解释”- 老板版: 这个薄片验证了哪项交付风险,为什么先不用 AI,下一步投入取决于什么。
- 一线版: 系统只改变任务的哪一步,何时会拒绝或升级,用户怎样查看依据。
- 工程版: Brief 版本怎样进入接口、关系状态、服务端身份、查询和测试;哪些能力明确留到以后。
上一周与下一周
标题“上一周与下一周”第 6 周不扩大任务范围,只把本周固定夹具替换成有合同、来源、质量状态、幂等和重放的数据入口。
来源与事实边界
标题“来源与事实边界”- Palantir Learn — Speedrun: Your First End-to-End Workflow:先跑通数据、转换、领域对象、应用、动作和测试的完整端到端路径;其产品按钮与 Ontology 实现不是本课要求。
- Anthropic — Building Effective Agents:在 AI 系统中先寻找最简单可行方案、需要时再增加复杂度;这是 AI 工程文章,不是 FDE 课程或本页架构的行业授权。
- Ramp — Forward Deployed Engineering:持续缩小客户问题和在快速交付与可复用能力之间取舍的团队实践;该文也具有招聘和文化传播目的。
核验日期:2026-08-25。 北辰 Brief、接口、关系数据、状态、请求、响应、ADR 和测试矩阵均为本教程的合成材料或原创设计。它们不代表真实 API 标准、客户数据、生产结果、行业架构或任何模型效果。