搭建系统最会流血的两处,其实是同一种病:PRD 抽取与双向同步

系列深挖篇。把「PRD→交互规格的 intake agent」和「schema↔代码的 round-trip」这两个搭建系统里最疼的单点挖到底,并揭示它们是同一种病——同一份信息在两种表示之间来回翻译时的边界问题。前者会静默编造缺失的逻辑,后者会静默漂移。负责任的解法都是同一句:把有损与歧义的部分显式化,而不是替它糊过去。含 intake 的完备性矩阵设计与 round-trip 四种策略的取舍。

系列前四篇搭好了整套架构(出码难题问题地图Harness协议族)。这一篇深挖其中最会流血的两个单点:协议里的 P12(PRD → 交互规格的 intake agent)P23(schema ↔ 代码的 round-trip)。挖到最后你会发现——它们不是两个问题,是同一种病。

一句话主线

这两处流血,本质是同一件事:一份信息在两种表示之间翻译时,边界上必然有损耗或歧义。intake 是「人的意图(散文)→ 机器规格」,病症是 AI 会静默编造缺失的部分;round-trip 是「机器规格 ↔ 代码」,病症是两边会静默漂移。负责任的解法也是同一句——把有损和歧义的部分显式化,而不是替它糊过去。

记住这个统一视角:凡是同一份真相有两个表示,你就有一个「谁是源头 + 怎么对账」的问题。 下面两个点,都是它的实例。


上半场:Intake Agent —— 从 PRD 里抽逻辑

为什么这是全系统唯一「模型变强也救不了」的点

回顾系列的一个核心区分:难题分两类——「信息在输入里,只是难提取」(分块、匹配、布局,模型变强会缓解)和「信息根本不在输入里」(业务逻辑、边界态,模型再强也变不出来)。

intake 是第二类的极致。PRD 是人写的散文,它有三种天生的病:

  1. 歧义:「提交后跳转」——跳去哪?只在成功时跳,还是失败也跳?
  2. 遗漏:PRD 几乎从不写「请求失败怎么办」「列表空了显示什么」「没权限时怎样」。那些 unhappy path,占真实工作量的一半,却几乎从不被写下来。
  3. 隐性知识:「按常规处理」——「常规」是作者脑子里的共识,不在纸上。

关键判断:这些信息不是「藏得深」,是「压根不存在于 PRD 里」。 所以让模型「更聪明地理解 PRD」是缘木求鱼——PRD 里没有的东西,理解得再透也读不出来。模型面对空缺,只会做一件最危险的事:自信地编一套看起来合理的逻辑填进去。

致命误区:把 intake agent 设计成「逻辑生成器」

直觉的做法是:把 PRD 喂给模型,让它输出一份完整的 InteractionSpec。这是错的,且是危险的错——因为对于 PRD 没覆盖的部分,模型会幻觉补全,而且补得像模像样,让人 review 时根本发现不了「这条逻辑其实是编的」。一个自信的错误,比一个明显的空缺危险得多。

正确的重构:它是「抽取器 + 缺口探测器」,不是生成器

把 intake agent 的目标彻底翻转:

它最有价值的产出,不是「逻辑」,而是「问题清单」。

也就是说,它的一等输出是它不确定、PRD 没说清的所有地方,整理成结构化的待决问题交给人;它只对PRD 明确写了的部分输出高置信度规格。这一下就把问题从「AI 悄悄猜逻辑」(危险)变成了「AI 精确地列出还有哪些没定」(安全且有用)。

具体设计四步:

① 接地(Grounding):先把 PRD 里的名词锚到组合树节点

抽逻辑之前,得先解决「PRD 说的『那个提交按钮』到底是页面里哪个节点」。没有这一步,抽出来的规则就是悬空的。

{ "kind":"intake.grounding", "payload": {
  "mentions": [
    { "phrase":"提交按钮", "prdRef":"prd#3.2", "node":"n_submitBtn", "confidence":0.9 },
    { "phrase":"列表",     "prdRef":"prd#2.1", "node":"n_grid",      "confidence":0.7 }
  ],
  "unresolved": [ { "phrase":"操作成功后的提示", "reason":"页面里找不到对应反馈组件" } ] }}

unresolved 本身就是一条要交给人的问题——接不上的名词,往往意味着物料缺失或 PRD 指向了不存在的东西。

② 完备性矩阵:用模板逼出「没写的部分」

这是 intake 最关键的设计。不要让模型自由发挥,而是给它一张必须逐格填的矩阵:对每一个动作 / 每一个数据依赖区域,强制回答一组固定问题——触发、守卫、成功、各错误码、空、加载中、无权限、并发/重复提交。PRD 里没答案的格子,不许留空,必须标成「gap」并生成一个待决问题。

