From 432b916cbaf51ef7a0df0fefb6da8d01bca9156e Mon Sep 17 00:00:00 2001 From: weichen Date: Sun, 11 Oct 2026 16:57:48 +0000 Subject: [PATCH] Repair object-shaped children maps into arrays in page specs Why: Live E2E on the dev instance - the model keyed section children as an object map (children.item.*), the strict decode rejected it, and the page fell back to a file artifact; the guidance fix made the model call present, this closes the remaining shape gap. Evidence: genui suite green incl. the reproduction case; post-deploy E2E rerun recorded in the Dev task. Impact: Repair strategy only; validation unchanged. Refs: Dev task fix/genui-page-visibility (E2E follow-up) --- internal/genui/genui.go | 43 ++++++++++++++++++++++++++++++++++++ internal/genui/genui_test.go | 16 ++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/internal/genui/genui.go b/internal/genui/genui.go index b9ab6f3..8504229 100644 --- a/internal/genui/genui.go +++ b/internal/genui/genui.go @@ -274,10 +274,53 @@ func normalizeSpecParam(v any) any { } } } + repairChildrenShape(m) repairPairs(m) return m } +// repairChildrenShape 递归修复 children 的常见形状偏差(真实模型实测): +// children 为对象(按 key 编号的子节点映射)→ 取其值转数组;单对象 → 包一层数组。 +func repairChildrenShape(node any) { + m, ok := node.(map[string]any) + if !ok { + return + } + if ch, ok := m["children"]; ok && ch != nil { + switch v := ch.(type) { + case map[string]any: + arr := make([]any, 0, len(v)) + for _, val := range v { + arr = append(arr, val) + } + m["children"] = arr + case []any: + for _, c := range v { + if cm, ok := c.(map[string]any); ok { + repairChildrenShape(cm) + } + } + default: + // 单对象子节点 + if cm, ok := ch.(map[string]any); ok { + m["children"] = []any{cm} + } + } + } + for _, v := range m { + if vm, ok := v.(map[string]any); ok { + repairChildrenShape(vm) + } + if va, ok := v.([]any); ok { + for _, item := range va { + if im, ok := item.(map[string]any); ok { + repairChildrenShape(im) + } + } + } + } +} + // repairPairs 递归把 "kv"/"cells" 下的对象项改写为二元组。 func repairPairs(node any) { m, ok := node.(map[string]any) diff --git a/internal/genui/genui_test.go b/internal/genui/genui_test.go index 17bf4d3..03d0eb3 100644 --- a/internal/genui/genui_test.go +++ b/internal/genui/genui_test.go @@ -280,3 +280,19 @@ type ledgerHost struct { func (h *ledgerHost) KVPut(key, value string) error { h.kv[key] = value; return nil } func (h *ledgerHost) Audit(action, target, evidence string) error { return nil } + +func TestNormalizeChildrenShape(t *testing.T) { + // 真实偏差(2026-10-11 dev 实测):模型把 children 写成按 key 编号的对象映射。 + in := map[string]any{ + "type": "section", "id": "n1", "title": "结论", + "children": map[string]any{ + "item": map[string]any{"type": "markdown", "id": "m1", "markdown": "结论先行"}, + }, + } + out := normalizeSpecParam(map[string]any{"title": "t", "nodes": []any{in}}) + nodes := out.(map[string]any)["nodes"].([]any) + ch := nodes[0].(map[string]any)["children"].([]any) + if len(ch) != 1 || ch[0].(map[string]any)["id"] != "m1" { + t.Fatalf("children should be repaired to array, got %v", ch) + } +}