实验结果展示数据契约设计规格
| 字段 | 值 |
|---|---|
| 版本 | v0.2.0 |
| 状态 | Accepted |
| 创建日期 | 2026-06-12 |
| 最后更新 | 2026-06-12 |
| 作者 | xiang.li |
| 前置 | 前端仿真洞察工作台设计规格 |
变更历史
| 版本 | 日期 | 变更说明 |
|---|---|---|
| v0.2.0 | 2026-06-12 | 行形态新增列筛选(in 多选)语义,新增消费者"筛选候选值发现"绑定列形态 |
| v0.1.0 | 2026-06-12 | 初版(Accepted) |
@tbl-spec-rdc-01 文档变更历史
概述
本 spec 冻结实验结果的展示数据契约:后端以三种数据形态(行形态分页、列形态投影、单行全量)向前端供给实验结果,每个前端消费者按其真实数据需求绑定唯一形态。契约覆盖端点语义、字段寻址协议、列发现机制和规模边界,是结果管理页面所有数据交互的验证基准。
本 spec 同时修正前置 spec《前端仿真洞察工作台设计规格 v0.1.0》"既有汇总指标、Gantt、实验管理的 API 契约不变"的表述:实验结果读取契约以本 spec 为准,该句不再成立(详见 集成点 节)。
背景
实验结果行 = 标量指标列(吞吐、利用率、成本、并行度等,已提升为数据库表字段)+ 大型 JSON 字段(配置快照、完整结果、搜索统计)。展示侧存在六类消费者,但历史上只有两种供给方式——"全量行"与"分页行"——导致两类问题:
- 列式需求被行式供给放大传输量:参数分析、Pareto 前沿、CSV 导出需要的是"全部行 × 少数字段"(数千行 × 几个标量 ≈ 数百 KB),实际拉取的是"全部行 × 全字段"(每行配置快照数 KB,合计数十 MB),浪费约两个数量级。
- 契约无冻结文档导致实现漂移:2026-06 的分页迁移中,后端、API 层、视图层各自处于不同迁移进度且无 single source of truth 可对照,结果管理页面在中间状态下功能断裂(任务列表无法获取数据)。
用户实验规模常态为单实验数千行结果、实验数量多,列式消费者的传输浪费是常态成本而非边缘场景,需要契约层面解决。
名词定义
| 名词 | 定义 | 与易混淆概念的区分 |
|---|---|---|
| 结果行 | 一次评估任务的持久化记录:标量指标 + 配置快照 + 完整结果 JSON | 不同于"任务"(运行时概念);结果行是任务完成后的不可变快照 |
| 行形态 | 按页返回完整结果行的供给方式,服务表格类消费者 | 行内含配置快照,与"单行全量"的区别是按页批量、不含完整结果 JSON 的全部明细 |
| 列形态 | 按请求字段列表对全部结果行做投影、按列组织返回的供给方式 | 不返回未请求的字段;行数全量但字段子集 |
| 单行全量 | 按结果行主键返回该行全部字段(含完整结果 JSON)的供给方式 | 端点每次返回 1 行;对比场景由消费者按 N 个选中主键调用 N 次 |
| 实验默认排序 | 实验类别约定的默认排序键的降序(部署类实验为综合评分,通信评估类实验为创建时间) | 列模板取样与列形态行序的共同基准;结果集不变时行序稳定 |
| 列筛选 | 以"字段路径 + 值集合"约束行形态结果集的服务端过滤,命中任一值的行保留(in 语义) | 作用于实验全部行而非当前页;与排序、分页可叠加,total 为筛选后计数 |
| 字段路径 | 以 <域>:<路径> 寻址结果行内字段的字符串协议,域 ∈ {标量列, config, result},路径为点分隔层级 | 同一协议同时用于排序键与投影字段,语义一致 |
| 列模板 | 实验内按默认排序取首行序列化得到的结构样本,供前端做列发现 | 不是 schema 定义;是结构样本,异构实验下可能不完整(见 局限 节) |
| 消费者 | 读取结果数据的前端功能单元(任务列表、参数分析、Pareto 前沿、CSV 导出、详情/对比、实验列表) | — |
目标与非目标
目标:
- 冻结三形态供给契约:每个消费者绑定唯一数据形态,形态间职责不重叠。
- 冻结字段路径协议:投影与排序共用同一寻址语法和校验规则。
- 冻结列发现机制:列模板的来源、稳定性保证与失效边界。
- 规模承诺:单实验 5000 行下,行形态与列形态请求的响应体 ≤ 2 MB、端到端响应 ≤ 1 s(单行全量不在此承诺内,单独预算见 非功能性需求 节)。
非目标:
- 不改变结果行的存储结构(不做配置快照去重、不拆表)。
- 不将分析计算(Pareto 支配判定、参数聚合)下推后端,前端保留计算职责。
- 不覆盖写入路径(结果入库、实验创建)与任务运行时状态(进度、取消)。
- 不定义事件级 trace 数据出口(归《前端仿真洞察工作台设计规格》Wave 2)。
用例说明
用例 1:打开实验详情(行形态)。用户从实验列表点入一个 5000 行的实验。前端先取实验元信息(名称、计数),再取第 1 页结果行(默认每页 100 行)与列模板。表格按列模板确定列集,用户点击"吞吐"列头,前端以字段路径作为排序键重新请求第 1 页,后端在全部 5000 行上排序后返回前 100 行。
用例 2:参数分析(列形态)。用户切到参数分析 Tab,从列模板派生的轴候选中选 X 轴 = 配置中的 batch 维度、Y 轴 = 吞吐。前端以两个字段路径请求投影,后端返回 5000 行 × 2 字段的列式数组(数十 KB),前端绘制散点。切换轴时仅重新请求新字段。
用例 3:跨页对比(单行全量)。用户在第 3 页勾选 2 行、第 7 页勾选 1 行,点击对比。前端按 3 个结果行主键逐一请求单行全量,获得完整结果 JSON(含 Gantt、内存明细)进入对比视图。
用例 4:CSV 导出(列形态)。用户配置了 18 个可见列后点导出。前端把 18 个可见列翻译成字段路径列表请求投影,后端返回全量行 × 18 字段,前端拼装 CSV 下载。导出内容与表格可见列一致。
用例 5:列筛选(行形态 + 列形态协同)。用户点开"算法"列头的筛选器,前端以该列的字段路径请求列形态投影(全量行 × 1 字段),去重后展示候选值勾选框(如 dmodk / ring / halving-doubling)。用户勾选两个值,前端把筛选条件附加到行形态请求,后端在全部行上过滤后分页返回,total 变为筛选后计数,页码回到第 1 页。再叠加点击"延迟"列头排序,排序在筛选结果集内进行。清除筛选后恢复全量。
详细设计
概念模型
供给侧三形态与消费侧六消费者的绑定关系是本契约的核心:
| 消费者 | 绑定形态 | 行范围 | 字段范围 |
|---|---|---|---|
| 实验列表 | 元信息(实验级,非结果行) | — | 实验 meta + 结果计数 |
| 任务列表表格 | 行形态 | 单页(≤500) | 标量 + 配置快照(不含完整结果明细) |
| 参数分析 | 列形态 | 全量 | 请求的轴字段 + 指标字段 |
| Pareto 前沿 | 列形态 | 全量 | 成本 + 性能两类标量 |
| CSV 导出 | 列形态 | 全量 | 用户可见列对应字段 |
| 筛选候选值发现 | 列形态 | 全量 | 被筛选的单字段(复用列形态端点投影单字段,前端去重,无独立去重端点) |
| 任务详情 / 对比 | 单行全量 | 1~N(用户选中) | 全字段含完整结果 JSON |
@tbl-spec-rdc-02 消费者与数据形态绑定
绑定是排他的:消费者不得越形态取数(例如参数分析不得退回拉全量行)。新增消费者时必须先在本表登记绑定形态。
设计原理
为什么是三形态而不是一个通用查询接口:六个消费者的(行范围 × 字段范围)组合只聚成三类——"页 × 全字段"、"全量 × 字段子集"、"单行 × 全字段"。三个专用形态各自语义封闭、可独立验收;通用查询接口(任意行 × 任意字段)表达力超出需求,校验面与误用面更大。
为什么列形态做在服务端投影而不是前端缓存全量:实验内各行配置快照仅在扫描维度上不同、其余内容相同,全量行传输的字节大部分是重复配置;服务端在数据库层按 JSON 路径抽取字段,传输量与"行数 × 请求字段数"成正比,与配置体积无关。规模常态为数千行时,这是数量级差异。
为什么排序在服务端:行形态只持有当前页,客户端排序只能排页内数据,语义错误。排序键与投影字段共用字段路径协议,避免两套寻址语法漂移。
为什么列发现用结构样本而不是 schema 注册:结果行的配置结构由评估配置决定,随评估功能演进而变化;维护显式 schema 注册表会产生第二个需要同步的事实源。取实验内固定首行作结构样本,零维护成本,代价是异构实验下样本可能不完整(见 局限 节)。
字段路径协议
字段路径是唯一的字段寻址语法,排序与投影共用:
- 语法:
<域>:<路径>或裸标量列名。域config寻址配置快照,域result寻址结果指标;路径为点分隔的层级键。 - 校验:分两段——域前缀必须命中域白名单(config / result,或裸标量列名命中排序白名单);剥离域前缀后的路径段必须匹配
^[A-Za-z0-9_.]+$。任一段不通过则整个请求拒绝(HTTP 400),禁止静默忽略。该约束同时是 SQL 注入的防线:路径经数据库 JSON 抽取函数参数化执行,禁止字符串拼接。完整文法以 附录 C 为权威定义。 - 缺失语义:语法合法但某行不存在该路径时,该行该字段取
null(异构行是合法状态);用于排序时null排在序列末端。 - 白名单:裸标量列名必须在服务端排序白名单内,白名单外拒绝(400)。白名单的语义是"真实存在的表列集合"(裸列名直接进入排序子句,必须封闭),应从数据模型自动派生,不维护手写清单。
- 零维护演进:新增 config 参数或 result 指标不需要任何白名单变更——
config:/result:路径仅做字符集校验 + 参数化抽取,新路径即写即用,旧行按缺失语义返回null。
三形态端点语义
行形态(分页):输入实验 ID、页码、页大小(上限 500)、排序键(字段路径)、排序方向、列筛选集合(可选)。输出当前页结果行数组、总行数(筛选后计数)、列模板。结果行含标量指标与配置快照;排序与筛选均作用于实验内全部行,处理顺序为先筛选、后排序、再分页。
列筛选语义:
- 每条筛选 = 字段路径 + 值集合,行的该字段值命中集合中任一值则保留(in 语义);多条筛选之间为与(AND)关系。
- 值集合元素必须为标量(数值 / 字符串 / 布尔);非标量值拒绝(400)。
- 字段寻址与校验完全复用字段路径协议——可排序的字段即可筛选,不引入第二套寻址。
- 缺失处置:行内不存在该字段路径时该字段为 null,不命中任何 in 值,故该行被过滤。这与排序中 null 保留并排末端是两种不同的结果处置——二者同源于同一缺失检测(字段路径不存在即 null),但处置方向相反:筛选删、排序留。
- 候选值由消费者经列形态投影该字段后去重得到(见 概念模型 绑定表),契约不提供独立的去重端点。
- 一期仅 in 操作符;其它 op 值拒绝(400)。数值区间筛选见 后续工作。
列形态(投影):输入实验 ID、字段路径列表(1~64 个)。输出按请求顺序的字段名数组 + 等长的列式值数组(行数 = 实验全量行数),行序固定为实验默认排序(见 名词定义)。不分页:列形态的目的就是全量小字段,分页反而破坏分析语义。本端点取代《实验数据库页面性能优化 plan》中"全量行 metrics 端点"的契约。
单行全量:输入结果行主键。输出该行全部字段,含完整结果 JSON。不存在时 404。
实验元信息:输入实验 ID。输出实验 meta 与结果计数,不含任何结果行。
约束与规则
- 形态排他(硬约束):消费者越形态取数视为契约违例,review 阶段拒绝。
- 页大小上限 500(硬约束):防止行形态退化为全量端点;默认页大小 100,500 是硬上限而非默认值。
- 投影字段数上限 64(硬约束):防止列形态退化为全量行端点。
- 筛选条数上限 16、单条值集合上限 64(硬约束):防止筛选参数膨胀为任意查询语言;超限拒绝(400)。该 64 与投影字段数上限 64 是相互独立的约束,数值巧合,无耦合。
- 列模板稳定性(硬约束):同一实验内列模板不随页码、排序参数变化,保证表格列集不因翻页时隐时现。
- 错误显式化(硬约束):非法排序键、非法字段路径、越界页大小一律 400 带原因,禁止降级到默认行为(与项目配置加载禁默认值规则同源)。
集成点
- 上游:结果行由评估管线写入,本 spec 不约束写入格式,但消费"标量列已提升为表字段"这一存储事实——新增需排序/投影的高频标量时,应提升为表字段而非留在 JSON 内(性能边界见 非功能性需求 节)。
- 下游:前端结果管理页面的全部数据请求按本契约实现;前端 API 层是契约的唯一翻译点,视图组件不直接感知端点形状。
- 对前置 spec 的修正:《前端仿真洞察工作台设计规格 v0.1.0》"既有汇总指标、Gantt、实验管理的 API 契约不变"中"实验管理"部分由本 spec 取代;该 spec 其余内容(trace 协议、WebSocket 信封、Wave 路线)不受影响。修正以该 spec 附录 B 实现说明 + 本 spec 本节双向链接固定。
引用
- 项目内:前端仿真洞察工作台设计规格、实验数据库页面性能优化 plan(已归档,本 spec 将其中已实现的契约决策升格冻结)。
- 业界:见 附录 A。
备选方案
| 维度 | 方案 A:三形态分层(选定) | 方案 B:全量行 + 前端缓存 | 方案 C:分析计算下推后端 | 方案 D:存储层配置去重 |
|---|---|---|---|---|
| 5000 行传输量 | 列式消费者数百 KB | 数十 MB(配置重复传输) | 数 KB(仅聚合结果) | 数 MB(仍是行式) |
| 前端分析灵活性 | 全保留(任意轴组合) | 全保留 | 丧失(轴组合 = 端点爆炸) | 全保留 |
| 改动面 | 新增投影端点,存储不动 | 无改动(现状) | 每种分析一个聚合端点 | 写入路径 + 全部读取方重构 |
| 失效风险 | 字段路径校验 | 内存与传输随行数线性恶化 | 分析需求变更即改后端 | 迁移期数据一致性风险 |
选择理由:方案 A 在不动存储、不丢前端灵活性的前提下把列式消费者的传输量压掉两个数量级,且字段路径协议与已落地的服务端排序同机制,无新概念。方案 B 是被本 spec 取代的现状;方案 C 用灵活性换传输量,在投影已足够小的情况下得不偿失;方案 D 收益(存储体积)与本 spec 要解决的问题(传输与契约)不对口。
与业界的差异:本项目结果行体积比主流实验跟踪系统的典型 run 重一个数量级,业界普遍的"整行全量下发"前提不成立,故采用 TensorBoard HParams 一系的服务端逐列投影路线(完整对比与理由见 附录 A)。
非功能性需求
| 维度 | 本 spec 的考虑 |
|---|---|
| 性能 | 单实验 5000 行:行形态单页 ≤ 1 s、≤ 2 MB;列形态 8 字段投影 ≤ 1 s、≤ 500 KB;单行全量 ≤ 500 ms。JSON 路径投影/排序在数据库层逐行解析 JSON,无索引加速;超出 2 万行时该方案需重估(见 局限 节) |
| 兼容性 | 消费者全在同 repo,契约变更一次性同步迁移,不留旧格式分支(遵循项目不向后兼容规则)。本 spec 取代旧"详情端点内嵌全量结果行"契约 |
| 可靠性 | 列形态全量请求失败不得静默降级为空图表,必须显式错误态;导出失败禁止回退到"仅当前页"的静默截断 |
| 安全性 | 字段路径正则白名单 + 参数化 JSON 抽取,杜绝 SQL 注入;服务运行于内网,鉴权 N/A |
| 可观测性 | N/A(内部工具,沿用服务端通用请求日志) |
局限与后续工作
局限
| 风险 / 局限 | 影响 | 缓解措施 |
|---|---|---|
| 列模板取单行样本,异构实验(行间配置结构不同)下列发现不完整 | 个别列不出现在候选中 | 实验内配置结构常态一致;发现异构需求后将样本扩展为多行键并集(后续工作 2) |
| JSON 路径投影/排序无索引,行数上限受数据库逐行解析速度约束 | 2 万行以上可能超性能预算 | 常态数千行在预算内;超界时把高频字段提升为表字段(集成点已约定),或引入物化列 |
| 列形态不分页,单字段超长字符串值(如长错误信息)可能放大响应体 | 响应体超预算 | 投影面向标量/短字符串;约束消费者不得投影完整结果 JSON 域的明细子树 |
| 三形态各自端点,消费者绑定靠 review 约束而非类型系统强制 | 新代码可能误用形态 | 前端 API 层收口为唯一翻译点;消费者绑定表作为 review 检查清单 |
后续工作
- 数值区间筛选(中优先级,前置:in 多选筛选落地后按需求触发):为数值列扩展 range 操作符,沿用同一筛选参数结构。
- 列模板多行并集(低优先级,前置:出现真实异构实验)。
- 2 万行以上规模档(中优先级,前置:实验规模增长到接近边界):物化列或列式缓存。
- 投影结果服务端缓存(低优先级,前置:同一实验的重复投影请求成为可观测热点)。
验收标准
| 场景 | 指标 | 目标值 | 测试方法 |
|---|---|---|---|
| 行形态分页 | 5000 行实验任意页响应时间 / 响应体 | ≤ 1 s / ≤ 2 MB | 集成测试构造 5000 行实验,请求多个页码计时测体积 |
| 行形态排序 | 标量键与 config 路径键排序正确性 | 与全量排序金标准一致 | 单测:构造已知乱序数据,校验各页拼接后的全序 |
| 列形态投影 | 5000 行 × 8 字段响应体 | ≤ 500 KB | 集成测试测响应体积 |
| 列形态缺失语义 | 部分行缺失字段 | 缺失处为 null,行数不缩减 | 单测:构造异构行 |
| 字段路径校验 | 非法路径(含 $、'、;、../) | 全部 400,无 SQL 执行 | 单测注入用例集 |
| 单行全量 | 任意页勾选行可对比 | 跨页对比数据完整(含结果 JSON) | 集成测试:跨页取 3 行断言字段全集 |
| 列模板稳定性 | 同实验不同页码/排序参数 | 列模板逐字节一致 | 集成测试对比多次请求 |
| 形态排他 | 前端代码无越形态取数 | 列式消费者零调用行形态/全量行接口 | 代码 review 检查清单 + grep 断言 |
| 列筛选正确性 | in 多选 + 多条 AND + 与排序分页叠加 | 与全量过滤金标准一致,total 为筛选后计数 | 单测:构造已知数据集,校验筛选+排序+翻页组合 |
| 列筛选校验 | 非法字段路径 / 超限条数或值集合 | 全部 400 | 单测边界用例(17 条筛选、65 个值、非法 path) |
| 筛选缺失语义 | 部分行缺失被筛字段 | 缺失行被过滤,不计入 total | 单测:构造异构行 |
| 实验元信息 | 详情元信息端点 | 响应不含结果行字段 | 单测断言响应 schema |
附录
附录 A:业界调研
四个实验跟踪系统在"大量 run × 宽配置字段"结果展示上的设计对比(调研日期 2026-06-12):
| 维度 | MLflow | W&B | Optuna Dashboard | TensorBoard HParams |
|---|---|---|---|---|
| 分页 | 服务端 token 分页(max_results + page_token) | GraphQL cursor 分页(edges/pageInfo) | 无分页;after-offset 增量拉取 + 服务端 TTL 缓存 | 服务端 offset 分页(start_index + slice_size + total_size) |
| 排序 | 服务端,order_by: "metrics.x DESC" | 服务端,order: "-summary_metrics.x" | 前端 DataGrid 客户端排序 | 服务端,ColParams 多键排序 |
| 字段投影 | 无,整行全量 | 粗粒度两档:config/summary 整块 all-or-nothing;仅时序 history 支持 keys + 采样 | 无,整 trial 全量 | 逐列 include_in_result,后端裁列,filter/sort/project/paginate 单请求闭环 |
| 列发现 | 无端点,前端从已加载 runs 推断 key 并集 | 无端点,前端从 JSON key 推断并集 | 后端预计算 union/intersection search space 随响应下发 | 独立 schema 端点(hparam_infos/metric_infos),数据请求按 schema 构造 |
@tbl-spec-rdc-03 业界实验跟踪系统结果展示设计对比
来源:MLflow REST API、MLflow Search Runs、W&B Public API、W&B Run docs、Optuna Dashboard _app.py、Optuna Dashboard _serializer.py、TB HParams api.proto、TB HParams http_api.md。
与本 spec 的对照:
- 业界主流(MLflow / W&B / Optuna)放弃行级字段投影、整行全量下发——前提是单 run 标量集合仅数 KB,分页/采样已足够控制总量;投影只在真正昂贵的时序数据上提供(W&B history)。本项目结果行携带配置快照与完整结果 JSON,行体积重一个数量级,该前提不成立,故走 TensorBoard HParams 的服务端逐列投影路线,但用平铺 query 参数(fields 列表)替代其 proto + ColParams 组合,降低协议复杂度。
- 列发现的三档梯度——前端从数据推断(MLflow/W&B)、后端预计算并集(Optuna)、独立 schema 端点(TB HParams)——本 spec 的列模板取第二档的简化变体(单行样本随行形态响应下发),以零维护成本换"异构实验列不全"的已声明局限。
- 排序全部主流系统在服务端做(Optuna 客户端排序为其已知缺陷来源),与本 spec 一致。
项目内引用:实验数据库页面性能优化 plan(已归档,本 spec 的契约前身)、前端仿真洞察工作台设计规格(产品定位与波次路线)。
附录 B:实现说明
单行全量端点与对比视图迁移(2026-06-12)
- 端点:
GET /api/evaluation/experiments/{experiment_id}/results/{result_id}(services/api/experiments.py)。按实验 category 分发模型,行形态同款 dict 之上合并 builder 未覆盖的表列(eval 行补full_result,comm 行补全部输入参数列),实现"全字段"语义;result_rank置 null(排名是列表上下文概念)。实验或结果行不存在、result_id 不属于该实验均 404。 - 对比视图绑定迁移:对比身份从 taskId 改为结果行主键(
ComparisonEntry.resultId)。修复两个问题:跨页/未加载行无法对比;搜索类任务一个 task 多结果行时旧路径经/tasks/{id}/results按 task_id.first()取任意行。ComparisonView按 entry 逐一调用单行全量端点(并发拉取、失败条目以横幅告知不静默丢弃),不再读取标签页缓存。 - Pareto 联动:列形态投影字段白名单增加
id(真实表列),PARETO_FIELDS增投影id,brush 框选后携带 result_id 进对比。 - 测试:
tests/services/test_experiments_projection.py::TestSingleResultRow覆盖 eval 行 full_result 嵌套字段、comm 行全列、404 三式(缺行/缺实验/跨实验越权)、投影 id 字段。
附录 C:完整接口签名 / 数据结构
行形态:
GET /api/evaluation/experiments/{experiment_id}/results
?page=<int, ≥1>
&page_size=<int, 1..500>
&sort_by=<字段路径 | 标量列名, 可选>
&sort_order=<asc | desc>
&filters=<JSON 数组, 可选, 1..16 条>
// 每条: { "field": <字段路径 | 标量列名>, "op": "in", "values": [<标量值>, ... ≤64 个] }
// 多条之间 AND;field 校验同 sort_by;超限 / 非法 field / 未知 op → 400
→ 200 {
results: ResultRow[], // 标量指标 + config_snapshot,不含完整结果 JSON 明细
total: int,
page: int,
page_size: int,
column_template: ResultRow | null
}
→ 400 非法 sort_by / sort_order / page_size
→ 404 实验不存在
列形态:
GET /api/evaluation/experiments/{experiment_id}/metrics
?fields=<字段路径列表, 逗号分隔, 1..64 个>
→ 200 {
fields: string[], // 按请求顺序回显
rows: (number|string|null)[][], // 行数 = 实验全量行数, 列序与 fields 对齐
total: int
}
→ 400 非法字段路径 / 字段数越界
→ 404 实验不存在
单行全量:
GET /api/evaluation/experiments/{experiment_id}/results/{result_id}
→ 200 ResultRow & { full_result: object } // 全字段
→ 404 实验或结果不存在
实验元信息:
GET /api/evaluation/experiments/{experiment_id}
→ 200 { id, name, description, category, total_tasks, created_at, updated_at }
字段路径文法:
field_path = scalar_name | domain ":" json_path
domain = "config" | "result"
json_path = segment ("." segment)*
segment = [A-Za-z0-9_]+
约束: domain 须命中域白名单; 剥离 domain 后的 json_path 匹配 ^[A-Za-z0-9_.]+$;
scalar_name 须在服务端排序白名单; 任一段不通过整个请求 400