背景
roadmap 里有一条待办:
修复不同用户安装相同 Skill 时,因目前 Skill slug 全局唯一导致无法安装、会自动新增 versiontag 的问题,排查其对安装流程 and 版本管理的影响
我看了下相关代码,把问题和影响面理了一下,想先对齐下方案再动手。
问题长啥样
A 装了个 user 级的 my-skill,B 也想装个同名的 my-skill(也是 user 级,俩人互相看不见)。结果 B 装的时候因为 slug 全局唯一,被自动改成了 my-skill-v2,连 SKILL.md 的 frontmatter 都给改写了。
这俩本来该各自独立存在的(created_by 不同、share_config 不同、互相不可见),就因为全局唯一约束,用不了自己想要的 slug。
根因
主要三处:
| 层 |
位置 |
现状 |
| DB |
models_business.py:234 |
Skill.slug 全局 unique=True |
| 文件系统 |
service.py:204 get_skills_root_dir |
目录 skills/{slug},slug 就是目录名,也全局唯一 |
| 判重 |
service.py:618 _generate_available_slug |
冲突时生成 {slug}-v{idx},这就是"自动新增 versiontag"的来源(其实是 slug 重命名,不是真的版本管理) |
Skill 本来就有 created_by 和 share_config(access_level 有 global/department/user 三档),访问控制 user_can_access_skill 也是按用户过滤的,偏偏 slug 唯一性是全局的——这俩对不上,就是问题根源。
影响面
安装流程,三条路径都走 _generate_available_slug:
- 会话里
install_skill 工具 -> import_skill_dir -> _import_skill_dir_impl(service.py:700)
- 上传
prepare_skill_upload -> _stage_skill_draft_item(service.py:658)-> confirm_skill_install_draft
- 远程
prepare_remote_skill_install -> 同上的草稿流程
版本管理:version/content_hash 主要是 builtin 在用;用户级 skill 那个"versiontag"其实就是 slug 加后缀重命名,改了唯一性之后跨用户不会再触发,但同一个用户重名安装还是得保留去重。
两个容易踩坑的连带点:
skill_dependencies 存的是别的 skill 的 slug 字符串(service.py:484 用 {skill.slug: skill} 按 slug 索引)。slug 要是不再全局唯一,依赖解析就会歧义、互相覆盖。
list_visible_skills_for_management 按 slug 去重(service.py:407),同名 skill 会被误去重。
另外按 slug 查的那堆函数(get_by_slug/exists_slug/get_skill_or_raise/sync_thread_readable_skills/delete_skill 之类,大概 10 处)都得加上 owner 维度。
我想这么改
只动真正出问题的 user 级 skill,共享级不动:
- user 级(
access_level=user):(created_by, slug) 联合唯一,文件放 skills/users/{uid}/{slug}
- 共享级(global/department/builtin):slug 还是全局唯一,目录还是
skills/{slug}
- 查询和依赖解析带上 user 上下文,再加 DB 迁移和文件迁移
难点是 PG 没法用一条 unique 约束同时表达"user 级按 uid 唯一 + 共享级全局唯一",得靠部分唯一索引 + 应用层兜。
想跟你确认几点
- "user 级按 (created_by, slug) 隔离、共享级保持全局唯一"这个方向 OK 吗?
skill_dependencies 的 slug 引用,要不要改成"在当前用户可见域里解析"?
- 老数据迁移(已有 user 级 skill 的目录搬迁)要不要兼容旧路径?
方案定了我就提 PR。
背景
roadmap 里有一条待办:
我看了下相关代码,把问题和影响面理了一下,想先对齐下方案再动手。
问题长啥样
A 装了个 user 级的
my-skill,B 也想装个同名的my-skill(也是 user 级,俩人互相看不见)。结果 B 装的时候因为 slug 全局唯一,被自动改成了my-skill-v2,连 SKILL.md 的 frontmatter 都给改写了。这俩本来该各自独立存在的(
created_by不同、share_config不同、互相不可见),就因为全局唯一约束,用不了自己想要的 slug。根因
主要三处:
models_business.py:234Skill.slug全局unique=Trueservice.py:204get_skills_root_dirskills/{slug},slug 就是目录名,也全局唯一service.py:618_generate_available_slug{slug}-v{idx},这就是"自动新增 versiontag"的来源(其实是 slug 重命名,不是真的版本管理)Skill本来就有created_by和share_config(access_level 有 global/department/user 三档),访问控制user_can_access_skill也是按用户过滤的,偏偏 slug 唯一性是全局的——这俩对不上,就是问题根源。影响面
安装流程,三条路径都走
_generate_available_slug:install_skill工具 ->import_skill_dir->_import_skill_dir_impl(service.py:700)prepare_skill_upload->_stage_skill_draft_item(service.py:658)->confirm_skill_install_draftprepare_remote_skill_install-> 同上的草稿流程版本管理:
version/content_hash主要是 builtin 在用;用户级 skill 那个"versiontag"其实就是 slug 加后缀重命名,改了唯一性之后跨用户不会再触发,但同一个用户重名安装还是得保留去重。两个容易踩坑的连带点:
skill_dependencies存的是别的 skill 的 slug 字符串(service.py:484用{skill.slug: skill}按 slug 索引)。slug 要是不再全局唯一,依赖解析就会歧义、互相覆盖。list_visible_skills_for_management按 slug 去重(service.py:407),同名 skill 会被误去重。另外按 slug 查的那堆函数(
get_by_slug/exists_slug/get_skill_or_raise/sync_thread_readable_skills/delete_skill之类,大概 10 处)都得加上 owner 维度。我想这么改
只动真正出问题的 user 级 skill,共享级不动:
access_level=user):(created_by, slug)联合唯一,文件放skills/users/{uid}/{slug}skills/{slug}难点是 PG 没法用一条 unique 约束同时表达"user 级按 uid 唯一 + 共享级全局唯一",得靠部分唯一索引 + 应用层兜。
想跟你确认几点
skill_dependencies的 slug 引用,要不要改成"在当前用户可见域里解析"?方案定了我就提 PR。