物料搭建 × AI Harness:一套完整表达生产级页面的协议族设计

本系列的规范篇。为「基于物料的 UI 还原 + AI Harness 控制的业务逻辑还原」这套架构,负责任地穷举出需要的全部协议/Schema——分六层、23 个协议,共享一个 Envelope、彼此用 id 交叉引用,逐一给出结构、关键字段与含义。目标是让这套协议族能完整表达复杂场景与真实应用页面,逼近生产运行表现;同时诚实标注它的边界与未解难点。

这是系列第四篇,也是「规范篇」。前三篇分别讲了出码的根本难题物料搭建的问题地图控制 AI 的 harness 设计。现在把它们收口成一个可落地的问题:要让这套架构完整表达各种复杂页面、逼近生产表现,我到底需要设计哪些协议和 Schema? 我会负责任地把这套协议族穷举出来——结构、字段、含义,一个不落;也会诚实标注它的边界。

一句话主线

完整表达一个生产级页面,靠的不是一个大 Schema,而是一套分层、共享同一个 Envelope、彼此用 id 交叉引用的协议族——覆盖「词汇 → 物料 → 结构 → 数据与逻辑 → 控制 → 产物」六层。哪一层的协议缺失或表达力不足,页面就会在那一层退回成「死页面」或「不可控产物」。

所以判断这套协议族「够不够」的标准,不是字段多不多,而是:上一篇讲的「活页面 5 层」(视觉 / 布局 / 数据 / 逻辑 / 边界)有没有被无缝覆盖。 文末我会用这个标准回过头验收。


先立一个共享地基:Envelope(信封)

穷举协议之前,先解决一件让整套协议族「成为一族」而不是「一堆散 JSON」的事——每一个协议实例都包在同一个 Envelope 里。 它承载所有跨协议通用的元信息,尤其是前一篇反复强调的 confidenceprovenance

// Envelope —— 所有协议实例的统一外壳
{
  "id":          "cmp_7f3a...",      // 全局唯一 id,跨协议引用的锚点
  "kind":        "ir.composition",   // 协议类型,形如 <层>.<名>,见下方目录
  "specVersion": "1.4.0",            // 该协议的语义化版本(semver)
  "confidence":  0.82,               // 0~1,此实例的可信度;人工确认后=1.0
  "provenance":  "model",            // 来源:model|human|api|design|rule|mixed
  "createdBy":   "material-matcher", // 产出它的 agent / 工具 / 人
  "refs":        ["blk_12", "mat_card_v2"], // 它引用了哪些其它实例的 id
  "extensions":  {},                 // 逃生舱:厂商/业务自定义扩展,不污染主 schema
  "payload":     { }                 // 具体协议内容,各协议在此展开
}

三个设计承诺,贯穿全族:

  • id + refs = 用引用代替嵌套。 协议之间不深度内联彼此,而是互相引用 id。这让每个产物可独立校验、独立回退、独立缓存——正是上一篇「可回退存档点」的物理实现。
  • confidence + provenance = 不确定性是一等公民。 每一条信息都知道自己「有多可信、从哪来」,人机门和防幻觉才有据可依。
  • extensions = 演进的逃生舱。 任何未预见的业务字段先进 extensions,稳定后再提升为一等字段。没有逃生舱的 schema 一定会在真实业务里被撑爆。

下面按六层展开 23 个协议。每个给:用途结构关键字段含义


L0 · 词汇层:所有协议共享的「名词表」

页面里一切引用的最底层符号。它们不描述页面,而是定义「说页面时能用哪些词」。

P1 · Design Token Protocol(设计令牌)

用途:把颜色、间距、字体等设计原子符号化,让所有上层协议引用 token 而非硬编码值——这是「高还原」和「可换肤」的共同前提。

