554 lines
30 KiB
Markdown
554 lines
30 KiB
Markdown
# 对象封装与分层完善性考察
|
||
|
||
> 落盘日期: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` —— **该分支永不命中** |
|