Skip to content

Graphical 协议设计规范(TFOnto · TFRS-356)

设计背景与目标

TFRobotServer 平台通过 @tfs_action 机制,结合 JsonSchema、JMESPath、x-namespace / x-actions / x-ref,实现了图结构(graphical)场景下低代码管理类 Palantir 本体 的协议标准——本体库(TFOnto BaseOntoStore)即管理单元:对象类型/关系类型/属性定义 (def 级,面板数据源)与实体/关系断言(实例级,主图)的增删改查联动。

本协议为 TFRS-356 定稿,取代旧 GraphStore/Owlready 世界观协议(IRI 即身份、 PageableList 伪分页、dict bag、class/base_iri 词汇——全部退役,守卫见 tests/unit_tests/robot/factory/test_no_graph_store_imports.py)。

关键数据结构:

  • tfrobotserver/dtos/factory/base.py:BaseGraphical / Graphical / BoundedList
  • tfrobotserver/dtos/factory/brain/memory/onto_graph_dto.py:ObjectTypeInfo / LinkTypeInfo / PropertyInfo / NamespaceInfo / EntityInfo / LinkInfo / NodeRefInfo / GraphPayload
  • tfrobotserver/robot/factory/brain/memory/store/onto_store/graph_manage_mixin.py: TFOnto 图管理 20 action(挂载于 RobotLanceGraphOntoStoreSetting)
  • tfrobotserver/robot/factory/brain/memory/store/onto_store/onto_trace_mixin.py: TFOnto 溯源 2 action(TFRS-438,同面并列)——正查 aget_backings(事实 → 原文)/ 反查 aget_element_facts(原文 → 事实);DTO 家族见 tfrobotserver/dtos/factory/brain/memory/onto_trace_dto.py。均为 pageable + get_list, 返回 BoundedList[T],纯读零副作用

基本约定

  • 所有图结构相关 Action 通过 @tfs_action 注册(挂在 RobotLanceGraphOntoStoreSetting)。
  • category="graphical" + resource_op_type="get_graph":返回类型必须继承 BaseGraphical (推荐 GraphPayload = Graphical[EntityInfo, LinkInfo] + limit/truncated 截断信号)。
  • category="pageable" + resource_op_type="get_list":返回 def 级列表(有界枚举时用 BoundedList[T],见 pageable_protocol.md)。
  • 写 action(add/update/delete):category=None。

单引用空间(核心约定,协议 v1)

全 DefId:图键(EntityInfo.id)/边端点(NodeRefInfo.id)/参数(x-ref 变量)/ 属性键(properties 的 key)一律 32-hex DefId。IRI 不进 wire——展示名由 name + 列表容器 Options(labels/keys/values)解析。旧协议双引用空间(IRI 键 + name 展示、 IRI↔DefId 往返映射、残引兜底)整体废除。

边端点:结构化 NodeRef

kernel NodeRef 是 EntityRef | AssertionRef 的 tagged union(高阶断言「边上挂边」 递归语义)——wire 形态结构化而非压扁字符串:

{"node": "entity", "id": "<32-hex>"}
{"node": "assertion", "id": "<32-hex>"}

LinkInfo.subject / LinkInfo.object / acreate_link 的 subject/object 参数均为该形状; {"node": "entity", "id": x} ↔ kernel EntityRef(entity_id=x);"assertion" ↔ AssertionRef(assertion_id=x)。

图结构 DTO(TFOnto 镜像,一等字段)

EntityInfo(主图节点)

字段 说明
id 图键(DefId)
name 显示名(可变;身份在 id)
namespace TFOnto namespace
objectTypeIds 归属对象类型(多类型,DefId 引用)
properties 属性键=PropertyDef id;值=值载荷(单值或列表;协议 v1 不承载 qualified/addressed 值级元数据)
annotations 长尾注解透传
provenance {backedBy: [ele_id 十进制字符串]}——整个实体的背书清单(事实级,非值级)。backedBy == [] = 无原始出处(人工修正),前端须显式标注不得留白(TFRS-438)。实体侧无 temporal

