租赁目录里只有一句“第二台相机属于相机类”,没有序列号。这可能表示资料还没收齐,也可能违反登记接口的必填规则。两种判断依赖不同问题:已有知识能够推出什么,以及当前数据是否满足交付要求。

本篇使用合成设备目录,实际运行 RDFLib、OWL-RL 与 pySHACL。实验既保存推导出的分类和关系,也保留缺字段的校验报告。它没有访问真实资产系统,更没有把图谱查询结果接入主线预订事务。

从关系事实到本体公理

RDF 图由主语、谓语、宾语三元组组成;命名资源使用 IRI,序列号这样的值可以使用字面量。这里的 Camera、Equipment、alice 和 camera1 都有独立标识,owns 表示主体拥有设备。名称的可读性便于人检查,机器依赖的仍是完整标识与关系。RDF 1.1 Concepts

models/E05/rental.ttl 明确写出 camera1 是 Camera,Camera 是 Equipment 的子类,alice 拥有 camera1。数据没有显式写“camera1 是 Equipment”,也没有直接声明 alice 属于 Party。这给实验留下可以核对的推导前后差异,而不是先填完整数据,再把读取结果称作推理。

本体部分还规定 owns 的定义域为 Party、值域为 Equipment,并把 ownedBy 声明为它的逆属性。定义域和值域在这套语义下用于分类推断,不能照搬成面向对象方法的参数类型检查。属性一旦成立,就可能增加主语和宾语的类别知识。OWL 2 Primer,第4节

flowchart LR
  C1[camera1] -->|显式类型| C[Camera]
  C -->|子类| E[Equipment]
  A[alice] -->|owns| C1
  C1 -.->|推导 ownedBy| A
  A -.->|定义域推导| P[Party]
  C1 -.->|子类推导| E

图中虚线对应实验新增的事实,实线对应输入三元组。相机目录提供的设备名称不决定 Java 聚合边界;本体类表达分类,主线 Equipment 的对象身份、退休约束和资源承诺仍由各自合同控制。把两个对象都标为 Equipment,不会自动解决同时预订同一时段的问题。

真实推导及其边界

实验固定 RDFLib 7.1.4、owlrl 7.1.4、pySHACL 0.30.1。RDFLib 负责解析 Turtle 和承载图,DeductiveClosure(OWLRL_Semantics).expand(...) 执行规则闭包。OWL-RL 项目说明其采用前向链式规则推导;这里没有自行拼出“推理成功”的 JSON,也没有执行完整 OWL DL 推理器。OWL-RL 项目说明

程序先检查输入中确实缺少 camera1 的 Equipment 类型,再复制三元组到另一个图进行推导。运行后,设备父类、alice 的 Party 类型和 ownedBy 反向关系都出现。另一个断言比较原图三元组数量,防止后续把推导结果误写成上游原始声明。

第二台相机没有 serial。闭包计算结束后,该值仍然不存在。开放世界解释允许知识不完整,不能从“目录没有写”直接推出“现实中没有”。这条观测只证明本次规则没有凭空生成序列号;它不证明完整本体一致,也不证明任意 OWL 公理都已执行。OWL 2 Primer,第2节

还有一个容易反转预期的输入:alice 拥有一个尚未分类的 someService。名字看起来像服务,但值域规则仍把它归为 Equipment。实验实际检查了这条类型事实。名称没有逻辑约束力;若输入关系本身录错,推导会沿着错误前提继续产生分类。业务必须另行审查关系来源和适用范围。

把完整性要求写成形状

目录登记规定每台设备恰好有一个字符串序列号。shapes.ttl 用 sh:targetClass 选择 Equipment,以 serial 为路径,分别给出最少一个、最多一个及字符串数据类型。pySHACL 对具体数据图产生 conforms 布尔值与报告图;报告包含出错节点、路径和约束种类。SHACL 规范

本实验把已计算的闭包交给校验器,并显式设置 inference="none",不让两个阶段暗中重复选择推理策略。第一次报告为 false,焦点是 camera2,路径为 serial,约束是 MinCount。报告给出了可修复位置,而不是笼统宣布整个“知识图谱错误”。

随后明确补入字符串 CAM-02,报告变成 true。再为同一设备增加不同字符串 CAM-02-OTHER,报告重新变成 false,约束变为 MaxCount。三次观察使用同一形状,差异只来自数据变化,因此可以区分遗漏修复和重复值引入。

