feat: align svglide schemas with anygen experiment

This commit is contained in:
songtianyi.theo
2026-07-03 15:01:11 +08:00
parent 66ea925c3a
commit d82d4e3333
3 changed files with 66 additions and 24 deletions

View File

@@ -148,9 +148,12 @@ func TestInitRunWritesDirectoryContract(t *testing.T) {
name string
want []string
}{
{name: "request.schema.json", want: []string{`"purpose"`, `"language"`, `"visual_style_query"`}},
{name: "design_brief.schema.json", want: []string{`"visual_system"`, `"narrative_spine"`, `"depth"`, `"tone"`}},
{name: "deck.schema.json", want: []string{`"main_title"`, `"style_instruction"`, `"aesthetic_direction"`}},
{name: "sources.schema.json", want: []string{`"retrieval"`}},
{name: "slide_content.schema.json", want: []string{`"source_refs"`, `"visuals"`}},
{name: "assets_plan.schema.json", want: []string{`"slide_id"`, `"status"`}},
{name: "slide_content.schema.json", want: []string{`"source_refs"`, `"visuals"`, `"chart"`, `"table"`, `"crop"`}},
{name: "assets_plan.schema.json", want: []string{`"experiment_unrestricted_assets"`, `"slide_id"`, `"status"`, `"deferred"`, `"chart"`, `"table"`, `"crop"`}},
{name: "quality.schema.json", want: []string{`"metrics"`}},
} {
raw, err := os.ReadFile(filepath.Join(root, "schemas", tc.name))

View File

@@ -9,9 +9,12 @@ func DefaultSchemas() map[string]string {
"properties": {
"title": {"type": "string"},
"input": {"type": "string"},
"purpose": {"type": "string"},
"audience": {"type": "string"},
"delivery_mode": {"type": "string"},
"pages": {"type": "integer"}
"language": {"type": "string"},
"pages": {"type": "integer"},
"visual_style_query": {"type": "array", "items": {"type": "string"}}
}
}
`,
@@ -64,11 +67,25 @@ func DefaultSchemas() map[string]string {
"design_brief.schema.json": `{
"type": "object",
"additionalProperties": false,
"required": ["narrative_spine", "depth", "tone"],
"required": ["narrative_spine", "depth", "tone", "visual_system"],
"properties": {
"narrative_spine": {"type": "string"},
"depth": {"type": "string"},
"tone": {"type": "string"}
"design_rationale": {"type": "string"},
"narrative_spine": {"type": "object"},
"depth": {"type": "object"},
"tone": {"type": "string"},
"visual_system": {
"type": "object",
"required": ["color_system", "typography", "layout_language"],
"properties": {
"color_system": {"type": "object"},
"typography": {"type": "object"},
"layout_language": {"type": "object"},
"imagery_treatment": {"type": "object"},
"material_texture": {"type": "object"},
"decoration_language": {"type": "object"},
"mood_coordinates": {"type": "object"}
}
}
}
}
`,
@@ -79,16 +96,30 @@ func DefaultSchemas() map[string]string {
"properties": {
"color_system": {"type": "object"},
"typography": {"type": "object"},
"layout_language": {"type": "string"}
"layout_language": {"type": "object"},
"imagery_treatment": {"type": "object"},
"material_texture": {"type": "object"},
"decoration_language": {"type": "object"},
"mood_coordinates": {"type": "object"}
}
}
`,
"deck.schema.json": `{
"type": "object",
"additionalProperties": false,
"required": ["title", "slides"],
"required": ["main_title", "style_instruction", "slides"],
"properties": {
"main_title": {"type": "string"},
"title": {"type": "string"},
"style_instruction": {
"type": "object",
"required": ["aesthetic_direction", "color_palette", "typography"],
"properties": {
"aesthetic_direction": {"type": "string"},
"color_palette": {"type": "object"},
"typography": {"type": "object"}
}
},
"slides": {
"type": "array",
"minItems": 1,
@@ -99,6 +130,7 @@ func DefaultSchemas() map[string]string {
"properties": {
"id": {"type": "string"},
"title": {"type": "string"},
"page_title": {"type": "string"},
"summary": {"type": "string"},
"role": {"type": "string"},
"key_message": {"type": "string"},
@@ -134,7 +166,7 @@ func DefaultSchemas() map[string]string {
"required": ["id", "type", "instruction"],
"properties": {
"id": {"type": "string"},
"type": {"type": "string", "enum": ["image", "diagram", "icon", "none"]},
"type": {"type": "string", "enum": ["image", "diagram", "icon", "chart", "table", "crop", "none"]},
"instruction": {"type": "string"}
}
}
@@ -148,8 +180,9 @@ func DefaultSchemas() map[string]string {
"assets_plan.schema.json": `{
"type": "object",
"additionalProperties": false,
"required": ["assets"],
"required": ["mode", "assets"],
"properties": {
"mode": {"type": "string", "enum": ["experiment_unrestricted_assets"]},
"assets": {
"type": "array",
"items": {
@@ -159,10 +192,10 @@ func DefaultSchemas() map[string]string {
"properties": {
"id": {"type": "string"},
"slide_id": {"type": "string"},
"type": {"type": "string", "enum": ["image", "diagram", "icon"]},
"path": {"type": "string", "pattern": "^assets/images/[^./%\\\\:](?:[^/%\\\\:.]|\\.[^./%\\\\:])*$"},
"type": {"type": "string", "enum": ["image", "diagram", "icon", "chart", "table", "crop"]},
"path": {"type": "string"},
"usage": {"type": "string"},
"status": {"type": "string", "enum": ["ready", "missing"]}
"status": {"type": "string", "enum": ["ready", "missing", "deferred"]}
}
}
}

View File

@@ -35,7 +35,7 @@ func TestValidateStageOutputsRejectsDeckSlidePathsThatPreviewRejects(t *testing.
t.Run(path, func(t *testing.T) {
initStatusTestRun(t)
setCurrentStageForStatusTest(t, StageOutline)
mustWriteTestFile(t, "demo/outline/deck.json", `{"title":"Demo Deck","slides":[{"id":"s1","title":"First claim","summary":"First summary","role":"cover","key_message":"First key message","path":"`+path+`"}]}`)
mustWriteTestFile(t, "demo/outline/deck.json", validSchemaDeckJSON(path))
err := ValidateStageOutputs("demo")
if err == nil {
@@ -51,7 +51,7 @@ func TestValidateStageOutputsRejectsDeckSlidePathsThatPreviewRejects(t *testing.
func TestCompleteCurrentStageRejectsInvalidDeckSlidePath(t *testing.T) {
initStatusTestRun(t)
setCurrentStageForStatusTest(t, StageOutline)
mustWriteTestFile(t, "demo/outline/deck.json", `{"title":"Demo Deck","slides":[{"id":"s1","title":"First claim","summary":"First summary","role":"cover","key_message":"First key message","path":"slides/a%20.svg"}]}`)
mustWriteTestFile(t, "demo/outline/deck.json", validSchemaDeckJSON("slides/a%20.svg"))
_, err := CompleteCurrentStage("demo")
if err == nil {
@@ -204,7 +204,7 @@ func TestValidateStageOutputsRejectsSlideContentMissingSourceRefsOrVisualIds(t *
func TestValidateStageOutputsRejectsAssetsMissingStatus(t *testing.T) {
initStatusTestRun(t)
setCurrentStageForStatusTest(t, StageAssets)
if err := os.WriteFile(filepath.Join("demo", "assets", "assets_plan.json"), []byte(`{"assets":[{"id":"a1","slide_id":"s1","type":"image","path":"assets/images/a.png","usage":"hero image"}]}`), 0o644); err != nil {
if err := os.WriteFile(filepath.Join("demo", "assets", "assets_plan.json"), []byte(`{"mode":"experiment_unrestricted_assets","assets":[{"id":"a1","slide_id":"s1","type":"image","path":"https://example.com/a.png","usage":"hero image"}]}`), 0o644); err != nil {
t.Fatal(err)
}
@@ -217,7 +217,7 @@ func TestValidateStageOutputsRejectsAssetsMissingStatus(t *testing.T) {
}
}
func TestValidateStageOutputsRejectsUnsafeAssetPaths(t *testing.T) {
func TestValidateStageOutputsAcceptsExperimentAssetPaths(t *testing.T) {
tests := []struct {
name string
path string
@@ -229,16 +229,13 @@ func TestValidateStageOutputsRejectsUnsafeAssetPaths(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
initStatusTestRun(t)
setCurrentStageForStatusTest(t, StageAssets)
if err := os.WriteFile(filepath.Join("demo", "assets", "assets_plan.json"), []byte(`{"assets":[{"id":"a1","slide_id":"s1","type":"image","path":"`+tt.path+`","usage":"hero image","status":"ready"}]}`), 0o644); err != nil {
if err := os.WriteFile(filepath.Join("demo", "assets", "assets_plan.json"), []byte(`{"mode":"experiment_unrestricted_assets","assets":[{"id":"a1","slide_id":"s1","type":"image","path":"`+tt.path+`","usage":"hero image","status":"ready"}]}`), 0o644); err != nil {
t.Fatal(err)
}
err := ValidateStageOutputs("demo")
if err == nil {
t.Fatal("expected asset path schema validation error")
}
if !strings.Contains(err.Error(), "assets/assets_plan.json") || !strings.Contains(err.Error(), "path") {
t.Fatalf("error = %v, want assets/assets_plan.json and path", err)
if err != nil {
t.Fatalf("expected experiment asset path to pass schema validation, got %v", err)
}
})
}
@@ -268,11 +265,20 @@ func TestDefaultSchemasIncludeAnyGenQualityContracts(t *testing.T) {
if !strings.Contains(schemas["assets_plan.schema.json"], `"slide_id"`) {
t.Fatalf("assets schema missing slide_id: %s", schemas["assets_plan.schema.json"])
}
for _, want := range []string{`"experiment_unrestricted_assets"`, `"chart"`, `"table"`, `"crop"`, `"deferred"`} {
if !strings.Contains(schemas["assets_plan.schema.json"], want) {
t.Fatalf("assets schema missing %s: %s", want, schemas["assets_plan.schema.json"])
}
}
if !strings.Contains(schemas["quality.schema.json"], `"metrics"`) {
t.Fatalf("quality schema missing metrics: %s", schemas["quality.schema.json"])
}
}
func validSchemaDeckJSON(path string) string {
return `{"main_title":"Demo Deck","style_instruction":{"aesthetic_direction":"Editorial report","color_palette":{},"typography":{}},"slides":[{"id":"s1","title":"First claim","summary":"First summary","role":"cover","key_message":"First key message","path":"` + path + `"}]}`
}
func validQualityReportJSON() string {
return `{"status":"passed","issues":[],"metrics":{"slides":1,"sources":1,"web_sources":0,"assets":0,"slides_with_source_refs":1,"slides_with_visuals":1}}`
}