{ "kind": "token.set", "payload": {
  "color":   { "brand.primary": "#2F6BFF", "text.body": "#1A1A1A" },
  "space":   { "sm": 8, "md": 16, "lg": 24 },          // 间距刻度
  "font":    { "body": { "family": "Inter", "size": 14, "line": 22 } },
  "radius":  { "card": 12 }, "shadow": { "card": "0 2 8 rgba(0,0,0,.08)" },
  "zIndex":  { "modal": 1000 }, "motion": { "fast": "150ms ease-out" },
  "breakpoint": { "sm": 640, "md": 1024, "lg": 1440 }  // 响应式断点
}}

关键点:token 是命名引用color.brand.primary),不是值。上层所有颜色/间距都写 token 名,换主题只换这一张表。

P2 · Semantic Ontology(语义本体 / 类型注册表)

用途:定义一套受控的语义标签词汇(「商品卡片」「主操作按钮」「筛选区」),供物料声明自己「是什么」、供匹配器检索。没有受控词汇,语义匹配就是各说各话。

{ "kind": "onto.registry", "payload": {
  "categories": ["navigation","dataDisplay","dataEntry","feedback","layout","business"],
  "semanticTags": {
    "product.card":   { "extends": "dataDisplay", "expects": ["title","price","image"] },
    "action.primary": { "extends": "dataEntry" }
  }
}}

expects 声明这个语义通常携带哪些字段——它同时是匹配的线索和数据绑定的提示。


L1 · 物料层:整套架构的核心资产

物料是搭建的原子。Material Manifest 是整个协议族里最重的一个协议——物料对外的一切能力、约束、可配置性,都在这里声明。

P3 · Material Manifest Protocol(物料契约)★核心

用途:一个物料对「人」和「搭建系统/AI」的完整自我描述。它决定了这个物料能不能被检索、被配置、被合法组合、被数据驱动。

{ "kind": "material.manifest", "id": "mat_product_card", "specVersion": "2.1.0",
  "payload": {
    "name": "ProductCard",
    "semanticTags": ["product.card"],          // ← 引用 P2,供匹配
    "category": "business",
    "granularity": "molecule",                 // atom|molecule|block|template,粒度

    "props": {                                  // 可配置面(对外暴露的参数)
      "title":   { "type": "string", "required": true, "bindable": true },
      "price":   { "type": "number", "bindable": true, "format": "currency" },
      "variant": { "type": "enum", "options": ["default","compact"], "default": "default" }
    },

    "slots": {                                  // 插槽:能放哪些子物料
      "footer": { "accepts": ["action.primary","action.secondary"], "max": 2 }
    },

    "events": {                                 // 对外抛出的事件(逻辑层的挂载点)
      "onClick":     { "payload": "{ id: string }" },
      "onFavorite":  { "payload": "{ id: string, next: boolean }" }
    },

    "states": ["default","hover","loading","disabled","empty","error"], // 状态覆盖
    "variants": ["default","compact"],

    "composition": {                            // 组合契约:谁能嵌我 / 我能嵌谁
      "allowedParents": ["list.grid","layout.section"],
      "forbiddenParents": ["dataEntry.form"]
    },

    "responsive": { "ownsBreakpoints": false }, // 响应式责任归属(自己管/外层管)
    "tokensUsed": ["color.brand.primary","space.md","radius.card"], // 消费的 token
    "a11y": { "role": "article", "focusable": true },
    "dataContract": {                           // 该物料期望的数据形状(对接数据绑定)
      "shape": { "id":"string","title":"string","price":"number","image":"url" }
    },
    "deprecations": [],                         // 废弃/迁移信息,支撑版本演进
    "preview": "asset://mat_product_card/thumb.png"
  }}

含义要点props.bindable 标记哪些字段可被数据绑定(P10 会用);slots.accepts 引用语义标签而非具体物料 id,保持解耦;composition 就是上一篇「组合契约」的落地;dataContract 是物料与数据层握手的接口。这一个协议同时服务了匹配、组合、绑定、逻辑挂载、版本治理五件事。

P4 · Catalog / Registry Protocol(物料目录)

用途:把成百上千个 Manifest 组织成可检索、可版本化的目录,供 catalog_search 工具(上一篇)查询。