flowchart TD
  R[Turtle原始事实] --> O[OWL-RL复制图并计算闭包]
  O --> S[SHACL设备形状校验]
  S --> M[缺序列号 false MinCount]
  M --> A[显式补入一个字符串]
  A --> Y[true]
  Y --> D[追加第二个不同字符串]
  D --> N[false MaxCount]

形状这里只约束指定路径,没有启用 sh:closed 来拒绝所有其他属性。“封闭业务约束”指登记时必须交齐所需值,不表示整个 RDF 世界从此采用封闭世界语义。conforms 为 true 也只覆盖本次选择的目标节点和形状;它没有查验序列号在现实中的真实性、全局唯一性或者物理设备是否存在。

值的数量与数据类型要分开检查

重复提交同一个序列号与提交两个不同序列号,在 RDF 图中不是同一件事。图采用三元组集合语义,同一个主语、谓语、宾语再次加入不会增加一条新事实。实验在补全后再次加入 CAM-02,检查图的大小不变,而且形状仍然通过。数据库接口如果用请求次数统计属性数量,会与这里的值集合产生差异。

另一个负例先移除字符串,再加入整数四十二。此时恰好存在一个值,最少和最多数量都满足,字符串类型约束仍然拒绝它。这能区分“字段存在”与“字段合法”。程序保存校验器报告中的约束标识,读者可以直接检查数据类型失败,而不用从变量名猜测原因。

开放世界与业务校验也不是互相排斥的架构选择。同一目录可以先根据受信关系补全分类,再检查该分类下的登记义务。关键是说明校验面对原始数据还是推导后数据。如果目标类只有通过推理才能得到,改变阶段顺序可能改变校验节点集合,继而改变缺值报告。

本例先推理后检查,保证被推导为设备的对象同样承担序列号要求。但 someService 的范围推导实验在独立图上执行,并没有混入已修复图的通过结果。如果将它并入登记图,它也需要满足设备形状。把独立场景的输出拼成一次总体通过,会失去输入与结果的对应关系。

形状通过之后还应保留数据来源。两个来源都给相机填写序列号时,内容相同会在合并图中折叠为一个值,内容不同则触发最多数量限制。该结果不能替代来源冲突裁决:需要追踪各自声明、采集时间以及优先级,才能决定采用哪个值。当前样本没有命名图和冲突解决器,因此只展示值集合层面的行为。

与交易模型对接时保留来源

主线预订用例要求设备资格、租期及回执共同满足不变量。目录推导得到 Equipment 分类,只提供候选事实,不能直接等价于 ACTIVE,更不能等价于当前窗口没有重叠承诺。把这些语义压成一个 available 布尔值,会重新引入前面章节拆开的歧义。

合适的边界是先说明输入契约:图来自哪个目录、取样时间是什么、应用采用哪套本体与形状版本。通过形状校验之后,翻译器才能将允许的字段交给交易领域对象。应用仍需执行设备状态和日历检查。实验没有实现该翻译器,因此这里是接口设计要求,不能把流程图当作已经落地的端到端链路。

原始图与推导图分开还影响解释能力。若查询返回 alice 是 Party,可以追到 owns 的定义域和原始拥有关系。实验仅保存两层图的构造过程及关键断言,没有完整证明树或撤回算法。以后删除拥有关系时,应重新计算派生事实或选用具备对应维护机制的存储,不能只删除一条边便假定所有旧推论自动消失。

本体变化和形状变化也应分别评审。把 Camera 的父类改掉会改变分类;把 minCount 从一改为零则放宽登记要求。两种变更可能让同一份目录得到不同结果,版本号和数据快照必须随报告保存。本例通过锁定源文件哈希保留这一对应关系,没有引入在线模型升级。

复跑与证据

下载 实验包,保留包中的 examples/software-modeling/ 路径。Python 需支持本篇锁定库;本次实跑使用 Python 3.14。独立入口会创建虚拟环境,按八个固定版本及 wheel 哈希安装依赖,首次运行需要访问官方 PyPI,安装后可复用缓存。

1
MODELING_E05_CACHE=/tmp/modeling-E05 bash examples/software-modeling/labs/E05/run.sh

MODELING_E05_CACHE 可指定读者可写目录,PYTHON_BINARY 可选择创建环境的 Python。执行时没有下载外部本体。正常路径十二项断言通过并返回零;在命令末尾增加 missing,程序输出缺值报告并返回一,便于外部脚本验证失败没有被成功日志掩盖。

原始输出位于 examples/software-modeling/evidence/E05/,模型、锁定清单和源码随包提供。有关设备身份与状态的交易约束可继续对照 第24篇;本篇的通过结果限定在分类、关系推导和选定形状校验范围内。