Minimize D&D NPC extraction contracts

This commit is contained in:
2026-07-22 18:51:26 +00:00
parent 14991cf58b
commit a263a0840c
43 changed files with 486 additions and 1395 deletions

View File

@@ -5,7 +5,6 @@ import (
"encoding/json"
"fmt"
"os"
"reflect"
"strings"
"testing"
@@ -40,30 +39,15 @@ func TestRunnerProcessesSeriatimInputWithProductionDNDNPCPipeline(t *testing.T)
client := &fakeNPCProductionLLMClient{response: npcProductionResponse{
NPCs: []npcProductionRecord{
{
Name: "Mira Thorn",
Aliases: []string{"The Greencloak"},
Description: "The first named NPC encountered.",
Relationships: []npcProductionRelationship{
{Target: "Hooded Guard", Relationship: "works with"},
},
Name: "Mira Thorn",
SourceRefs: []npcProductionSourceRef{{StartUnitID: 1, EndUnitID: 1}},
},
{
Name: "The Greencloak",
Aliases: []string{"Mira"},
Description: "A later description that must not replace the first.",
Relationships: []npcProductionRelationship{
{Target: "Hooded Guard", Relationship: "trusts"},
},
Name: " mira thorn ",
SourceRefs: []npcProductionSourceRef{{StartUnitID: 2, EndUnitID: 2}},
},
{
Name: "Hooded Guard",
Aliases: []string{},
Description: "An unnamed but distinguishable sentry.",
Relationships: []npcProductionRelationship{
{Target: "The Greencloak", Relationship: "reports to"},
},
Name: "Hooded Guard",
SourceRefs: []npcProductionSourceRef{{StartUnitID: 3, EndUnitID: 3}},
},
}},
@@ -87,15 +71,12 @@ func TestRunnerProcessesSeriatimInputWithProductionDNDNPCPipeline(t *testing.T)
t.Fatalf("NPC output = %#v, want repeated name consolidated and group/PC omitted", value.NPCs)
}
first, second := value.NPCs[0], value.NPCs[1]
if first.Name != "Mira Thorn" || first.Description != "The first named NPC encountered." || !reflect.DeepEqual(first.Aliases, []string{"The Greencloak", "Mira"}) {
if first.Name != "Mira Thorn" || len(first.SourceRefs) != 2 {
t.Fatalf("first NPC = %#v, want consolidated Mira identity", first)
}
if first.ID != identity.DeriveID(first.Name) || second.Name != "Hooded Guard" || second.ID != identity.DeriveID(second.Name) {
t.Fatalf("NPC IDs = %q/%q, want derived IDs", first.ID, second.ID)
}
if first.Relationships[0].Target != "Hooded Guard" || second.Relationships[0].Target != "Mira Thorn" {
t.Fatalf("relationship targets = %q/%q, want canonical target rewrite", first.Relationships[0].Target, second.Relationships[0].Target)
}
for _, npc := range value.NPCs {
for _, ref := range npc.SourceRefs {
if ref.SourceID != doc.ID {
@@ -103,8 +84,8 @@ func TestRunnerProcessesSeriatimInputWithProductionDNDNPCPipeline(t *testing.T)
}
}
}
if !hasNPCWarning(output.Warnings, "duplicate_npc_collapsed") || !hasNPCWarning(output.Warnings, "relationship_target_canonicalized") {
t.Fatalf("warnings = %#v, want consolidation and target warnings", output.Warnings)
if !hasNPCWarning(output.Warnings, "duplicate_npc_collapsed") {
t.Fatalf("warnings = %#v, want name-only consolidation warning", output.Warnings)
}
if output.Manifest.ValidationStatus != "approved" || len(output.Manifest.ArtifactLanes) != 1 {
t.Fatalf("manifest = %#v, want approved NPC lane", output.Manifest)
@@ -149,25 +130,25 @@ func TestProductionNPCPipelineRoutesSemanticCandidatesToDeterministicValidators(
}{
{
name: "blank string",
response: []byte(`{"npcs":[{"name":"","aliases":[],"description":"A participant.","relationships":[],"source_refs":[{"start_unit_id":1,"end_unit_id":1}]}]}`),
response: []byte(`{"npcs":[{"name":"","source_refs":[{"start_unit_id":1,"end_unit_id":1}]}]}`),
reasonCode: npcshape.ReasonCode,
validatorName: npcshape.Key,
},
{
name: "empty evidence",
response: []byte(`{"npcs":[{"name":"Mira Thorn","aliases":[],"description":"A participant.","relationships":[],"source_refs":[]}]}`),
response: []byte(`{"npcs":[{"name":"Mira Thorn","source_refs":[]}]}`),
reasonCode: npcshape.ReasonCode,
validatorName: npcshape.Key,
},
{
name: "nonpositive unit candidate",
response: []byte(`{"npcs":[{"name":"Mira Thorn","aliases":[],"description":"A participant.","relationships":[],"source_refs":[{"start_unit_id":0,"end_unit_id":1}]}]}`),
response: []byte(`{"npcs":[{"name":"Mira Thorn","source_refs":[{"start_unit_id":0,"end_unit_id":1}]}]}`),
reasonCode: npcsourcerefs.ReasonCode,
validatorName: npcsourcerefs.Key,
},
{
name: "unknown unit candidate",
response: []byte(`{"npcs":[{"name":"Mira Thorn","aliases":[],"description":"A participant.","relationships":[],"source_refs":[{"start_unit_id":99,"end_unit_id":99}]}]}`),
response: []byte(`{"npcs":[{"name":"Mira Thorn","source_refs":[{"start_unit_id":99,"end_unit_id":99}]}]}`),
reasonCode: npcsourcerefs.ReasonCode,
validatorName: npcsourcerefs.Key,
},
@@ -194,16 +175,8 @@ type npcProductionResponse struct {
}
type npcProductionRecord struct {
Name string `json:"name"`
Aliases []string `json:"aliases"`
Description string `json:"description"`
Relationships []npcProductionRelationship `json:"relationships"`
SourceRefs []npcProductionSourceRef `json:"source_refs"`
}
type npcProductionRelationship struct {
Target string `json:"target"`
Relationship string `json:"relationship"`
Name string `json:"name"`
SourceRefs []npcProductionSourceRef `json:"source_refs"`
}
type npcProductionSourceRef struct {