Validate schemas without relying on source filenames

This commit is contained in:
2026-11-05 15:59:30 +00:00
parent ff57dbf8b3
commit ac1e150168
5 changed files with 86 additions and 1 deletions

View File

@@ -0,0 +1,46 @@
package validate
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"testing"
"testing/fstest"
"go.maximumdirect.net/promptkit/internal/domain"
)
func TestSchemaPreparationDoesNotDependOnSourceName(t *testing.T) {
for _, test := range []struct {
name, schema string
rejected bool
}{
{"source name", `{"$defs":{"v":{"type":"integer"}},"$ref":"schema.json#/$defs/v"}`, true},
{"unused source name", `{"$defs":{"v":{"$ref":"schema.json"}}}`, true},
{"fragment", `{"$defs":{"v":{"type":"integer"}},"$ref":"#/$defs/v"}`, false},
{"explicit ID", `{"$id":"https://example.invalid/schema.json","$defs":{"v":{"type":"integer"}},"$ref":"schema.json#/$defs/v"}`, false},
{"embedded ID", `{"$defs":{"v":{"$id":"child","type":"integer"}},"$ref":"child"}`, false},
} {
t.Run(test.name, func(t *testing.T) {
dir := t.TempDir()
if err := os.WriteFile(filepath.Join(dir, "schema.json"), []byte(test.schema), 0600); err != nil {
t.Fatal(err)
}
fsys := fstest.MapFS{"schema.json": &fstest.MapFile{Data: []byte(test.schema)}}
for i, validator := range []ValidationPreparer{NewStandardValidator(dir), NewFSValidator(fsys, ".")} {
t.Run(fmt.Sprint(i), func(t *testing.T) {
_, err := validator.PrepareValidation(context.Background(), domain.OutputContract{Format: domain.FormatJSON, ValidationMode: domain.ValidationJSONSchema, SchemaPath: "schema.json"})
if test.rejected {
if err == nil || !strings.Contains(err.Error(), "source location") {
t.Fatalf("expected source-independent rejection, got %v", err)
}
} else if err != nil {
t.Fatal(err)
}
})
}
})
}
}

View File

@@ -424,6 +424,20 @@ func (l externalSchemaLoader) Load(resourceURL string) (any, error) {
}
func compileSchemaDocument(ctx context.Context, resourceURL string, document any) (*jsonschema.Schema, error) {
root, err := compileSchemaAt(ctx, resourceURL, document)
if err != nil {
return nil, err
}
// File and private-FS retrieval URLs are never available to the provider.
// Compiling again under an unrelated HTTPS base detects reliance on those
// aliases without rewriting references or duplicating URI resolution rules.
if _, err := compileSchemaAt(ctx, "https://promptkit.invalid/submitted-schema", document); err != nil {
return nil, fmt.Errorf("schema is not independent of its source location; use fragment references or explicit schema IDs: %w", err)
}
return root, nil
}
func compileSchemaAt(ctx context.Context, resourceURL string, document any) (*jsonschema.Schema, error) {
if err := ctx.Err(); err != nil {
return nil, err
}