审查:wolf 语言域两份草稿 vs 主线现状,2026-08-25 20:28
审查对象:
D:\GitHub\synthrt-refactor\docs\language-level-1-draft.md(契约草案,311 行)
D:\GitHub\synthrt-refactor\docs\language-g2p-variants-wolf-draft.md(wolf 变体参考,273 行)
对照基准:主线 D:\GitHub\synthrt\docs\ds-spec-2.4.md(871 行)与 docs/DevHelp.md(786 行)。
总评:契约设计本身站得住——三段式 G2P/S2P/Onset 的切分、变体粒度即分派粒度、暴露面不泄露内部子资源标识,这几条都对,而且与 spec 2.4 的思路一致。问题几乎全部集中在与主线运行时脱节:草稿写于 spec 2.4 的一个较早版本(refactor 仓库那份 832 行的副本),而主线这段时间长出了 role、runtimeLevel、Load 事务、provider discovery 全序等一批机制。
其中有一处两份草稿互相矛盾,一处违反规范对 interface 契约的硬性要求。
一、阻断级:不改就是错的
B1. 所有 imports 示例都缺 role,按现行规范是无效声明
role 出现次数:两份草稿各 0 次,主线 spec 2.4 的 Singer 示例 3 次。
主线《imports》一节把 role 定为必选字段:
- 必选字段
role:该 import 在当前模块内唯一的用途名称,使用 contrib-id 的单段语法
ref:被引用模块的 ModuleReference
受影响的具体位置:
- 契约草案 §5 的歌手
imports 示例(三条全缺)
- 变体草案 §3 的 pipe-chain 声明示例(一条缺)
主线现在长这样:
"imports": [
{ "role": "acoustic", "ref": ":inference/acoustic" },
{ "role": "pitch", "ref": "bar:inference/pitch", "options": { "roles": ["pitch"] } }
]
改动量很小,但示例是被人照抄的,留着就会批量产出加载失败的包。
B2. pipe-chain 的序位绑定与运行时冲突,而且两份草稿自相矛盾
变体草案 §3 写:
imports 中第 i 个 G2P 条目注入第 i 个启用的 model 步(声明顺序对应)
以及 step 表注 2:
步内不写任何模块引用,后端取本模块 imports 中同序位的条目
而 DevHelp §2.3 明确:
代码应使用 ContribSpec::findImport(role) 定位 import,不依赖数组位置。
更麻烦的是,契约草案 §1.5 自己说的是相反的话:
语言 imports 按目标契约角色匹配,序位不承载语义
两份草稿对同一个机制给出了相反的规定。
需要说明的是,序位绑定并非不可实现——主线规范确实承诺「加载器必须保持声明顺序,不得排序或重排」。但它逆着 role 机制走,而 role 正是为这件事设计的。
建议:model step 的 params 增加一个 role 字段,直接点名它消费哪个 import。这样 step 与 import 的对应关系写在配置里、可读可校验,也不再依赖「启用的 model 步」这个需要先解析整条链才能算出来的隐式序号。
B3. 语言 imports 的「按目标契约匹配」不是主线机制
契约草案 §1.5 的基数表按目标契约给出(G2P 恰好 1、S2P 恰好 1、Onset 0..1)。主线把这件事交给导入方的 variant:
role是导入方为该条目指定的本地 slot。导入模块通过role区分各项用途,并由自己的variant规定哪些 role 必须存在以及每个 role 接受哪一种目标契约。
所以那张表应当重写成 role 表:
| role |
目标契约 |
必需性 |
g2p |
org.openvpi.lang.G2pInference |
必需 |
s2p |
org.openvpi.lang.S2pInference |
必需 |
onset |
org.openvpi.lang.OnsetInference |
可选 |
功能上两种写法在「恰好 1」的情形下等价,但主线的写法是唯一有效的表述方式,而且它天然支持将来一个 role 出现多个实例。
同时缺了一条:主线规定
Singer 等导入方不得仅因遇到自身不认识的 role 而拒绝整个模块;导入方仍可严格要求自己契约规定的 role 存在且只指向规定的目标契约。
契约草案 §1.5 说「Level 1 内集合恒定」,容易被读成「出现表外 role 即失败」。必须补一句:表列 role 必须存在且契约正确,表外 role 一律放行。这条正是为了让第三方类别以后能往语言里挂新 role 而不破坏兼容。
二、设计级:与规范的硬性要求冲突
D1. error 的值域下放给变体,违反《interface 契约规范》
契约草案 §2.4 的 error 变量写着「失败类型(值域枚举见各变体文档)」,而变体草案里散落着具体值:PhonemeGenerationFailed(§3 注 4)、ModelInferenceFailed、DriverUnavailable(§3 model 步行为)。
主线《interface 契约规范》要求契约规范至少必须包含:
- 运行时输入与输出的数据结构、数据类型、形状和单位
- 必选能力、可选能力及其语义
- 错误条件及其可观察语义
error 是契约面的输出变量,导入方要根据它决定兜底。值域由变体定义意味着换一个变体,导入方的错误处理就失效——这恰好是 variant 轴存在的前提(同契约各变体对导入方可互换)被打破。
建议:把 error 的枚举提升为契约词,Level 1 至少固定几个通用值(转换失败 / 后端不可用 / 语言不支持之类),变体只能在契约允许的范围内细化,不得自定义新值。变体草案里现有那三个值正好可以作为契约值的候选。
顺带一提,同一节的 mode 枚举(convert/copy/skip)是写死在契约里的,处理方式是对的——error 应当照它办。
D2. 类别注册名用裸词 language 存疑
契约草案 §1.1 写「本类别注册名为 language」。而主线里凡是举语言类别的例子,用的都是反向域名:
- spec 2.4《
imports》::com.vendor.language/cmn 引用「一个第三方语言模块」
- DevHelp §3.1:
ContribCategoryRegistry::Add<MyCategory>("com.example.language", "")
按 spec 2.4 对 variant 的规定,裸词是规范自身保留、第三方用反向域名。类别名很可能适用同一条规矩,而 wolf 相对 synthrt 是第三方。
这条我没有找到规范里的明文规定(只有两处示例的一致倾向),所以列为待确认而非错误。但它影响所有语言包的 desc.json 和所有 ModuleReference 的写法,值得在动手前问清楚——改起来是全量的。
三、同步级:陈旧引用与遗漏
S1. 行号引用已经失效
变体草案里有 spec 2.4(:561)、:565/:571/:589、:603/:619 一类引用。这些指向的是 refactor 仓库那份 832 行的副本,主线已经是 871 行且章节重排过,行号对不上了。
契约草案用的是章节名引用(《模块 provider 发现》《何时递增 Level》),这些我逐条查过,在主线里全部存在。建议变体草案统一改成章节名——行号引用进活文档就是维护陷阱。
S2. 未提及 runtimeLevel
runtimeLevel 在草案中出现 0 次,主线 spec 中 11 次。DevHelp §2.4:
desc.json 必须显式写 runtimeLevel: 1。
契约草案 §6 在规定公共语言包该长什么样,却没提这个必选字段。至少要在 §6 提一句。
S3. 未说明变量展开由谁执行
两份草稿都正确写了「使用前已完成 ${vars} 展开」,但没说是谁展开的。DevHelp §2.4:
Loader 会在 Category 和解释器看到 JSON 前统一完成展开,扩展代码不得再次展开字符串。
对 wolf 的解释器实现者是要紧的一句——重复展开会把用户数据里的 ${...} 二次解释。建议在两份草稿的「路径解析」约定里各补一句。
四、已经对齐的部分(不必再动)
为免下一轮重复劳动,把核查过、确认与主线一致的列出来:
exports 这个词——主线已从 schema 改名 exports,两份草稿用的都是新名
$version 只写在 desc.json,模块声明不携带 $version 与 id——与主线一致
- 模块三元组
(interface, level, variant) 全部必填、精确匹配——一致
configuration 由 variant 全权规定、不参与 provider 选择——与主线《模块 provider 发现》末段一致
- provider 首匹配规则——契约草案 §2.5 的「按搜索全序取首个」与主线一致;变体草案 §1.1 的「同一变体名只许一个 provider 承载」被正确地表述为 wolf 的自律而非规范规则
formatVersion 与 level 分层——变体草案 §1.2 的两层划分,与主线《何时递增 Level》的判据同构
- 多语言
name——主线已改用 BCP 47,草稿未涉及具体语言码,无冲突
- 章节名引用——契约草案引用的《贡献类别是开放的》《推理解释器》《何时递增 Level》《模块 provider 发现》《interface 契约规范》《加载事务》《字符串变量》在主线中逐条存在
五、命名空间的一个观察(非缺陷)
契约草案用 org.openvpi.lang.*(Language、G2pInference、S2pInference、OnsetInference),主线 Singer 示例用的是 org.openvpi.svs.singer.DiffSinger,Inference 用 org.openvpi.svs.*。
即 SVS 域的层次是 org.openvpi.svs.<类别>.<契约>,而语言域用的是 org.openvpi.lang.<契约>,少一层。两种都合法,但如果 svs.singer. 这个多一层的形态是有意为之(为了同类别下多契约留位置),语言域大概也该跟上,写成 org.openvpi.lang.g2p.* 之类。
这纯属一致性问题,不影响功能,但契约名一旦发布就不能改,值得现在定。
六、建议的处理顺序
- B1(补
role)——机械改动,先做,防止示例被照抄
- D2(类别名)与第五节(契约命名空间)——两个「一旦发布不可改」的决定,要在写代码前定
- B3(role 表)与 B2(pipe-chain 绑定)——同一个机制的两面,一起改
- D1(
error 值域上提)——需要设计,不是改写
- S1/S2/S3——收尾
审查:wolf 语言域两份草稿 vs 主线现状,2026-08-25 20:28
审查对象:
D:\GitHub\synthrt-refactor\docs\language-level-1-draft.md(契约草案,311 行)D:\GitHub\synthrt-refactor\docs\language-g2p-variants-wolf-draft.md(wolf 变体参考,273 行)对照基准:主线
D:\GitHub\synthrt\docs\ds-spec-2.4.md(871 行)与docs/DevHelp.md(786 行)。总评:契约设计本身站得住——三段式 G2P/S2P/Onset 的切分、变体粒度即分派粒度、暴露面不泄露内部子资源标识,这几条都对,而且与 spec 2.4 的思路一致。问题几乎全部集中在与主线运行时脱节:草稿写于 spec 2.4 的一个较早版本(refactor 仓库那份 832 行的副本),而主线这段时间长出了
role、runtimeLevel、Load 事务、provider discovery 全序等一批机制。其中有一处两份草稿互相矛盾,一处违反规范对 interface 契约的硬性要求。
一、阻断级:不改就是错的
B1. 所有
imports示例都缺role,按现行规范是无效声明role出现次数:两份草稿各 0 次,主线 spec 2.4 的 Singer 示例 3 次。主线《
imports》一节把role定为必选字段:受影响的具体位置:
imports示例(三条全缺)主线现在长这样:
改动量很小,但示例是被人照抄的,留着就会批量产出加载失败的包。
B2. pipe-chain 的序位绑定与运行时冲突,而且两份草稿自相矛盾
变体草案 §3 写:
以及 step 表注 2:
而 DevHelp §2.3 明确:
更麻烦的是,契约草案 §1.5 自己说的是相反的话:
两份草稿对同一个机制给出了相反的规定。
需要说明的是,序位绑定并非不可实现——主线规范确实承诺「加载器必须保持声明顺序,不得排序或重排」。但它逆着 role 机制走,而 role 正是为这件事设计的。
建议:
modelstep 的params增加一个role字段,直接点名它消费哪个 import。这样 step 与 import 的对应关系写在配置里、可读可校验,也不再依赖「启用的 model 步」这个需要先解析整条链才能算出来的隐式序号。B3. 语言
imports的「按目标契约匹配」不是主线机制契约草案 §1.5 的基数表按目标契约给出(G2P 恰好 1、S2P 恰好 1、Onset 0..1)。主线把这件事交给导入方的 variant:
所以那张表应当重写成 role 表:
g2porg.openvpi.lang.G2pInferences2porg.openvpi.lang.S2pInferenceonsetorg.openvpi.lang.OnsetInference功能上两种写法在「恰好 1」的情形下等价,但主线的写法是唯一有效的表述方式,而且它天然支持将来一个 role 出现多个实例。
同时缺了一条:主线规定
契约草案 §1.5 说「Level 1 内集合恒定」,容易被读成「出现表外 role 即失败」。必须补一句:表列 role 必须存在且契约正确,表外 role 一律放行。这条正是为了让第三方类别以后能往语言里挂新 role 而不破坏兼容。
二、设计级:与规范的硬性要求冲突
D1.
error的值域下放给变体,违反《interface 契约规范》契约草案 §2.4 的
error变量写着「失败类型(值域枚举见各变体文档)」,而变体草案里散落着具体值:PhonemeGenerationFailed(§3 注 4)、ModelInferenceFailed、DriverUnavailable(§3 model 步行为)。主线《interface 契约规范》要求契约规范至少必须包含:
error是契约面的输出变量,导入方要根据它决定兜底。值域由变体定义意味着换一个变体,导入方的错误处理就失效——这恰好是variant轴存在的前提(同契约各变体对导入方可互换)被打破。建议:把
error的枚举提升为契约词,Level 1 至少固定几个通用值(转换失败 / 后端不可用 / 语言不支持之类),变体只能在契约允许的范围内细化,不得自定义新值。变体草案里现有那三个值正好可以作为契约值的候选。顺带一提,同一节的
mode枚举(convert/copy/skip)是写死在契约里的,处理方式是对的——error应当照它办。D2. 类别注册名用裸词
language存疑契约草案 §1.1 写「本类别注册名为
language」。而主线里凡是举语言类别的例子,用的都是反向域名:imports》::com.vendor.language/cmn引用「一个第三方语言模块」ContribCategoryRegistry::Add<MyCategory>("com.example.language", "")按 spec 2.4 对
variant的规定,裸词是规范自身保留、第三方用反向域名。类别名很可能适用同一条规矩,而 wolf 相对 synthrt 是第三方。这条我没有找到规范里的明文规定(只有两处示例的一致倾向),所以列为待确认而非错误。但它影响所有语言包的
desc.json和所有 ModuleReference 的写法,值得在动手前问清楚——改起来是全量的。三、同步级:陈旧引用与遗漏
S1. 行号引用已经失效
变体草案里有
spec 2.4(:561)、:565/:571/:589、:603/:619一类引用。这些指向的是 refactor 仓库那份 832 行的副本,主线已经是 871 行且章节重排过,行号对不上了。契约草案用的是章节名引用(《模块 provider 发现》《何时递增 Level》),这些我逐条查过,在主线里全部存在。建议变体草案统一改成章节名——行号引用进活文档就是维护陷阱。
S2. 未提及
runtimeLevelruntimeLevel在草案中出现 0 次,主线 spec 中 11 次。DevHelp §2.4:契约草案 §6 在规定公共语言包该长什么样,却没提这个必选字段。至少要在 §6 提一句。
S3. 未说明变量展开由谁执行
两份草稿都正确写了「使用前已完成
${vars}展开」,但没说是谁展开的。DevHelp §2.4:对 wolf 的解释器实现者是要紧的一句——重复展开会把用户数据里的
${...}二次解释。建议在两份草稿的「路径解析」约定里各补一句。四、已经对齐的部分(不必再动)
为免下一轮重复劳动,把核查过、确认与主线一致的列出来:
exports这个词——主线已从schema改名exports,两份草稿用的都是新名$version只写在desc.json,模块声明不携带$version与id——与主线一致(interface, level, variant)全部必填、精确匹配——一致configuration由variant全权规定、不参与 provider 选择——与主线《模块 provider 发现》末段一致formatVersion与level分层——变体草案 §1.2 的两层划分,与主线《何时递增 Level》的判据同构name——主线已改用 BCP 47,草稿未涉及具体语言码,无冲突五、命名空间的一个观察(非缺陷)
契约草案用
org.openvpi.lang.*(Language、G2pInference、S2pInference、OnsetInference),主线 Singer 示例用的是org.openvpi.svs.singer.DiffSinger,Inference 用org.openvpi.svs.*。即 SVS 域的层次是
org.openvpi.svs.<类别>.<契约>,而语言域用的是org.openvpi.lang.<契约>,少一层。两种都合法,但如果svs.singer.这个多一层的形态是有意为之(为了同类别下多契约留位置),语言域大概也该跟上,写成org.openvpi.lang.g2p.*之类。这纯属一致性问题,不影响功能,但契约名一旦发布就不能改,值得现在定。
六、建议的处理顺序
role)——机械改动,先做,防止示例被照抄error值域上提)——需要设计,不是改写