{ "kind": "catalog.index", "payload": {
  "materials": [ { "id":"mat_product_card", "latest":"2.1.0",
                   "versions":["1.0.0","2.0.0","2.1.0"],
                   "visualEmbedding":"vec://...", "semanticTags":["product.card"] } ],
  "coverageStats": { "byCategory": { "business": 142, "layout": 30 } }
}}

visualEmbedding 支撑视觉检索,coverageStats 支撑上一篇的「覆盖率度量」。


L2 · 结构还原层:从设计稿到页面骨架

这一层是流水线的 IR 链(上一篇的 artifact 1→4),把「一张图」逐级精化成「一棵带布局的组件树」。

P5 · Design Source Protocol(设计输入)

用途:把设计来源归一化,屏蔽「截图 / Figma / Sketch」的差异。

{ "kind": "ir.designSource", "payload": {
  "type": "figma",                    // figma|screenshot|sketch
  "artboards": [ { "id":"ab_1", "viewport":{"w":1440,"h":900},
                   "raster":"asset://ab_1.png",
                   "nativeTree": { } } ]   // 若有源文件,携带原生层级(信息量远大于截图)
}}

诚实提示:type=screenshotnativeTree 为空,后续全靠视觉推断,保真度天花板明显更低。输入的信息量,从这里就决定了下限。

P6 · Layout Tree Protocol(视觉分解树)

用途:分块器的产物——把设计切成带 bbox、嵌套、语义猜测的块。

{ "kind": "ir.layoutTree", "payload": {
  "blocks": [ { "id":"blk_12", "bbox":[24,80,392,520], "parent":"blk_root",
                "children":["blk_13"], "ocrText":["iPhone 15","¥5999"],
                "semanticGuess":"product.card", "confidence":0.78 } ] }}

P7 · Binding Map Protocol(选料图)

用途:匹配器产物——每个块 → 物料候选 + 置信度 + 是否需人工。

{ "kind": "ir.bindingMap", "payload": {
  "bindings": [ { "block":"blk_12",
     "candidates":[ {"material":"mat_product_card","version":"2.1.0","score":0.86},
                    {"material":"mat_media_card","score":0.61} ],
     "chosen":"mat_product_card", "confidence":0.86, "needsHuman":false } ] }}

P8 · Composition Tree Protocol(组合树 / 页面结构 DSL)★核心

用途:这是页面的静态骨架的最终形态——组件实例的树,含解析后的布局模型和 props。L3 的所有数据/逻辑都挂在它的节点上。

{ "kind": "ir.composition", "id":"page_home", "payload": {
  "root": {
    "node":"n_root", "material":"layout.section",
    "layout": { "model":"flex", "direction":"column", "gap":"space.lg" }, // 引用 token
    "children": [
      { "node":"n_grid", "material":"list.grid",
        "layout": { "model":"grid", "cols":{"sm":1,"md":3}, "gap":"space.md" }, // 响应式
        "repeat": { "over":"$.products", "as":"item" },   // ← 列表渲染,绑数据源
        "children": [
          { "node":"n_card", "material":"mat_product_card", "version":"2.1.0",
            "props": { "title":"$item.title", "price":"$item.price" }, // 绑定表达式
            "slots": { "footer":[ {"node":"n_btn","material":"action.primary"} ] } }
        ] } ] }
}}

关键设计repeat.over 用表达式引用数据(解决「长列表」——上一篇 pix2code 的老大难);props 值可以是字面量或 $ 绑定表达式,把静态结构和动态数据在同一棵树上缝合;layout.cols 按断点取值,把响应式编码进结构。这棵树是整个页面的「主干」,其余协议都是挂在它上面的枝叶。


L3 · 数据与逻辑层:让页面「活」起来(第三输入的落地)

这是上一篇反复强调的命门——信息不在设计稿也不在物料,必须由专门协议显式承载。这一层最关键,也最难。

P9 · Data Contract Protocol(接口契约)

用途:把后端接口归一化(通常从 OpenAPI/Swagger 经 MCP 导入),供数据绑定引用。

