mirror of
https://github.com/iflytek/skillhub.git
synced 2026-10-15 04:18:20 +00:00
305 lines
21 KiB
Markdown
305 lines
21 KiB
Markdown
## Context
|
||
|
||
需求背景与范围见 `proposal.md`。当前 Web 可以手工创建 Suite、编辑 DRAFT,并基于已有快照创建
|
||
新 SuiteVersion,但不能一次提交多个本地 Skill 文件夹、自动比较内容并统一跟踪成员发布。当前
|
||
Suite 使用不可变 SuiteVersion 快照,成员必须是精确的
|
||
PUBLISHED SkillVersion,并允许引用符合可见性要求的跨 Namespace PUBLIC Skill。普通 Skill 发布
|
||
可能涉及对象存储、异步扫描和独立人工审核。技能市场已经使用 `/search`,套件专区已经使用
|
||
`/suites`、类型化资源接口和 Suite 卡片。Skill 详情目前只有在该 Skill 是 Entry Skill 时才展示
|
||
所属 Suite。Suite 的 summary 和 overview 当前可为空,因此已发布 Suite 可能只能显示空内容提示。
|
||
现有 Label 用于 Skill 容器分类,Tag 是后端版本别名。
|
||
|
||
## Goals / Non-Goals
|
||
|
||
**目标:**
|
||
|
||
- 将一次 ZIP 或根目录上传转换为稳定、可检查的 Suite 创建或更新计划。
|
||
- 允许在操作者具备 Skill 创建权限的 Namespace 中,从携带包创建新 Skill。
|
||
- 允许继续精确引用合规的非本人所有 Skill,且不尝试发布它们。
|
||
- 只发布操作者已经具备独立发布权限的携带包成员。
|
||
- 扫描和审核期间不创建临时或部分有效的 SuiteVersion。
|
||
- 让重试、异步审核、权限变化和终止失败都可观察。
|
||
- 在任意成员 Skill 上安全展示当前用户可见的所属 Suite。
|
||
- 让 Suite 拥有自己的 Label,并要求新发布版本具备有效摘要和概述。
|
||
- 限制归档处理、数据库查询、扫描和反向引用投影的资源消耗。
|
||
|
||
**非目标:**
|
||
|
||
- 为操作者只能引用、不能管理的 Skill 发布新内容。
|
||
- 替代单 Skill 的独立扫描/审核或 Suite 自身审核。
|
||
- 合并技能市场和套件专区,或者改变两个入口的路由与产品定位。
|
||
- 批量修改成员 Label、增加 Suite Tag、迁移 Namespace、按成员关键词参与排名、CLI 上传
|
||
Bundle、嵌套 Suite 或跨 Registry 成员。
|
||
|
||
## Decisions
|
||
|
||
### 控制台套件清单:采用方案 2(2026-09-16 用户确认)
|
||
|
||
移除“我的套件”的独立发布任务页签。套件、尚未形成套件的创建操作统一服务端分页;按坐标归并,
|
||
生成套件后不重复列出成功操作。顶部采用 288px 搜索框、状态下拉和轻量“需处理”数量提示。
|
||
数量针对当前搜索所有页,而非本页;筛选、分页和计数在数据库中完成。新工作台接口保留旧列表接口
|
||
兼容性,每次固定两条 SQL,不读取成员、执行计划 JSON 或包内容;每页 12 条,搜索只在点击搜索
|
||
或按 Enter 后执行,输入与已提交条件分离,取消过时请求,仅存在运行/等待操作时每 5 秒轮询。正式套件版本审核复用现有“我的审核进度”。
|
||
|
||
### 1. 创建和更新共用 Manifest,成员分为“携带包”和“精确引用”
|
||
|
||
Manifest 描述完整目标成员集合和顺序,每个成员只能选择一种形式:
|
||
|
||
- **携带包成员**:指向一个以 `SKILL.md` 为根的包目录。Skill 不存在时,操作者必须在目标
|
||
Namespace 具备创建权限;Skill 已存在时,操作者必须具备该 Skill 的发布权限。通过规范化包
|
||
fingerprint 判断创建 Skill、创建 SkillVersion,还是复用现有精确版本。
|
||
- **精确引用成员**:指向一个已有、精确、PUBLISHED 的 SkillVersion,不携带包,Bundle 不修改它。
|
||
操作者只需满足现有 Suite 编排和读取权限,不需要拥有该 Skill。
|
||
|
||
创建模式没有基准快照,全部成员均为新增。更新模式以明确的 SuiteVersion 为基准,只有基准成员
|
||
未出现在 Manifest 中时才视为移除,不能因为没有包目录就视为移除。引用成员可以新增,也可以
|
||
显式重新固定到另一个合规 PUBLISHED 版本。Entry Skill 可以使用任一成员形式。
|
||
|
||
实现协议使用归档根级唯一文件 `SUITE.yaml`。首版结构固定为:
|
||
|
||
```yaml
|
||
apiVersion: skillhub.iflytek.com/v1alpha1
|
||
kind: SkillSuiteBundle
|
||
metadata:
|
||
namespace: global
|
||
slug: clinical-workflow
|
||
spec:
|
||
mode: CREATE # 或 UPDATE
|
||
baseVersion: 1.0.0 # 仅 UPDATE 必填,CREATE 禁止提供
|
||
version: 1.1.0
|
||
displayName: 临床工作流
|
||
summary: 套件摘要
|
||
overview: 套件 Markdown 概述
|
||
visibility: PUBLIC
|
||
changelog: 本次更新说明
|
||
entry: "@global/intake"
|
||
members:
|
||
- skill: "@global/intake"
|
||
package:
|
||
path: skills/intake
|
||
visibility: PUBLIC # 仅新 Skill 必填
|
||
- skill: "@global/shared-dictionary"
|
||
reference:
|
||
version: 2.3.1
|
||
```
|
||
|
||
协议采用严格字段集合,不识别的字段直接报错,避免拼写错误被静默忽略。`metadata` 坐标和
|
||
`spec.version` 是目标 Suite 身份;成员 `skill` 是唯一 Skill 身份;`SKILL.md` 的 `name`、
|
||
`description` 和可选 `version` 继续作为 Skill 自身发布元数据。若携带包的 `SKILL.md` 解析出的
|
||
slug 与成员 `skill` 的 slug 不同,或显式版本与预览解析出的目标版本冲突,预览阻塞,不设置覆盖
|
||
优先级。引用成员只能提供精确 `version`,不能同时携带包。已有 Skill 的 `package.visibility`
|
||
可以省略并继承当前值;若显式提供则必须与当前值一致。新 Skill 必须明确提供该字段。
|
||
|
||
**备选方案:要求每个成员都有包目录。** 不采用。Suite 可以合法引用其他用户的公开 Skill,强制
|
||
打包会错误暗示操作者可以重新发布或复制这些内容。
|
||
|
||
### 2. Suite 创建和更新使用同一个持久化 Saga
|
||
|
||
预览阶段保存有期限且归属当前操作者的 `PreviewSession`、临时归档和完整计划,但不占用 Suite 坐标
|
||
或版本。`PREVIEW_READY` 是 PreviewSession 状态,不是执行操作状态。确认事务先创建持久化
|
||
`ExecutionOperation` 并原子获取目标 Suite 坐标或版本占用;获取失败时必须在任何成员生命周期
|
||
副作用前结束,并要求重新预览。
|
||
|
||
ExecutionOperation 保存创建/更新模式、目标 Namespace/Suite/版本、操作者、归档摘要、完整成员计划、
|
||
已创建 Skill/SkillVersion ID、精确引用 ID 和失败原因。对外状态为 `RUNNING`、
|
||
`WAITING_FOR_MEMBERS`、`BLOCKED_RETRYABLE`、`REPREVIEW_REQUIRED`、`SUITE_DRAFT_CREATED` 和
|
||
`CANCELLED`。只有 PreviewSession 使用 `PREVIEW_READY`、`CONFIRMED` 和 `EXPIRED`;等待人工审核的
|
||
ExecutionOperation 不受 PreviewSession TTL 影响。
|
||
|
||
只有需要创建或发生变化且有权限的携带包成员进入现有 Skill 发布流程。所有新版本成为 PUBLISHED,
|
||
并且复用成员和引用成员最终仍符合要求后,更新模式才创建 SuiteVersion DRAFT;创建模式原子创建
|
||
Suite 容器及首个 DRAFT。任一成员失败都会阻止生成 Suite 草稿,但不会生成部分 SuiteVersion。
|
||
领域事件触发状态协调,同时使用有界定时恢复处理事件丢失或乱序。
|
||
|
||
**备选方案:SuiteVersion 暂时引用未发布成员。** 不采用。这会破坏 #828 的精确 PUBLISHED
|
||
版本不变量,并把临时成员处理扩散到 Suite 校验、审核、详情、删除和安装全链路。
|
||
|
||
### 3. 文件只提交一次,先预览再确认
|
||
|
||
Web 接受一个 ZIP;支持目录选择的浏览器还可以选择一个根目录,并以同一归档协议提交。服务端将
|
||
归档流式写入临时对象存储,校验 Manifest 和携带包成员,计算 fingerprint,批量读取基准快照、
|
||
精确引用和操作者权限。预览为每个成员分别返回成员关系变化 `ADDED`、`UPDATED`、`UNCHANGED`、
|
||
`REMOVED`,以及发布动作 `CREATE_SKILL`、`CREATE_VERSION`、`REUSE_VERSION`、`REFERENCE_VERSION`、
|
||
`NONE`,避免混淆“加入 Suite”和“创建 Skill”。预览不产生任何生命周期副作用。
|
||
|
||
确认使用不透明 token,并绑定操作者、模式、目标坐标、归档摘要、目标 Suite 版本和完整计划。
|
||
只有确认成功创建的非终态 ExecutionOperation 才独占目标 Suite 坐标或版本。预览阶段解析出的包
|
||
版本和引用 ID 在确认与重试过程中保持稳定。创建模式在成员就绪前只通过执行操作占用目标坐标,
|
||
不创建对普通用户可见的空 Suite。
|
||
|
||
### 4. 每个异步边界都保留独立权限
|
||
|
||
预览明确区分四种权限:
|
||
|
||
- 在目标 Namespace 创建 Suite,或管理已有 Suite 并创建新版本的权限;
|
||
- 在目标 Namespace 创建新 Skill 的权限;
|
||
- 读取和编排精确引用版本的权限;
|
||
- 发布携带包 Skill 的权限。
|
||
|
||
服务端在预览、确认、每次创建 Skill/SkillVersion 和 Suite 草稿创建时重新检查对应权限。新 Skill
|
||
坐标冲突、目标 Namespace 不可写、失去包发布权限或引用失效都会阻止对应写入或最终创建 Suite
|
||
草稿。Suite 所有权不会扩大成员 Skill 权限,错误响应不得泄露无权读取的资源元数据。
|
||
|
||
### 5. 部分成员发布必须可观察且不可破坏性补偿
|
||
|
||
确认前尽量发现包、版本、所有权、可见性和策略等确定性错误。但运行期仍可能出现某个成员版本
|
||
已经创建、另一个成员失败的情况。重试只处理未完成工作。取消会停止后续编排并阻止创建
|
||
SuiteVersion,但不会删除独立创建的 SkillVersion 或已经形成的审核历史。
|
||
|
||
Bundle 不得调用会自动撤回其他待审版本或删除替换已有版本的发布路径。预览发现同一 Skill 已有
|
||
`PENDING_REVIEW` 版本,或目标版本号已经对应任一非 `PUBLISHED` 版本时,直接阻塞并要求用户先在
|
||
现有 Skill 流程中处理。这里复用的是普通 Skill 的校验、存储、扫描、审核和审计规则,不是无条件
|
||
复用当前具有替换副作用的内存型发布方法。
|
||
|
||
成员状态收敛规则如下:
|
||
|
||
| SkillVersion 状态或事件 | Bundle 状态 | 可执行动作 | ID 处理 |
|
||
|---|---|---|---|
|
||
| `SCANNING`、`PENDING_REVIEW` | `WAITING_FOR_MEMBERS` | 查看扫描或审核进度 | 保持预览绑定 ID |
|
||
| PRIVATE 新版本进入 `UPLOADED` | `RUNNING` | 使用 Bundle 确认中已明确授予的私有发布授权,重新鉴权后执行现有 confirm-publish 转换 | 保持 ID |
|
||
| `PUBLISHED` | 该成员完成 | 等待其他成员或创建 Suite 草稿 | 保持 ID |
|
||
| `SCAN_FAILED` | 能以同一 ID 重扫时为 `BLOCKED_RETRYABLE`,否则为 `REPREVIEW_REQUIRED` | 重扫或重新上传预览 | 不得静默换 ID |
|
||
| `REJECTED` | `REPREVIEW_REQUIRED` | 修改内容后重新上传并预览 | 原 ID 不再自动恢复 |
|
||
| 待审版本被撤回为 `UPLOADED` | `REPREVIEW_REQUIRED` | 重新预览 | 不自动重新提交审核 |
|
||
| 已绑定版本被删除、替换、下架或引用身份变化 | `REPREVIEW_REQUIRED` | 重新预览 | 不跟随新 ID |
|
||
| 权限暂时撤销或 Namespace 冻结且计划身份未变化 | `BLOCKED_RETRYABLE` | 恢复权限或状态后重试,也可由有权角色取消 | 保持 ID |
|
||
|
||
`BLOCKED_RETRYABLE` 保留坐标/版本占用,只允许同一 ExecutionOperation 按原计划和原 ID 重试。
|
||
`REPREVIEW_REQUIRED` 是终态,进入时与坐标/版本占用在同一事务中释放。`CANCELLED` 和
|
||
`SUITE_DRAFT_CREATED` 同样在终态事务中释放占用。数据库使用仅覆盖占用状态的唯一约束,防止释放
|
||
与新确认并发时出现双写。等待审核和可重试阻塞不会因 PreviewSession 过期而自动释放;原操作者或
|
||
当前治理角色可以按权限取消长期操作。
|
||
|
||
状态读取只允许原操作者或当前有权治理目标 Suite/Namespace 的角色。读取时仍按当前权限过滤成员
|
||
元数据;重试和取消必须重新授权。最终创建 Suite 草稿前再次检查 Namespace 可写、Suite 创建或
|
||
管理权限、已有 Suite 仍为 ACTIVE、目标坐标/版本占用和全部成员资格。权限或 Namespace 状态暂时
|
||
不可用且计划身份未变化时进入 `BLOCKED_RETRYABLE`;资源身份或目标计划失效时进入
|
||
`REPREVIEW_REQUIRED`。
|
||
|
||
新 Skill 的目标可见性必须由 Manifest 明确提供。已有 Skill 始终继承当前 Skill 可见性,本功能不
|
||
承担可见性迁移;Manifest 提供不同值时阻止确认。预览展示每个携带包成员的最终可见性以及审核或
|
||
PRIVATE 直接发布路径,并在确认、成员写入和 Suite 草稿创建前使用现有受众兼容规则重新检查。
|
||
|
||
预览还保存每个成员当前 warning 集合及其摘要。确认页逐成员展示 warning,用户必须对 warning
|
||
执行独立于通用 Bundle 确认的明确确认;确认请求绑定已展示的 warning 摘要。warning 变化会使
|
||
预览失效,服务端不得因为用户只点击通用确认就静默启用普通发布的 `confirmWarnings`。
|
||
|
||
### 6. 保持现有 Skill fingerprint 和 Suite 快照模型
|
||
|
||
每个包目录都规范化为以自身 `SKILL.md` 为根的普通 Skill 包,并使用现有校验器和 fingerprint
|
||
算法。ZIP 文件顺序、压缩元数据、压缩级别和外层目录不参与内容身份。纯引用成员只比较精确
|
||
SkillVersion 身份和有效性,不下载或计算其包内容。
|
||
|
||
Manifest 提供目标 Suite/SuiteVersion 的展示信息、完整顺序和 Entry Skill。新建和变化包继续
|
||
使用现有发布版本规则;自动解析的版本在预览时固定。最终结果仍是一个只包含精确 PUBLISHED
|
||
引用的普通 DRAFT SuiteVersion。
|
||
|
||
### 7. 限制资源成本并保证归档安全
|
||
|
||
归档采用流式解析,拒绝危险路径、链接、重复规范化名称、过度解压以及超过配置的归档/成员/文件
|
||
限制。预览把成员文件保存为临时对象定位信息、大小、内容类型和已计算摘要,而不是把最大 Bundle
|
||
展开为一组常驻内存的 `byte[]`。确认后的发布边界从暂存对象流式读取并复用预览摘要,不重新解压
|
||
或计算 fingerprint;普通发布需要的文件 hash、包归档和存储对象也从该流式输入生成。当前成员、
|
||
引用、版本和权限批量读取。纯引用和未变化成员不复制对象、不扫描、不审核。
|
||
|
||
首版复用 `skillhub.publish.max-package-size` 同时限制 ZIP 压缩体积和整个 Bundle 的解压后总量,
|
||
单文件限制继续复用 `skillhub.publish.max-single-file-size`;Bundle 总文件数上限为普通单 Skill 文件数
|
||
上限乘以协议允许的 100 个成员。上传流先写入独立临时目录,每个 ZIP 文件只在解压时计算一次
|
||
SHA-256 并写入临时对象,分析阶段只读取本地暂存文件,返回计划不保留文件字节。
|
||
|
||
外层解析和成员解析分开执行。外层只负责唯一 Manifest、成员目录边界和文件归属;成员目录去除
|
||
自身前缀后,必须成为一个普通的、根部含 `SKILL.md` 的 Skill 包,并复用现有
|
||
`SkillPackageValidator`、元数据解析、合规规则和错误/警告等级。成员目录不得重叠或嵌套,未声明的
|
||
`SKILL.md` 和无法归属的普通文件均阻止确认,避免服务端猜测用户意图。Manifest 与 `SKILL.md`
|
||
重复字段的优先级必须在协议中唯一确定;必须一致的字段发生冲突时直接报错。
|
||
|
||
格式校验、fingerprint 和权限分析都发生在预览阶段;Scanner 仍属于确认后的普通 Skill 生命周期,
|
||
不为了预览创建扫描任务。任一成员存在阻塞性格式错误时,页面可以展示全部已发现问题,但整个
|
||
Bundle 不可确认。
|
||
|
||
协调任务依靠带索引的操作/成员状态,不重新读取归档。反向引用使用集合式、有上限或分页的查询,
|
||
不得逐条查询 Suite。
|
||
|
||
### 8. 技能市场与套件专区保持分开
|
||
|
||
技能市场不增加类型切换。`/search` 继续只展示 Skill,并保留 Skill Label、收藏和排序。
|
||
`/suites` 继续作为 Suite 专区,使用类型化资源接口和现有 Suite 卡片。
|
||
|
||
唯一新增的发现能力是反向成员关系:Skill 详情查询最新、ACTIVE、非隐藏、PUBLISHED 且包含当前
|
||
Skill 的 SuiteVersion,不再要求当前 Skill 必须是 Entry。结果标明 Entry 身份,只返回用户有权
|
||
读取的兄弟成员摘要;无权读取的成员仅显示数量。成员关键词暂不影响 Suite 排名。
|
||
|
||
### 9. Suite 拥有自己的 Label,但不向成员级联
|
||
|
||
复用 `LabelDefinition`、本地化、`visibleInFilter`、排序以及 NORMAL/PRIVILEGED 权限语义,但使用
|
||
独立的 Suite-to-Label 关联。Suite Label 用于 Suite 容器展示和套件专区筛选。增删 Label 不创建
|
||
SuiteVersion,也不修改任何成员 Skill Label。
|
||
|
||
**备选方案:把现有 Skill Label 关联迁移为通用多态资源表。** 暂不采用。这会迁移稳定的 Skill
|
||
数据并削弱数据库外键约束,当前收益不足。独立关联可以复用 Label 定义,同时保持所有权清晰。
|
||
|
||
Tag 继续属于 SkillVersion。SuiteVersion 已经有显式版本,因此本次不增加 Suite Tag,也不向
|
||
成员批量设置 Tag。
|
||
|
||
### 10. 在发布边界要求有效摘要和概述
|
||
|
||
DRAFT SuiteVersion 可以在 summary 或 overview 尚未完成时保存。提交审核、PRIVATE 直接发布和
|
||
最终批准时,两者都必须非空。summary 是套件专区、卡片和引用使用的简短说明;overview 是
|
||
Markdown,说明用途、成员职责或顺序、预期输入输出和使用边界。服务端负责强制校验,不自动写入
|
||
“精选技能组合”之类的泛化文案。
|
||
|
||
已有字段为空的 PUBLISHED SuiteVersion 仍可读取和安装,避免破坏历史数据;创建下一个版本时
|
||
必须补齐。Bundle 可以显式提供新值,也可以继承当前 SuiteVersion 的非空值,预览必须展示最终
|
||
解析结果。
|
||
|
||
### 11. Suite 概述和 Entry Skill 说明各自承担职责
|
||
|
||
Suite overview 说明组合用途、适用场景、成员分工或调用顺序、预期输入输出和使用边界。编辑器提供
|
||
这些章节的结构化模板和完整性提示,但不以机械字数门槛鼓励填充内容,也不自动拿成员文档冒充
|
||
Suite 作者的概述。
|
||
|
||
Suite 详情可以在独立折叠区域按需展示 Entry Skill 固定 SkillVersion 的 `SKILL.md`。内容必须延迟
|
||
加载、复用该精确版本现有读取权限和安全 Markdown 渲染,并明确标注来源坐标与版本。Entry Skill
|
||
说明不能替代 Suite overview;无权访问、版本失效或加载失败时,只保留合规的成员状态或跳转入口。
|
||
|
||
### 12. Web 将导入作为现有创建新版本流程的一种方式
|
||
|
||
“创建 Suite”和“创建新版本”均先选择“从技能市场组合”或“从本地导入”。手工组合保留现有页面
|
||
和 API;本地导入进入上传、差异预览、明确确认和操作进度。更新预览必须突出显示因未出现在完整
|
||
Manifest 中而被移除的成员。确认文案列出将创建的 Skill/SkillVersion 数量、复用和引用数量、
|
||
移除数量以及最终只创建 Suite 草稿这一结果。
|
||
|
||
进度页逐成员展示创建、扫描、审核、复用、引用、失败和阻塞状态,并提供允许范围内的重试、取消、
|
||
审核详情和最终草稿入口。页面刷新或重新登录后可以依靠操作 ID 恢复进度,不依赖仍保留在浏览器
|
||
内存中的文件。
|
||
|
||
## Risks / Trade-offs
|
||
|
||
- **引用的公开 Skill 后续可能失效** → 创建 Suite 草稿前重新校验;发布后继续使用现有 degraded
|
||
机制。
|
||
- **Bundle 阻塞后可能留下独立成员版本** → 展示每个成员结果;重试和取消不得破坏成员生命周期。
|
||
- **事件可能重复、丢失或乱序** → 使用幂等协调和有界恢复任务。
|
||
- **预览后权限可能变化** → 每个异步边界重新检查对应权限,失败时阻止而不是放宽权限。
|
||
- **并发创建可能争用坐标,更新可能争用版本** → 操作记录独占目标 Suite 坐标和版本,最终创建
|
||
草稿时重新检查唯一性并保留乐观锁。
|
||
- **创建模式的某些 Skill 已发布、Suite 最终失败** → 独立 Skill 生命周期不回滚;进度页明确展示,
|
||
重试只继续未完成工作。
|
||
- **恶意归档可能消耗 CPU 或内存** → 流式处理、限制解压与文件规模,每个包只计算一次 hash。
|
||
- **反向引用可能泄露私有成员构成** → 服务端过滤 Suite 和兄弟成员,只暴露不可访问成员数量。
|
||
- **新必填展示信息可能阻塞历史 Suite 更新** → 历史发布快照保持可读,仅在提交新版本时要求补齐。
|
||
- **普通作者可能尝试自助配置特权 Label** → 复用现有 Label 权限类型并由服务端执行。
|
||
|
||
## Migration Plan
|
||
|
||
1. 增加操作表和索引,不修改现有 Skill/Suite 生命周期表。
|
||
2. 先部署预览和状态读取,再启用确认接口与协调任务。
|
||
3. 所有实例均能识别操作记录和协调事件后,再开启确认。
|
||
4. Bundle 进度、Suite Label/展示信息校验和任意成员反向引用可以独立部署;`/search` 与
|
||
`/suites` 路由保持不变。
|
||
5. 回滚时关闭新增接口和任务。已有 SkillVersion、SuiteVersion 继续有效;非终态操作保留,供兼容
|
||
版本取消或过期处理。
|
||
|
||
预览默认有效 30 分钟,可通过 `skillhub.suite.bundle.preview-ttl`(环境变量
|
||
`SKILLHUB_SUITE_BUNDLE_PREVIEW_TTL`)调整。确认入口默认关闭,待所有实例完成升级后通过
|
||
`skillhub.suite.bundle.confirmation-enabled`(环境变量
|
||
`SKILLHUB_SUITE_BUNDLE_CONFIRMATION_ENABLED`)显式开启。
|