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