30 KiB
对象封装与分层完善性考察
落盘日期:2026-09-19 状态:考察结论(第 1、5 节为判断;第 2–4 节为实测证据;第 6 节为推论,未实施) 目的:从对象封装与层次两个正交视角,考察本项目当前的完善程度,定位结构性缺口的确切位置 关联文档:
docs/09_Research/RS03_本体论与语义层专题.md(第 8 节的切口判断;本稿是其代码侧取证,两稿结论互相印证)docs/09_Research/RS02_外部智能体平台架构对照.md(六层的外部对照)docs/02_Architecture/对象标准化与解耦总则.md(本稿第 2 节的判据来源)docs/01_System_Overall/SY22_角色技能应用统一任务架构.md(六层定义,本稿第 3 节的判据来源)docs/02_Architecture/AR09_对象命名规范.md(对象命名规范)
目录
0. 本稿性质与判据来源
与 RS02 / RS03 的关键区别:本稿不含任何外部检索。
RS02(外部平台对照)与 RS03(本体论专题)的结论依赖公开文档,需要用「高/中/低」可信度分档、需要标注「未查到」。本稿的全部结论来自对本仓库的直接读取与 grep 计数,证据类型不同,因此不适用那套分档 —— 本稿每一处断言都在第 8 节有可复现的命令或代码位置。
唯一的分档区分是「实测」与「判断」:
| 标记 | 含义 |
|---|---|
| 【实测】 | 直接 grep / 读文件得到,第 8 节有索引,可复现 |
| 【判断】 | 基于实测的推论,可能被推翻 |
两个思路的判据来自项目自己的文档,不是外部理论:
- 封装的判据来自
docs/02_Architecture/对象标准化与解耦总则.md:§2 定义对象为对象定义 + 绑定关系 + 运行态数据;§4 给出五条解耦原则(原则 1 后端唯一目录源 / 原则 2 页面不能成为数据源 / 原则 3 配置文件不能充当生产目录 / 原则 4 对象定义与用户态分离 / 原则 5 编号系统化);§6 指定绑定关系应显式化为四张关系表。 - 层次的判据来自
SY22 §4.2–4.6的六层定义:外部连接层 / 本体与语义上下文层 / 总线与编排层 / 能力层 / 对象层 / 工作台与运行治理层。
本稿的方法论立场:用项目自己写下的标准去量项目自己。如果量出来不合格,那说明标准和实现之间有一段落差 —— 不预设是哪一方的错。
1. 结论摘要
这个项目把「对象是什么」做得很好,把「对象之间怎么动」还没开始做。
1.1 两条思路各自的发现
| 思路 | 发现 | 标记 |
|---|---|---|
| 封装 | 定义层做到了(表 / 编号 / state / 后端唯一目录源) | 【实测】 |
| 封装 | 关系层不是「没建」,是「建成了字符串且没人读」 —— 三种表达形式,读取方分别是 0 / 1(仅UI) / 1(仅UI) | 【实测】 |
| 封装 | 正确做法已在同一仓库里(xapp_definition 用真列 + 索引),只是没推广 |
【实测】 |
| 封装 | buildActionRecords 前后端各有一个实现;同一对象 3 个生产者 |
【实测】 |
| 封装 | 运行态层是伪造的(缺省时生成一段看起来像发生过的记录) | 【实测】 |
| 层次 | 六层在代码里没有对应物,因此没有任何机制能阻止跨层调用 | 【实测】 |
| 层次 | 第 1 层越位(持有对象-动作目录)、第 2 层缺位(无动作无权限)、第 4 层空转(治理字段零读取方)、第 6 层越位(前端自推风险与审批) | 【实测】 |
| 层次 | action 没有归属层 —— 五个碎片分散在五层,无一有完整责任 |
【实测】+【判断】 |
1.2 两条思路的交点
封装思路说「专员没封装住动作」;层次思路说「动作没有归属层」。这是同一件事的两种说法,且互为因果形成闭环。
详见第 4 节。
1.3 一句话判断
| 做得好 | 还没开始 |
|---|---|
| 对象定义、编号、状态、目录源唯一 | 对象之间的关系(真关系而非字符串) |
| 系统目录与用户态分离 | 运行态的真实记录(而非伪造) |
| 命名与迁移收口(判据干净) | 层与层之间的边界 |
| 可删除封装有明确目标 | 动作的责任主体 |
证据表明产品自己知道下一步是「动」:三个 output 连接器已声明 insert_* / update_*、Mode: "write"、Capabilities: ["write","upsert"],全部是 blueprint(见 §3.2 与 RS03 §8.5)。
2. 思路一:对象封装
2.1 判据
docs/02_Architecture/对象标准化与解耦总则.md §2:一个对象 = 对象定义 + 绑定关系 + 运行态数据。三者必须分层,不能混。
补充判据(来自§4 五条原则):
- 原则 1:后端是唯一目录源 —— 前端只请求、归一化、渲染
- 原则 2:页面不能成为数据源
- 原则 3:配置文件只能承担 fallback / 样式增强 / 本地 demo
- 原则 4:
系统目录 != 用户偏好 != 运行态挂载 - 原则 5:编号是正式对象属性,不是前端算出的 UI 字符串
2.2 逐对象打分
| 对象 | 定义表 | 编号 | 关系怎么表达 | 运行态 | 判定 |
|---|---|---|---|---|---|
| 应用 | xapp_definition |
✓ | 真列 + 索引(specialist_key / skill_key) |
user_xapp_center 分离 ✓ |
最完整 |
| 专员 | specialist |
✓ | 9 个 type:text 字段,技能是分隔符字符串 |
4 个 *RecordsJSON,缺省时伪造 |
定义扎实,关系未封 |
| 技能 | skill_definition |
✓ | 3 个 JSON 字符串(ActionRefs / PolicyRefs / OntologyBinding) |
— | 关系三个全空转 |
| 动作 | action_definition |
✓ | OntologyBindingJSON(字符串)+ ConnectorRef |
— | 有表无运行时 |
| 连接器 | registry 字面量 | — | Definition.Actions / Objects |
— | 不是对象,是代码 |
2.2.1 定义层:做到了
【实测】每个对象都有后端表、有编号(display_code / eailogic_code)、有 state / sort_order、前端只消费。
原则 1(后端唯一目录源)成立,原则 4(系统目录与用户态分离)成立 —— xapp_definition(系统目录)与 user_xapp_center(用户收藏 / 最近 / 自定义)是两张表。
原则 5(编号系统化)成立 —— 专 / EAI-S-、能 / EAI-K-、应 / EAI-A-、连 / EAI-C- 落地为 display_code + eailogic_code 两个真列。
这是本项目最扎实的部分,不应被下文的批评抹掉。
2.3 关系层的真实形态
docs/02_Architecture/对象标准化与解耦总则.md §6 说:
后面不再继续把绑定关系藏在自由文本里。……应该逐步补成显式关系:
specialist_skill_binding/xapp_specialist_binding/xapp_skill_binding/specialist_connector_binding
【实测】这四张表全仓各 0 处。但**「四张表没建」不是本节的重点** —— §9 明确说了「先强对象,再补关系」,推迟建表是有意的决策。
重点是:关系不是「没建」,而是已经用另外三种形式建了,而它们都没有读取方。
2.3.1 三种表达形式
| 形式 | 实例 | 字段数 | 运行时读取方 |
|---|---|---|---|
| 分隔符文本 | specialist.BaseSkills = "事项处理,合同审查",靠 splitText() 运行时切 |
9 个 | 有(但只作字符串切分,不作关系) |
| JSON 字符串(指向别层) | ActionRefsJSON / PolicyRefsJSON / OntologyBindingJSON |
3 个 | 0 / 0 / 0 |
| JSON 字符串(自描述记录) | ActionRecordsJSON / PermissionRecordsJSON / ResultRecordsJSON / InteractionCardJSON |
4 个 | 有(前端读,见 2.3.3) |
2.3.2 指向别层的三个字段,逐个核实
【实测】排除 model / seed / api / validation / admin_handlers 之后的真正运行时读取方:
| 字段 | 后端运行时读者 | 前端读者 | 实际用途 |
|---|---|---|---|
PolicyRefsJSON |
无 | 无 | 全仓零读取方 |
ActionRefsJSON |
无 | skillCatalog.js:57 |
把动作 key 渲染成**「步骤 1」「步骤 2」** |
OntologyBindingJSON |
无 | skillCatalog.js:72 |
把 object_types / action_types 渲染成标签 |
结论【实测】+【判断】:关系只被用来显示,从不参与执行。
ActionRefsJSON 的渲染目标尤其能说明问题 —— 一个动作引用列表,在 UI 上的最终形态是「步骤 1 / 步骤 2 / 步骤 3」,detail 是动作 key 字符串本身,expectedOutput 为空。
2.3.3 自描述记录的读者是前端
【实测】ActionRecordsJSON(15 处) / ResultRecordsJSON(13 处) / PermissionRecordsJSON(8 处) / InputsRecordsJSON(8 处) 在前端有真实消费。这不是缺陷 —— 后端算好视图模型给前端渲染,是合理做法。
但要注意它们的来源:见 §2.6,这些记录在缺省时是后端生成的。
2.3.4 与前一轮发现的一致性
RS03 §8.5 发现「连接器层持有全平台唯一真实的对象-动作目录」。本节发现「技能/专员层持有三个指向别层的字符串」。
同一个现象的两种尺寸:真实的关系结构长在第 1 层(连接器),而第 2 层该有的关系只剩字符串壳子。
2.4 正确做法已在仓库里
【实测】xapp_definition 绑专员 / 技能用的是真列 + 索引:
SpecialistKey string `gorm:"column:specialist_key;size:64;default:'';index" json:"specialist_key"`
SkillKey string `gorm:"column:skill_key;size:64;default:'';index" json:"skill_key"`
而 specialist 绑技能用的是:
BaseSkills string `gorm:"type:text" json:"base_skills"`
对比:
xapp_definition → 专员/技能 |
specialist → 技能 |
|
|---|---|---|
| 形式 | 真列 | 分隔符字符串 |
| 可索引 | ✓ | ✗ |
| 可 join | ✓ | ✗ |
| 可加外键约束 | ✓ | ✗ |
| 查询 | WHERE specialist_key = ? |
全表扫 + 应用层切分 |
这不是「项目不会做」,是「同一个仓库里两种做法并存,正确的那种没被当成标准」。
该判断与 RS03 §8.5 完全同构 —— 那里是「对象-动作模式写在了连接器契约里」,这里是「真关系写在了应用定义里」。两处都是:正确模式已存在,只是不在它该在的层,也没被推广。
2.5 封装破裂:一个概念三个生产者
「封装」的定义是一个概念只有一处定义、一个权威。以下实测显示恰好相反。
| 概念 | 后端实现 | 前端实现 | 生产者数 |
|---|---|---|---|
buildActionRecords |
specialists/runtime/records.go:131 |
specialists/runtime/specialistFlow.js:165 |
2 |
inferRiskLevel |
records.go:230 |
StudioPage.vue:1418、MarketPage.vue:1176 |
3 |
inferActionType |
records.go:242 |
specialistFlow.js:65(getActionType)、MarketPage.vue:1169 |
3 |
| 审批判定 | records.go:216(approvalState) |
MarketPage.vue:1182(shouldRequireApproval) |
2 |
动作记录这一种对象,有 3 个生产者:后端 records.go、前端 specialistFlow.js、前端 MarketPage.vue。
这不是有人偷懒 —— 是因为没有权威的那一处可抄。
【实测】这与 RS03 §8.4.1 找到的「同一概念 5 套词表」是同一件事的不同侧面:词表分叉是封装破裂的症状,封装破裂是词表分叉的原因。
2.6 运行态层是伪造的
docs/02_Architecture/对象标准化与解耦总则.md §2.3 定义运行态为「这次执行过程中发生了什么」。
【实测】records.go 的实际写法是:
if strings.TrimSpace(item.ActionRecordsJSON) == "" {
item.ActionRecordsJSON = jsonutil.MustJSON(buildActionRecords(item))
}
缺省时生成一段。 生成方式是:
skills := splitText(item.BaseSkills) // 切分隔符字符串
...
"risk_level": inferRiskLevel(value), // 从中文名子串猜
"action_type": inferActionType(value), // 从中文名子串猜
"approval_state": approvalState(item.SpecialistMode, value),
所以这层不是「记录发生了什么」,是「编一段看起来像发生了什么的话」。
【实测】其中 PermissionRecordsJSON 由 buildPermissionRecords 生成,内容为硬编码的四条(资源范围 / 访问边界 / 审批要求 / 禁止动作),与本次执行无关。
一处必须说清的区分:这不等于「这些字段没用」。它们作为初始展示数据是有效的 —— 一个刚创建、还没跑过的专员,UI 需要显示点什么。问题在于它们与「真实运行态」共用同一组字段名,且没有任何标记区分二者。 消费方无法知道读到的是记录还是占位。
2.7 小结
| 三件 | 状态 |
|---|---|
| 对象定义 | 做到了 |
| 绑定关系 | 建成了字符串,无读取方(正确做法已在应用对象上示范) |
| 运行态数据 | 缺省时伪造,且与真实记录共用字段名 |
3. 思路二:层次
3.1 六层在代码里没有对应物
【实测】internal/ 顶层目录:
ai api auth config connectors jsonutil middleware
model repository skills specialists store web xapps
按技术职责与对象类型划分,不按层划分。 六层的名字(外部连接 / 本体与语义 / 总线与编排 / 能力 / 对象 / 工作台治理)在目录结构中无对应物。
【实测】grep ontology|semantic|bus|orchestrat 在 internal/ 的命中,全部落在 skills/packages/*/manifest.go 与 skill_definition.go —— 没有任何一个是「本体层」或「总线层」的代码模块。
3.1.1 后果(【判断】,但很重要)
分层是逻辑概念,不必一一对应目录 —— 这一点必须先承认。但后果需要说清:
没有任何一层有代码边界,因此没有任何机制能阻止跨层调用。没有边界,就没有「越界」这回事。
这解释了为什么第 2 节与第 3 节找到的所有问题都以同一种形状出现 —— 不是五种独立的疏漏,是同一个原因的五次显形。
3.2 逐层:空位与越位
| 层 | 应该管什么(SY22) | 实际 | 判定 |
|---|---|---|---|
| 1 外部连接 | 协议适配 | 持有全平台唯一真实的对象-动作目录;是全仓唯一运行时读对象定义处(task_runtime.go:269) |
越位(干了第 2 层的活) |
| 2 本体与语义上下文 | 业务对象、工作对象与关系 | 对象有表;关系是字符串;无动作、无权限(SY22 §4.2 原文即不含) | 缺位 |
| 3 总线与编排 | 任务总线 / 事件总线 / 能力编排 | specialists/runtime 存在,但策略是「AI 还是单连接器」硬编码二选一(ChooseTaskStrategy) |
弱 |
| 4 能力层 | skill / action / policy |
action_definition 表有、5 个治理字段有、GetByKey 写好 —— 全部零调用方 |
空转 |
| 5 对象层 | 任务对象、产物 | task_record / task_run / task_artifact |
相对健康 |
| 6 工作台与运行治理 | 呈现 + 治理 | 前端自己推动作类型、风险等级、审批判定,自己拼动作记录 | 越位(干了第 4 层的活) |
3.2.1 最锋利的一对
【实测】第 4 层:action_definition 的 RiskLevel / ApprovalMode / AuditLevel / InputSchemaJSON / ExposedToUser 零运行时读取方,ActionDefinitionRepo.GetByKey 写好了且有唯一索引但无调用方。
【实测】第 6 层:前端 MarketPage.vue:1176 / StudioPage.vue:1418 自己实现 inferRiskLevel,MarketPage.vue:1182 自己实现 shouldRequireApproval。
第 4 层不是没实现 —— 是实现完了没有消费方,而消费方在另一层重新实现了。
这比「没实现」难查得多,因为两边看起来都「有」。
3.2.2 第 4 层的运行时风险实际来自哪里
【实测】records.go:230:
func inferRiskLevel(value string) string {
switch {
case strings.Contains(value, "发布"), strings.Contains(value, "审批"),
strings.Contains(value, "同步"), strings.Contains(value, "回写"):
return "high"
case strings.Contains(value, "分析"), strings.Contains(value, "生成"),
strings.Contains(value, "汇总"):
return "medium"
default:
return "low"
}
}
风险等级由技能的中文名子串决定,而非由第 4 层声明。且动作条目本身也不是从 action_definition 读的,而是从 specialist.BaseSkills 这个分隔符文本字段切出来的(records.go:132)。
3.3 action 没有归属层
这是层次思路的核心发现。
| 「动作」的碎片 | 在哪层 | 有无读取方 |
|---|---|---|
| 声明 | 第 4 层 action_definition |
✗ |
| 关系 | 指向第 2 层 OntologyBindingJSON |
✗(仅前端渲染成标签) |
| 治理 | 第 4 层 RiskLevel / ApprovalMode / AuditLevel |
✗ |
| 执行实现 | 第 1 层 Definition.Actions |
✗(全仓零次) |
| 展示 | 第 6 层前端 | ✓ 自己重算 |
五层各持一块碎片,没有任何一层对「动作」负完整责任。
这正是 RS03 §8 那个判断的具体机制:切口切错不是抽象的层次问题 —— 它的表现是 一个对象被五层瓜分,每层只拿到一个没有读取方的碎片。
4. 两个思路的交点
| 思路 | 说法 |
|---|---|
| 封装 | 专员没有封装住「动作」 |
| 层次 | 「动作」没有归属层 |
同一件事的两种说法 —— 而且它们互为因果,形成闭环。
没有归属层
→ 每层各拿一块
→ 没有唯一权威
→ 消费方就地重算(§2.5)
→ 对象永远封不住
→ 没有「这个对象归这层」的边界
→ 层只能是文档上的概念(§3.1)
→ 更没有归属层
修任何一边都会带动另一边。 这一点对实施顺序有直接含义(见 §6)。
4.1 一个反直觉的推论
通常的做法是「先定好分层,再按层实现」。但本项目的证据指向相反的顺序:
分层之所以没落地,不是因为没画清楚(SY22 画得很清楚),而是因为没有任何一个对象真正需要一个层。当对象本身还只是自描述的字面量时,「这一层管什么」是个空问题 —— 什么都可以放任何地方。
【判断】让某一个对象真正横跨完整的生命周期(声明→治理→执行→审计),是让分层变得有意义的必要前提,而不是它的结果。
5. 完善性总评
5.1 做到了的(【实测】)
| 项 | 状态 |
|---|---|
对象定义层:表 / 编号 / state / sort_order / 后端唯一目录源 |
原则 1 成立 |
系统目录与用户态分离(xapp_definition vs user_xapp_center) |
原则 4 成立 |
编号系统化(display_code + eailogic_code 两个真列) |
原则 5 成立 |
| 应用对象的关系用真列 + 索引 | 做法正确 |
| 命名与迁移收口 | 判据干净(见 更名收尾说明.md:「改名字的地方保留旧名,用名字的地方改新名」) |
| 可删除封装有明确目标(AR10 / AR11「删目录 + 删注册 + 跑卸载」) | 目标明确 |
对象命名有规范性文件(AR09)且与 TOP_CODING_RULES.md G03 配套 |
规范存在 |
5.2 没做到的(【实测】)
| 项 | 状态 |
|---|---|
| 关系层 | 三种字符串形式,指向别层的三个字段读取方为 0 |
| 运行态层 | 缺省时伪造,且与真实记录共用字段名 |
| 层次 | 无代码边界,无机制阻止跨层调用 |
| 动作 | 五个碎片分散五层,无责任主体 |
| 前端兜底目录 | 前端有静态目录,且在部分路径上优先于后端数据(skillCatalog.js 的 fallback 分支先返回) |
5.3 一句话
这个项目把「对象是什么」做得很好,把「对象之间怎么动」还没开始做。
而 §1.3 已述:产品自己已经声明了「动」的那一半(三个 output 连接器、insert_* / update_*、Capabilities: ["write","upsert"]),全部停在 blueprint。
6. 对本项目的推论(未实施)
本节是推论,不是结论。未实施,未验证。
由 §4.1 的反直觉推论出发:
不先修分层,也不先补关系表。先让「一个动作」横跨完整生命周期。
理由(三条,均可从本稿实测推出):
- 它同时给动作一个归属层、给对象一个唯一实现 —— §4 的闭环从任一端切开都可以,而横切一刀同时切两端。
- 它让第 4 层空转的治理字段第一次有意义 —— 写动作必须回答「谁批准、写哪个对象、记什么审计」,
RiskLevel/ApprovalMode/AuditLevel/ConnectorRef从装饰变成必需。 - 它是产品已经画好的路线 —— 三个
output连接器连写入对象都定义好了(ObjectDefinition.Mode: "write"、FilterHint: "按业务键 upsert"),不是新方向。
候选切口(按风险从低到高):
| 候选 | 风险 | 说明 |
|---|---|---|
dingtalk_table_output / feishu_bitable_output / wecom_sheet_output 三选一 |
低 | 写自己拥有的外部表格,不碰 ERP 主数据 |
kingdee(唯一有真实代码包的连接器) |
高 | 写 ERP 主数据;且当前只读,需先确认 K3Cloud 写接口与授权 |
验收判据(可证伪):
Definition.Actions出现第一个读取方action_definition的某个治理字段被运行时读取并改变行为- 「同一概念 N 套词表」(RS03 §8.4.1)减到 1 套
- 一个动作的完整链路只有一个实现,不分叉到前端
若上述判据无法达成 —— 那说明本项目实际要做的是只读语义层(RS03 §8.6 的路线 B),此时应改的是命名与文档,而非继续加对象类型。这个判断留给实测结果,不在此预设。
7. 未考察 / 存疑项
7.1 未考察(本轮范围外,不得据此推断)
ai/auth/middleware/web目录 —— 未审计,本稿结论不覆盖tasks/ 项目 / 知识库相关模型 —— 第 5 层「相对健康」是基于模型命名与结构的印象,未逐字段核实user_xapp_center内的FavoriteKeys/RecentKeys/CustomXApps三个分隔符字段 —— 已见于模型定义,未核实其读取方与一致性(原则 4 说用户态要分离,已分离;但内部仍是字符串)- AR10 / AR11 的可删除封装 —— 有规范文件,未核实实现是否满足「删目录 + 删注册 + 跑卸载」
- 前端静态目录的优先级影响面 —— 已知
skillCatalog.js的 fallback 分支先于远端返回,未穷举其他 store 是否有同样写法 specialist的 9 个type:text字段中,哪些实际存 JSON 而非分隔符文本 ——splitText()只用于BaseSkills/GeneratedSkills/InfoSources,其余 6 个未逐一核实
7.2 存疑(【判断】,可能被推翻)
| 项 | 存疑点 |
|---|---|
| §2.6「运行态是伪造的」 | 伪造的记录作为初始展示数据是否合理,取决于产品意图;本稿只断言「与真实记录共用字段名、无区分标记」,未断言这些字段无用 |
| §3.2 第 3 层评为「弱」 | 仅基于 ChooseTaskStrategy 的硬编码二选一,未系统审计 specialists/runtime 全部逻辑 |
| §4.1「先横切一个对象,再修分层」 | 这是推论;存在相反可能 —— 分层先立、对象自然归位。本稿倾向前者,理由见 §4.1,但未验证 |
| §5.2「前端兜底目录优先于后端」 | 已核实 skillCatalog.js 的一个分支,未确认在生产数据下是否真会走到该分支 |
7.3 与 RS03 的关系
| RS03 | 本稿 | |
|---|---|---|
| 视角 | 外部标准(Palantir / Fabric / 工业标准)对照 | 项目自身标准(总则 / SY22)度量 |
| 结论 | 第 2 层与第 4 层切口切错 | 动作无归属层;关系是字符串;运行态伪造 |
| 关系 | 互相印证 | 本稿是 RS03 §8 的代码侧取证 |
RS03 §8.4.1 记载的一处自我更正(初稿误写「没有权限裁决、没有审计」)在本稿 §3.2.1 得到系统性表述:字段齐全,零读取方 —— 比「没有」更麻烦。
8. 证据索引
所有路径相对于仓库根。命令均可在
eai_agentplatform/backend-go或eai_agentplatform/frontend/src下复现。
8.1 对象定义表
| 位置 | 事实 |
|---|---|
backend-go/internal/specialists/model/specialist.go:24-32 |
9 个 type:text 关系字段:ConnectorScope / PermissionScope / ResourceBindings / InfoSources / BaseSkills / AIAssistance / GeneratedSkills / RuleFileMarkdown / AllowedSkills |
backend-go/internal/specialists/model/specialist.go:33-36 |
4 个 *RecordsJSON:InputsRecordsJSON / PermissionRecordsJSON / ActionRecordsJSON / ResultRecordsJSON |
backend-go/internal/skills/model/skill_definition.go:26-28 |
ActionRefsJSON / PolicyRefsJSON / OntologyBindingJSON |
backend-go/internal/xapps/model/model.go:25-26 |
SpecialistKey / SkillKey —— 真列 + index |
backend-go/internal/xapps/model/user_center.go:9-11 |
FavoriteKeys / RecentKeys / CustomXApps(用户态,分隔符字符串) |
8.2 关系字段的读取方
| 字段 | 后端运行时 | 前端 | 结论 |
|---|---|---|---|
PolicyRefsJSON |
0 | 0 | 全仓零读取方 |
ActionRefsJSON |
0 | skills/store/skillCatalog.js:57 |
仅渲染「步骤 N」 |
OntologyBindingJSON |
0 | skills/store/skillCatalog.js:72 |
仅渲染标签 |
复现:grep -rn '<字段名>' --include='*.go' . | grep -v '_test.go' | grep -v '/model/' | grep -v 'seed.go'
8.3 四张提议关系表
specialist_skill_binding / xapp_specialist_binding / xapp_skill_binding / specialist_connector_binding —— 各 0 处(grep -rn '<表名>' --include='*.go' . | wc -l)
8.4 封装破裂:重复实现
| 概念 | 后端 | 前端 |
|---|---|---|
buildActionRecords |
internal/specialists/runtime/records.go:131 |
frontend/src/specialists/runtime/specialistFlow.js:165 |
inferRiskLevel |
records.go:230 |
frontend/src/views/workbench/StudioPage.vue:1418、MarketPage.vue:1176 |
inferActionType |
records.go:242 |
specialistFlow.js:65(getActionType)、MarketPage.vue:1169 |
| 审批判定 | records.go:216(approvalState) |
MarketPage.vue:1182(shouldRequireApproval) |
8.5 第 4 层空转
| 位置 | 事实 |
|---|---|
backend-go/internal/model/action_definition.go:15-18 |
RiskLevel / ApprovalMode / AuditLevel / ExposedToUser 齐全 |
backend-go/internal/repository/action_definition.go:36-42 |
GetByKey 写好,key 有唯一索引,无调用方 |
internal/api/action_definition.go |
ActionDefinitionRepo 的唯一引用方(后台 CRUD) |
8.6 Definition.Actions 零读取方
grep -rn '\.Actions' --include='*.go' . → 无匹配。
连接器 registry 共 33 个连接器定义、上百个动作声明,无一有读取方。
8.7 写连接器全部 blueprint
| Key | Direction | Capabilities | 动作 | Mode / Status |
|---|---|---|---|---|
dingtalk_table_output |
output |
["write","upsert","record"] |
insert_table_record / update_table_record / append_result_report |
blueprint / blueprint |
feishu_bitable_output |
output |
["write","upsert","record"] |
insert_bitable_record / update_bitable_record / sync_result_view |
blueprint / blueprint |
wecom_sheet_output |
output |
["write","upsert","record"] |
insert_sheet_record / update_sheet_record / notify_sheet_owner |
blueprint / blueprint |
三者 ObjectDefinition.Mode 均为 "write",FilterHint 均描述 upsert 语义。
8.8 连接器层只有读动词
internal/connectors/registry/registry.go 导出:ListDefinitions / GetDefinition / Query —— 唯一数据动词是 Query。
internal/connectors/packages/ 下只有 kingdee 一个真实代码包,其 Capabilities: ["read","query","schema_hint"],5 个动作全为 fetch_* / query_*。
注:
kingdee.go:299的postJSON(http.MethodPost)是 K3Cloud WebAPI 的查询传输方式,非写操作。
8.9 六层在代码中无对应物
ls internal/ → ai api auth config connectors jsonutil middleware model repository skills specialists store web xapps
grep -rln 'ontology|semantic|bus|orchestrat' --include='*.go' internal/ → 命中全部落在 skills/packages/*/manifest.go 与 skill_definition.go
8.10 运行态伪造
backend-go/internal/specialists/runtime/records.go:16-34 —— 四个 *RecordsJSON 的 if empty { generate } 写法:
if strings.TrimSpace(item.InputsRecordsJSON) == "" {
item.InputsRecordsJSON = jsonutil.MustJSON(buildInputsRecords(item))
}
records.go:82-127 —— buildPermissionRecords 返回硬编码四条,与本次执行无关。
8.11 与 RS03 相关的既有取证
| 位置 | 事实 |
|---|---|
internal/model/action_definition.go:19、internal/skills/model/skill_definition.go:28 |
OntologyBindingJSON 字段定义 |
全仓 OntologyBindingJSON 共 22 处,全部落在 model / validation / seed / admin handler |
运行时读取方 0 |
internal/specialists/runtime/task_runtime.go:269,279 |
全仓唯一运行时读对象目录处(definition.Objects[0],UseDemo: true) |
frontend/src/specialists/runtime/specialistFlow.js:58,186 |
判断 approval_state === 'approval_required';后端只产出 ready / pending —— 该分支永不命中 |