LinkInfo(主图边)

字段 说明
id 断言 id(DefId;= kernel LinkAssertion.id)
name 关系类型显示名(派生)
namespace namespace
linkTypeId 关系类型引用(DefId)
subject / object NodeRefInfo(结构化端点)
properties 关系级元数据(值载荷)
annotations 长尾注解透传
provenance 同 EntityInfo.provenance(TFRS-438)
temporal {validFrom, validTo, recordedAt} 或 null——双时态,仅断言有(内核 LinkAssertion.temporal)

def 级 DTO(面板数据源,pageable 容器)

  • ObjectTypeInfo:id/name/namespace/version/superTypeIds/equivalentToIds/disjointWithIds/namePropertyIds/primaryPropertyId/pos/annotations
  • LinkTypeInfo:id/name/namespace/version/domainIds/rangeIds/inverseOfId/superTypeIds/equivalentToIds/disjointWithIds/characteristics(7 bool)/triggerWords/annotations
  • PropertyInfo:id/name/namespace/version/isAnnotation/domainBindings[{objectTypeId, required, immutable}]/dataRange(JSON Schema 原样)/superTypeIds/.../isFunctional/isInverseFunctional/computedById/annotations
  • NamespaceInfo:namespace(def 级诚实枚举;kernel 无 list_namespaces 原语)

引用字段全部是 DefId,不出现 IRI。

图响应 GraphPayload

{
  "nodes": [{"id": "<32-hex>", "name": "Alice", "objectTypeIds": ["<32-hex>"], ...}],
  "edges": [{"id": "<32-hex>", "linkTypeId": "<32-hex>", "subject": {"node": "entity", "id": "<32-hex>"}, "object": {"node": "entity", "id": "<32-hex>"}, ...}],
  "limit": 1000,
  "truncated": false
}

limit/truncated:kernel find_entities/get_links 有界枚举(≤ limit,无游标); truncated = len(nodes) == limit or len(edges) == limit(边界歧义报告「可能还有更多」, 诚实偏向)。limit 默认 1000(= kernel DEFAULT_QUERY_LIMIT)。

主图类别名解析管线(前端必读)

协议分层:主图(GraphPayload)只承载实例层(实体+断言);def 名表 (objectTypeId/linkTypeId/propertyId → name)单一事实源 = def 级列表容器 (aget_object_types 等——Options labels=items[*].name/keys=items[*].id 挂在其上)。 图与面板永不各执一份 defs 名表(杜绝漂移)。因此主图响应自足性为有意取舍: 节点类别名解析按以下管线,顺序从上到下、首个可用者命中:

  1. 链路共享(标准路径):图视图「面板+主图」一体打开 = 同一条 DataProtocol 链路—— aget_object_types 与 aget_graph 并行加载,namespace 共享(index.md §4.2 全局 namespace 语义)——主图节点类别名从面板数据解析,零额外请求。
  2. x-fetch 补全(单图嵌入路径):仅渲染主图、无面板数据时,按 x-namespace 声明 的 objectTypeId/objectTypeIds 变量缺省触发 x-fetch 补全动作 aget_object_types (graphical_protocol.md 旧 §2/x-fetch 既有机制:触发条件=x-ref/x-default 均不可得、 按 (action+params) 缓存去重、链路内最大触发 10 次;ObjectTypeBoundedList 的 Options 即构建 id→name 映射的来源)。
  3. 异常兜底:仅当上述两者均不可得(补全失败/未知资源)时才回退 DefId 短尾—— 短尾不是正常路径,类别名是语义字段(「Person」≠ 32-hex)。

前端实现应引擎化该管线(按 schema 声明驱动,非手写具体调用);单独嵌入组件自行 消耗此管线,一体视图消费管线 1。

典型接口结构

1. 主图查询 Action(get_graph)

