fix(skills): 技能文件包 - 修复旧数据迁移回填元数据超限

This commit is contained in:
ddCat
2026-09-22 11:28:32 +08:00
parent c8a91fd2e9
commit 9d9cec8e2e
3 changed files with 77 additions and 1 deletions

View File

@@ -118,7 +118,9 @@ func (s *Service) EnsureSkillPackages() error {
}
for index := range skills {
skill := &skills[index]
archive, err := archiveFromMarkdown([]byte(skill.Instruction), skill.Name, skill.Description)
// Legacy fields are only fallbacks for package metadata. Bound them at
// this migration boundary without rewriting source fields or relaxing uploads.
archive, err := archiveFromMarkdown([]byte(skill.Instruction), truncateSkillMetadata(skill.Name, 80), truncateSkillMetadata(skill.Description, 500))
if err != nil {
return fmt.Errorf("迁移技能 %s 文件包失败: %w", skill.ID, err)
}

View File

@@ -3,6 +3,7 @@ package skills
import (
"archive/zip"
"bytes"
"encoding/json"
"strings"
"testing"
@@ -27,6 +28,73 @@ func TestArchiveFromMarkdownInfersMetadata(t *testing.T) {
}
}
func TestReportedBuiltinSkillPackage(t *testing.T) {
var definitions []builtinSkillDefinition
if err := json.Unmarshal(builtinSkillsJSON, &definitions); err != nil {
t.Fatal(err)
}
for _, skill := range definitions {
if skill.SkillID != "16000000000049" {
continue
}
archive, err := archiveFromMarkdown([]byte(skill.Instruction), skill.SkillName, skill.Description)
if err != nil {
t.Fatal(err)
}
if len([]rune(archive.Metadata.Description)) != 500 || string(archive.Files["SKILL.md"]) != skill.Instruction {
t.Fatal("reported skill must have bounded metadata and unchanged instruction")
}
return
}
t.Fatal("reported builtin skill is missing")
}
func TestEnsureSkillPackagesBoundsLegacyFallbackMetadata(t *testing.T) {
for _, source := range []int{3, skillSourceUser} {
db, err := gorm.Open(sqlite.Open("file:"+kernel.NewID()+"?mode=memory&cache=shared"), &gorm.Config{})
if err != nil {
t.Fatal(err)
}
sqlDB, err := db.DB()
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = sqlDB.Close() })
if err := db.AutoMigrate(&model.Skill{}, &model.SkillVersion{}, &model.SkillFile{}); err != nil {
t.Fatal(err)
}
svc := New(repository.New(db), t.TempDir(), nil)
skill := model.Skill{ID: kernel.NewID(), Name: strings.Repeat("名", 81), Description: strings.Repeat("文", 503), Instruction: "## 旧技能\n", Status: skillStatusEnabled, Source: source}
if err := db.Create(&skill).Error; err != nil {
t.Fatal(err)
}
for range 2 {
if err := svc.EnsureSkillPackages(); err != nil {
t.Fatal(err)
}
}
assertSkillVersionCount(t, db, skill.ID, 1)
var saved model.Skill
if err := db.First(&saved, "id = ?", skill.ID).Error; err != nil {
t.Fatal(err)
}
if saved.Name != skill.Name || saved.Description != skill.Description || saved.Instruction != skill.Instruction {
t.Fatal("migration changed original skill fields")
}
version, err := svc.repo.SkillVersion(saved.CurrentVersionID)
if err != nil {
t.Fatal(err)
}
body, err := svc.readSkillArchiveEntry(version, "SKILL.md")
if err != nil || string(body) != skill.Instruction {
t.Fatalf("original instruction not preserved: %v", err)
}
}
if _, err := archiveFromMarkdown([]byte("## 旧技能\n"), strings.Repeat("名", 81), strings.Repeat("文", 503)); err == nil {
t.Fatal("non-migration archive input must still reject oversized fallback metadata")
}
}
func TestArchiveFromMarkdownTruncatesInferredDescriptionWithinLimit(t *testing.T) {
longDescription := bytes.Repeat([]byte("描述内容。"), 140)
data := append([]byte("# 风格库四级匹配序\n\n"), longDescription...)

View File

@@ -5,6 +5,12 @@ description: 当前项目已实现功能的手工验证清单与未确认边界
# 待测试
## 技能文件包迁移元数据长度
- [x] 启动迁移对旧技能名称、简介的回填元数据按字符数限制长度,省略号计入上限;不改写原始名称、简介和正文,不放宽非迁移入口的校验。
- [x] 新增回归先复现旧回填路径的长度错误,修复后技能域全量测试通过;覆盖内置与用户旧技能、重复初始化不重复建版本、原始正文和字段保留,以及 `16000000000049` 内置技能包。
- [ ] 在报错用户的实际部署核对后端镜像与构建提交,并用包含修复的新产物验证启动迁移。本地通过不代表用户环境已修复;本轮未发布镜像、未操作生产数据库。
## 模型失败原因在画布卡片展示
- [x] 后端 Provider、错误分类和请求日志专项回归通过;本地模拟上游验证 HTTP 400 与 HTTP 200 业务失败均保留安全原因,覆盖图片响应和通用 JSON 响应。前端 4 项专项测试通过,覆盖具体原因、HTTP 状态提示、审核元数据及 URL 隐藏。