Files
pj0235-eai_agentplatform/docs/09_Research/RS04_对象封装与分层完善性考察.md
eaiadmin 90031b75f3 docs: 重构仓库文档目录并迁移训练素材
按当前架构重组 docs 目录,统一中文命名与目录分层,并将训练原材料迁移到独立目录以保持架构文档边界清晰。
2026-09-22 23:23:16 +08:00

554 lines
30 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 对象封装与分层完善性考察
> 落盘日期: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. [本稿性质与判据来源](#0-本稿性质与判据来源)
1. [结论摘要](#1-结论摘要)
2. [思路一:对象封装](#2-思路一对象封装)
3. [思路二:层次](#3-思路二层次)
4. [两个思路的交点](#4-两个思路的交点)
5. [完善性总评](#5-完善性总评)
6. [对本项目的推论(未实施)](#6-对本项目的推论未实施)
7. [未考察 / 存疑项](#7-未考察--存疑项)
8. [证据索引](#8-证据索引)
---
## 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` 绑专员 / 技能用的是**真列 + 索引**:
```go
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` 绑技能用的是:
```go
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` 的实际写法是:
```go
if strings.TrimSpace(item.ActionRecordsJSON) == "" {
item.ActionRecordsJSON = jsonutil.MustJSON(buildActionRecords(item))
}
```
**缺省时生成一段。** 生成方式是:
```go
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`:
```go
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 的反直觉推论出发:
**不先修分层,也不先补关系表。先让「一个动作」横跨完整生命周期。**
理由(三条,均可从本稿实测推出):
1. **它同时给动作一个归属层、给对象一个唯一实现** —— §4 的闭环从任一端切开都可以,而横切一刀同时切两端。
2. **它让第 4 层空转的治理字段第一次有意义** —— 写动作必须回答「谁批准、写哪个对象、记什么审计」,`RiskLevel` / `ApprovalMode` / `AuditLevel` / `ConnectorRef` 从装饰变成必需。
3. **它是产品已经画好的路线** —— 三个 `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 未考察(本轮范围外,不得据此推断)
1. **`ai` / `auth` / `middleware` / `web` 目录** —— 未审计,本稿结论不覆盖
2. **`tasks` / 项目 / 知识库相关模型** —— 第 5 层「相对健康」是**基于模型命名与结构的印象,未逐字段核实**
3. **`user_xapp_center` 内的 `FavoriteKeys` / `RecentKeys` / `CustomXApps` 三个分隔符字段** —— 已见于模型定义,**未核实其读取方与一致性**(原则 4 说用户态要分离,已分离;但内部仍是字符串)
4. **AR10 / AR11 的可删除封装** —— 有规范文件,**未核实实现是否满足「删目录 + 删注册 + 跑卸载」**
5. **前端静态目录的优先级影响面** —— 已知 `skillCatalog.js` 的 fallback 分支先于远端返回,**未穷举其他 store 是否有同样写法**
6. **`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 }` 写法:
```go
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` —— **该分支永不命中** |