{ "kind":"intake.coverageMatrix", "payload": {
  "action":"submitOrder", "node":"n_submitBtn",
  "cells": {
    "trigger":  { "value":"click", "provenance":"prd#3.2", "confidence":0.95 },
    "guard":    { "value":"表单校验通过", "provenance":"prd#3.2", "confidence":0.8 },
    "success":  { "value":"跳转订单详情", "provenance":"prd#3.2", "confidence":0.7 },
    "error.500":{ "gap":true, "proposal":"toast 报错并保留草稿", "needsHuman":true },
    "error.401":{ "gap":true, "proposal":"跳登录", "needsHuman":true },
    "empty":    { "notApplicable":true },
    "loading":  { "gap":true, "proposal":"按钮 loading + 禁用防重复", "needsHuman":true },
    "concurrent":{ "gap":true, "proposal":"提交中禁用", "needsHuman":true }
  }}}

矩阵把「这个 PRD 覆盖了错误处理吗」从一个靠人记性去发现的漏,变成了一个被结构强制检查的格子。模型可以给 proposal(一个合理默认建议),但每个 gap 都 needsHuman=true——建议归建议,决策权交回人。

③ 歧义作为一等输出:把「问题」结构化

模型不确定的地方,不要猜,输出结构化的澄清请求:

{ "kind":"intake.openQuestion", "payload": {
  "id":"q_07", "about":"submitOrder.success",
  "question":"提交成功后是跳订单详情,还是留在原页并刷新列表?",
  "options":["跳订单详情(/order/:id)","留在原页刷新列表"],
  "prdRef":"prd#3.2", "blocking":true }}

blocking 标记这条不解决就不能继续——把「必须澄清」和「可用默认兜着」分开,避免每个小疑问都卡住流程。

④ 确认回路 + 溯源:让抽出来的逻辑可审计

每条最终生效的规则,都带 provenance(追到 PRD 第几节)和 confidence,并记录是「模型抽的」还是「人确认/修改的」。人的每次修正回灌 few-shot(协议 P21)。能追溯到 PRD 出处的逻辑,才敢在生产里用;追不到出处的,就是定时炸弹。

上半场小结

intake 的病是静默编造,药是把缺口显式成问题。一句话:你无法抽取不存在的东西——intake agent 的职责不是替空缺填上合理的逻辑,而是精确地描出空缺的形状,交给唯一知道答案的人。


下半场:Round-trip —— schema 与代码的双向同步

为什么低代码总在这里流血

协议 P23 要解决的是:搭建器产出代码后,有人手改了代码,改动能不能同步回 schema?这个「双向」二字,是几乎所有低代码平台的伤口。

先看清楚不对称性:

  • 正向(schema → 代码)很容易:它是一个确定性的纯函数,给定 schema 必然生成同样的代码。
  • 反向(代码 → schema)是杀手:生成是有损且膨胀的——一个 schema 节点会展开成几十行代码,中间还夹着人手写的胶水。想把任意手改过的代码解析回 schema,一般情况下是不可能的。

用一句话点破本质:代码的表达空间远大于 schema 的表达空间。 人能写出无数种 schema 根本表达不了的代码。所以「完全的双向同步」在数学上就不成立——不是工程没做好,是这条路本身有天花板。

它其实是一个分布式一致性问题

换个视角就通透了:同一份真相(这个页面)有两个可编辑的副本(schema 和代码),两边都能改。 这不就是 Git 面对的同一个问题吗——两个分支各自演进,怎么合并。这在编程语言理论里有个名字叫双向变换(bidirectional transformation / lens),而它的困难是公认的。

具体的流血点有五个:

  1. 生成不是双射:代码空间比 schema 空间大,手写代码可能没有任何 schema 对应物。
  2. 身份锚定丢失:要把一处代码改动映射回某个 schema 节点,需要稳定的 id 把「代码区间 ↔ IR 节点」锚起来(就是 P23 的 source map)。一旦重新生成时代码顺序被打乱,锚点就断了。
  3. 三方合并:如果 schema 和代码同时被改,你面对的是一个跨表示的三方 merge,比 Git 的文本 merge 更难,因为两边根本不是同一种语言。
  4. 逃生舱失控:真实项目一定需要「这块让我直接写代码」。一旦某块被手写代码接管,它就脱离了 schema 的管辖。
  5. 重新生成毁掉手改:最朴素的「从 schema 重新生成代码」会无脑覆盖掉所有手工改动。

四种策略,没有银弹,只有取舍

面对这个天花板,成熟系统的做法其实就四种。把取舍摊开:

