Improve D&D validation reliability

This commit is contained in:
2026-08-29 01:24:45 +00:00
parent 4da9360d74
commit 917d150279
55 changed files with 1300 additions and 565 deletions

View File

@@ -75,18 +75,18 @@ func TestExtractReturnsSemanticallyInvalidResponseForDeterministicValidation(t *
if err != nil {
t.Fatalf("loadResponseSchema() error = %v", err)
}
if err := validateJSONSchema(t, map[string]any{"kind": "unrecognized", "title": " ", "summary": ""}, schema.JSONSchema); err != nil {
if err := validateJSONSchema(t, map[string]any{"kind": "narrative", "title": " ", "summary": ""}, schema.JSONSchema); err != nil {
t.Fatalf("semantic candidate rejected by private schema: %v", err)
}
client := &fakeSceneDescriptionsLLMClient{response: extractionResponse{
Kind: dnd.SceneKind("unrecognized"), Title: " ", Summary: "",
Kind: dnd.SceneKindNarrative, Title: " ", Summary: "",
}}
result, err := newExtractor(t, client).Extract(context.Background(), extractionRequest())
if err != nil {
t.Fatalf("Extract() error = %v, want nil", err)
}
scene := result.Value.Scenes[0]
if scene.Kind != dnd.SceneKind("unrecognized") || scene.Title != "" || scene.Summary != "" {
if scene.Kind != dnd.SceneKindNarrative || scene.Title != "" || scene.Summary != "" {
t.Fatalf("scene = %#v, want semantic candidates returned for deterministic validation", scene)
}
}

View File

@@ -7,6 +7,8 @@ import (
"testing"
"github.com/santhosh-tekuri/jsonschema/v6"
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
)
func TestLoadResponseSchemaUsesStrictPrivateSceneDescriptionContract(t *testing.T) {
@@ -30,7 +32,7 @@ func TestLoadResponseSchemaUsesStrictPrivateSceneDescriptionContract(t *testing.
{name: "unknown framework field", response: map[string]any{"kind": "combat", "title": "Ambush", "summary": "Bandits strike.", "id": "assigned-later"}},
{name: "unknown application field", response: map[string]any{"kind": "combat", "title": "Ambush", "summary": "Bandits strike.", "source_ref": map[string]any{}}},
{name: "collection is not allowed", response: map[string]any{"kind": "combat", "title": "Ambush", "summary": "Bandits strike.", "scenes": []any{}}},
{name: "unsupported kind", response: map[string]any{"kind": "interlude", "title": "Ambush", "summary": "Bandits strike."}, valid: true},
{name: "unsupported kind", response: map[string]any{"kind": "interlude", "title": "Ambush", "summary": "Bandits strike."}},
{name: "empty title", response: map[string]any{"kind": "combat", "title": "", "summary": "Bandits strike."}, valid: true},
{name: "empty summary", response: map[string]any{"kind": "combat", "title": "Ambush", "summary": ""}, valid: true},
{name: "wrong kind type", response: map[string]any{"kind": 7, "title": "Ambush", "summary": "Bandits strike."}},
@@ -64,6 +66,19 @@ func TestResponseSchemaIsMutationSafeAndDiagnosticsRedactContent(t *testing.T) {
}
}
func TestResponseSchemaAcceptsEverySupportedKind(t *testing.T) {
schema, err := loadResponseSchema()
if err != nil {
t.Fatal(err)
}
for _, kind := range []string{string(dnd.SceneKindCombat), string(dnd.SceneKindNarrative), string(dnd.SceneKindRecap), string(dnd.SceneKindMeta)} {
response := map[string]any{"kind": kind, "title": "Title", "summary": "Summary"}
if err := validateJSONSchema(t, response, schema.JSONSchema); err != nil {
t.Fatalf("supported scene kind %q was rejected: %v", kind, err)
}
}
}
func validateJSONSchema(t *testing.T, instance map[string]any, schemaContent []byte) error {
t.Helper()
content, err := json.Marshal(instance)