GraphManageMixin.aget_graph(object_type_ids=None, link_type_ids=None, limit=1000) -> GraphPayload

  • objectTypeIds 过滤「端点实体的图键消融」:仅保留端点实体在结果集内的边 (高阶断言端点不参评)。
  • 面二读 gate 由 kernel _assert_validated 自守(def 图 dirty 时 find_entities/get_links 会 graph_not_validated——先补 def/删悬挂引用再查询)。

2. 面板/列表 Action(get_list)

Action 返回 用途
aget_object_types ObjectTypeBoundedList 本体层类型树(superTypeIds 为 DAG)
aget_link_types LinkTypeBoundedList 属性层关系类型列表
aget_properties(prop_type=data/annotation) PropertyBoundedList 属性层数据/注解属性列表
aget_namespaces NamespaceBoundedList namespace 下拉(写参数 x-default 来源)

每个列表容器带 Options(labels=items[*].name、keys=items[*].id、 values=items[*].id)——撑起 id 空间与 name 展示的 UI 联动闭环(单引用空间唯一解析面)。

2.5 溯源 Action(get_list,TFRS-438,纯读零副作用)

Action 参数(wire 用 snake_case,即 python 形参名) 返回 用途
aget_backings facts(必填,NodeRefInfo[])/ limit(默认 1000) FactBackingBoundedList 正查:事实 → 背书原文
aget_element_facts ele_ids(必填,int[])/ namespace(必填,x-ref: namespaceId)/ limit ElementFactsBoundedList 反查:原文 → 产出的事实

⚠ 参数名是 snake_case(ele_ids 不是 eleIds、facts 不是别的)——action params schema 的字段名即 python 形参名,传 camelCase 会被 validate_python 拒为 Field required。 这与返回体的 camelCase(backedBy/pageNumber)不同,两处别记混。

消费口径(两条最容易误用的):

  • 正查:items 与入参 facts 同序;sources == [] = 无原始出处(人工在图管理页面 写入的事实不落溯源),前端须显式标注而非渲染成空白;found=false = 该事实已不在图上。
  • 反查:items 按入参顺序(重复 eleId 收敛一行);missing=true = 孤儿行(溯源行还在、 图对象已被删除),此时 entity/assertion 皆空,跳过或标记由消费方定夺(本 action 不抛)。 namespace 必填:element_onto_index 是多本体共库的显式作用域列,缺它会串台。

这两个容器不带 Options(溯源结果是叶子数据,不供 x-ref 下拉联动),与上面 def 级列表不同。

3. 写 Action(add/update/delete,治理信封)

资源 Action kernel 承载
object_type / link_type / property acreate_* / aupdate_* / adelete_* put_def(upsert by id)/ delete_def
entity acreate_entity / aupdate_entity / adelete_entity put_entity(skip_immutable_check=True) / delete_entity
link acreate_link / aupdate_link / adelete_link put_link / delete_link

承重墙(协议对 kernel 契约的显式承诺):

  1. def 版本单调 + 写前引用预检:create 不传 id(kernel 铸)、version=1;update = get_def 旧 version+1 全量写入——name 可改(rename-safe,id 不变);BREAKING diff 内核上抛(onto.store.breaking_change,不做 force 覆盖参数)。 引用预检(B1 防线):put_def 前 validate_def_integrity 单 def 校验(悬挂引用/ primary 特性 fail-loud)——每 action 重建 store(kernel 冷启动 strict 拒载脏图), API 一旦自造脏图(super_type_ids/primary_property 引用未建 def 等)则后续所有 action (含恢复动作)在构建时死锁;预检保证 API 面不产脏图,引用型字段须依赖先建 (DAG 拓扑序)。 并发写-写/删-写仍存在 kernel 级 TOCTOU 残余(预检通过后被并发删除——最坏情形为 下一 action fail-loud,非静默损坏;多管理端并发受 kernel 已知残余约束,见 lancegraph_store.py 版本单调注释)。
  2. 面二 gate:entity/link 写前 validate_graph_integrity()(非 strict,全图 scope=None 才盖戳);violations → onto.store.integrity_violation(明细进摘要), 不做 allow_dirty_write 逃生舱。
  3. primary_property 自动去重(TFROB-695):primary 属性命中既有实体时 kernel 合并 进既有实体(输入 id 不落存储)——acreate_entity/aupdate_entity 返回前按 (namespace, primary 值) find_entities 谓词回读有效 id。注意 primary_property 属性 须 is_functional=True 且 is_inverse_functional=True(kernel 完整性校验 PRIMARY_NOT_INVERSE_FUNCTIONAL 会挡 gate)。
  4. error 面:kernel TFOntoStoreError 沿既有全局异常处理透出(500+repr); 稳定错误码面另行评估。

