From 711bc7f99c02aebb5dd5fbd28320d055dba040e9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 13 Apr 2026 07:03:19 +0000 Subject: [PATCH 1/5] Initial plan From 984a5f1cabff28a0558017c715beb9e049f7a76a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 13 Apr 2026 07:29:24 +0000 Subject: [PATCH 2/5] perf: optimize ParseWorkflow by replacing JSON roundtrip and using builtin cache Replace the json.Marshal/json.Unmarshal roundtrip in validateWithSchema with a direct recursive type normalization function (normalizeForJSONSchema). This avoids the serialization/deserialization overhead while still converting YAML-native types (uint64/int64) to JSON-compatible float64 for schema validation. Also optimize import field extraction for builtin virtual files (engine definitions) by using the process-level frontmatter cache (ExtractFrontmatterFromBuiltinFile) instead of re-parsing the same YAML content that was already parsed during processIncludedFileWithVisited. BenchmarkParseWorkflow results: - Before: ~320,000 ns/op, 170,000 B/op, 2,661 allocs/op - After: ~261,000 ns/op, 142,200 B/op, 2,081 allocs/op - Speed: ~18% faster - Memory: ~16% less - Allocations: ~22% fewer Agent-Logs-Url: https://github.com/github/gh-aw/sessions/cb3dee46-6a28-4a88-952e-7fc85d445324 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/parser/import_field_extractor.go | 10 +++- pkg/parser/schema_compiler.go | 71 +++++++++++++++++++++------- 2 files changed, 64 insertions(+), 17 deletions(-) diff --git a/pkg/parser/import_field_extractor.go b/pkg/parser/import_field_extractor.go index 515266370e6..e2ea4848f98 100644 --- a/pkg/parser/import_field_extractor.go +++ b/pkg/parser/import_field_extractor.go @@ -147,7 +147,15 @@ func (acc *importAccumulator) extractAllImportFields(content []byte, item import // All subsequent field extractions use the pre-parsed result. // When inputs are present we parse the already-substituted content so that all // frontmatter fields (runtimes, mcp-servers, etc.) reflect the resolved values. - parsed, err := ExtractFrontmatterFromContent(rawContent) + // For builtin files, use the process-level cache to avoid redundant YAML re-parsing + // (processIncludedFileWithVisited already populated this cache). + var parsed *FrontmatterResult + var err error + if strings.HasPrefix(item.fullPath, BuiltinPathPrefix) && len(item.inputs) == 0 { + parsed, err = ExtractFrontmatterFromBuiltinFile(item.fullPath, content) + } else { + parsed, err = ExtractFrontmatterFromContent(rawContent) + } var fm map[string]any if err == nil { fm = parsed.Frontmatter diff --git a/pkg/parser/schema_compiler.go b/pkg/parser/schema_compiler.go index c6405dd5485..81cff775ced 100644 --- a/pkg/parser/schema_compiler.go +++ b/pkg/parser/schema_compiler.go @@ -189,6 +189,53 @@ func GetSafeOutputTypeKeys() ([]string, error) { return keys, nil } +// normalizeForJSONSchema recursively converts YAML-native Go types to JSON-compatible +// types for JSON schema validation. goccy/go-yaml produces uint64 for positive integers +// and int64 for negative integers, but JSON schema validators expect float64 for all +// numbers (matching encoding/json's unmarshaling behavior). This avoids the overhead +// of a json.Marshal + json.Unmarshal roundtrip. +func normalizeForJSONSchema(v any) any { + switch val := v.(type) { + case map[string]any: + normalized := make(map[string]any, len(val)) + for k, elem := range val { + normalized[k] = normalizeForJSONSchema(elem) + } + return normalized + case []any: + normalized := make([]any, len(val)) + for i, elem := range val { + normalized[i] = normalizeForJSONSchema(elem) + } + return normalized + case int: + return float64(val) + case int8: + return float64(val) + case int16: + return float64(val) + case int32: + return float64(val) + case int64: + return float64(val) + case uint: + return float64(val) + case uint8: + return float64(val) + case uint16: + return float64(val) + case uint32: + return float64(val) + case uint64: + return float64(val) + case float32: + return float64(val) + default: + // string, bool, float64, nil pass through unchanged + return v + } +} + func validateWithSchema(frontmatter map[string]any, schemaJSON, context string) error { schemaCompilerLog.Printf("Validating frontmatter against schema for context: %s (%d fields)", context, len(frontmatter)) @@ -217,27 +264,19 @@ func validateWithSchema(frontmatter map[string]any, schemaJSON, context string) return fmt.Errorf("schema validation error for %s: %w", context, err) } - // Convert frontmatter to JSON and back to normalize types for validation - // Handle nil frontmatter as empty object to satisfy schema validation - var frontmatterToValidate map[string]any + // Normalize YAML-native Go types to JSON-compatible types for schema validation. + // goccy/go-yaml produces uint64/int64 for integers, but JSON schema validators + // expect float64 for all numbers (matching encoding/json's behavior). + // This in-place normalization avoids the overhead of a json.Marshal/Unmarshal roundtrip. + var normalized any if frontmatter == nil { - frontmatterToValidate = make(map[string]any) + normalized = make(map[string]any) } else { - frontmatterToValidate = frontmatter - } - - frontmatterJSON, err := json.Marshal(frontmatterToValidate) - if err != nil { - return fmt.Errorf("schema validation error for %s: failed to marshal frontmatter: %w", context, err) - } - - var normalizedFrontmatter any - if err := json.Unmarshal(frontmatterJSON, &normalizedFrontmatter); err != nil { - return fmt.Errorf("schema validation error for %s: failed to unmarshal frontmatter: %w", context, err) + normalized = normalizeForJSONSchema(frontmatter) } // Validate the normalized frontmatter - if err := schema.Validate(normalizedFrontmatter); err != nil { + if err := schema.Validate(normalized); err != nil { schemaCompilerLog.Printf("Schema validation failed for %s: %v", context, err) return err } From d833eb774dd8b1a52184e8e4d5d21c77066f512a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 13 Apr 2026 07:30:29 +0000 Subject: [PATCH 3/5] docs: add comment explaining builtin cache skip for files with inputs Agent-Logs-Url: https://github.com/github/gh-aw/sessions/cb3dee46-6a28-4a88-952e-7fc85d445324 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/parser/import_field_extractor.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/parser/import_field_extractor.go b/pkg/parser/import_field_extractor.go index e2ea4848f98..38c7e0827eb 100644 --- a/pkg/parser/import_field_extractor.go +++ b/pkg/parser/import_field_extractor.go @@ -147,8 +147,10 @@ func (acc *importAccumulator) extractAllImportFields(content []byte, item import // All subsequent field extractions use the pre-parsed result. // When inputs are present we parse the already-substituted content so that all // frontmatter fields (runtimes, mcp-servers, etc.) reflect the resolved values. - // For builtin files, use the process-level cache to avoid redundant YAML re-parsing - // (processIncludedFileWithVisited already populated this cache). + // For builtin files without inputs, use the process-level cache to avoid redundant + // YAML re-parsing (processIncludedFileWithVisited already populated this cache). + // Builtin files WITH inputs must skip the cache because input substitution modifies + // the content, so the cached (unsubstituted) result would be stale. var parsed *FrontmatterResult var err error if strings.HasPrefix(item.fullPath, BuiltinPathPrefix) && len(item.inputs) == 0 { From e3068b28b61f614adb69ac899139da8c7ccdfc57 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 13 Apr 2026 13:56:46 +0000 Subject: [PATCH 4/5] test: add tests for normalizeForJSONSchema and builtin cache-hit path Address review feedback: - Fix misleading "in-place" wording in normalizeForJSONSchema comment (it returns a copy, not mutating the caller's maps) - Add TestNormalizeForJSONSchema with table-driven coverage of all integer types, float types, and passthrough types - Add TestNormalizeForJSONSchema_NestedMap and _Slice for recursive cases - Add TestValidateWithSchema_YAMLIntegerTypes for end-to-end validation of YAML-native integer types against number/integer schema types - Add TestExtractAllImportFields_BuiltinCacheHit and _BuiltinWithInputsBypassesCache for cache path coverage Agent-Logs-Url: https://github.com/github/gh-aw/sessions/f93d819e-783c-48c8-ba3e-3adafe779d6e Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/parser/import_field_extractor_test.go | 83 +++++++++++++ pkg/parser/schema_compiler.go | 13 +- pkg/parser/schema_test.go | 142 ++++++++++++++++++++++ 3 files changed, 232 insertions(+), 6 deletions(-) diff --git a/pkg/parser/import_field_extractor_test.go b/pkg/parser/import_field_extractor_test.go index e80fccfdcca..caa06ca19e8 100644 --- a/pkg/parser/import_field_extractor_test.go +++ b/pkg/parser/import_field_extractor_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) // TestComputeImportRelPath verifies that computeImportRelPath produces the correct @@ -200,3 +201,85 @@ imports: assert.Contains(t, importsResult.MergedJobs, "apm", "MergedJobs should contain the 'apm' job") assert.Contains(t, importsResult.MergedJobs, "ubuntu-slim", "MergedJobs should contain the job runner") } + +// TestExtractAllImportFields_BuiltinCacheHit verifies that extractAllImportFields uses the +// process-level builtin frontmatter cache for builtin files without inputs. +func TestExtractAllImportFields_BuiltinCacheHit(t *testing.T) { + builtinPath := BuiltinPathPrefix + "test/cache-hit.md" + content := []byte(`--- +tools: + bash: ["echo"] +engine: claude +--- + +# Cache Hit Test +`) + + // Register the builtin virtual file + RegisterBuiltinVirtualFile(builtinPath, content) + + // Warm the cache by parsing once + cachedResult, err := ExtractFrontmatterFromBuiltinFile(builtinPath, content) + require.NoError(t, err, "should parse builtin file without error") + assert.NotNil(t, cachedResult, "cached result should not be nil") + + // Verify the cache is populated + cached, ok := GetBuiltinFrontmatterCache(builtinPath) + assert.True(t, ok, "builtin cache should have an entry for the path") + assert.Equal(t, cachedResult, cached, "cached result should match") + + // Call extractAllImportFields with no inputs — should hit the cache + acc := newImportAccumulator() + item := importQueueItem{ + fullPath: builtinPath, + importPath: "test/cache-hit.md", + sectionName: "", + inputs: nil, + } + visited := map[string]bool{builtinPath: true} + + err = acc.extractAllImportFields(content, item, visited) + require.NoError(t, err, "extractAllImportFields should succeed for builtin file without inputs") + + // Verify engine was extracted from the cached frontmatter + assert.NotEmpty(t, acc.engines, "engines should be populated from cached builtin file") + assert.Contains(t, acc.engines[0], "claude", "engine should be 'claude' from the builtin file") +} + +// TestExtractAllImportFields_BuiltinWithInputsBypassesCache verifies that builtin files +// with inputs bypass the cache and use the substituted content. +func TestExtractAllImportFields_BuiltinWithInputsBypassesCache(t *testing.T) { + builtinPath := BuiltinPathPrefix + "test/cache-bypass.md" + content := []byte(`--- +tools: + bash: ["echo"] +engine: copilot +--- + +# Cache Bypass Test +`) + + // Register the builtin virtual file + RegisterBuiltinVirtualFile(builtinPath, content) + + // Warm the cache + _, err := ExtractFrontmatterFromBuiltinFile(builtinPath, content) + require.NoError(t, err, "should parse builtin file without error") + + // Call extractAllImportFields WITH inputs — should bypass the cache + acc := newImportAccumulator() + item := importQueueItem{ + fullPath: builtinPath, + importPath: "test/cache-bypass.md", + sectionName: "", + inputs: map[string]any{"key": "value"}, + } + visited := map[string]bool{builtinPath: true} + + err = acc.extractAllImportFields(content, item, visited) + require.NoError(t, err, "extractAllImportFields should succeed for builtin file with inputs") + + // Verify engine was still extracted (from direct parse, not cache) + assert.NotEmpty(t, acc.engines, "engines should be populated even when bypassing cache") + assert.Contains(t, acc.engines[0], "copilot", "engine should be 'copilot' from the builtin file") +} diff --git a/pkg/parser/schema_compiler.go b/pkg/parser/schema_compiler.go index 81cff775ced..5558b20ceee 100644 --- a/pkg/parser/schema_compiler.go +++ b/pkg/parser/schema_compiler.go @@ -189,11 +189,12 @@ func GetSafeOutputTypeKeys() ([]string, error) { return keys, nil } -// normalizeForJSONSchema recursively converts YAML-native Go types to JSON-compatible -// types for JSON schema validation. goccy/go-yaml produces uint64 for positive integers -// and int64 for negative integers, but JSON schema validators expect float64 for all -// numbers (matching encoding/json's unmarshaling behavior). This avoids the overhead -// of a json.Marshal + json.Unmarshal roundtrip. +// normalizeForJSONSchema recursively returns a normalized copy of v with YAML-native +// Go types converted to JSON-compatible types for JSON schema validation. It does +// not mutate the caller's maps or slices. goccy/go-yaml produces uint64 for +// positive integers and int64 for negative integers, but JSON schema validators +// expect float64 for all numbers (matching encoding/json's unmarshaling behavior). +// This avoids the overhead of a json.Marshal + json.Unmarshal roundtrip. func normalizeForJSONSchema(v any) any { switch val := v.(type) { case map[string]any: @@ -267,7 +268,7 @@ func validateWithSchema(frontmatter map[string]any, schemaJSON, context string) // Normalize YAML-native Go types to JSON-compatible types for schema validation. // goccy/go-yaml produces uint64/int64 for integers, but JSON schema validators // expect float64 for all numbers (matching encoding/json's behavior). - // This in-place normalization avoids the overhead of a json.Marshal/Unmarshal roundtrip. + // This avoids the overhead of a json.Marshal/Unmarshal roundtrip. var normalized any if frontmatter == nil { normalized = make(map[string]any) diff --git a/pkg/parser/schema_test.go b/pkg/parser/schema_test.go index 612e26fa431..723e77a4593 100644 --- a/pkg/parser/schema_test.go +++ b/pkg/parser/schema_test.go @@ -320,3 +320,145 @@ func TestGetSafeOutputTypeKeys(t *testing.T) { } } } + +// TestNormalizeForJSONSchema verifies that normalizeForJSONSchema correctly converts +// YAML-native integer types to float64 while leaving other types unchanged. +func TestNormalizeForJSONSchema(t *testing.T) { + tests := []struct { + name string + input any + expected any + }{ + // Integer type conversions + {name: "int", input: int(42), expected: float64(42)}, + {name: "int8", input: int8(8), expected: float64(8)}, + {name: "int16", input: int16(16), expected: float64(16)}, + {name: "int32", input: int32(32), expected: float64(32)}, + {name: "int64", input: int64(64), expected: float64(64)}, + {name: "int64 negative", input: int64(-5), expected: float64(-5)}, + + // Unsigned integer type conversions + {name: "uint", input: uint(42), expected: float64(42)}, + {name: "uint8", input: uint8(8), expected: float64(8)}, + {name: "uint16", input: uint16(16), expected: float64(16)}, + {name: "uint32", input: uint32(32), expected: float64(32)}, + {name: "uint64", input: uint64(64), expected: float64(64)}, + {name: "uint64 large", input: uint64(9999999999999), expected: float64(9999999999999)}, + + // Float type conversions + {name: "float32", input: float32(3.14), expected: float64(float32(3.14))}, + + // Pass-through types + {name: "float64 passthrough", input: float64(2.718), expected: float64(2.718)}, + {name: "string passthrough", input: "hello", expected: "hello"}, + {name: "bool true passthrough", input: true, expected: true}, + {name: "bool false passthrough", input: false, expected: false}, + {name: "nil passthrough", input: nil, expected: nil}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := normalizeForJSONSchema(tt.input) + if result != tt.expected { + t.Errorf("normalizeForJSONSchema(%T(%v)) = %T(%v), want %T(%v)", + tt.input, tt.input, result, result, tt.expected, tt.expected) + } + }) + } +} + +// TestNormalizeForJSONSchema_NestedMap verifies recursive normalization of maps. +func TestNormalizeForJSONSchema_NestedMap(t *testing.T) { + input := map[string]any{ + "name": "test", + "count": uint64(42), + "offset": int64(-3), + "enabled": true, + "nested": map[string]any{ + "port": uint64(8080), + "label": "inner", + }, + } + + result := normalizeForJSONSchema(input) + resultMap, ok := result.(map[string]any) + if !ok { + t.Fatalf("expected map[string]any, got %T", result) + } + + if resultMap["name"] != "test" { + t.Errorf("name: got %v, want test", resultMap["name"]) + } + if resultMap["count"] != float64(42) { + t.Errorf("count: got %T(%v), want float64(42)", resultMap["count"], resultMap["count"]) + } + if resultMap["offset"] != float64(-3) { + t.Errorf("offset: got %T(%v), want float64(-3)", resultMap["offset"], resultMap["offset"]) + } + if resultMap["enabled"] != true { + t.Errorf("enabled: got %v, want true", resultMap["enabled"]) + } + + nestedMap, ok := resultMap["nested"].(map[string]any) + if !ok { + t.Fatalf("nested: expected map[string]any, got %T", resultMap["nested"]) + } + if nestedMap["port"] != float64(8080) { + t.Errorf("nested.port: got %T(%v), want float64(8080)", nestedMap["port"], nestedMap["port"]) + } + if nestedMap["label"] != "inner" { + t.Errorf("nested.label: got %v, want inner", nestedMap["label"]) + } + + // Verify the original input is NOT mutated + if input["count"] != uint64(42) { + t.Errorf("original input mutated: count is %T(%v), expected uint64(42)", input["count"], input["count"]) + } +} + +// TestNormalizeForJSONSchema_Slice verifies recursive normalization of slices. +func TestNormalizeForJSONSchema_Slice(t *testing.T) { + input := []any{uint64(1), "two", int64(-3), true, nil, float64(4.5)} + + result := normalizeForJSONSchema(input) + resultSlice, ok := result.([]any) + if !ok { + t.Fatalf("expected []any, got %T", result) + } + + expected := []any{float64(1), "two", float64(-3), true, nil, float64(4.5)} + if len(resultSlice) != len(expected) { + t.Fatalf("length mismatch: got %d, want %d", len(resultSlice), len(expected)) + } + for i, want := range expected { + if resultSlice[i] != want { + t.Errorf("[%d]: got %T(%v), want %T(%v)", i, resultSlice[i], resultSlice[i], want, want) + } + } +} + +// TestValidateWithSchema_YAMLIntegerTypes verifies that validateWithSchema accepts +// YAML-native integer types (uint64/int64) when the schema expects number/integer. +func TestValidateWithSchema_YAMLIntegerTypes(t *testing.T) { + schema := `{ + "type": "object", + "properties": { + "timeout-minutes": {"type": "integer"}, + "max-retries": {"type": "number"}, + "name": {"type": "string"} + }, + "additionalProperties": false + }` + + // Simulate what goccy/go-yaml produces: uint64 for positive, int64 for negative + frontmatter := map[string]any{ + "timeout-minutes": uint64(20), + "max-retries": int64(3), + "name": "test", + } + + err := validateWithSchema(frontmatter, schema, "yaml integer types") + if err != nil { + t.Errorf("validateWithSchema should accept YAML integer types, got: %v", err) + } +} From 664b081de9ee5bd22ac5b03969b6eb5f867089b8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 13 Apr 2026 14:26:21 +0000 Subject: [PATCH 5/5] fix: handle typed slices ([]string) in normalizeForJSONSchema via reflect fallback Agent-Logs-Url: https://github.com/github/gh-aw/sessions/c863009f-265a-4560-90d8-92bf669bda0c Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/parser/schema_compiler.go | 20 ++++++++++++++++ pkg/parser/schema_test.go | 45 +++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/pkg/parser/schema_compiler.go b/pkg/parser/schema_compiler.go index 5558b20ceee..0e95577f96d 100644 --- a/pkg/parser/schema_compiler.go +++ b/pkg/parser/schema_compiler.go @@ -7,6 +7,7 @@ import ( "fmt" "os" "path/filepath" + "reflect" "regexp" "sort" "strings" @@ -194,6 +195,8 @@ func GetSafeOutputTypeKeys() ([]string, error) { // not mutate the caller's maps or slices. goccy/go-yaml produces uint64 for // positive integers and int64 for negative integers, but JSON schema validators // expect float64 for all numbers (matching encoding/json's unmarshaling behavior). +// goccy/go-yaml may also produce typed slices (e.g. []string) instead of []any; +// the reflection fallback converts these so the schema validator sees []any. // This avoids the overhead of a json.Marshal + json.Unmarshal roundtrip. func normalizeForJSONSchema(v any) any { switch val := v.(type) { @@ -232,6 +235,23 @@ func normalizeForJSONSchema(v any) any { case float32: return float64(val) default: + // Use reflection to handle typed slices (e.g. []string) and typed maps + // that goccy/go-yaml may produce instead of []any / map[string]any. + rv := reflect.ValueOf(v) + switch rv.Kind() { + case reflect.Slice: + normalized := make([]any, rv.Len()) + for i := range rv.Len() { + normalized[i] = normalizeForJSONSchema(rv.Index(i).Interface()) + } + return normalized + case reflect.Map: + normalized := make(map[string]any, rv.Len()) + for _, key := range rv.MapKeys() { + normalized[key.String()] = normalizeForJSONSchema(rv.MapIndex(key).Interface()) + } + return normalized + } // string, bool, float64, nil pass through unchanged return v } diff --git a/pkg/parser/schema_test.go b/pkg/parser/schema_test.go index 723e77a4593..c74bbeeaadd 100644 --- a/pkg/parser/schema_test.go +++ b/pkg/parser/schema_test.go @@ -437,6 +437,51 @@ func TestNormalizeForJSONSchema_Slice(t *testing.T) { } } +// TestNormalizeForJSONSchema_TypedSlice verifies that typed slices (e.g. []string) +// are converted to []any, since goccy/go-yaml may produce typed slices that the +// JSON schema validator does not recognize. +func TestNormalizeForJSONSchema_TypedSlice(t *testing.T) { + input := []string{"a", "b", "c"} + + result := normalizeForJSONSchema(input) + resultSlice, ok := result.([]any) + if !ok { + t.Fatalf("expected []any, got %T", result) + } + + if len(resultSlice) != 3 { + t.Fatalf("length mismatch: got %d, want 3", len(resultSlice)) + } + for i, want := range []string{"a", "b", "c"} { + if resultSlice[i] != want { + t.Errorf("[%d]: got %T(%v), want %T(%v)", i, resultSlice[i], resultSlice[i], want, want) + } + } +} + +// TestValidateWithSchema_YAMLTypedSlice verifies that validateWithSchema accepts +// typed slices (e.g. []string) that goccy/go-yaml produces for array fields. +func TestValidateWithSchema_YAMLTypedSlice(t *testing.T) { + schema := `{ + "type": "object", + "properties": { + "tags": {"type": "array", "items": {"type": "string"}}, + "name": {"type": "string"} + }, + "additionalProperties": false + }` + + frontmatter := map[string]any{ + "tags": []string{"v1", "v2"}, + "name": "test", + } + + err := validateWithSchema(frontmatter, schema, "yaml typed slice") + if err != nil { + t.Errorf("validateWithSchema should accept typed slices, got: %v", err) + } +} + // TestValidateWithSchema_YAMLIntegerTypes verifies that validateWithSchema accepts // YAML-native integer types (uint64/int64) when the schema expects number/integer. func TestValidateWithSchema_YAMLIntegerTypes(t *testing.T) {