策略做法换来什么代价适合
A. 一次性弹出(eject)schema→代码生成一次,之后代码归你,不再回改最简单、最诚实,代码完全自由失去「继续可视化编辑」生成脚手架、一次性交付
B. 运行时 schema(不生成代码)运行时直接解释 schema,根本没有代码产物双向同步问题消失(只有一个源头)表达力有天花板,逃生舱是受限插件槽中后台、表单、配置化页面
C. 保护区(protected regions)生成代码里显式划「勿动区(重生成)」和「你的代码区(保留)」兼顾可视化 + 手写,务实的中间态边界维护成本高,仍可能冲突大多数需要长期共存的场景
D. 真·双向变换(lens)形式化定义反向变换理论最强,任意小改可回流极难,只对受限改动(如仅改 props)可行局部、约束严格的编辑

这就是为什么协议 P23 里那个 roundTrip 字段要取值 structural / props-only / none——它本质是在逐块声明这里用的是 C 里的哪种保护级别、或退化到 A。绝大多数成功的系统选 B 或 C,而不是 D。追求 D(万物皆可双向回改)的团队,基本都在这里流干了血。

决定性的一步:不是「实现双向」,而是「画清并诚实标注边界」

负责任的 round-trip 设计,核心动作不是去实现那个不可能的完全双向,而是:

明确地画出「哪些改动可同步、哪些不可」的边界,并把这条边界对用户显式化。

比如:props 值的修改可以双向(D 在小范围可行);结构增删走保护区(C);一旦进了逃生舱写了自定义代码,就明确标记「此块已脱管(none)」,UI 上直接告诉用户「这里改了就回不来了」。把「哪里能改回、哪里不能」明明白白告诉用户,比假装「随便改都能同步」然后在某次重新生成时悄悄吃掉他两小时的手改,负责任一万倍。

下半场小结

round-trip 的病是静默漂移,药是显式声明可同步边界 + 单一源头。而「选 A/B/C/D 哪种」根本不是技术选型,是产品哲学选型——它等价于系列第三、四篇里那个分叉:你的页面产物,究竟是「代码」还是「运行时 schema」。


合流:两个点是同一种病

现在把两半场并起来看,那句主线就立住了:

Intake(P12)Round-trip(P23)
边界人的意图(散文)→ 机器规格机器规格 ↔ 代码
方向单向摄入双向同步
病症静默编造缺失的逻辑静默漂移 / 覆盖手改
根因信息不在输入里(欠约束)表达空间不对等(不可双射)
把缺口显式成问题把可同步边界显式标注

它们共享同一个母题:每当同一份信息要跨越一个表示边界,边界上一定有信息损耗或歧义;不负责任的系统假装它不存在(猜、或悄悄覆盖),负责任的系统把它显式地摆到台面上(问、或标注)。 这也正是整个系列反复出现的那条线——confidenceprovenance、单一指标会骗你、自动 vs 人工的边界——全都是同一件事的不同侧面:显式化你不知道的东西。

预设反对:「等模型更强,这俩不都自动了?」

会缓解一部分,但根都拔不掉

  • intake:模型再强也读不出 PRD 里没写的东西。它只会更擅长编造——反而更危险,因为编得更像真的。完备性矩阵和「缺口即问题」的结构,任何模型强度下都省不掉。
  • round-trip:代码表达空间大于 schema 这个数学事实,不随模型变强而改变。反向变换的不可能性是结构性的,不是能力性的。

能力提升能移动边界的位置,但消不掉边界本身。 承认这一点,才不会在错误的方向上投入。


三条可迁移的设计律

跳出搭建系统,这两处流血教给我们的,能用在任何跨表示的系统上:

  1. 在信息会丢失的边界上,让「丢了什么」成为一等产出。 intake 输出问题清单,round-trip 输出 roundTrip 标注——都是把损耗显式化,而不是掩盖。
  2. 面对「同一真相的两个可编辑副本」,先定源头,再谈同步。 要么单一源头(运行时 schema,B),要么接受部分/最终一致并显式 merge(保护区,C)。假装两边都是源头且永远一致,是一切同步噩梦的开端。
  3. 区分「能力问题」和「结构问题」。 能力问题(信息难提取)会随模型变强消失;结构问题(信息不存在、表达空间不对等)不会。把判断力和工程投入押在结构问题上——那才是护城河所在。

写在最后:五篇走到这里,越挖越会发现——从设计稿到生产页面的全部难度,几乎都能归到一句话:把隐性的、跨边界会丢失的信息,显式化成可校验、可追溯、可演进的结构。 intake 显式化「PRD 没说的」,round-trip 显式化「代码与 schema 对不上的」。工具会一直变强,但「诚实地面对信息在哪里丢失」这件事,永远是工程师而不是模型的责任。