{ "kind": "data.contract", "payload": {
  "endpoints": [ { "id":"ep_products", "method":"GET", "path":"/api/products",
    "query": { "page":"number", "keyword":"string" },
    "response": { "shape": { "list":[{"id":"string","title":"string","price":"number"}],
                             "total":"number" } },
    "errors": [ {"code":401,"meaning":"unauth"}, {"code":500,"meaning":"server"} ] } ] }}

errors 是常被忽略却对边界态至关重要的字段——P14 要靠它。

P10 · Data Binding Protocol(数据绑定)

用途:把组合树节点的 bindable props / repeat 源,映射到接口字段,含转换。

{ "kind": "logic.dataBinding", "payload": {
  "sources": [ { "id":"src_products", "endpoint":"ep_products",
                 "params": { "page":"$state.page", "keyword":"$state.kw" },
                 "trigger":"onMount|onParamChange" } ],
  "bindings": [ { "target":"n_grid.repeat.over", "expr":"$src_products.list" },
                { "target":"n_card.props.price", "expr":"item.price",
                  "transform":"currency('CNY')" } ] }}

transform 处理格式化/单位换算等最常见的「字段对不上」问题。

P11 · State Model Protocol(状态模型)★核心

用途:定义页面的状态原子、派生状态、作用域。跨物料联动的本质是共享状态,所以这个协议是联动能成立的基础。

{ "kind": "logic.state", "payload": {
  "atoms": [ { "id":"page", "scope":"page", "initial":1 },
             { "id":"kw",   "scope":"page", "initial":"" },
             { "id":"selectedCity", "scope":"page", "initial":null } ],
  "derived": [ { "id":"canSubmit", "expr":"form.valid && !submitting" } ], // 派生态
  "scopes": ["global","page","subtree","local"] }}

P12 · Interaction Spec Protocol(交互规格)★核心 / 最难

用途:把 PRD 里的业务规则结构化成「触发-守卫-动作」条目。由 intake agent 抽取、人工确认后生效(上一篇的第三输入入口二)。

{ "kind": "logic.interaction", "payload": {
  "rules": [ {
    "id":"r_submit",
    "trigger": { "node":"n_submitBtn", "event":"onClick" },
    "guard":   "$derived.canSubmit",                    // 守卫条件
    "actions": [
      { "type":"call", "endpoint":"ep_createOrder", "payload":"$form.values" },
      { "type":"onError",   "do":[ {"type":"keepDraft"}, {"type":"toast","msg":"提交失败"} ] },
      { "type":"onSuccess", "do":[ {"type":"navigate","to":"/order/$resp.id"} ] }
    ],
    "provenance":"prd#3.2", "confidence":0.6, "needsHuman":true } ] }}

每条规则携带 provenance(追溯到 PRD 第几节)与 confidence——这是让「AI 从散文里抽出来的逻辑」可审计的关键

P13 · Logic Graph Protocol(逻辑编排图)

用途:把散落的交互规则、数据源、状态编译成一张可执行、可分析的有向图(事件→动作→状态变更→再触发)。它是 P10~P12 的「编译产物」,用于检测环、竞态、悬空引用。

{ "kind":"logic.graph", "payload": {
  "nodes":[ {"id":"g_click","type":"event"}, {"id":"g_call","type":"effect"},
            {"id":"g_page","type":"state"} ],
  "edges":[ {"from":"g_click","to":"g_call"}, {"from":"g_call","to":"g_page"},
            {"from":"g_page","to":"src_products.refetch"} ], // 联动在图上显式成边
  "diagnostics": { "cycles":[], "danglingRefs":[] } }}

把「联动」画成图上的边——这样跨物料联动就从「隐式约定」变成了「可检查的显式结构」。

P14 · Boundary & Lifecycle Protocol(边界态与生命周期)

用途:为每个数据依赖区域声明 loading/empty/error/权限的兜底——上一篇说的「占一半工作量却没人画」的部分。

{ "kind":"logic.boundary", "payload": {
  "regions": [ { "target":"n_grid", "source":"src_products",
    "loading": { "material":"feedback.skeleton" },
    "empty":   { "when":"$src_products.list.length===0", "material":"feedback.empty" },
    "error":   { "byCode": { "401":{"action":"redirectLogin"},
                             "default":{"material":"feedback.errorRetry"} } },
    "timeoutMs": 8000 } ] }}