x-namespace / x-actions / x-ref 与主图的最佳实践

  • 在 nodes/edges 的每个元素 schema 层级声明 x-namespace 与 x-actions; 变量名:entityId / linkId / linkTypeId / objectTypeId / propertyId / namespaceId / linkSourceId / linkTargetId——所有 Action 共享同一 namespace。
  • x-actions:EntityInfo → ["aupdate_entity", "adelete_entity"]; LinkInfo → ["aupdate_link", "adelete_link"];def 级 DTO 同理行级操作。
  • x-ref 直接引用变量名(如 acreate_entity 的 namespace 参数 x-ref: namespaceId);x-default 支持从 namespace 动态取值。
  • Options 结构(labels/keys/values 三个 JMESPath 字段)为选项型变量注入, 供写 action 表单下拉使用(def 级列表容器即来源)。

update 家族声明契约(TFRS-391 修订 · 协议定稿)

update = 全量替换语义,表单预填当前值(防静默清空):

  • 可编辑参数经 x-default 声明为对应行 DTO x-namespace 变量——前端 resolveParamsFromSchema 按声明预填当前值;身份字段(*_id/namespace/ subject/object,即 x-ref 引用字段)保持只读、不预填。
  • 声明了上下文绑定的字段解析失败(无上下文触发——表单不在某行数据上打开)时, 由渲染引擎按协议声明拦截提交——声明缺失意味着字段不进提交体,后端 full-replacement(list(None or []))会静默清空旧值(TFRS-391 根因)。
  • 前端按声明通用实现(解析顺序恒为 x-ref → x-default → default),禁止 针对单一 Action 的特殊补丁;update 家族声明审计清单 = 本协议文件 §「图结构 DTO」 各 DTO 的 x-namespace 全集(aupdate_entity/aupdate_link/ aupdate_object_type/aupdate_link_type/aupdate_property 均可编辑字段全覆盖)。
  • required/immutable 预填取首个 binding(domainBindings[0]):本面写路径恒对全部 object_type_ids 分配同值,正常数据无偏差;他源导入存在多 binding 差异值时经 update 会被归一为首 binding 值(协议接受,语义在表单内可见)。
  • computed 属性(computedBy):本面无编辑参数(协议 v1 不暴露派生来源写入面); update 全量重写时 computed_by 由服务端继承 existing 保全——不会因仅改名把派生属性 清空成普通属性(TFRS-391 尾浪)。computed_by 一经建立不提供任何 API 移除(v1 定稿); 陈旧引用(被引 FunctionDef 已不存在)会使 update 被写安全预检拒绝——经 delete+recreate 或协议迁移/重建修复,勿当作新 Bug 报。

返回类型约束(与 tfs_action 行为一致)

  • resource_op_type="get_graph" 且 category="graphical":返回类型必须继承 BaseGraphical——推荐 -> GraphPayload。
  • resource_op_type="get_list" 且 category="pageable":返回类型必须继承 BasePageableList——推荐 -> ObjectTypeBoundedList 等(BoundedList 为有界枚举容器)。
  • 否则在 @tfs_action 注册阶段抛出 TypeError(「返回类型不符合协议」)。