◈本体论工程手册
方法论

五 · 治理:版本、变更与 CI

本体当代码:版本、兼容性、影响面、回滚

一个本体上线三个月后,真正的风险不是「建得不对」,而是「改不动」。改不动的原因有四个:不知道谁在用、不知道改了会炸什么、不敢删、删了没法回滚。这一章讲怎么把这四件事变成流程。

本体当代码:三件必须落到仓库里的事

一、源进 git,二进制产物不进。 可 diff 是前提。Protégé 的工程文件、Excel 的建模表、某些工具的二进制导出,都不适合当源——它们无法在 code review 里被读。这不是审美问题:无法 review 的变更,实际上就不是受控变更。

二、每次变更是一个 PR,带三样东西。 变更说明(改了什么语义)、影响面报告(谁会受影响)、迁移方案(已有数据怎么办)。第三样最常被漏掉,而它是唯一能防止「合并后下游全红」的东西。

三、生成物与源的一致性由 CI 保证。 如果 JSON Schema、SQL DDL、文档都是生成的,那么 CI 必须验证「重新生成一遍,仓库里的产物没有变化」。否则手改生成物的情况一定会在三个月内发生,而且没人会发现。

版本:语义化版本为什么不够用

1.2.3 这套约定假设「破坏性变更」是可判定的。对本体,它不成立:加一个类通常是无害的(增补),但如果这个类名与下游某个类冲突,它就是破坏性的;删一个属性显然是破坏性的,但如果从没有人写过它,它就是无害的。

可行的替代是把变更分成四类,分别处理:

类型例子处理
增补新增类、新增可选属性、新增枚举成员次版本号 +1,无需迁移
收紧属性从可选变必填、枚举收窄、基数从 0..n 变 1..n次版本号 +1,必须有迁移方案与过渡期
放宽必填变可选、枚举加值次版本号 +1,无迁移
破坏删除类/属性、改 IRI、类型变更主版本号 +1,必须走弃用流程

关键判断:「收紧」在技术上不是破坏性变更,在实践中是最危险的一类。 它会让存量数据一夜之间变成不合规,而 schema 本身仍然「有效」。这一类必须单独识别、单独排期。

兼容性:三条底线

一、IRI 一旦发布,不改。 改 IRI 等于换了一个实体。如果一定要改,旧 IRI 必须作为 owl:deprecated 的别名保留,并指向新 IRI,且至少保留两个主版本。

二、弃用而不是删除。 流程:标记 deprecated + 记录替代项 + 记录弃用日期 → 观察使用情况(查询日志里还有没有人用)→ 至少一个主版本后再物理删除。跳过观察步骤直接删,是最常见的线上事故来源。

三、标识符里不要编码易变信息。 「部门代码 + 序号」这类 ID,在组织架构调整时会全部失效。需要版本时,把版本作为独立段(例如 dtmi:...;1 这种显式版本段),而不是混进名字里。

影响面:三类,都要算

改一条公理之前,至少回答三个问题:

一、有多少存量数据会变成不合规? 方法:用新模型对全量(或抽样)数据跑一次校验,统计新增违规数与涉及对象数。这一步是机械的,必须自动化。

二、有多少下游查询会变? 方法:如果查询是集中管理的,可以静态分析引用;如果是分散的,只能靠查询日志。没有查询日志的团队,实际上无法回答这个问题——这也是为什么「先有日志、再有本体」的顺序更安全。

三、推论会新增还是消失? 方法:对同一份数据,跑旧模型和新模型的闭包,取差集。这是最容易被忽略、也最容易造成「数字悄悄变了」的一类影响。

有一个极具说服力的例子:某次变更只增加了一条 domain 声明,不增删任何数据,闭包却多出 901 条推论。语义变更的爆炸半径与 diff 行数无关,所以影响面必须按语义算,不能按文本算。

CI 流水线:六步

一份可用的本体流水线,最小形态是六个步骤:

步骤做什么失败后果
1 语法解析所有源文件,检查 IRI、前缀、重复定义阻断
2 生成从源重新生成所有产物,比对仓库阻断
3 校验对样本数据跑 SHACL,统计违规率与新增违规阻断(新增违规 > 阈值)
4 一致性跑推理机检查不可满足的类阻断
5 影响面与新模型对比,输出三类影响报告警告(附在 PR 里)
6 发布打标签、生成变更日志、发布到仓库仅主分支

关于第 3 步的阈值:不要设「零违规」,存量数据几乎不可能零违规。可行的是增量阈值——本次变更不得新增违规,且存量违规数不得上升。这一条能挡住 90% 的意外破坏。

角色与评审

本体评审最常见的失败是「所有人都参加,没人负责」。可行的分工是三个角色:

  • 建模者:对语义负责,写变更说明与迁移方案。
  • 消费者代表:至少一个下游系统的负责人,对影响面报告签字。
  • 数据管家:对存量数据的合规率负责,决定豁免与过渡期。

三个角色里,消费者代表最容易被省略,而它恰恰是唯一能发现「这个改动会炸我」的人。

双时态与历史:要不要现在做

如果业务问过「上个月末这个客户属于哪个分层」,那你就需要历史。两个层次:

  • 有效时间(valid time):事实在现实世界中何时为真。
  • 事务时间(transaction time):系统何时得知这条事实。

只记事务时间,能回答「系统当时以为什么」;两个都记,才能回答「当时实际是什么、我们当时以为是什么」。后者在合规与对账场景中是刚需,代价是每条事实都要带两个时间区间,且所有查询都要指定「按哪个时间看」。

推断:不要在第一天就上双时态。 它会让每个查询复杂一倍,而在项目早期,「当时以为」和「当时实际」的差别通常不重要。等业务第一次因为「上个月的报表和这个月对不上」而开会时,再引入——但那时要接受一次数据模型重构。

许可证:容易被完全忽略的一层

本体是资产,资产有许可。三类风险:

  • 整包引入外部词表:某些词表是 copyleft 或带署名要求的,衍生作品可能被要求同样开放。
  • 映射到外部 IRI:映射关系本身的许可,可能与被映射词表的许可不同。
  • 生成物的许可:从 OWL 生成的文档、代码,许可继承自哪一层,常在合同里说不清。

做法:每个外部词表在引入时记录三项——来源 URL、许可证、引入范围(整包 / 摘用 / 仅对齐)。这份记录在并购尽调时会被要求提供。