error.byCode 直接消费 P9 的 errors——协议间的引用闭环在这里体现。

P15 · Routing / Navigation Protocol(路由导航)

用途:多页流转、传参、进入守卫(权限)。

{ "kind":"nav.routing", "payload": {
  "routes":[ { "path":"/order/:id", "page":"page_orderDetail",
               "params":{"id":"string"}, "guard":"$auth.isLogin" } ],
  "transitions":[ { "from":"page_home", "to":"/order/:id", "carry":["selectedItem"] } ] }}

L4 · 控制层:Harness 如何被配置与约束

这一层不描述页面,而是描述「AI 怎么被组织去生产页面」——上一篇 harness 的协议化。

P16 · Agent / Skill Manifest(SKILL.md 的形式化)

{ "kind":"harness.skill", "payload": {
  "name":"material-matcher", "role":"把 block 匹配到物料",
  "inputs":["ir.layoutTree"], "output":"ir.bindingMap",
  "tools":["catalog_search","visual_embed","token_resolver"],
  "constraints":["material_id 必须来自工具返回","confidence<0.75 → needsHuman"],
  "model":{"tier":"vision","effort":"medium"} }}

P17 · Tool Contract(工具契约)

即上一篇的 function-calling schema,标准化 name/description/input_schema/output_schema/sideEffectssideEffects 标记工具是否有副作用(只读 vs 会改状态),供编排层决定能否并发/重试。

P18 · Confidence & Provenance(可信度与溯源规范)

用途:跨切面统一「可信度怎么算、来源怎么标、阈值怎么定」。它不单独产出实例,而是规定 Envelope 里那两个字段的取值语义与合成规则(如:一个产物的 confidence = 其依赖产物 confidence 的下界)。把它单列为协议,是因为「不确定性怎么传播」必须全族统一,否则门就形同虚设。

P19 · Gate / HITL Protocol(校验门与人机门)

{ "kind":"harness.gate", "payload": {
  "gates":[ { "after":"ir.bindingMap", "type":"confidence",
              "rule":"confidence < 0.75 || needsHuman",
              "onFail":"routeToHuman", "ui":"materialPicker" },
            { "after":"logic.interaction", "type":"human", "rule":"always" } ] }}

逻辑规格默认 always 过人工——因为它的幻觉代价最高。

P20 · Verification Report(多维校验报告)

用途:呼应 Design2Code——分维度报,绝不合成总分

{ "kind":"harness.verifyReport", "payload": {
  "visual":  { "score":0.94, "method":"clip+ssim" },
  "element": { "recall":0.88, "missing":["blk_31"], "misplaced":["blk_12"] },
  "logic":   { "passed":7, "failed":1,
               "failures":[ {"rule":"r_submit","reason":"未发出 POST /order"} ] },
  "verdict": "fail", "blame":"logic.interaction#r_submit" }}   // 定位坏环,供回修

logic 维度靠可执行断言得出(点提交是否真的发请求)——逻辑靠执行来验,不靠观看来验。

P21 · Feedback / Correction Protocol(反馈回流)

用途:把人工在门处的每次修正,结构化记录,回灌物料目录与 few-shot 样本库,让系统越用越准。

{ "kind":"harness.correction", "payload": {
  "at":"ir.bindingMap", "block":"blk_12",
  "modelChose":"mat_media_card", "humanChose":"mat_product_card",
  "signal":"negative", "reuseAs":"fewshot" }}

L5 · 产物层:可运行与可回改

P22 · Page Runtime Schema(页面运行时 Schema)

用途:把 L2~L3 的所有 IR 编译成一个自洽、可被运行时直接渲染的序列化页面(若走 schema-driven 运行时路线)。它是 Composition Tree + DataBinding + Logic + Boundary + Routing 的合并投影,去掉了所有 confidence/候选等「过程信息」,只保留「运行所需」。它是「过程 IR」与「运行产物」的分水岭。

