Document ordered pipeline operations and retire sequential examples

This commit is contained in:
2026-07-21 22:25:11 +00:00
parent 9184072839
commit 7071102ab7
18 changed files with 431 additions and 526 deletions

View File

@@ -152,79 +152,6 @@ func TestCombatNormalizerRejectsCampaignReferenceBinding(t *testing.T) {
}
}
func TestSequentialNPCOutputGroundsCombatAtBothStageLocalReferences(t *testing.T) {
registries := productionNPCRegistries(t)
catalog := moduleCatalog(registries)
configValue := loadSequentialCombatPipelineConfig(t)
npcEffective, err := configValue.Resolve(config.ResolveInput{PipelineID: "dnd-npcs", Catalog: catalog})
if err != nil {
t.Fatalf("Resolve(NPC) error = %v", err)
}
npcClient := &fakeNPCProductionLLMClient{response: npcProductionResponse{NPCs: []npcProductionRecord{
{Name: "Mira Thorn", Aliases: []string{"The Greencloak", "Mira"}, Description: "A ranger.", Relationships: []npcProductionRelationship{}, SourceRefs: []npcProductionSourceRef{{StartUnitID: 1, EndUnitID: 2}}},
{Name: "Hooded Guard", Aliases: []string{}, Description: "A sentry.", Relationships: []npcProductionRelationship{}, SourceRefs: []npcProductionSourceRef{{StartUnitID: 3, EndUnitID: 3}}},
}}}
npcOutput, err := runPreparedPipeline(t, registries, npcEffective.ResolvedPipeline, npcClient, pipeline.RunInput{RawInput: readNPCFixture(t)})
if err != nil || len(npcOutput.NormalizeOutputs) != 1 {
t.Fatalf("NPC run error = %v output = %#v, want one normalized NPC lane", err, npcOutput.NormalizeOutputs)
}
npcPayload := npcOutput.NormalizeOutputs[0].Artifact.Content
if _, err := npccodec.New().Decode(npcPayload); err != nil {
t.Fatalf("Decode(NPC output) error = %v", err)
}
npcPath := filepath.Join(t.TempDir(), "npc-run", "lanes", "npcs.json")
if err := os.MkdirAll(filepath.Dir(npcPath), 0o700); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(npcPath, npcPayload, 0o600); err != nil {
t.Fatal(err)
}
combatEffective, err := configValue.Resolve(config.ResolveInput{
PipelineID: "dnd-combat",
Catalog: catalog,
ReferenceOverrides: []pipeline.ReferenceBinding{
{Stage: pipeline.StageExtract, LaneID: "combat", SlotName: "npcs", Source: npcPath, BindingSource: contracts.ReferenceBindingSourceCLI},
{Stage: pipeline.StageNormalize, LaneID: "combat", SlotName: "npcs", Source: npcPath, BindingSource: contracts.ReferenceBindingSourceCLI},
},
})
if err != nil {
t.Fatalf("Resolve(combat) error = %v", err)
}
materialized, warnings, err := pipeline.MaterializeReferences(combatEffective.ResolvedPipeline, catalog, pipeline.ReferenceMaterializationOptions{})
if err != nil || len(warnings) != 0 {
t.Fatalf("MaterializeReferences() error = %v warnings = %#v", err, warnings)
}
client := &fakeCombatLLMClient{responses: []string{combatTestTurnResponse("The Greencloak", "turn", "watches", "Hooded Guard", 1, 1)}}
output, err := runPreparedPipeline(t, registries, materialized, client, pipeline.RunInput{RawInput: readNPCFixture(t)})
if err != nil {
t.Fatalf("Run(combat) error = %v", err)
}
value, err := combatcodec.New().Decode(output.NormalizeOutputs[0].Artifact.Content)
if err != nil {
t.Fatalf("Decode(combat output) error = %v", err)
}
if len(value.CombatTurns) != 1 || value.CombatTurns[0].Actor != "Mira Thorn" || value.CombatTurns[0].Actions[0].Targets[0] != "Hooded Guard" {
t.Fatalf("combat value = %#v, want canonical actor and target", value)
}
for _, ref := range value.CombatTurns[0].SourceRefs {
if ref.SourceID != "npc-session" {
t.Fatalf("combat source ref = %#v, want transcript evidence only", ref)
}
}
if len(output.Manifest.References) != 2 {
t.Fatalf("manifest references = %#v, want both stage-local NPC provenance entries", output.Manifest.References)
}
for _, provenance := range output.Manifest.References {
if provenance.SlotName != "npcs" || provenance.LaneID != "combat" || !strings.Contains(provenance.OriginURI, "npcs.json") || provenance.BindingSource != contracts.ReferenceBindingSourceCLI {
t.Fatalf("NPC provenance = %#v, want combat stage-local CLI binding", provenance)
}
}
if len(client.requests) != 1 || string(client.requests[0].Inputs[combatextract.NPCRegistryReferenceSlot].Content) != string(npcPayload) || client.requests[0].Inputs[combatextract.NPCRegistryReferenceSlot].OriginURI != "" {
t.Fatalf("combat NPC prompt input = %#v, want canonical payload without path provenance", client.requests)
}
}
func TestCombatPreparationRejectsMalformedOrOversizedNPCReferencesBeforeExecution(t *testing.T) {
registries := productionNPCRegistries(t)
catalog := moduleCatalog(registries)
@@ -313,19 +240,6 @@ func combatOnlyConfig() config.Config {
return cfg
}
func loadSequentialCombatPipelineConfig(t *testing.T) config.Config {
t.Helper()
fileConfig, err := config.LoadFileConfig(repositoryPathForIntegration("examples", "dnd-npc-combat-sequential.config.yml"))
if err != nil {
t.Fatalf("LoadFileConfig() error = %v", err)
}
cfg := config.Default()
if err := cfg.ApplyFileConfig(fileConfig); err != nil {
t.Fatalf("ApplyFileConfig() error = %v", err)
}
return cfg
}
func repositoryPathForIntegration(parts ...string) string {
_, file, _, _ := runtime.Caller(0)
return filepath.Join(append([]string{filepath.Dir(file), "..", "..", ".."}, parts...)...)