P23 · Source Map / Round-trip Protocol(溯源映射 / 可回改)

用途:解决上一篇点名的低代码头号顽疾——产物(代码或 schema)与 IR 之间的双向映射。每段产出的代码/schema 片段,记录它源自哪个 IR 节点(node id),使得「手改了代码能否同步回搭建器」成为可能。

{ "kind":"output.sourceMap", "payload": {
  "mappings":[ { "outputRange":"OrderPage.tsx:40-58", "irNode":"n_card",
                 "editable":true, "roundTrip":"structural" } ] }}

roundTrip 诚实标注每处的回改能力:structural(结构改动能回流)/ props-only(只能回流属性)/ none(手改后脱管)。把「哪些能回改、哪些不能」显式化,比假装全都能回改更负责任。


用「活页面 5 层」验收覆盖度

回到主线定的验收标准——这套协议族到底盖没盖全?

活页面的层由哪些协议承载覆盖
① 视觉物料P1 Token · P3 Manifest · P4 Catalog
② 布局关系P6 LayoutTree · P8 Composition(layout/响应式)
③ 数据P9 Contract · P10 Binding · P11 State
④ 逻辑/交互P12 Interaction · P13 LogicGraph · P15 Routing
⑤ 状态/边界P14 Boundary · P11 State(派生态)
(横切)不确定性与控制P16–P21 + Envelope
(横切)可运行 & 可回改P22 Runtime · P23 SourceMap

五层都有归属,且横切的「可控」与「可维护」也各有协议。这才敢说「完整表达」。


负责任地说:这套协议族的边界

穷举了 23 个协议,但我不想给你一个「银弹」的错觉。诚实标注四条边界:

  1. 这是一套自洽的设计提案,不是经过生产打磨的标准。 真实落地一定会在 P8/P12/P13(结构与逻辑)反复迭代——那是复杂度真正聚集的地方。字段会增、语义会变,extensions 逃生舱就是为此准备的。
  2. 有几块我刻意留成了「接口占位」而非展开:动效/过渡(设计稿天然缺失,需独立的 motion 协议)、实时/推送(WebSocket 状态流)、离线/乐观更新、埋点与实验分流。它们都能作为新协议挂进这个 Envelope 体系,但每一块都值得单独一篇。
  3. 协议能表达 ≠ AI 能自动填对。 协议只是「能被正确表达」的容器;L3 逻辑层的内容能否被自动生成,取决于第三输入的质量——协议解决的是「表达力」,不解决「信息从哪来」。这两件事不能混为一谈。
  4. 完备性是渐近的,不是一次到位的。 判断这套东西成不成熟,不看协议数量,看它能不能在不破坏已有页面的前提下,通过加协议/加字段吸收新场景。可演进性,才是生产级的真正门槛。

三条可迁移的设计律

抽掉具体字段,真正能带走、用到任何复杂系统建模上的,是这三条:

  1. 先定 Envelope,再定协议。 让「跨协议通用的东西」(id、版本、可信度、溯源、扩展位)只定义一次。共享地基决定了这堆 schema 是「一族」还是「一堆」。
  2. id 引用代替深度嵌套。 引用式结构换来的是可独立校验、可回退、可缓存、可定位坏环——这些工程属性远比「一个大 JSON 看起来完整」值钱。
  3. 把不确定性和可回改性做成一等协议(P18、P23),而不是事后打补丁。一个系统对「我不确定」和「我这里能不能改回去」的表达能力,决定了它离生产有多远。

写在最后:四篇连起来是一条完整的路——出码的根本难题搭建的问题地图控制 AI 的 harness → 用协议族把它完整表达出来。而所有协议归根结底在做同一件事:把一个「活页面」里那些看不见的信息——组合约束、数据来源、业务逻辑、边界兜底、不确定性——从「藏在人脑和 PRD 里」变成「显式、可校验、可演进的结构」。 页面从设计稿到生产的距离,本质就是这些隐性信息被显式化的程度。协议,就是显式化的语言。