2026-07-03 06:01:34 +00:00
package cli
import (
"bytes"
2026-07-04 00:59:32 +00:00
"context"
2026-07-04 01:06:04 +00:00
"encoding/json"
"errors"
2026-07-06 17:24:15 +00:00
"io/fs"
2026-07-03 17:01:24 +00:00
"os"
"path/filepath"
2026-07-04 00:59:32 +00:00
"reflect"
2026-07-05 14:27:23 +00:00
"sort"
2026-07-03 06:01:34 +00:00
"strings"
"testing"
2026-07-06 01:36:55 +00:00
"time"
2026-07-03 17:01:24 +00:00
2026-07-04 01:06:04 +00:00
"gitea.maximumdirect.net/eric/notarius/internal/core/artifacts"
2026-07-04 00:59:32 +00:00
"gitea.maximumdirect.net/eric/notarius/internal/core/config"
2026-07-04 01:11:57 +00:00
"gitea.maximumdirect.net/eric/notarius/internal/core/diagnostics"
2026-07-05 14:44:44 +00:00
"gitea.maximumdirect.net/eric/notarius/internal/core/source"
2026-07-03 17:01:24 +00:00
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
2026-07-04 13:05:39 +00:00
"gitea.maximumdirect.net/eric/notarius/internal/modules/chunk/dnd/scenes"
2026-07-04 00:59:32 +00:00
"gitea.maximumdirect.net/eric/notarius/internal/modules/chunk/generic"
"gitea.maximumdirect.net/eric/notarius/internal/modules/extract/dnd/spells"
"gitea.maximumdirect.net/eric/notarius/internal/modules/input/seriatim"
"gitea.maximumdirect.net/eric/notarius/internal/modules/merge/appendorder"
"gitea.maximumdirect.net/eric/notarius/internal/modules/normalize/noop"
jsonoutput "gitea.maximumdirect.net/eric/notarius/internal/modules/output/json"
2026-07-06 01:36:55 +00:00
"gitea.maximumdirect.net/eric/scriptorium"
2026-07-03 06:01:34 +00:00
)
func TestRunNoArgsWritesUsageToStdout ( t * testing . T ) {
var stdout bytes . Buffer
var stderr bytes . Buffer
code := Run ( nil , & stdout , & stderr )
if code != 0 {
t . Fatalf ( "Run() code = %d, want 0" , code )
}
if stdout . String ( ) != usage {
t . Fatalf ( "stdout = %q, want %q" , stdout . String ( ) , usage )
}
if stderr . Len ( ) != 0 {
t . Fatalf ( "stderr = %q, want empty" , stderr . String ( ) )
}
}
func TestRunHelpArgsWriteUsageToStdout ( t * testing . T ) {
tests := [ ] struct {
name string
args [ ] string
} {
{ name : "help" , args : [ ] string { "help" } } ,
{ name : "long help flag" , args : [ ] string { "--help" } } ,
{ name : "short help flag" , args : [ ] string { "-h" } } ,
}
for _ , tt := range tests {
t . Run ( tt . name , func ( t * testing . T ) {
var stdout bytes . Buffer
var stderr bytes . Buffer
code := Run ( tt . args , & stdout , & stderr )
if code != 0 {
t . Fatalf ( "Run() code = %d, want 0" , code )
}
if stdout . String ( ) != usage {
t . Fatalf ( "stdout = %q, want %q" , stdout . String ( ) , usage )
}
2026-07-03 17:01:24 +00:00
if ! strings . Contains ( stdout . String ( ) , "config validate" ) || ! strings . Contains ( stdout . String ( ) , "pipelines list" ) {
t . Fatalf ( "usage does not mention new commands: %q" , stdout . String ( ) )
}
2026-07-03 06:01:34 +00:00
if stderr . Len ( ) != 0 {
t . Fatalf ( "stderr = %q, want empty" , stderr . String ( ) )
}
} )
}
}
func TestRunUnknownCommandWritesErrorAndUsageToStderr ( t * testing . T ) {
var stdout bytes . Buffer
var stderr bytes . Buffer
code := Run ( [ ] string { "extract" } , & stdout , & stderr )
if code != 2 {
t . Fatalf ( "Run() code = %d, want 2" , code )
}
if stdout . Len ( ) != 0 {
t . Fatalf ( "stdout = %q, want empty" , stdout . String ( ) )
}
gotStderr := stderr . String ( )
if ! strings . Contains ( gotStderr , "notarius: unknown command \"extract\"" ) {
t . Fatalf ( "stderr = %q, want unknown command error" , gotStderr )
}
if ! strings . Contains ( gotStderr , usage ) {
t . Fatalf ( "stderr = %q, want usage" , gotStderr )
}
}
2026-07-03 17:01:24 +00:00
func TestRunConfigValidateSuccessWithFakeCatalog ( t * testing . T ) {
configPath := writeTestConfig ( t , testConfigYAML ( "example" , "events" , "notes" ) )
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( [ ] string { "config" , "validate" , "--config" , configPath , "--pipeline" , "example" } , & stdout , & stderr , Options {
Catalog : fakeCatalog ( t ) ,
} )
if code != 0 {
t . Fatalf ( "RunWithOptions() code = %d, stderr=%q" , code , stderr . String ( ) )
}
if ! strings . Contains ( stdout . String ( ) , "is valid for pipeline" ) {
t . Fatalf ( "stdout = %q, want validation success" , stdout . String ( ) )
}
if stderr . Len ( ) != 0 {
t . Fatalf ( "stderr = %q, want empty" , stderr . String ( ) )
}
}
2026-07-04 00:59:32 +00:00
func TestProductionCatalogIncludesDefaultModules ( t * testing . T ) {
catalog , err := productionCatalog ( )
if err != nil {
t . Fatalf ( "productionCatalog() error = %v, want nil" , err )
}
tests := [ ] struct {
name string
got func ( ) ( pipeline . ModuleSpec , bool )
want pipeline . ModuleSpec
} {
{
name : "seriatim input" ,
got : func ( ) ( pipeline . ModuleSpec , bool ) { return catalog . Inputs . Spec ( seriatim . Key ) } ,
want : seriatim . ModuleSpec ( ) ,
} ,
{
name : "generic chunker" ,
got : func ( ) ( pipeline . ModuleSpec , bool ) { return catalog . Chunkers . Spec ( generic . Key ) } ,
want : generic . ModuleSpec ( ) ,
} ,
2026-07-04 13:05:39 +00:00
{
name : "dnd scenes chunker" ,
got : func ( ) ( pipeline . ModuleSpec , bool ) { return catalog . Chunkers . Spec ( scenes . Key ) } ,
want : scenes . ModuleSpec ( ) ,
} ,
2026-07-04 00:59:32 +00:00
{
name : "dnd spells extractor" ,
got : func ( ) ( pipeline . ModuleSpec , bool ) { return catalog . Extractors . Spec ( spells . Key ) } ,
want : spells . ModuleSpec ( ) ,
} ,
{
name : "appendorder merger" ,
got : func ( ) ( pipeline . ModuleSpec , bool ) { return catalog . Mergers . Spec ( appendorder . Key ) } ,
want : appendorder . ModuleSpec ( ) ,
} ,
{
name : "noop normalizer" ,
got : func ( ) ( pipeline . ModuleSpec , bool ) { return catalog . Normalizers . Spec ( noop . Key ) } ,
want : noop . ModuleSpec ( ) ,
} ,
{
name : "json output" ,
got : func ( ) ( pipeline . ModuleSpec , bool ) { return catalog . Outputs . Spec ( jsonoutput . Key ) } ,
want : jsonoutput . ModuleSpec ( ) ,
} ,
}
for _ , test := range tests {
t . Run ( test . name , func ( t * testing . T ) {
got , ok := test . got ( )
if ! ok {
t . Fatalf ( "module spec ok = false, want true" )
}
if ! reflect . DeepEqual ( got , test . want ) {
t . Fatalf ( "module spec = %#v, want %#v" , got , test . want )
}
} )
}
}
2026-07-06 01:36:55 +00:00
func TestProductionPromptAssetsRegisterAndPrepareDndPrompts ( t * testing . T ) {
registry , err := productionPromptAssets ( )
if err != nil {
t . Fatalf ( "productionPromptAssets() error = %v, want nil" , err )
}
promptFS , err := registry . PromptFS ( )
if err != nil {
t . Fatalf ( "PromptFS() error = %v, want nil" , err )
}
for _ , name := range [ ] string {
"dnd.scenes/dnd.scenes.yaml" ,
"dnd.scenes/task.md" ,
"dnd.scenes/instructions.md" ,
2026-07-06 11:26:21 -05:00
"dnd.scenes/sharedassets/common-dnd-system.md" ,
"dnd.scenes/sharedassets/common-dnd-transcript.md" ,
"dnd.scenes/sharedassets/common-dnd-references.md" ,
2026-07-06 01:36:55 +00:00
"dnd.spells/dnd.spells.yaml" ,
"dnd.spells/task.md" ,
"dnd.spells/instructions.md" ,
2026-07-06 11:26:21 -05:00
"dnd.spells/sharedassets/common-dnd-system.md" ,
"dnd.spells/sharedassets/common-dnd-transcript.md" ,
"dnd.spells/sharedassets/common-dnd-references.md" ,
2026-07-06 01:36:55 +00:00
} {
if _ , err := promptFS . Open ( name ) ; err != nil {
t . Fatalf ( "PromptFS().Open(%q) error = %v, want nil" , name , err )
2026-07-06 17:24:15 +00:00
}
}
for _ , name := range [ ] string {
"common-dnd-system.md" ,
"common-dnd-transcript.md" ,
"common-dnd-references.md" ,
} {
if _ , err := promptFS . Open ( name ) ; ! errors . Is ( err , fs . ErrNotExist ) {
t . Fatalf ( "PromptFS().Open(%q) error = %v, want not exist" , name , err )
2026-07-06 01:36:55 +00:00
}
}
options , err := registry . ScriptoriumOptions ( )
if err != nil {
t . Fatalf ( "ScriptoriumOptions() error = %v, want nil" , err )
}
options = append ( options , scriptorium . WithProfiles ( scriptorium . OpenAICompatibleProfile ( scriptorium . OpenAICompatibleProfileConfig {
ID : "production-test-profile" ,
Endpoint : "http://127.0.0.1:1/v1" ,
Model : "production-test-model" ,
} ) ) )
engine , err := scriptorium . NewEngine ( scriptorium . Config { Timeout : time . Second } , options ... )
if err != nil {
t . Fatalf ( "NewEngine() error = %v, want nil" , err )
}
2026-07-06 11:26:21 -05:00
sceneTranscript := ` { "id":"session-1","segments":[ { "id":"u1","text":"We enter the crypt."}]} `
2026-07-06 01:36:55 +00:00
scenesPrepared , err := engine . Prepare ( context . Background ( ) , scriptorium . RunRequest {
PromptID : scenes . PromptID ,
PromptVersion : scenes . ResponseSchemaVersion ,
ProfileID : "production-test-profile" ,
Inputs : map [ string ] scriptorium . ArtifactRef {
2026-07-06 11:26:21 -05:00
"transcript" : scriptorium . InlineWithURI ( "file:///session.json" , sceneTranscript ) ,
"players" : scriptorium . Inline ( "Alice: Aria" ) ,
"party" : scriptorium . Inline ( "Aria: cleric" ) ,
2026-07-06 01:36:55 +00:00
"glossary" : scriptorium . Inline ( "Brightmantle: temple" ) ,
} ,
} )
if err != nil {
t . Fatalf ( "scene Prepare() error = %v, want nil" , err )
}
if got := len ( scenesPrepared . Messages ) ; got != 5 {
t . Fatalf ( "scene message count = %d, want 5" , got )
}
2026-07-06 11:26:21 -05:00
if ! strings . Contains ( scenesPrepared . Messages [ 1 ] . Content , sceneTranscript ) {
t . Fatalf ( "scene transcript message did not include source input" )
}
for _ , want := range [ ] string { "Alice: Aria" , "Aria: cleric" , "Brightmantle: temple" } {
if ! strings . Contains ( scenesPrepared . Messages [ 2 ] . Content , want ) {
t . Fatalf ( "scene reference message missing %q" , want )
}
2026-07-06 01:36:55 +00:00
}
2026-07-06 11:26:21 -05:00
spellTranscript := ` { "id":"session-1","segments":[ { "id":"u1","text":"Mira casts shield."}]} `
2026-07-06 01:36:55 +00:00
spellsPrepared , err := engine . Prepare ( context . Background ( ) , scriptorium . RunRequest {
PromptID : spells . PromptID ,
PromptVersion : spells . SchemaVersion ,
ProfileID : "production-test-profile" ,
Inputs : map [ string ] scriptorium . ArtifactRef {
2026-07-06 11:26:21 -05:00
"transcript" : scriptorium . InlineWithURI ( "file:///session.json" , spellTranscript ) ,
"players" : scriptorium . Inline ( "Dana: Mira" ) ,
"party" : scriptorium . Inline ( "Mira: wizard" ) ,
2026-07-06 01:36:55 +00:00
"glossary" : scriptorium . Inline ( "Shield: abjuration" ) ,
} ,
} )
if err != nil {
t . Fatalf ( "spell Prepare() error = %v, want nil" , err )
}
if got := len ( spellsPrepared . Messages ) ; got != 5 {
t . Fatalf ( "spell message count = %d, want 5" , got )
}
2026-07-06 11:26:21 -05:00
if ! strings . Contains ( spellsPrepared . Messages [ 1 ] . Content , spellTranscript ) {
t . Fatalf ( "spell transcript message did not include source input" )
}
for _ , want := range [ ] string { "Dana: Mira" , "Mira: wizard" , "Shield: abjuration" } {
if ! strings . Contains ( spellsPrepared . Messages [ 2 ] . Content , want ) {
t . Fatalf ( "spell reference message missing %q" , want )
}
2026-07-06 01:36:55 +00:00
}
}
2026-07-04 00:59:32 +00:00
func TestRunConfigValidateUsesProductionCatalogByDefault ( t * testing . T ) {
configPath := writeTestConfig ( t , mvpConfigYAML ( "dnd-session" , "dnd/spells" ) )
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( [ ] string { "config" , "validate" , "--config" , configPath , "--pipeline" , "dnd-session" } , & stdout , & stderr , Options { } )
if code != 0 {
t . Fatalf ( "RunWithOptions() code = %d, stderr=%q" , code , stderr . String ( ) )
}
if ! strings . Contains ( stdout . String ( ) , "is valid for pipeline" ) {
t . Fatalf ( "stdout = %q, want validation success" , stdout . String ( ) )
}
}
2026-07-04 13:05:39 +00:00
func TestRunConfigValidateAcceptsDNDScenesChunker ( t * testing . T ) {
configPath := writeTestConfig ( t , mvpConfigYAMLWithChunk ( "dnd-session" , scenes . Key , "dnd/spells" ) )
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( [ ] string { "config" , "validate" , "--config" , configPath , "--pipeline" , "dnd-session" } , & stdout , & stderr , Options { } )
if code != 0 {
t . Fatalf ( "RunWithOptions() code = %d, stderr=%q" , code , stderr . String ( ) )
}
if ! strings . Contains ( stdout . String ( ) , "is valid for pipeline" ) {
t . Fatalf ( "stdout = %q, want validation success" , stdout . String ( ) )
}
}
2026-07-04 00:59:32 +00:00
func TestRunConfigValidateUnknownProductionModuleIncludesContext ( t * testing . T ) {
configPath := writeTestConfig ( t , mvpConfigYAML ( "dnd-session" , "missing/extract" ) )
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( [ ] string { "config" , "validate" , "--config" , configPath , "--pipeline" , "dnd-session" } , & stdout , & stderr , Options { } )
if code != 1 {
t . Fatalf ( "RunWithOptions() code = %d, want 1" , code )
}
got := stderr . String ( )
for _ , want := range [ ] string { "dnd-session" , "extract" , "missing/extract" , "not registered" } {
if ! strings . Contains ( got , want ) {
t . Fatalf ( "stderr = %q, want substring %q" , got , want )
}
}
}
2026-07-03 17:01:24 +00:00
func TestRunConfigValidateReportsParseErrors ( t * testing . T ) {
2026-07-05 18:03:23 +00:00
configPath := writeFile ( t , "config.yml" , "version: 1\n" )
2026-07-03 17:01:24 +00:00
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( [ ] string { "config" , "validate" , "--config" , configPath } , & stdout , & stderr , Options { } )
if code != 1 {
t . Fatalf ( "RunWithOptions() code = %d, want 1" , code )
}
if stdout . Len ( ) != 0 {
t . Fatalf ( "stdout = %q, want empty" , stdout . String ( ) )
}
if ! strings . Contains ( stderr . String ( ) , "unsupported config version" ) {
t . Fatalf ( "stderr = %q, want parse error" , stderr . String ( ) )
}
}
func TestRunConfigValidatePipelineOnlySuccessAndInvalidLane ( t * testing . T ) {
configPath := writeTestConfig ( t , testConfigYAML ( "example" , "events" , "notes" ) )
t . Run ( "success" , func ( t * testing . T ) {
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( [ ] string { "config" , "validate" , "--config" , configPath , "--pipeline" , "example" , "--only" , "notes" } , & stdout , & stderr , Options {
Catalog : fakeCatalog ( t ) ,
} )
if code != 0 {
t . Fatalf ( "RunWithOptions() code = %d, stderr=%q" , code , stderr . String ( ) )
}
} )
t . Run ( "invalid lane" , func ( t * testing . T ) {
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( [ ] string { "config" , "validate" , "--config" , configPath , "--pipeline" , "example" , "--only" , "missing" } , & stdout , & stderr , Options {
Catalog : fakeCatalog ( t ) ,
} )
if code != 1 {
t . Fatalf ( "RunWithOptions() code = %d, want 1" , code )
}
if ! strings . Contains ( stderr . String ( ) , "selected artifact lane" ) {
t . Fatalf ( "stderr = %q, want invalid lane error" , stderr . String ( ) )
}
} )
}
func TestRunConfigValidateOnlyWithoutPipelineFails ( t * testing . T ) {
configPath := writeTestConfig ( t , testConfigYAML ( "example" , "events" ) )
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( [ ] string { "config" , "validate" , "--config" , configPath , "--only" , "events" } , & stdout , & stderr , Options { } )
if code != 2 {
t . Fatalf ( "RunWithOptions() code = %d, want 2" , code )
}
if ! strings . Contains ( stderr . String ( ) , "--only requires --pipeline" ) {
t . Fatalf ( "stderr = %q, want only/pipeline error" , stderr . String ( ) )
}
}
2026-07-03 20:54:22 +00:00
func TestRunConfigValidateRejectsMalformedOnlyValues ( t * testing . T ) {
configPath := writeTestConfig ( t , testConfigYAML ( "example" , "events" , "notes" ) )
tests := [ ] string { "," , "notes," , ",notes" , "events, ,notes" }
for _ , only := range tests {
t . Run ( only , func ( t * testing . T ) {
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( [ ] string { "config" , "validate" , "--config" , configPath , "--pipeline" , "example" , "--only" , only } , & stdout , & stderr , Options {
Catalog : fakeCatalog ( t ) ,
} )
if code != 2 {
t . Fatalf ( "RunWithOptions() code = %d, want 2" , code )
}
if stdout . Len ( ) != 0 {
t . Fatalf ( "stdout = %q, want empty" , stdout . String ( ) )
}
if ! strings . Contains ( stderr . String ( ) , "--only" ) {
t . Fatalf ( "stderr = %q, want --only error" , stderr . String ( ) )
}
} )
}
}
2026-07-03 17:01:24 +00:00
func TestRunPipelinesListSortedTextOutput ( t * testing . T ) {
configPath := writeTestConfig ( t , testConfigYAMLForPipelines ( map [ string ] [ ] string {
"zeta" : { "events" } ,
"alpha" : { "events" } ,
} ) )
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( [ ] string { "pipelines" , "list" , "--config" , configPath } , & stdout , & stderr , Options { } )
if code != 0 {
t . Fatalf ( "RunWithOptions() code = %d, stderr=%q" , code , stderr . String ( ) )
}
if got , want := stdout . String ( ) , "alpha\nzeta\n" ; got != want {
t . Fatalf ( "stdout = %q, want %q" , got , want )
}
}
func TestRunPipelinesListStableJSONOutput ( t * testing . T ) {
configPath := writeTestConfig ( t , testConfigYAMLForPipelines ( map [ string ] [ ] string {
"b" : { "events" } ,
"a" : { "events" } ,
} ) )
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( [ ] string { "pipelines" , "list" , "--config" , configPath , "--json" } , & stdout , & stderr , Options { } )
if code != 0 {
t . Fatalf ( "RunWithOptions() code = %d, stderr=%q" , code , stderr . String ( ) )
}
if got , want := stdout . String ( ) , "{\"pipelines\":[\"a\",\"b\"]}\n" ; got != want {
t . Fatalf ( "stdout = %q, want %q" , got , want )
}
}
func TestRunUsesNotariusConfigWhenConfigFlagAbsent ( t * testing . T ) {
configPath := writeTestConfig ( t , testConfigYAML ( "example" , "events" ) )
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( [ ] string { "pipelines" , "list" } , & stdout , & stderr , Options {
LookupEnv : mapLookup ( map [ string ] string { "NOTARIUS_CONFIG" : configPath } ) ,
} )
if code != 0 {
t . Fatalf ( "RunWithOptions() code = %d, stderr=%q" , code , stderr . String ( ) )
}
if got , want := stdout . String ( ) , "example\n" ; got != want {
t . Fatalf ( "stdout = %q, want %q" , got , want )
}
}
2026-07-05 18:03:23 +00:00
func TestRunConfigValidateRejectsStaleLLMProfiles ( t * testing . T ) {
configPath := writeTestConfig ( t , ` version : 2
2026-07-03 17:01:24 +00:00
llm_profiles :
2026-07-05 18:03:23 +00:00
default : { }
2026-07-03 17:01:24 +00:00
pipelines :
example :
input : fake / input
artifacts :
events :
extract : fake / extract
` )
var stdout bytes . Buffer
var stderr bytes . Buffer
2026-07-05 18:03:23 +00:00
code := RunWithOptions ( [ ] string { "config" , "validate" , "--config" , configPath } , & stdout , & stderr , Options { } )
2026-07-03 17:01:24 +00:00
2026-07-05 18:03:23 +00:00
if code != 1 {
t . Fatalf ( "RunWithOptions() code = %d, want 1" , code )
}
if ! strings . Contains ( stderr . String ( ) , "llm_profiles" ) {
t . Fatalf ( "stderr = %q, want stale llm_profiles error" , stderr . String ( ) )
2026-07-03 17:01:24 +00:00
}
}
func TestRunMissingConfigPathProducesActionableError ( t * testing . T ) {
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( [ ] string { "config" , "validate" , "--config" , filepath . Join ( t . TempDir ( ) , "missing.yml" ) } , & stdout , & stderr , Options { } )
if code != 1 {
t . Fatalf ( "RunWithOptions() code = %d, want 1" , code )
}
if ! strings . Contains ( stderr . String ( ) , "config file" ) || ! strings . Contains ( stderr . String ( ) , "not available" ) {
t . Fatalf ( "stderr = %q, want actionable missing config error" , stderr . String ( ) )
}
}
func TestRunRejectsAdHocStructuralFlags ( t * testing . T ) {
configPath := writeTestConfig ( t , testConfigYAML ( "example" , "events" ) )
flags := [ ] string { "--extractor" , "--chunker" , "--input" , "--merge" , "--normalize" }
for _ , flagName := range flags {
t . Run ( flagName , func ( t * testing . T ) {
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( [ ] string { "config" , "validate" , "--config" , configPath , flagName , "value" } , & stdout , & stderr , Options { } )
if code != 2 {
t . Fatalf ( "RunWithOptions() code = %d, want 2" , code )
}
if ! strings . Contains ( stderr . String ( ) , "flag provided but not defined" ) {
t . Fatalf ( "stderr = %q, want invalid flag error" , stderr . String ( ) )
}
} )
}
}
func TestRunInvalidFlagsExitTwo ( t * testing . T ) {
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( [ ] string { "pipelines" , "list" , "--bogus" } , & stdout , & stderr , Options { } )
if code != 2 {
t . Fatalf ( "RunWithOptions() code = %d, want 2" , code )
}
if ! strings . Contains ( stderr . String ( ) , "flag provided but not defined" ) {
t . Fatalf ( "stderr = %q, want invalid flag error" , stderr . String ( ) )
}
}
2026-07-05 18:21:37 +00:00
func TestProductionLLMClientFactoryBuildsScriptoriumRuntime ( t * testing . T ) {
2026-07-04 00:59:32 +00:00
cfg := config . Default ( )
2026-07-05 18:21:37 +00:00
client , profiles , err := productionLLMClientFactory ( context . Background ( ) , cfg , "mistral-small-3" )
if err != nil {
t . Fatalf ( "productionLLMClientFactory() error = %v, want nil" , err )
}
if client == nil {
t . Fatal ( "productionLLMClientFactory() client = nil, want client" )
2026-07-04 00:59:32 +00:00
}
2026-07-05 18:21:37 +00:00
if profiles != nil {
t . Fatalf ( "productionLLMClientFactory() profiles = %#v, want runtime-reported profiles" , profiles )
2026-07-04 00:59:32 +00:00
}
}
2026-07-04 01:06:04 +00:00
func TestRunPipelineMissingPipelineID ( t * testing . T ) {
configPath := writeTestConfig ( t , mvpConfigYAML ( "dnd-session" , "dnd/spells" ) )
inputPath := writeSeriatimInput ( t )
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( [ ] string { "run" , "--config" , configPath , "--input" , inputPath } , & stdout , & stderr , Options { } )
if code != 2 {
t . Fatalf ( "RunWithOptions() code = %d, want 2" , code )
}
if ! strings . Contains ( stderr . String ( ) , "pipeline ID" ) {
t . Fatalf ( "stderr = %q, want missing pipeline ID error" , stderr . String ( ) )
}
}
func TestRunPipelineMissingInputFlag ( t * testing . T ) {
configPath := writeTestConfig ( t , mvpConfigYAML ( "dnd-session" , "dnd/spells" ) )
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( [ ] string { "run" , "dnd-session" , "--config" , configPath } , & stdout , & stderr , Options { } )
if code != 2 {
t . Fatalf ( "RunWithOptions() code = %d, want 2" , code )
}
if ! strings . Contains ( stderr . String ( ) , "--input" ) {
t . Fatalf ( "stderr = %q, want missing input error" , stderr . String ( ) )
}
}
func TestRunPipelineRejectsUnknownFlag ( t * testing . T ) {
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( [ ] string { "run" , "dnd-session" , "--extractor" , "dnd/spells" } , & stdout , & stderr , Options { } )
if code != 2 {
t . Fatalf ( "RunWithOptions() code = %d, want 2" , code )
}
if ! strings . Contains ( stderr . String ( ) , "flag provided but not defined" ) {
t . Fatalf ( "stderr = %q, want invalid flag error" , stderr . String ( ) )
}
}
func TestRunPipelineUnknownPipeline ( t * testing . T ) {
configPath := writeTestConfig ( t , mvpConfigYAML ( "dnd-session" , "dnd/spells" ) )
inputPath := writeSeriatimInput ( t )
2026-07-04 01:11:57 +00:00
diagnosticsDir := t . TempDir ( )
2026-07-04 01:06:04 +00:00
var stdout bytes . Buffer
var stderr bytes . Buffer
2026-07-04 01:11:57 +00:00
code := RunWithOptions ( [ ] string { "run" , "missing" , "--config" , configPath , "--input" , inputPath , "--diagnostics-dir" , diagnosticsDir } , & stdout , & stderr , Options {
2026-07-04 01:06:04 +00:00
LLMClientFactory : fakeLLMFactory ( newFakeRunLLMClient ( false ) , nil ) ,
} )
if code != 1 {
t . Fatalf ( "RunWithOptions() code = %d, want 1" , code )
}
if ! strings . Contains ( stderr . String ( ) , "pipeline \"missing\" is not configured" ) {
t . Fatalf ( "stderr = %q, want unknown pipeline error" , stderr . String ( ) )
}
}
func TestRunPipelineUnknownOnlyLane ( t * testing . T ) {
configPath := writeTestConfig ( t , mvpConfigYAML ( "dnd-session" , "dnd/spells" ) )
inputPath := writeSeriatimInput ( t )
2026-07-04 01:11:57 +00:00
diagnosticsDir := t . TempDir ( )
2026-07-04 01:06:04 +00:00
var stdout bytes . Buffer
var stderr bytes . Buffer
2026-07-04 01:11:57 +00:00
code := RunWithOptions ( [ ] string { "run" , "dnd-session" , "--config" , configPath , "--input" , inputPath , "--only" , "missing" , "--diagnostics-dir" , diagnosticsDir } , & stdout , & stderr , Options {
2026-07-04 01:06:04 +00:00
LLMClientFactory : fakeLLMFactory ( newFakeRunLLMClient ( false ) , nil ) ,
} )
if code != 1 {
t . Fatalf ( "RunWithOptions() code = %d, want 1" , code )
}
if ! strings . Contains ( stderr . String ( ) , "selected artifact lane" ) {
t . Fatalf ( "stderr = %q, want selected lane error" , stderr . String ( ) )
}
}
func TestRunPipelineInvalidInputPath ( t * testing . T ) {
configPath := writeTestConfig ( t , mvpConfigYAML ( "dnd-session" , "dnd/spells" ) )
inputPath := filepath . Join ( t . TempDir ( ) , "missing.json" )
2026-07-04 01:11:57 +00:00
diagnosticsDir := t . TempDir ( )
2026-07-04 01:06:04 +00:00
var stdout bytes . Buffer
var stderr bytes . Buffer
2026-07-04 01:11:57 +00:00
code := RunWithOptions ( [ ] string { "run" , "dnd-session" , "--config" , configPath , "--input" , inputPath , "--diagnostics-dir" , diagnosticsDir } , & stdout , & stderr , Options {
2026-07-04 01:06:04 +00:00
LLMClientFactory : fakeLLMFactory ( newFakeRunLLMClient ( false ) , nil ) ,
} )
if code != 1 {
t . Fatalf ( "RunWithOptions() code = %d, want 1" , code )
}
if ! strings . Contains ( stderr . String ( ) , "read input" ) {
t . Fatalf ( "stderr = %q, want input read error" , stderr . String ( ) )
}
}
func TestRunPipelineSuccessUsesProductionRegistriesAndFakeLLM ( t * testing . T ) {
configPath := writeTestConfig ( t , mvpConfigYAML ( "dnd-session" , "dnd/spells" ) )
inputPath := writeSeriatimInput ( t )
2026-07-04 01:11:57 +00:00
outputDir := t . TempDir ( )
diagnosticsDir := t . TempDir ( )
2026-07-04 01:06:04 +00:00
client := newFakeRunLLMClient ( false )
var stdout bytes . Buffer
var stderr bytes . Buffer
2026-07-04 01:11:57 +00:00
code := RunWithOptions ( [ ] string { "run" , "dnd-session" , "--config" , configPath , "--input" , inputPath , "--output-dir" , outputDir , "--diagnostics-dir" , diagnosticsDir } , & stdout , & stderr , Options {
2026-07-04 01:06:04 +00:00
LLMClientFactory : fakeLLMFactory ( client , nil ) ,
} )
if code != 0 {
t . Fatalf ( "RunWithOptions() code = %d, stderr=%q" , code , stderr . String ( ) )
}
if client . calls != 1 {
t . Fatalf ( "LLM calls = %d, want 1" , client . calls )
}
2026-07-07 18:58:23 +00:00
for _ , want := range [ ] string { "dnd-session" , "outputs=1" , "rejected=0" , outputDir } {
2026-07-04 01:06:04 +00:00
if ! strings . Contains ( stdout . String ( ) , want ) {
t . Fatalf ( "stdout = %q, want substring %q" , stdout . String ( ) , want )
}
}
if stderr . Len ( ) != 0 {
t . Fatalf ( "stderr = %q, want empty" , stderr . String ( ) )
}
}
func TestRunPipelineOnlySelectsRequestedLane ( t * testing . T ) {
configPath := writeTestConfig ( t , mvpConfigYAMLForLanes ( "dnd-session" , "spells" , "rituals" ) )
inputPath := writeSeriatimInput ( t )
2026-07-04 01:11:57 +00:00
outputDir := t . TempDir ( )
diagnosticsDir := t . TempDir ( )
2026-07-04 01:06:04 +00:00
client := newFakeRunLLMClient ( false )
var stdout bytes . Buffer
var stderr bytes . Buffer
2026-07-04 01:11:57 +00:00
code := RunWithOptions ( [ ] string { "run" , "dnd-session" , "--config" , configPath , "--input" , inputPath , "--only" , "spells" , "--output-dir" , outputDir , "--diagnostics-dir" , diagnosticsDir } , & stdout , & stderr , Options {
2026-07-04 01:06:04 +00:00
LLMClientFactory : fakeLLMFactory ( client , nil ) ,
} )
if code != 0 {
t . Fatalf ( "RunWithOptions() code = %d, stderr=%q" , code , stderr . String ( ) )
}
if client . calls != 1 {
t . Fatalf ( "LLM calls = %d, want only selected lane to run once" , client . calls )
}
2026-07-07 18:58:23 +00:00
if ! strings . Contains ( stdout . String ( ) , "outputs=1" ) {
t . Fatalf ( "stdout = %q, want output count" , stdout . String ( ) )
2026-07-04 01:06:04 +00:00
}
}
func TestRunPipelineLLMFactoryFailure ( t * testing . T ) {
configPath := writeTestConfig ( t , mvpConfigYAML ( "dnd-session" , "dnd/spells" ) )
inputPath := writeSeriatimInput ( t )
2026-07-04 01:11:57 +00:00
diagnosticsDir := t . TempDir ( )
2026-07-04 01:06:04 +00:00
var stdout bytes . Buffer
var stderr bytes . Buffer
2026-07-04 01:11:57 +00:00
code := RunWithOptions ( [ ] string { "run" , "dnd-session" , "--config" , configPath , "--input" , inputPath , "--diagnostics-dir" , diagnosticsDir } , & stdout , & stderr , Options {
2026-07-04 01:06:04 +00:00
LLMClientFactory : fakeLLMFactory ( newFakeRunLLMClient ( false ) , errors . New ( "factory unavailable" ) ) ,
} )
if code != 1 {
t . Fatalf ( "RunWithOptions() code = %d, want 1" , code )
}
if ! strings . Contains ( stderr . String ( ) , "create LLM client" ) || ! strings . Contains ( stderr . String ( ) , "factory unavailable" ) {
t . Fatalf ( "stderr = %q, want LLM factory error" , stderr . String ( ) )
}
}
2026-07-07 18:58:23 +00:00
func TestRunPipelineCarriesInvalidLLMSourceRefsAsRawOutput ( t * testing . T ) {
2026-07-04 01:06:04 +00:00
configPath := writeTestConfig ( t , mvpConfigYAML ( "dnd-session" , "dnd/spells" ) )
inputPath := writeSeriatimInput ( t )
2026-07-04 01:11:57 +00:00
outputDir := t . TempDir ( )
diagnosticsDir := t . TempDir ( )
2026-07-04 01:06:04 +00:00
client := newFakeRunLLMClient ( true )
var stdout bytes . Buffer
var stderr bytes . Buffer
2026-07-04 01:11:57 +00:00
code := RunWithOptions ( [ ] string { "run" , "dnd-session" , "--config" , configPath , "--input" , inputPath , "--output-dir" , outputDir , "--diagnostics-dir" , diagnosticsDir } , & stdout , & stderr , Options {
2026-07-04 01:06:04 +00:00
LLMClientFactory : fakeLLMFactory ( client , nil ) ,
} )
if code != 0 {
t . Fatalf ( "RunWithOptions() code = %d, stderr=%q" , code , stderr . String ( ) )
}
2026-07-07 18:58:23 +00:00
if ! strings . Contains ( stdout . String ( ) , "outputs=1" ) || ! strings . Contains ( stdout . String ( ) , "rejected=0" ) {
t . Fatalf ( "stdout = %q, want raw output count" , stdout . String ( ) )
2026-07-04 01:06:04 +00:00
}
}
func TestRunPipelineLLMProfileOverrideSelectsFactoryProfile ( t * testing . T ) {
2026-07-05 18:03:23 +00:00
profilePath := writeScriptoriumProfileFile ( t , "runtime" , "http://profile.test/v1" , "test-model" )
configPath := writeTestConfig ( t , mvpConfigYAMLWithProfileFile ( "dnd-session" , profilePath ) )
2026-07-04 01:06:04 +00:00
inputPath := writeSeriatimInput ( t )
2026-07-04 01:11:57 +00:00
outputDir := t . TempDir ( )
diagnosticsDir := t . TempDir ( )
2026-07-04 01:06:04 +00:00
client := newFakeRunLLMClient ( false )
factory := & recordingLLMFactory { client : client }
var stdout bytes . Buffer
var stderr bytes . Buffer
2026-07-04 01:11:57 +00:00
code := RunWithOptions ( [ ] string { "run" , "dnd-session" , "--config" , configPath , "--input" , inputPath , "--llm-profile" , "runtime" , "--output-dir" , outputDir , "--diagnostics-dir" , diagnosticsDir } , & stdout , & stderr , Options {
2026-07-04 01:06:04 +00:00
LLMClientFactory : factory . build ,
} )
if code != 0 {
t . Fatalf ( "RunWithOptions() code = %d, stderr=%q" , code , stderr . String ( ) )
}
if got , want := strings . Join ( factory . profileIDs , "," ) , "runtime" ; got != want {
t . Fatalf ( "factory profile IDs = %q, want %q" , got , want )
}
}
2026-07-05 18:03:23 +00:00
func TestRunConfigValidateRejectsUnknownExplicitScriptoriumProfile ( t * testing . T ) {
profilePath := writeScriptoriumProfileFile ( t , "known" , "http://profile.test/v1" , "test-model" )
configPath := writeTestConfig ( t , ` version : 2
scriptorium :
profile_file : ` +profilePath+ `
pipelines :
example :
input : fake / input
artifacts :
events :
extract :
module : fake / extract
llm_profile : missing
` )
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( [ ] string { "config" , "validate" , "--config" , configPath , "--pipeline" , "example" } , & stdout , & stderr , Options {
Catalog : fakeCatalog ( t ) ,
} )
if code != 1 {
t . Fatalf ( "RunWithOptions() code = %d, want 1" , code )
}
if ! strings . Contains ( stderr . String ( ) , "Scriptorium profile" ) || ! strings . Contains ( stderr . String ( ) , "missing" ) {
t . Fatalf ( "stderr = %q, want unknown Scriptorium profile" , stderr . String ( ) )
}
}
2026-07-07 19:10:55 +00:00
func TestRunConfigValidateIncludesMergeAndIgnoresNonLLMStageScriptoriumProfiles ( t * testing . T ) {
2026-07-05 18:26:31 -05:00
profilePath := writeScriptoriumProfileFile ( t , "known" , "http://profile.test/v1" , "test-model" )
configPath := writeTestConfig ( t , ` version : 2
scriptorium :
profile_file : ` +profilePath+ `
pipelines :
example :
input :
module : fake / input
llm_profile : missing - input
output :
module : json
llm_profile : missing - output
artifacts :
events :
extract : fake / extract
merge :
module : appendorder
llm_profile : missing - merge
` )
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( [ ] string { "config" , "validate" , "--config" , configPath , "--pipeline" , "example" } , & stdout , & stderr , Options {
Catalog : fakeCatalog ( t ) ,
} )
2026-07-07 19:10:55 +00:00
if code != 1 {
t . Fatalf ( "RunWithOptions() code = %d, want failure for missing merge profile" , code )
2026-07-05 18:26:31 -05:00
}
2026-07-07 19:10:55 +00:00
if ! strings . Contains ( stderr . String ( ) , "Scriptorium profile" ) || ! strings . Contains ( stderr . String ( ) , "missing-merge" ) {
t . Fatalf ( "stderr = %q, want missing merge profile error" , stderr . String ( ) )
2026-07-05 18:26:31 -05:00
}
}
func TestEffectiveLLMProfileIDsUsesLLMCapableStagesOnly ( t * testing . T ) {
resolved := pipeline . ResolvedPipeline {
Input : pipeline . ModuleBinding { LLMProfile : "input-profile" } ,
Chunk : pipeline . ModuleBinding { LLMProfile : "chunk-profile" } ,
Output : pipeline . ModuleBinding { LLMProfile : "output-profile" } ,
ArtifactLanes : [ ] pipeline . ResolvedArtifactLane {
{
Extract : pipeline . ModuleBinding { LLMProfile : "extract-profile" } ,
Merge : pipeline . ModuleBinding { LLMProfile : "merge-profile" } ,
Normalize : pipeline . ModuleBinding { LLMProfile : "normalize-profile" } ,
Validators : [ ] pipeline . ModuleBinding { { LLMProfile : "validator-profile" } } ,
} ,
} ,
}
got := effectiveLLMProfileIDs ( resolved )
2026-07-07 19:10:55 +00:00
want := [ ] string { "chunk-profile" , "extract-profile" , "merge-profile" , "normalize-profile" }
2026-07-05 18:26:31 -05:00
if ! reflect . DeepEqual ( got , want ) {
t . Fatalf ( "effectiveLLMProfileIDs() = %#v, want %#v" , got , want )
}
}
2026-07-05 17:51:36 +00:00
func TestRunPipelineSessionIDFlagRecordsExplicitTrimmedValue ( t * testing . T ) {
configPath := writeTestConfig ( t , mvpConfigYAML ( "dnd-session" , "dnd/spells" ) )
inputPath := writeSeriatimInput ( t )
outputDir := t . TempDir ( )
diagnosticsDir := t . TempDir ( )
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( [ ] string {
"run" , "dnd-session" ,
"--config" , configPath ,
"--input" , inputPath ,
"--session-id" , " external-session " ,
"--output-dir" , outputDir ,
"--diagnostics-dir" , diagnosticsDir ,
} , & stdout , & stderr , Options {
LLMClientFactory : fakeLLMFactory ( newFakeRunLLMClient ( false ) , nil ) ,
} )
if code != 0 {
t . Fatalf ( "RunWithOptions() code = %d, stderr=%q" , code , stderr . String ( ) )
}
var manifest artifacts . RunManifest
readJSONFile ( t , filepath . Join ( onlyChildDir ( t , outputDir ) , "manifest.json" ) , & manifest )
if got := manifest . Metadata [ "session_id" ] ; got != "external-session" {
t . Fatalf ( "manifest metadata = %#v, want trimmed session ID" , manifest . Metadata )
}
}
func TestRunPipelineSessionIDDefaultsToParsedSourceID ( t * testing . T ) {
configPath := writeTestConfig ( t , mvpConfigYAML ( "dnd-session" , "dnd/spells" ) )
inputPath := writeSeriatimInput ( t )
outputDir := t . TempDir ( )
diagnosticsDir := t . TempDir ( )
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( [ ] string {
"run" , "dnd-session" ,
"--config" , configPath ,
"--input" , inputPath ,
"--output-dir" , outputDir ,
"--diagnostics-dir" , diagnosticsDir ,
} , & stdout , & stderr , Options {
LLMClientFactory : fakeLLMFactory ( newFakeRunLLMClient ( false ) , nil ) ,
} )
if code != 0 {
t . Fatalf ( "RunWithOptions() code = %d, stderr=%q" , code , stderr . String ( ) )
}
var manifest artifacts . RunManifest
readJSONFile ( t , filepath . Join ( onlyChildDir ( t , outputDir ) , "manifest.json" ) , & manifest )
if got := manifest . Metadata [ "session_id" ] ; got != "session-alpha" {
t . Fatalf ( "manifest metadata = %#v, want parsed source ID default" , manifest . Metadata )
}
}
func TestRunPipelineSessionIDFlagRejectsMissingOrBlankValue ( t * testing . T ) {
configPath := writeTestConfig ( t , mvpConfigYAML ( "dnd-session" , "dnd/spells" ) )
inputPath := writeSeriatimInput ( t )
tests := [ ] struct {
name string
args [ ] string
want string
} {
{
name : "missing value" ,
args : [ ] string { "run" , "dnd-session" , "--config" , configPath , "--input" , inputPath , "--session-id" } ,
want : "flag needs an argument" ,
} ,
{
name : "blank value" ,
args : [ ] string { "run" , "dnd-session" , "--config" , configPath , "--input" , inputPath , "--session-id" , " \t " } ,
want : "--session-id must not be empty" ,
} ,
}
for _ , test := range tests {
t . Run ( test . name , func ( t * testing . T ) {
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( test . args , & stdout , & stderr , Options {
LLMClientFactory : fakeLLMFactory ( newFakeRunLLMClient ( false ) , nil ) ,
} )
if code != 2 {
t . Fatalf ( "RunWithOptions() code = %d, want 2" , code )
}
if ! strings . Contains ( stderr . String ( ) , test . want ) {
t . Fatalf ( "stderr = %q, want substring %q" , stderr . String ( ) , test . want )
}
} )
}
}
2026-07-05 14:27:23 +00:00
func TestRunPipelineReferenceFlagBindsUnambiguousSlot ( t * testing . T ) {
configPath := writeTestConfig ( t , testConfigYAML ( "example" , "events" ) )
inputPath := filepath . Join ( t . TempDir ( ) , "missing.json" )
2026-07-05 14:32:35 +00:00
referencePath := writeFile ( t , "roster.yml" , "Aria\n" )
2026-07-05 14:27:23 +00:00
diagnosticsDir := t . TempDir ( )
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( [ ] string {
"run" , "example" ,
"--config" , configPath ,
"--input" , inputPath ,
"--diagnostics-dir" , diagnosticsDir ,
2026-07-05 14:32:35 +00:00
"--reference" , "roster=" + referencePath ,
2026-07-05 14:27:23 +00:00
} , & stdout , & stderr , Options {
Catalog : fakeCatalog ( t , pipeline . ModuleSpec {
Key : "fake/extract" ,
Stage : pipeline . StageExtract ,
Requires : [ ] string { "chunks" } ,
Provides : [ ] string { "artifact" } ,
ReferenceSlots : [ ] contracts . ReferenceSlot {
{ Name : "roster" } ,
} ,
} ) ,
} )
if code != 1 || ! strings . Contains ( stderr . String ( ) , "read input" ) {
t . Fatalf ( "RunWithOptions() code = %d stderr=%q, want read input failure after resolution" , code , stderr . String ( ) )
}
resolved := readResolvedPipeline ( t , diagnosticsDir )
2026-07-05 16:12:38 +00:00
refs := resolved . ArtifactLanes [ 0 ] . ExtractReferences . Bindings
2026-07-05 14:27:23 +00:00
want := [ ] pipeline . ReferenceBinding {
2026-07-05 14:32:35 +00:00
{ LaneID : "events" , SlotName : "roster" , Source : referencePath , BindingSource : contracts . ReferenceBindingSourceCLI } ,
2026-07-05 14:27:23 +00:00
}
if ! reflect . DeepEqual ( refs , want ) {
t . Fatalf ( "resolved references = %#v, want %#v" , refs , want )
}
}
func TestRunPipelineReferenceFlagBindsLaneQualifiedSlot ( t * testing . T ) {
configPath := writeTestConfig ( t , testConfigYAML ( "example" , "events" , "notes" ) )
inputPath := filepath . Join ( t . TempDir ( ) , "missing.json" )
2026-07-05 14:32:35 +00:00
referencePath := writeFile ( t , "notes.yml" , "Notes\n" )
2026-07-05 14:27:23 +00:00
diagnosticsDir := t . TempDir ( )
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( [ ] string {
"run" , "example" ,
"--config" , configPath ,
"--input" , inputPath ,
"--only" , "events,notes" ,
"--diagnostics-dir" , diagnosticsDir ,
2026-07-05 14:32:35 +00:00
"--reference" , "notes.roster=" + referencePath ,
2026-07-05 14:27:23 +00:00
} , & stdout , & stderr , Options {
Catalog : fakeCatalog ( t , pipeline . ModuleSpec {
Key : "fake/extract" ,
Stage : pipeline . StageExtract ,
Requires : [ ] string { "chunks" } ,
Provides : [ ] string { "artifact" } ,
ReferenceSlots : [ ] contracts . ReferenceSlot {
{ Name : "roster" } ,
} ,
} ) ,
} )
if code != 1 || ! strings . Contains ( stderr . String ( ) , "read input" ) {
t . Fatalf ( "RunWithOptions() code = %d stderr=%q, want read input failure after resolution" , code , stderr . String ( ) )
}
resolved := readResolvedPipeline ( t , diagnosticsDir )
events := resolvedArtifactLane ( t , resolved , "events" )
2026-07-05 16:12:38 +00:00
if len ( events . ExtractReferences . Bindings ) != 0 {
t . Fatalf ( "events references = %#v, want none" , events . ExtractReferences . Bindings )
2026-07-05 14:27:23 +00:00
}
notes := resolvedArtifactLane ( t , resolved , "notes" )
2026-07-05 16:12:38 +00:00
if len ( notes . ExtractReferences . Bindings ) != 1 || notes . ExtractReferences . Bindings [ 0 ] . Source != referencePath {
t . Fatalf ( "notes references = %#v, want lane-qualified binding" , notes . ExtractReferences . Bindings )
2026-07-05 14:27:23 +00:00
}
}
2026-07-05 16:31:23 +00:00
func TestRunPipelineReferenceFlagBindsChunkQualifiedSlot ( t * testing . T ) {
configPath := writeTestConfig ( t , testConfigYAML ( "example" , "events" ) )
inputPath := filepath . Join ( t . TempDir ( ) , "missing.json" )
referencePath := writeFile ( t , "scenes.md" , "Scenes\n" )
diagnosticsDir := t . TempDir ( )
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( [ ] string {
"run" , "example" ,
"--config" , configPath ,
"--input" , inputPath ,
"--diagnostics-dir" , diagnosticsDir ,
"--reference" , "chunk.scene_guide=" + referencePath ,
} , & stdout , & stderr , Options {
Catalog : fakeCatalog ( t , pipeline . ModuleSpec {
Key : "generic" ,
Stage : pipeline . StageChunk ,
Requires : [ ] string { "source" } ,
Provides : [ ] string { "chunks" } ,
ReferenceSlots : [ ] contracts . ReferenceSlot {
{ Name : "scene_guide" } ,
} ,
} ) ,
} )
if code != 1 || ! strings . Contains ( stderr . String ( ) , "read input" ) {
t . Fatalf ( "RunWithOptions() code = %d stderr=%q, want read input failure after resolution" , code , stderr . String ( ) )
}
resolved := readResolvedPipeline ( t , diagnosticsDir )
refs := resolved . ChunkReferences . Bindings
want := [ ] pipeline . ReferenceBinding {
{ SlotName : "scene_guide" , Source : referencePath , BindingSource : contracts . ReferenceBindingSourceCLI } ,
}
if ! reflect . DeepEqual ( refs , want ) {
t . Fatalf ( "chunk references = %#v, want %#v" , refs , want )
}
}
func TestRunPipelineReferenceFlagBindsExplicitExtractSlot ( t * testing . T ) {
configPath := writeTestConfig ( t , testConfigYAML ( "example" , "events" ) )
inputPath := filepath . Join ( t . TempDir ( ) , "missing.json" )
referencePath := writeFile ( t , "roster.yml" , "Aria\n" )
diagnosticsDir := t . TempDir ( )
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( [ ] string {
"run" , "example" ,
"--config" , configPath ,
"--input" , inputPath ,
"--diagnostics-dir" , diagnosticsDir ,
"--reference" , "events.extract.roster=" + referencePath ,
} , & stdout , & stderr , Options {
Catalog : fakeCatalog ( t , pipeline . ModuleSpec {
Key : "fake/extract" ,
Stage : pipeline . StageExtract ,
Requires : [ ] string { "chunks" } ,
Provides : [ ] string { "artifact" } ,
ReferenceSlots : [ ] contracts . ReferenceSlot {
{ Name : "roster" } ,
} ,
} ) ,
} )
if code != 1 || ! strings . Contains ( stderr . String ( ) , "read input" ) {
t . Fatalf ( "RunWithOptions() code = %d stderr=%q, want read input failure after resolution" , code , stderr . String ( ) )
}
resolved := readResolvedPipeline ( t , diagnosticsDir )
refs := resolved . ArtifactLanes [ 0 ] . ExtractReferences . Bindings
want := [ ] pipeline . ReferenceBinding {
{ LaneID : "events" , SlotName : "roster" , Source : referencePath , BindingSource : contracts . ReferenceBindingSourceCLI } ,
}
if ! reflect . DeepEqual ( refs , want ) {
t . Fatalf ( "extract references = %#v, want %#v" , refs , want )
}
}
func TestRunPipelineReferenceFlagBindsExplicitNormalizeSlot ( t * testing . T ) {
configPath := writeTestConfig ( t , testConfigYAML ( "example" , "events" ) )
inputPath := filepath . Join ( t . TempDir ( ) , "missing.json" )
referencePath := writeFile ( t , "normalize.md" , "Normalize\n" )
diagnosticsDir := t . TempDir ( )
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( [ ] string {
"run" , "example" ,
"--config" , configPath ,
"--input" , inputPath ,
"--diagnostics-dir" , diagnosticsDir ,
"--reference" , "events.normalize.notes=" + referencePath ,
} , & stdout , & stderr , Options {
Catalog : fakeCatalog ( t , pipeline . ModuleSpec {
Key : "noop" ,
Stage : pipeline . StageNormalize ,
Requires : [ ] string { "merged" } ,
Provides : [ ] string { "normalized" } ,
ReferenceSlots : [ ] contracts . ReferenceSlot {
{ Name : "notes" } ,
} ,
} ) ,
} )
if code != 1 || ! strings . Contains ( stderr . String ( ) , "read input" ) {
t . Fatalf ( "RunWithOptions() code = %d stderr=%q, want read input failure after resolution" , code , stderr . String ( ) )
}
resolved := readResolvedPipeline ( t , diagnosticsDir )
refs := resolved . ArtifactLanes [ 0 ] . NormalizeReferences . Bindings
want := [ ] pipeline . ReferenceBinding {
{ LaneID : "events" , SlotName : "notes" , Source : referencePath , BindingSource : contracts . ReferenceBindingSourceCLI } ,
}
if ! reflect . DeepEqual ( refs , want ) {
t . Fatalf ( "normalize references = %#v, want %#v" , refs , want )
}
}
2026-07-07 19:10:55 +00:00
func TestRunPipelineReferenceFlagBindsExplicitMergeSlot ( t * testing . T ) {
configPath := writeTestConfig ( t , testConfigYAML ( "example" , "events" ) )
inputPath := filepath . Join ( t . TempDir ( ) , "missing.json" )
referencePath := writeFile ( t , "merge.md" , "Merge notes\n" )
diagnosticsDir := t . TempDir ( )
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( [ ] string {
"run" , "example" ,
"--config" , configPath ,
"--input" , inputPath ,
"--diagnostics-dir" , diagnosticsDir ,
"--reference" , "events.merge.notes=" + referencePath ,
} , & stdout , & stderr , Options {
Catalog : fakeCatalog ( t , pipeline . ModuleSpec {
Key : "appendorder" ,
Stage : pipeline . StageMerge ,
Requires : [ ] string { "artifact" } ,
Provides : [ ] string { "merged" } ,
ReferenceSlots : [ ] contracts . ReferenceSlot {
{ Name : "notes" } ,
} ,
} ) ,
} )
if code != 1 || ! strings . Contains ( stderr . String ( ) , "read input" ) {
t . Fatalf ( "RunWithOptions() code = %d stderr=%q, want read input failure after resolution" , code , stderr . String ( ) )
}
resolved := readResolvedPipeline ( t , diagnosticsDir )
refs := resolved . ArtifactLanes [ 0 ] . MergeReferences . Bindings
want := [ ] pipeline . ReferenceBinding {
{ LaneID : "events" , SlotName : "notes" , Source : referencePath , BindingSource : contracts . ReferenceBindingSourceCLI } ,
}
if ! reflect . DeepEqual ( refs , want ) {
t . Fatalf ( "merge references = %#v, want %#v" , refs , want )
}
}
func TestRunPipelineReferenceFlagBindsUnambiguousMergeSlot ( t * testing . T ) {
configPath := writeTestConfig ( t , testConfigYAML ( "example" , "events" ) )
inputPath := filepath . Join ( t . TempDir ( ) , "missing.json" )
referencePath := writeFile ( t , "merge.md" , "Merge notes\n" )
diagnosticsDir := t . TempDir ( )
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( [ ] string {
"run" , "example" ,
"--config" , configPath ,
"--input" , inputPath ,
"--diagnostics-dir" , diagnosticsDir ,
"--reference" , "merge.notes=" + referencePath ,
} , & stdout , & stderr , Options {
Catalog : fakeCatalog ( t , pipeline . ModuleSpec {
Key : "appendorder" ,
Stage : pipeline . StageMerge ,
Requires : [ ] string { "artifact" } ,
Provides : [ ] string { "merged" } ,
ReferenceSlots : [ ] contracts . ReferenceSlot {
{ Name : "notes" } ,
} ,
} ) ,
} )
if code != 1 || ! strings . Contains ( stderr . String ( ) , "read input" ) {
t . Fatalf ( "RunWithOptions() code = %d stderr=%q, want read input failure after resolution" , code , stderr . String ( ) )
}
resolved := readResolvedPipeline ( t , diagnosticsDir )
refs := resolved . ArtifactLanes [ 0 ] . MergeReferences . Bindings
if len ( refs ) != 1 || refs [ 0 ] . Source != referencePath || refs [ 0 ] . LaneID != "events" {
t . Fatalf ( "merge references = %#v, want unambiguous merge binding" , refs )
}
}
2026-07-05 16:31:23 +00:00
func TestRunPipelineReferenceFlagBindsFlatSlotAcrossOneTarget ( t * testing . T ) {
configPath := writeTestConfig ( t , testConfigYAML ( "example" , "events" ) )
inputPath := filepath . Join ( t . TempDir ( ) , "missing.json" )
referencePath := writeFile ( t , "normalize.md" , "Normalize\n" )
diagnosticsDir := t . TempDir ( )
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( [ ] string {
"run" , "example" ,
"--config" , configPath ,
"--input" , inputPath ,
"--diagnostics-dir" , diagnosticsDir ,
"--reference" , "notes=" + referencePath ,
} , & stdout , & stderr , Options {
Catalog : fakeCatalog ( t , pipeline . ModuleSpec {
Key : "noop" ,
Stage : pipeline . StageNormalize ,
Requires : [ ] string { "merged" } ,
Provides : [ ] string { "normalized" } ,
ReferenceSlots : [ ] contracts . ReferenceSlot {
{ Name : "notes" } ,
} ,
} ) ,
} )
if code != 1 || ! strings . Contains ( stderr . String ( ) , "read input" ) {
t . Fatalf ( "RunWithOptions() code = %d stderr=%q, want read input failure after resolution" , code , stderr . String ( ) )
}
resolved := readResolvedPipeline ( t , diagnosticsDir )
if refs := resolved . ArtifactLanes [ 0 ] . NormalizeReferences . Bindings ; len ( refs ) != 1 || refs [ 0 ] . Source != referencePath {
t . Fatalf ( "normalize references = %#v, want flat binding" , refs )
}
}
func TestRunPipelineReferenceFlagBindsLaneSlotAcrossOneTarget ( t * testing . T ) {
configPath := writeTestConfig ( t , testConfigYAML ( "example" , "events" ) )
inputPath := filepath . Join ( t . TempDir ( ) , "missing.json" )
referencePath := writeFile ( t , "normalize.md" , "Normalize\n" )
diagnosticsDir := t . TempDir ( )
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( [ ] string {
"run" , "example" ,
"--config" , configPath ,
"--input" , inputPath ,
"--diagnostics-dir" , diagnosticsDir ,
"--reference" , "events.notes=" + referencePath ,
} , & stdout , & stderr , Options {
Catalog : fakeCatalog ( t , pipeline . ModuleSpec {
Key : "noop" ,
Stage : pipeline . StageNormalize ,
Requires : [ ] string { "merged" } ,
Provides : [ ] string { "normalized" } ,
ReferenceSlots : [ ] contracts . ReferenceSlot {
{ Name : "notes" } ,
} ,
} ) ,
} )
if code != 1 || ! strings . Contains ( stderr . String ( ) , "read input" ) {
t . Fatalf ( "RunWithOptions() code = %d stderr=%q, want read input failure after resolution" , code , stderr . String ( ) )
}
resolved := readResolvedPipeline ( t , diagnosticsDir )
if refs := resolved . ArtifactLanes [ 0 ] . NormalizeReferences . Bindings ; len ( refs ) != 1 || refs [ 0 ] . Source != referencePath {
t . Fatalf ( "normalize references = %#v, want lane-qualified binding" , refs )
}
}
2026-07-05 14:27:23 +00:00
func TestRunPipelineReferenceFlagRejectsAmbiguousFlatSlot ( t * testing . T ) {
configPath := writeTestConfig ( t , testConfigYAML ( "example" , "events" , "notes" ) )
inputPath := writeSeriatimInput ( t )
diagnosticsDir := t . TempDir ( )
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( [ ] string {
"run" , "example" ,
"--config" , configPath ,
"--input" , inputPath ,
"--diagnostics-dir" , diagnosticsDir ,
"--reference" , "roster=./roster.yml" ,
} , & stdout , & stderr , Options {
Catalog : fakeCatalog ( t , pipeline . ModuleSpec {
Key : "fake/extract" ,
Stage : pipeline . StageExtract ,
Requires : [ ] string { "chunks" } ,
Provides : [ ] string { "artifact" } ,
ReferenceSlots : [ ] contracts . ReferenceSlot {
{ Name : "roster" } ,
} ,
} ) ,
} )
if code != 1 {
t . Fatalf ( "RunWithOptions() code = %d, want 1" , code )
}
2026-07-05 16:31:23 +00:00
if ! strings . Contains ( stderr . String ( ) , "multiple selected targets" ) || ! strings . Contains ( stderr . String ( ) , "events.extract.roster" ) || ! strings . Contains ( stderr . String ( ) , "notes.extract.roster" ) {
2026-07-05 14:27:23 +00:00
t . Fatalf ( "stderr = %q, want ambiguous reference error" , stderr . String ( ) )
}
}
2026-07-05 16:31:23 +00:00
func TestRunPipelineReferenceFlagRejectsAmbiguousFlatSlotAcrossChunkAndExtract ( t * testing . T ) {
configPath := writeTestConfig ( t , testConfigYAML ( "example" , "events" ) )
inputPath := writeSeriatimInput ( t )
diagnosticsDir := t . TempDir ( )
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( [ ] string {
"run" , "example" ,
"--config" , configPath ,
"--input" , inputPath ,
"--diagnostics-dir" , diagnosticsDir ,
"--reference" , "context=./context.md" ,
} , & stdout , & stderr , Options {
Catalog : fakeCatalog ( t ,
pipeline . ModuleSpec {
Key : "generic" ,
Stage : pipeline . StageChunk ,
Requires : [ ] string { "source" } ,
Provides : [ ] string { "chunks" } ,
ReferenceSlots : [ ] contracts . ReferenceSlot {
{ Name : "context" } ,
} ,
} ,
pipeline . ModuleSpec {
Key : "fake/extract" ,
Stage : pipeline . StageExtract ,
Requires : [ ] string { "chunks" } ,
Provides : [ ] string { "artifact" } ,
ReferenceSlots : [ ] contracts . ReferenceSlot {
{ Name : "context" } ,
} ,
} ,
) ,
} )
if code != 1 {
t . Fatalf ( "RunWithOptions() code = %d, want 1" , code )
}
if ! strings . Contains ( stderr . String ( ) , "multiple selected targets" ) || ! strings . Contains ( stderr . String ( ) , "chunk.context" ) || ! strings . Contains ( stderr . String ( ) , "events.extract.context" ) {
t . Fatalf ( "stderr = %q, want ambiguous chunk/extract reference error" , stderr . String ( ) )
}
}
func TestRunPipelineReferenceFlagRejectsAmbiguousLaneSlotAcrossExtractAndNormalize ( t * testing . T ) {
configPath := writeTestConfig ( t , testConfigYAML ( "example" , "events" ) )
inputPath := writeSeriatimInput ( t )
diagnosticsDir := t . TempDir ( )
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( [ ] string {
"run" , "example" ,
"--config" , configPath ,
"--input" , inputPath ,
"--diagnostics-dir" , diagnosticsDir ,
"--reference" , "events.context=./context.md" ,
} , & stdout , & stderr , Options {
Catalog : fakeCatalog ( t ,
pipeline . ModuleSpec {
Key : "fake/extract" ,
Stage : pipeline . StageExtract ,
Requires : [ ] string { "chunks" } ,
Provides : [ ] string { "artifact" } ,
ReferenceSlots : [ ] contracts . ReferenceSlot {
{ Name : "context" } ,
} ,
} ,
pipeline . ModuleSpec {
Key : "noop" ,
Stage : pipeline . StageNormalize ,
Requires : [ ] string { "merged" } ,
Provides : [ ] string { "normalized" } ,
ReferenceSlots : [ ] contracts . ReferenceSlot {
{ Name : "context" } ,
} ,
} ,
) ,
} )
if code != 1 {
t . Fatalf ( "RunWithOptions() code = %d, want 1" , code )
}
if ! strings . Contains ( stderr . String ( ) , "multiple selected targets in lane" ) || ! strings . Contains ( stderr . String ( ) , "events.extract.context" ) || ! strings . Contains ( stderr . String ( ) , "events.normalize.context" ) {
t . Fatalf ( "stderr = %q, want ambiguous lane reference error" , stderr . String ( ) )
}
}
2026-07-05 14:27:23 +00:00
func TestRunPipelineReferenceFlagsRejectMalformedValues ( t * testing . T ) {
tests := [ ] struct {
name string
args [ ] string
want string
} {
{ name : "missing equals" , args : [ ] string { "--reference" , "roster" } , want : "slot=path" } ,
{ name : "empty path" , args : [ ] string { "--reference" , "roster=" } , want : "path must not be empty" } ,
{ name : "empty slot" , args : [ ] string { "--reference" , "=./roster.yml" } , want : "slot must not be empty" } ,
2026-07-07 19:10:55 +00:00
{ name : "unsupported explicit stage" , args : [ ] string { "--reference" , "a.b.c=./roster.yml" } , want : "lane.extract.slot, lane.merge.slot, or lane.normalize.slot" } ,
{ name : "too many selector parts" , args : [ ] string { "--reference" , "a.b.c.d=./roster.yml" } , want : "slot, chunk.slot, merge.slot, lane.slot, lane.extract.slot, lane.merge.slot, or lane.normalize.slot" } ,
2026-07-05 16:31:23 +00:00
{ name : "unbind with equals" , args : [ ] string { "--without-reference" , "roster=./roster.yml" } , want : "without =path" } ,
2026-07-05 14:27:23 +00:00
}
for _ , test := range tests {
t . Run ( test . name , func ( t * testing . T ) {
configPath := writeTestConfig ( t , testConfigYAML ( "example" , "events" ) )
inputPath := writeSeriatimInput ( t )
var stdout bytes . Buffer
var stderr bytes . Buffer
args := [ ] string { "run" , "example" , "--config" , configPath , "--input" , inputPath }
args = append ( args , test . args ... )
code := RunWithOptions ( args , & stdout , & stderr , Options { Catalog : fakeCatalog ( t ) } )
if code != 2 {
t . Fatalf ( "RunWithOptions() code = %d, want 2" , code )
}
if ! strings . Contains ( stderr . String ( ) , test . want ) {
t . Fatalf ( "stderr = %q, want substring %q" , stderr . String ( ) , test . want )
}
} )
}
}
2026-07-05 16:31:23 +00:00
func TestRunPipelineWithoutReferenceRemovesConfigBindingsForEligibleTargets ( t * testing . T ) {
configPath := writeTestConfig ( t , testConfigYAMLWithPipelineReferences ( "example" , "events" , map [ string ] string {
2026-07-07 19:10:55 +00:00
"context" : "./config-context.md" ,
"merge_notes" : "./config-merge.md" ,
"notes" : "./config-notes.md" ,
"roster" : "./config-roster.yml" ,
2026-07-05 16:31:23 +00:00
} ) )
inputPath := filepath . Join ( t . TempDir ( ) , "missing.json" )
diagnosticsDir := t . TempDir ( )
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( [ ] string {
"run" , "example" ,
"--config" , configPath ,
"--input" , inputPath ,
"--diagnostics-dir" , diagnosticsDir ,
"--without-reference" , "chunk.context" ,
"--without-reference" , "events.extract.roster" ,
2026-07-07 19:10:55 +00:00
"--without-reference" , "events.merge.merge_notes" ,
2026-07-05 16:31:23 +00:00
"--without-reference" , "events.normalize.notes" ,
} , & stdout , & stderr , Options {
Catalog : fakeCatalog ( t ,
pipeline . ModuleSpec {
Key : "generic" ,
Stage : pipeline . StageChunk ,
Requires : [ ] string { "source" } ,
Provides : [ ] string { "chunks" } ,
ReferenceSlots : [ ] contracts . ReferenceSlot {
{ Name : "context" } ,
} ,
} ,
2026-07-07 19:10:55 +00:00
pipeline . ModuleSpec {
Key : "appendorder" ,
Stage : pipeline . StageMerge ,
Requires : [ ] string { "artifact" } ,
Provides : [ ] string { "merged" } ,
ReferenceSlots : [ ] contracts . ReferenceSlot {
{ Name : "merge_notes" } ,
} ,
} ,
2026-07-05 16:31:23 +00:00
pipeline . ModuleSpec {
Key : "fake/extract" ,
Stage : pipeline . StageExtract ,
Requires : [ ] string { "chunks" } ,
Provides : [ ] string { "artifact" } ,
ReferenceSlots : [ ] contracts . ReferenceSlot {
{ Name : "roster" } ,
} ,
} ,
pipeline . ModuleSpec {
Key : "noop" ,
Stage : pipeline . StageNormalize ,
Requires : [ ] string { "merged" } ,
Provides : [ ] string { "normalized" } ,
ReferenceSlots : [ ] contracts . ReferenceSlot {
{ Name : "notes" } ,
} ,
} ,
) ,
} )
if code != 1 || ! strings . Contains ( stderr . String ( ) , "read input" ) {
t . Fatalf ( "RunWithOptions() code = %d stderr=%q, want read input failure after resolution" , code , stderr . String ( ) )
}
resolved := readResolvedPipeline ( t , diagnosticsDir )
if refs := resolved . ChunkReferences . Bindings ; len ( refs ) != 0 {
t . Fatalf ( "chunk references = %#v, want none" , refs )
}
if refs := resolved . ArtifactLanes [ 0 ] . ExtractReferences . Bindings ; len ( refs ) != 0 {
t . Fatalf ( "extract references = %#v, want none" , refs )
}
2026-07-07 19:10:55 +00:00
if refs := resolved . ArtifactLanes [ 0 ] . MergeReferences . Bindings ; len ( refs ) != 0 {
t . Fatalf ( "merge references = %#v, want none" , refs )
}
2026-07-05 16:31:23 +00:00
if refs := resolved . ArtifactLanes [ 0 ] . NormalizeReferences . Bindings ; len ( refs ) != 0 {
t . Fatalf ( "normalize references = %#v, want none" , refs )
}
}
2026-07-05 14:27:23 +00:00
func TestRunPipelineWithoutReferenceRemovesOptionalConfigBinding ( t * testing . T ) {
configPath := writeTestConfig ( t , testConfigYAMLWithReferences ( "example" , "events" , map [ string ] string { "roster" : "./config-roster.yml" } ) )
inputPath := filepath . Join ( t . TempDir ( ) , "missing.json" )
diagnosticsDir := t . TempDir ( )
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( [ ] string {
"run" , "example" ,
"--config" , configPath ,
"--input" , inputPath ,
"--diagnostics-dir" , diagnosticsDir ,
"--without-reference" , "roster" ,
} , & stdout , & stderr , Options {
Catalog : fakeCatalog ( t , pipeline . ModuleSpec {
Key : "fake/extract" ,
Stage : pipeline . StageExtract ,
Requires : [ ] string { "chunks" } ,
Provides : [ ] string { "artifact" } ,
ReferenceSlots : [ ] contracts . ReferenceSlot {
{ Name : "roster" } ,
} ,
} ) ,
} )
if code != 1 || ! strings . Contains ( stderr . String ( ) , "read input" ) {
t . Fatalf ( "RunWithOptions() code = %d stderr=%q, want read input failure after resolution" , code , stderr . String ( ) )
}
resolved := readResolvedPipeline ( t , diagnosticsDir )
2026-07-05 16:12:38 +00:00
if refs := resolved . ArtifactLanes [ 0 ] . ExtractReferences . Bindings ; len ( refs ) != 0 {
2026-07-05 14:27:23 +00:00
t . Fatalf ( "references = %#v, want unbound optional slot" , refs )
}
}
2026-07-05 16:31:23 +00:00
func TestRunPipelineWithoutReferenceFailsWhenRequiredChunkSlotWouldBeMissing ( t * testing . T ) {
configPath := writeTestConfig ( t , testConfigYAMLWithPipelineReferences ( "example" , "events" , map [ string ] string { "context" : "./config-context.md" } ) )
inputPath := writeSeriatimInput ( t )
diagnosticsDir := t . TempDir ( )
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( [ ] string {
"run" , "example" ,
"--config" , configPath ,
"--input" , inputPath ,
"--diagnostics-dir" , diagnosticsDir ,
"--without-reference" , "chunk.context" ,
} , & stdout , & stderr , Options {
Catalog : fakeCatalog ( t , pipeline . ModuleSpec {
Key : "generic" ,
Stage : pipeline . StageChunk ,
Requires : [ ] string { "source" } ,
Provides : [ ] string { "chunks" } ,
ReferenceSlots : [ ] contracts . ReferenceSlot {
{ Name : "context" , Required : true } ,
} ,
} ) ,
} )
if code != 1 {
t . Fatalf ( "RunWithOptions() code = %d, want 1" , code )
}
if ! strings . Contains ( stderr . String ( ) , "required reference slot" ) || ! strings . Contains ( stderr . String ( ) , "context" ) {
t . Fatalf ( "stderr = %q, want required chunk reference error" , stderr . String ( ) )
}
}
func TestRunPipelineWithoutReferenceFailsWhenRequiredNormalizeSlotWouldBeMissing ( t * testing . T ) {
configPath := writeTestConfig ( t , testConfigYAMLWithPipelineReferences ( "example" , "events" , map [ string ] string { "notes" : "./config-notes.md" } ) )
inputPath := writeSeriatimInput ( t )
diagnosticsDir := t . TempDir ( )
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( [ ] string {
"run" , "example" ,
"--config" , configPath ,
"--input" , inputPath ,
"--diagnostics-dir" , diagnosticsDir ,
"--without-reference" , "events.normalize.notes" ,
} , & stdout , & stderr , Options {
Catalog : fakeCatalog ( t , pipeline . ModuleSpec {
Key : "noop" ,
Stage : pipeline . StageNormalize ,
Requires : [ ] string { "merged" } ,
Provides : [ ] string { "normalized" } ,
ReferenceSlots : [ ] contracts . ReferenceSlot {
{ Name : "notes" , Required : true } ,
} ,
} ) ,
} )
if code != 1 {
t . Fatalf ( "RunWithOptions() code = %d, want 1" , code )
}
if ! strings . Contains ( stderr . String ( ) , "required reference slot" ) || ! strings . Contains ( stderr . String ( ) , "notes" ) {
t . Fatalf ( "stderr = %q, want required normalize reference error" , stderr . String ( ) )
}
}
func TestRunPipelineReferenceFlagRejectsUnselectedLaneSelector ( t * testing . T ) {
configPath := writeTestConfig ( t , testConfigYAML ( "example" , "events" , "notes" ) )
inputPath := writeSeriatimInput ( t )
referencePath := writeFile ( t , "notes.yml" , "Notes\n" )
diagnosticsDir := t . TempDir ( )
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( [ ] string {
"run" , "example" ,
"--config" , configPath ,
"--input" , inputPath ,
"--only" , "events" ,
"--diagnostics-dir" , diagnosticsDir ,
"--reference" , "notes.extract.roster=" + referencePath ,
} , & stdout , & stderr , Options {
Catalog : fakeCatalog ( t , pipeline . ModuleSpec {
Key : "fake/extract" ,
Stage : pipeline . StageExtract ,
Requires : [ ] string { "chunks" } ,
Provides : [ ] string { "artifact" } ,
ReferenceSlots : [ ] contracts . ReferenceSlot {
{ Name : "roster" } ,
} ,
} ) ,
} )
if code != 1 {
t . Fatalf ( "RunWithOptions() code = %d, want 1" , code )
}
if ! strings . Contains ( stderr . String ( ) , ` reference lane "notes" is not selected ` ) {
t . Fatalf ( "stderr = %q, want unselected lane error" , stderr . String ( ) )
}
}
2026-07-05 14:27:23 +00:00
func TestRunPipelineWithoutReferenceFailsWhenRequiredSlotWouldBeMissing ( t * testing . T ) {
configPath := writeTestConfig ( t , testConfigYAMLWithReferences ( "example" , "events" , map [ string ] string { "roster" : "./config-roster.yml" } ) )
inputPath := writeSeriatimInput ( t )
diagnosticsDir := t . TempDir ( )
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( [ ] string {
"run" , "example" ,
"--config" , configPath ,
"--input" , inputPath ,
"--diagnostics-dir" , diagnosticsDir ,
"--without-reference" , "roster" ,
} , & stdout , & stderr , Options {
Catalog : fakeCatalog ( t , pipeline . ModuleSpec {
Key : "fake/extract" ,
Stage : pipeline . StageExtract ,
Requires : [ ] string { "chunks" } ,
Provides : [ ] string { "artifact" } ,
ReferenceSlots : [ ] contracts . ReferenceSlot {
{ Name : "roster" , Required : true } ,
} ,
} ) ,
} )
if code != 1 {
t . Fatalf ( "RunWithOptions() code = %d, want 1" , code )
}
if ! strings . Contains ( stderr . String ( ) , "required reference slot" ) || ! strings . Contains ( stderr . String ( ) , "roster" ) {
t . Fatalf ( "stderr = %q, want required reference error" , stderr . String ( ) )
}
}
2026-07-04 01:11:57 +00:00
func TestRunPipelineWritesDurableOutputFiles ( t * testing . T ) {
diagnosticsDir := t . TempDir ( )
outputDir := t . TempDir ( )
configPath := writeTestConfig ( t , mvpConfigYAMLWithDiagnostics ( "dnd-session" , diagnosticsDir , "always" ) )
inputPath := writeSeriatimInput ( t )
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( [ ] string { "run" , "dnd-session" , "--config" , configPath , "--input" , inputPath , "--output-dir" , outputDir } , & stdout , & stderr , Options {
LLMClientFactory : fakeLLMFactory ( newFakeRunLLMClient ( false ) , nil ) ,
} )
if code != 0 {
t . Fatalf ( "RunWithOptions() code = %d, stderr=%q" , code , stderr . String ( ) )
}
runOutputDir := onlyChildDir ( t , outputDir )
for _ , name := range [ ] string {
"index.json" ,
2026-07-07 19:27:28 +00:00
"lanes/spells.json" ,
2026-07-04 01:11:57 +00:00
"manifest.json" ,
"rejected.json" ,
"warnings.json" ,
} {
if _ , err := os . Stat ( filepath . Join ( runOutputDir , filepath . FromSlash ( name ) ) ) ; err != nil {
t . Fatalf ( "expected output file %q: %v" , name , err )
}
}
if ! strings . Contains ( stdout . String ( ) , runOutputDir ) {
t . Fatalf ( "stdout = %q, want output path %q" , stdout . String ( ) , runOutputDir )
}
assertNoTemporaryFiles ( t , runOutputDir )
}
2026-07-05 14:44:44 +00:00
func TestRunPipelineReferenceBytesProduceDistinctManifests ( t * testing . T ) {
run := func ( t * testing . T , referenceText string ) artifacts . RunManifest {
t . Helper ( )
diagnosticsDir := t . TempDir ( )
outputDir := t . TempDir ( )
configDir := t . TempDir ( )
referencePath := filepath . Join ( configDir , "roster.txt" )
if err := os . WriteFile ( referencePath , [ ] byte ( referenceText ) , 0 o644 ) ; err != nil {
t . Fatalf ( "write reference: %v" , err )
}
configPath := filepath . Join ( configDir , "config.yml" )
if err := os . WriteFile ( configPath , [ ] byte ( testConfigYAMLWithReferencesAndDiagnostics ( "example" , "events" , diagnosticsDir , map [ string ] string { "roster" : "roster.txt" } ) ) , 0 o644 ) ; err != nil {
t . Fatalf ( "write config: %v" , err )
}
inputPath := writeFile ( t , "source.txt" , "source text" )
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( [ ] string { "run" , "example" , "--config" , configPath , "--input" , inputPath , "--output-dir" , outputDir } , & stdout , & stderr , Options {
Catalog : fakeCatalog ( t , pipeline . ModuleSpec {
Key : "fake/extract" ,
Stage : pipeline . StageExtract ,
Requires : [ ] string { "chunks" } ,
Provides : [ ] string { "artifact" } ,
ReferenceSlots : [ ] contracts . ReferenceSlot {
{ Name : "roster" } ,
} ,
} ) ,
Registries : fakeExecutionRegistries ( t ) ,
LLMClientFactory : fakeLLMFactory ( newFakeRunLLMClient ( false ) , nil ) ,
} )
if code != 0 {
t . Fatalf ( "RunWithOptions() code = %d, stderr=%q" , code , stderr . String ( ) )
}
var manifest artifacts . RunManifest
readJSONFile ( t , filepath . Join ( onlyChildDir ( t , outputDir ) , "manifest.json" ) , & manifest )
if len ( manifest . References ) != 1 {
t . Fatalf ( "manifest references = %#v, want one entry" , manifest . References )
}
if ! reflect . DeepEqual ( manifest . SourceDigests , [ ] string { "sha256:source" } ) {
t . Fatalf ( "source digests = %#v, want source-only digest" , manifest . SourceDigests )
}
var resolvedReferences [ ] artifacts . ReferenceProvenance
readJSONFile ( t , filepath . Join ( onlyChildDir ( t , diagnosticsDir ) , diagnostics . ArtifactResolvedReferences ) , & resolvedReferences )
if ! reflect . DeepEqual ( resolvedReferences , manifest . References ) {
t . Fatalf ( "resolved references = %#v, want manifest references %#v" , resolvedReferences , manifest . References )
}
2026-07-05 17:51:36 +00:00
runManifestJSON := string ( readFile ( t , filepath . Join ( onlyChildDir ( t , diagnosticsDir ) , diagnostics . ArtifactRunManifest ) ) )
if strings . Contains ( runManifestJSON , "source text" ) || strings . Contains ( runManifestJSON , referenceText ) {
t . Fatalf ( "run manifest diagnostics contains raw prompt material: %s" , runManifestJSON )
}
2026-07-05 14:44:44 +00:00
resolvedReferenceJSON := string ( readFile ( t , filepath . Join ( onlyChildDir ( t , diagnosticsDir ) , diagnostics . ArtifactResolvedReferences ) ) )
if strings . Contains ( resolvedReferenceJSON , referenceText ) || strings . Contains ( resolvedReferenceJSON , "content" ) {
t . Fatalf ( "resolved references diagnostics contains content: %s" , resolvedReferenceJSON )
}
return manifest
}
first := run ( t , "first roster" )
second := run ( t , "second roster" )
if first . References [ 0 ] . Digest == second . References [ 0 ] . Digest {
t . Fatalf ( "reference digests match for different bytes: %q" , first . References [ 0 ] . Digest )
}
if first . PipelineDigest != second . PipelineDigest {
t . Fatalf ( "pipeline digests differ = %q vs %q, want reference bytes outside pipeline identity" , first . PipelineDigest , second . PipelineDigest )
}
}
2026-07-04 01:11:57 +00:00
func TestRunPipelineRejectsUnsafeOutputFileName ( t * testing . T ) {
diagnosticsDir := t . TempDir ( )
outputDir := t . TempDir ( )
configPath := writeTestConfig ( t , mvpConfigYAMLWithDiagnostics ( "dnd-session" , diagnosticsDir , "always" ) )
inputPath := writeSeriatimInput ( t )
registries := registriesWithOutput ( t , unsafeOutputEncoder { } )
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( [ ] string { "run" , "dnd-session" , "--config" , configPath , "--input" , inputPath , "--output-dir" , outputDir } , & stdout , & stderr , Options {
Registries : registries ,
LLMClientFactory : fakeLLMFactory ( newFakeRunLLMClient ( false ) , nil ) ,
} )
if code != 1 {
t . Fatalf ( "RunWithOptions() code = %d, want 1" , code )
}
if ! strings . Contains ( stderr . String ( ) , "output file name" ) {
t . Fatalf ( "stderr = %q, want unsafe output file error" , stderr . String ( ) )
}
runDir := onlyChildDir ( t , diagnosticsDir )
if got := string ( readFile ( t , filepath . Join ( runDir , diagnostics . ArtifactErrorLog ) ) ) ; ! strings . Contains ( got , "output file name" ) {
t . Fatalf ( "error log = %q, want unsafe output file error" , got )
}
2026-07-03 21:26:05 -05:00
if _ , err := os . Stat ( filepath . Join ( runDir , diagnostics . ArtifactRunManifest ) ) ; err != nil {
t . Fatalf ( "expected diagnostics manifest after unsafe output file failure: %v" , err )
}
}
func TestRunPipelineWritesDiagnosticsArtifactsWhenDurableOutputWriteFails ( t * testing . T ) {
diagnosticsDir := t . TempDir ( )
configPath := writeTestConfig ( t , mvpConfigYAMLWithDiagnostics ( "dnd-session" , diagnosticsDir , "always" ) )
inputPath := writeSeriatimInput ( t )
outputRootFile := writeFile ( t , "not-a-directory" , "occupied" )
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( [ ] string { "run" , "dnd-session" , "--config" , configPath , "--input" , inputPath , "--output-dir" , outputRootFile } , & stdout , & stderr , Options {
LLMClientFactory : fakeLLMFactory ( newFakeRunLLMClient ( false ) , nil ) ,
} )
if code != 1 {
t . Fatalf ( "RunWithOptions() code = %d, want 1" , code )
}
if ! strings . Contains ( stderr . String ( ) , "create output directory" ) {
t . Fatalf ( "stderr = %q, want output directory error" , stderr . String ( ) )
}
runDir := onlyChildDir ( t , diagnosticsDir )
for _ , name := range [ ] string {
diagnostics . ArtifactRunManifest ,
diagnostics . ArtifactWarnings ,
diagnostics . ArtifactRunReport ,
diagnostics . ArtifactErrorLog ,
} {
if _ , err := os . Stat ( filepath . Join ( runDir , name ) ) ; err != nil {
t . Fatalf ( "expected diagnostics artifact %q after durable output write failure: %v" , name , err )
}
}
2026-07-04 01:11:57 +00:00
}
func TestRunPipelineWritesDiagnosticsArtifactsOnSuccess ( t * testing . T ) {
diagnosticsDir := t . TempDir ( )
outputDir := t . TempDir ( )
configPath := writeTestConfig ( t , mvpConfigYAMLWithDiagnostics ( "dnd-session" , diagnosticsDir , "always" ) )
inputPath := writeSeriatimInput ( t )
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( [ ] string { "run" , "dnd-session" , "--config" , configPath , "--input" , inputPath , "--output-dir" , outputDir } , & stdout , & stderr , Options {
LLMClientFactory : fakeLLMFactory ( newFakeRunLLMClient ( false ) , nil ) ,
} )
if code != 0 {
t . Fatalf ( "RunWithOptions() code = %d, stderr=%q" , code , stderr . String ( ) )
}
runDir := onlyChildDir ( t , diagnosticsDir )
for _ , name := range [ ] string {
diagnostics . ArtifactInvocationMetadata ,
diagnostics . ArtifactEffectiveConfig ,
diagnostics . ArtifactResolvedPipeline ,
diagnostics . ArtifactRunManifest ,
diagnostics . ArtifactRunReport ,
diagnostics . ArtifactWarnings ,
} {
if _ , err := os . Stat ( filepath . Join ( runDir , name ) ) ; err != nil {
t . Fatalf ( "expected diagnostics artifact %q: %v" , name , err )
}
}
report := string ( readFile ( t , filepath . Join ( runDir , diagnostics . ArtifactRunReport ) ) )
2026-07-07 18:58:23 +00:00
if ! strings . Contains ( report , ` "output_count": 1 ` ) || ! strings . Contains ( report , ` "validation_status": "approved" ` ) || ! strings . Contains ( report , outputDir ) {
2026-07-04 01:11:57 +00:00
t . Fatalf ( "unexpected run report: %s" , report )
}
}
func TestRunPipelineWritesErrorLogAfterDiagnosticsCreation ( t * testing . T ) {
diagnosticsDir := t . TempDir ( )
configPath := writeTestConfig ( t , mvpConfigYAMLWithDiagnostics ( "dnd-session" , diagnosticsDir , "always" ) )
inputPath := writeSeriatimInput ( t )
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( [ ] string { "run" , "dnd-session" , "--config" , configPath , "--input" , inputPath } , & stdout , & stderr , Options {
LLMClientFactory : fakeLLMFactory ( newFakeRunLLMClient ( false ) , errors . New ( "factory unavailable" ) ) ,
} )
if code != 1 {
t . Fatalf ( "RunWithOptions() code = %d, want 1" , code )
}
runDir := onlyChildDir ( t , diagnosticsDir )
if got := string ( readFile ( t , filepath . Join ( runDir , diagnostics . ArtifactErrorLog ) ) ) ; ! strings . Contains ( got , "factory unavailable" ) {
t . Fatalf ( "error log = %q, want factory error" , got )
}
}
func TestRunPipelineRetentionNeverRemovesSuccessfulWarningFreeDiagnostics ( t * testing . T ) {
diagnosticsDir := t . TempDir ( )
outputDir := t . TempDir ( )
configPath := writeTestConfig ( t , mvpConfigYAMLWithDiagnostics ( "dnd-session" , diagnosticsDir , "never" ) )
inputPath := writeSeriatimInput ( t )
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( [ ] string { "run" , "dnd-session" , "--config" , configPath , "--input" , inputPath , "--output-dir" , outputDir } , & stdout , & stderr , Options {
LLMClientFactory : fakeLLMFactory ( newFakeRunLLMClient ( false ) , nil ) ,
} )
if code != 0 {
t . Fatalf ( "RunWithOptions() code = %d, stderr=%q" , code , stderr . String ( ) )
}
if entries := childDirs ( t , diagnosticsDir ) ; len ( entries ) != 0 {
t . Fatalf ( "diagnostics run dirs = %v, want none" , entries )
}
}
func TestRunPipelineWarningsAreDiagnosedAndReported ( t * testing . T ) {
diagnosticsDir := t . TempDir ( )
outputDir := t . TempDir ( )
configPath := writeTestConfig ( t , mvpConfigYAMLWithDiagnostics ( "dnd-session" , diagnosticsDir , "auto" ) )
inputPath := writeSeriatimInput ( t )
registries := registriesWithOutput ( t , warningOutputEncoder { } )
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( [ ] string { "run" , "dnd-session" , "--config" , configPath , "--input" , inputPath , "--output-dir" , outputDir } , & stdout , & stderr , Options {
Registries : registries ,
LLMClientFactory : fakeLLMFactory ( newFakeRunLLMClient ( false ) , nil ) ,
} )
if code != 0 {
t . Fatalf ( "RunWithOptions() code = %d, stderr=%q" , code , stderr . String ( ) )
}
if ! strings . Contains ( stderr . String ( ) , "1 warning" ) {
t . Fatalf ( "stderr = %q, want warning count" , stderr . String ( ) )
}
runDir := onlyChildDir ( t , diagnosticsDir )
warnings := string ( readFile ( t , filepath . Join ( runDir , diagnostics . ArtifactWarnings ) ) )
if ! strings . Contains ( warnings , "synthetic_warning" ) {
t . Fatalf ( "warnings artifact = %q, want synthetic warning" , warnings )
}
}
func TestRunPipelineDiagnosticsDirFlagOverridesConfig ( t * testing . T ) {
configDiagnosticsDir := t . TempDir ( )
overrideDiagnosticsDir := t . TempDir ( )
outputDir := t . TempDir ( )
configPath := writeTestConfig ( t , mvpConfigYAMLWithDiagnostics ( "dnd-session" , configDiagnosticsDir , "always" ) )
inputPath := writeSeriatimInput ( t )
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( [ ] string { "run" , "dnd-session" , "--config" , configPath , "--input" , inputPath , "--output-dir" , outputDir , "--diagnostics-dir" , overrideDiagnosticsDir } , & stdout , & stderr , Options {
LLMClientFactory : fakeLLMFactory ( newFakeRunLLMClient ( false ) , nil ) ,
} )
if code != 0 {
t . Fatalf ( "RunWithOptions() code = %d, stderr=%q" , code , stderr . String ( ) )
}
if entries := childDirs ( t , configDiagnosticsDir ) ; len ( entries ) != 0 {
t . Fatalf ( "config diagnostics dir entries = %v, want none" , entries )
}
if entries := childDirs ( t , overrideDiagnosticsDir ) ; len ( entries ) != 1 {
t . Fatalf ( "override diagnostics dir entries = %v, want one run dir" , entries )
}
}
2026-07-04 01:15:59 +00:00
func TestExampleFixtureConfigValidateAndPipelinesList ( t * testing . T ) {
configPath := fixturePath ( t , "examples/dnd-spells.config.yml" )
t . Run ( "validate configured pipeline" , func ( t * testing . T ) {
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( [ ] string { "config" , "validate" , "--config" , configPath , "--pipeline" , "dnd-session" } , & stdout , & stderr , Options { } )
if code != 0 {
t . Fatalf ( "RunWithOptions() code = %d, stderr=%q" , code , stderr . String ( ) )
}
if ! strings . Contains ( stdout . String ( ) , "dnd-session" ) {
t . Fatalf ( "stdout = %q, want pipeline ID" , stdout . String ( ) )
}
} )
t . Run ( "list configured pipelines" , func ( t * testing . T ) {
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( [ ] string { "pipelines" , "list" , "--config" , configPath } , & stdout , & stderr , Options { } )
if code != 0 {
t . Fatalf ( "RunWithOptions() code = %d, stderr=%q" , code , stderr . String ( ) )
}
if got , want := stdout . String ( ) , "dnd-session\n" ; got != want {
t . Fatalf ( "stdout = %q, want %q" , got , want )
}
} )
}
func TestExampleFixtureRunWritesExpectedJSON ( t * testing . T ) {
configPath := fixturePath ( t , "examples/dnd-spells.config.yml" )
inputPath := fixturePath ( t , "examples/seriatim-minimal-transcript.json" )
outputDir := t . TempDir ( )
diagnosticsDir := t . TempDir ( )
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( [ ] string { "run" , "dnd-session" , "--config" , configPath , "--input" , inputPath , "--output-dir" , outputDir , "--diagnostics-dir" , diagnosticsDir } , & stdout , & stderr , Options {
LLMClientFactory : fakeLLMFactory ( newFakeRunLLMClient ( false ) , nil ) ,
} )
if code != 0 {
t . Fatalf ( "RunWithOptions() code = %d, stderr=%q" , code , stderr . String ( ) )
}
runOutputDir := onlyChildDir ( t , outputDir )
if ! strings . Contains ( stdout . String ( ) , runOutputDir ) {
t . Fatalf ( "stdout = %q, want output path %q" , stdout . String ( ) , runOutputDir )
}
var manifest artifacts . RunManifest
readJSONFile ( t , filepath . Join ( runOutputDir , "manifest.json" ) , & manifest )
if manifest . PipelineID != "dnd-session" {
t . Fatalf ( "manifest pipeline ID = %q, want dnd-session" , manifest . PipelineID )
}
if manifest . PipelineDigest == "" {
t . Fatal ( "manifest pipeline digest is empty" )
}
if manifest . ValidationStatus != "approved" {
t . Fatalf ( "validation status = %q, want approved" , manifest . ValidationStatus )
}
if len ( manifest . ArtifactLanes ) != 1 {
t . Fatalf ( "artifact lanes = %#v, want one lane" , manifest . ArtifactLanes )
}
extractorMetadata , ok := manifest . ArtifactLanes [ 0 ] . Metadata [ "extractor" ] . ( map [ string ] any )
if ! ok {
t . Fatalf ( "extractor metadata = %#v, want object" , manifest . ArtifactLanes [ 0 ] . Metadata )
}
if extractorMetadata [ "prompt_id" ] != "dnd.spells" || extractorMetadata [ "response_schema_key" ] != "dnd_spells" {
t . Fatalf ( "extractor metadata = %#v, want prompt/schema identifiers" , extractorMetadata )
}
2026-07-07 18:58:23 +00:00
var spellOutput struct {
SpellCasts [ ] struct {
Caster string ` json:"caster" `
Spell string ` json:"spell" `
Effect string ` json:"effect" `
SourceRefs [ ] source . SourceRef ` json:"source_refs" `
} ` json:"spell_casts" `
2026-07-04 01:15:59 +00:00
}
2026-07-07 19:27:28 +00:00
readJSONFile ( t , filepath . Join ( runOutputDir , "lanes" , "spells.json" ) , & spellOutput )
2026-07-07 18:58:23 +00:00
if len ( spellOutput . SpellCasts ) != 1 {
t . Fatalf ( "spell output = %#v, want one spell cast" , spellOutput )
2026-07-04 01:15:59 +00:00
}
2026-07-07 18:58:23 +00:00
payload := spellOutput . SpellCasts [ 0 ]
2026-07-04 01:15:59 +00:00
if payload . Caster != "Aria" || payload . Spell != "Cure Wounds" || payload . Effect == "" {
t . Fatalf ( "payload = %#v, want deterministic spell output" , payload )
}
2026-07-07 18:58:23 +00:00
if len ( payload . SourceRefs ) != 1 {
t . Fatalf ( "source refs = %#v, want one source ref" , payload . SourceRefs )
2026-07-04 01:15:59 +00:00
}
2026-07-07 18:58:23 +00:00
ref := payload . SourceRefs [ 0 ]
2026-07-07 18:34:23 +00:00
if ref . SourceID != "session-alpha" || ref . StartUnitID != 1 || ref . EndUnitID != 1 {
2026-07-04 01:15:59 +00:00
t . Fatalf ( "source ref = %#v, want fixture source ref" , ref )
}
warnings := string ( readFile ( t , filepath . Join ( runOutputDir , "warnings.json" ) ) )
if ! strings . Contains ( warnings , ` "warnings": [] ` ) {
t . Fatalf ( "warnings output = %s, want empty warnings" , warnings )
}
}
2026-07-04 13:08:45 +00:00
func TestExampleFixtureRunWithDNDScenesRecordsChunkerAndWarnings ( t * testing . T ) {
configPath := writeTestConfig ( t , mvpConfigYAMLWithChunk ( "dnd-session" , scenes . Key , "dnd/spells" ) )
inputPath := fixturePath ( t , "examples/seriatim-minimal-transcript.json" )
outputDir := t . TempDir ( )
diagnosticsDir := t . TempDir ( )
client := newSceneRunLLMClient ( "Scene boundary was ambiguous." )
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( [ ] string { "run" , "dnd-session" , "--config" , configPath , "--input" , inputPath , "--output-dir" , outputDir , "--diagnostics-dir" , diagnosticsDir } , & stdout , & stderr , Options {
LLMClientFactory : fakeLLMFactory ( client , nil ) ,
} )
if code != 0 {
t . Fatalf ( "RunWithOptions() code = %d, stderr=%q" , code , stderr . String ( ) )
}
if client . calls != 2 {
t . Fatalf ( "LLM calls = %d, want chunking and extraction calls" , client . calls )
}
if ! strings . Contains ( stderr . String ( ) , "1 warning" ) {
t . Fatalf ( "stderr = %q, want warning count" , stderr . String ( ) )
}
runOutputDir := onlyChildDir ( t , outputDir )
manifestBytes := readFile ( t , filepath . Join ( runOutputDir , "manifest.json" ) )
var manifest artifacts . RunManifest
if err := json . Unmarshal ( manifestBytes , & manifest ) ; err != nil {
t . Fatalf ( "unmarshal manifest: %v" , err )
}
if manifest . Chunker != scenes . Key {
t . Fatalf ( "manifest chunker = %q, want %q" , manifest . Chunker , scenes . Key )
}
2026-07-05 13:23:56 +00:00
chunkerMetadata := manifest . ModuleMetadata [ "chunker" ]
if chunkerMetadata == nil {
t . Fatalf ( "module metadata chunker = %#v, want object" , manifest . ModuleMetadata [ "chunker" ] )
}
wantMetadataKeys := [ ] string {
"prompt_id" ,
"prompt_version" ,
"prompt_sha256" ,
"response_schema_key" ,
"response_schema_id" ,
"response_schema_name" ,
"response_schema_version" ,
"response_schema_sha256" ,
}
if len ( chunkerMetadata ) != len ( wantMetadataKeys ) {
t . Fatalf ( "chunker metadata keys = %#v, want %d keys" , chunkerMetadata , len ( wantMetadataKeys ) )
}
for _ , key := range wantMetadataKeys {
value , ok := chunkerMetadata [ key ]
if ! ok {
t . Fatalf ( "chunker metadata missing key %q: %#v" , key , chunkerMetadata )
}
if _ , ok := value . ( string ) ; ! ok {
t . Fatalf ( "chunker metadata[%q] = %#v, want string" , key , value )
}
}
for _ , forbidden := range [ ] string { "prompt" , "schema" , "source" , "text" , "payload" , "api_key" , "secret" , "token" } {
if _ , ok := chunkerMetadata [ forbidden ] ; ok {
t . Fatalf ( "chunker metadata leaked forbidden key %q: %#v" , forbidden , chunkerMetadata )
}
}
2026-07-04 13:08:45 +00:00
for _ , forbidden := range [ ] string { "Source document ID:" , "Aria casts Cure Wounds." , "spell_casts" , "Scene boundary was ambiguous." } {
if strings . Contains ( string ( manifestBytes ) , forbidden ) {
t . Fatalf ( "manifest leaked %q: %s" , forbidden , manifestBytes )
}
}
warnings := string ( readFile ( t , filepath . Join ( runOutputDir , "warnings.json" ) ) )
if ! strings . Contains ( warnings , "scene_boundary_caveat" ) || ! strings . Contains ( warnings , "Scene boundary was ambiguous." ) {
t . Fatalf ( "warnings output = %s, want scene boundary caveat" , warnings )
}
}
2026-07-04 01:15:59 +00:00
func TestExampleFixtureRunOnlySpells ( t * testing . T ) {
configPath := fixturePath ( t , "examples/dnd-spells.config.yml" )
inputPath := fixturePath ( t , "examples/seriatim-minimal-transcript.json" )
outputDir := t . TempDir ( )
diagnosticsDir := t . TempDir ( )
client := newFakeRunLLMClient ( false )
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( [ ] string { "run" , "dnd-session" , "--config" , configPath , "--input" , inputPath , "--only" , "spells" , "--output-dir" , outputDir , "--diagnostics-dir" , diagnosticsDir } , & stdout , & stderr , Options {
LLMClientFactory : fakeLLMFactory ( client , nil ) ,
} )
if code != 0 {
t . Fatalf ( "RunWithOptions() code = %d, stderr=%q" , code , stderr . String ( ) )
}
if client . calls != 1 {
t . Fatalf ( "LLM calls = %d, want selected lane once" , client . calls )
}
if _ , err := os . Stat ( filepath . Join ( onlyChildDir ( t , outputDir ) , "manifest.json" ) ) ; err != nil {
t . Fatalf ( "expected manifest output: %v" , err )
}
}
func TestExampleFixtureFailureCoverage ( t * testing . T ) {
configPath := fixturePath ( t , "examples/dnd-spells.config.yml" )
inputPath := fixturePath ( t , "examples/seriatim-minimal-transcript.json" )
tests := [ ] struct {
name string
args [ ] string
factory LLMClientFactory
wantCode int
wantStderr string
wantOutputStatus string
} {
{
name : "missing config" ,
args : [ ] string { "config" , "validate" , "--config" , filepath . Join ( filepath . Dir ( configPath ) , "missing.yml" ) , "--pipeline" , "dnd-session" } ,
wantCode : 1 ,
wantStderr : "config file" ,
} ,
{
name : "unknown pipeline" ,
args : [ ] string { "run" , "missing" , "--config" , configPath , "--input" , inputPath } ,
factory : fakeLLMFactory ( newFakeRunLLMClient ( false ) , nil ) ,
wantCode : 1 ,
wantStderr : "not configured" ,
} ,
{
name : "invalid Seriatim input" ,
args : [ ] string { "run" , "dnd-session" , "--config" , configPath , "--input" , fixturePath ( t , "internal/cli/testdata/invalid-seriatim-empty-segments.json" ) } ,
factory : fakeLLMFactory ( newFakeRunLLMClient ( false ) , nil ) ,
wantCode : 1 ,
wantStderr : "segments must not be empty" ,
} ,
{
name : "invalid only lane" ,
args : [ ] string { "run" , "dnd-session" , "--config" , configPath , "--input" , inputPath , "--only" , "missing" } ,
factory : fakeLLMFactory ( newFakeRunLLMClient ( false ) , nil ) ,
wantCode : 1 ,
wantStderr : "selected artifact lane" ,
} ,
{
name : "fake LLM failure" ,
args : [ ] string { "run" , "dnd-session" , "--config" , configPath , "--input" , inputPath } ,
factory : fakeLLMFactory ( newErrorRunLLMClient ( errors . New ( "completion unavailable" ) ) , nil ) ,
wantCode : 1 ,
wantStderr : "completion unavailable" ,
} ,
{
2026-07-07 19:23:35 +00:00
name : "malformed LLM response carried as raw output" ,
args : [ ] string { "run" , "dnd-session" , "--config" , configPath , "--input" , inputPath } ,
factory : fakeLLMFactory ( newMalformedRunLLMClient ( ) , nil ) ,
wantCode : 0 ,
wantOutputStatus : "approved" ,
2026-07-04 01:15:59 +00:00
} ,
{
2026-07-07 18:58:23 +00:00
name : "invalid source reference raw output" ,
2026-07-04 01:15:59 +00:00
args : [ ] string { "run" , "dnd-session" , "--config" , configPath , "--input" , inputPath } ,
factory : fakeLLMFactory ( newFakeRunLLMClient ( true ) , nil ) ,
wantCode : 0 ,
2026-07-07 18:58:23 +00:00
wantOutputStatus : "approved" ,
2026-07-04 01:15:59 +00:00
} ,
}
for _ , test := range tests {
t . Run ( test . name , func ( t * testing . T ) {
outputDir := t . TempDir ( )
diagnosticsDir := t . TempDir ( )
args := append ( [ ] string ( nil ) , test . args ... )
if args [ 0 ] == "run" {
args = append ( args , "--output-dir" , outputDir , "--diagnostics-dir" , diagnosticsDir )
}
factory := test . factory
if factory == nil {
factory = fakeLLMFactory ( newFakeRunLLMClient ( false ) , nil )
}
var stdout bytes . Buffer
var stderr bytes . Buffer
code := RunWithOptions ( args , & stdout , & stderr , Options { LLMClientFactory : factory } )
if code != test . wantCode {
t . Fatalf ( "RunWithOptions() code = %d, want %d; stderr=%q" , code , test . wantCode , stderr . String ( ) )
}
if test . wantStderr != "" && ! strings . Contains ( stderr . String ( ) , test . wantStderr ) {
t . Fatalf ( "stderr = %q, want substring %q" , stderr . String ( ) , test . wantStderr )
}
if test . wantOutputStatus != "" {
runOutputDir := onlyChildDir ( t , outputDir )
var manifest artifacts . RunManifest
readJSONFile ( t , filepath . Join ( runOutputDir , "manifest.json" ) , & manifest )
if manifest . ValidationStatus != test . wantOutputStatus {
t . Fatalf ( "validation status = %q, want %q" , manifest . ValidationStatus , test . wantOutputStatus )
}
}
} )
}
}
2026-07-03 17:01:24 +00:00
func writeTestConfig ( t * testing . T , content string ) string {
t . Helper ( )
return writeFile ( t , "config.yml" , content )
}
func writeFile ( t * testing . T , name string , content string ) string {
t . Helper ( )
path := filepath . Join ( t . TempDir ( ) , name )
if err := os . WriteFile ( path , [ ] byte ( content ) , 0 o644 ) ; err != nil {
t . Fatalf ( "write %s: %v" , name , err )
}
return path
}
2026-07-04 01:15:59 +00:00
func fixturePath ( t * testing . T , name string ) string {
t . Helper ( )
path := filepath . Join ( ".." , ".." , filepath . FromSlash ( name ) )
if _ , err := os . Stat ( path ) ; err != nil {
t . Fatalf ( "fixture %q is not available at %q: %v" , name , path , err )
}
return path
}
2026-07-03 17:01:24 +00:00
func testConfigYAML ( pipelineID string , laneIDs ... string ) string {
return testConfigYAMLForPipelines ( map [ string ] [ ] string { pipelineID : laneIDs } )
}
func testConfigYAMLForPipelines ( pipelines map [ string ] [ ] string ) string {
var b strings . Builder
2026-07-05 18:03:23 +00:00
b . WriteString ( "version: 2\n" )
2026-07-03 17:01:24 +00:00
b . WriteString ( "pipelines:\n" )
for pipelineID , laneIDs := range pipelines {
b . WriteString ( " " + pipelineID + ":\n" )
b . WriteString ( " input: fake/input\n" )
b . WriteString ( " artifacts:\n" )
for _ , laneID := range laneIDs {
b . WriteString ( " " + laneID + ":\n" )
b . WriteString ( " extract: fake/extract\n" )
}
}
return b . String ( )
}
2026-07-05 14:27:23 +00:00
func testConfigYAMLWithReferences ( pipelineID string , laneID string , references map [ string ] string ) string {
var b strings . Builder
2026-07-05 18:03:23 +00:00
b . WriteString ( "version: 2\n" )
2026-07-05 14:27:23 +00:00
b . WriteString ( "pipelines:\n" )
b . WriteString ( " " + pipelineID + ":\n" )
b . WriteString ( " input: fake/input\n" )
b . WriteString ( " artifacts:\n" )
b . WriteString ( " " + laneID + ":\n" )
b . WriteString ( " extract: fake/extract\n" )
b . WriteString ( " references:\n" )
keys := make ( [ ] string , 0 , len ( references ) )
for key := range references {
keys = append ( keys , key )
}
sort . Strings ( keys )
for _ , key := range keys {
b . WriteString ( " " + key + ": " + references [ key ] + "\n" )
}
return b . String ( )
}
2026-07-05 16:31:23 +00:00
func testConfigYAMLWithPipelineReferences ( pipelineID string , laneID string , references map [ string ] string ) string {
var b strings . Builder
2026-07-05 18:03:23 +00:00
b . WriteString ( "version: 2\n" )
2026-07-05 16:31:23 +00:00
b . WriteString ( "pipelines:\n" )
b . WriteString ( " " + pipelineID + ":\n" )
b . WriteString ( " input: fake/input\n" )
b . WriteString ( " references:\n" )
keys := make ( [ ] string , 0 , len ( references ) )
for key := range references {
keys = append ( keys , key )
}
sort . Strings ( keys )
for _ , key := range keys {
b . WriteString ( " " + key + ": " + references [ key ] + "\n" )
}
b . WriteString ( " artifacts:\n" )
b . WriteString ( " " + laneID + ":\n" )
b . WriteString ( " extract: fake/extract\n" )
return b . String ( )
}
2026-07-05 14:44:44 +00:00
func testConfigYAMLWithReferencesAndDiagnostics ( pipelineID string , laneID string , diagnosticsDir string , references map [ string ] string ) string {
var b strings . Builder
2026-07-05 18:03:23 +00:00
b . WriteString ( "version: 2\n" )
2026-07-05 14:44:44 +00:00
b . WriteString ( "diagnostics:\n" )
b . WriteString ( " work_dir: " + diagnosticsDir + "\n" )
b . WriteString ( " retention: always\n" )
b . WriteString ( "pipelines:\n" )
b . WriteString ( " " + pipelineID + ":\n" )
b . WriteString ( " input: fake/input\n" )
b . WriteString ( " artifacts:\n" )
b . WriteString ( " " + laneID + ":\n" )
b . WriteString ( " extract: fake/extract\n" )
b . WriteString ( " references:\n" )
keys := make ( [ ] string , 0 , len ( references ) )
for key := range references {
keys = append ( keys , key )
}
sort . Strings ( keys )
for _ , key := range keys {
b . WriteString ( " " + key + ": " + references [ key ] + "\n" )
}
return b . String ( )
}
2026-07-04 00:59:32 +00:00
func mvpConfigYAML ( pipelineID string , extractor string ) string {
2026-07-05 18:03:23 +00:00
return ` version : 2
2026-07-04 00:59:32 +00:00
pipelines :
` + pipelineID + ` :
input : seriatim
artifacts :
spells :
extract : ` + extractor + `
`
}
2026-07-04 13:05:39 +00:00
func mvpConfigYAMLWithChunk ( pipelineID string , chunker string , extractor string ) string {
2026-07-05 18:03:23 +00:00
return ` version : 2
2026-07-04 13:05:39 +00:00
pipelines :
` + pipelineID + ` :
input : seriatim
chunk : ` + chunker + `
artifacts :
spells :
extract : ` + extractor + `
`
}
2026-07-04 01:06:04 +00:00
func mvpConfigYAMLForLanes ( pipelineID string , laneIDs ... string ) string {
var b strings . Builder
2026-07-05 18:03:23 +00:00
b . WriteString ( "version: 2\n" )
2026-07-04 01:06:04 +00:00
b . WriteString ( "pipelines:\n" )
b . WriteString ( " " + pipelineID + ":\n" )
b . WriteString ( " input: seriatim\n" )
b . WriteString ( " artifacts:\n" )
for _ , laneID := range laneIDs {
b . WriteString ( " " + laneID + ":\n" )
b . WriteString ( " extract: dnd/spells\n" )
}
return b . String ( )
}
2026-07-05 18:03:23 +00:00
func mvpConfigYAMLWithProfileFile ( pipelineID string , profileFile string ) string {
return ` version : 2
scriptorium :
profile_file : ` + profileFile + `
2026-07-04 01:06:04 +00:00
pipelines :
` + pipelineID + ` :
input : seriatim
artifacts :
spells :
extract : dnd / spells
`
}
2026-07-05 18:03:23 +00:00
func writeScriptoriumProfileFile ( t * testing . T , id string , endpoint string , model string ) string {
t . Helper ( )
return writeFile ( t , id + ".profile.yml" , ` id: ` + id + `
endpoint : ` +endpoint+ `
model : ` +model+ `
` )
}
2026-07-04 01:11:57 +00:00
func mvpConfigYAMLWithDiagnostics ( pipelineID , diagnosticsDir , retention string ) string {
2026-07-05 18:03:23 +00:00
return ` version : 2
2026-07-04 01:11:57 +00:00
diagnostics :
work_dir : ` + diagnosticsDir + `
retention : ` + retention + `
pipelines :
` + pipelineID + ` :
input : seriatim
artifacts :
spells :
extract : dnd / spells
`
}
2026-07-04 01:06:04 +00:00
func writeSeriatimInput ( t * testing . T ) string {
t . Helper ( )
return writeFile ( t , "source.json" , ` {
"metadata" : {
"id" : "session-alpha"
} ,
"segments" : [
{
2026-07-07 18:34:23 +00:00
"id" : 1 ,
2026-07-04 01:06:04 +00:00
"start" : 0 ,
"end" : 1 ,
"speaker" : "Aria" ,
"text" : "Aria casts Cure Wounds."
}
]
} ` )
}
type fakeRunLLMClient struct {
invalidSourceRef bool
calls int
2026-07-04 01:15:59 +00:00
err error
payload map [ string ] any
2026-07-04 13:08:45 +00:00
sceneCaveat string
2026-07-04 01:06:04 +00:00
}
func newFakeRunLLMClient ( invalidSourceRef bool ) * fakeRunLLMClient {
return & fakeRunLLMClient { invalidSourceRef : invalidSourceRef }
}
2026-07-04 01:15:59 +00:00
func newErrorRunLLMClient ( err error ) * fakeRunLLMClient {
return & fakeRunLLMClient { err : err }
}
func newMalformedRunLLMClient ( ) * fakeRunLLMClient {
return & fakeRunLLMClient { payload : map [ string ] any { } }
}
2026-07-04 13:08:45 +00:00
func newSceneRunLLMClient ( caveat string ) * fakeRunLLMClient {
return & fakeRunLLMClient { sceneCaveat : caveat }
}
2026-07-04 01:06:04 +00:00
func ( client * fakeRunLLMClient ) CompleteStructured ( ctx context . Context , req contracts . StructuredCompletionRequest , out any ) ( contracts . StructuredCompletionResponse , error ) {
client . calls ++
2026-07-04 01:15:59 +00:00
if client . err != nil {
return contracts . StructuredCompletionResponse { } , client . err
}
2026-07-04 13:08:45 +00:00
if req . StageName == scenes . Key && client . payload == nil {
payload := map [ string ] any {
"scenes" : [ ] map [ string ] any {
{
2026-07-07 18:34:23 +00:00
"start_unit_id" : 1 ,
"end_unit_id" : 2 ,
2026-07-04 13:08:45 +00:00
"short_title" : "Opening spell" ,
"primary_mode" : "Narrative" ,
"main_participants" : [ ] string { "Aria" } ,
"summary" : "Aria casts a spell." ,
"boundary_note" : "The provided source units form one scene." ,
"boundary_confidence" : "High" ,
} ,
} ,
"boundary_caveats" : [ ] string { } ,
}
if client . sceneCaveat != "" {
payload [ "boundary_caveats" ] = [ ] string { client . sceneCaveat }
}
encoded , err := json . Marshal ( payload )
if err != nil {
return contracts . StructuredCompletionResponse { } , err
}
if err := json . Unmarshal ( encoded , out ) ; err != nil {
return contracts . StructuredCompletionResponse { } , err
}
return contracts . StructuredCompletionResponse { Content : encoded } , nil
}
2026-07-07 18:34:23 +00:00
startUnitID := 1
2026-07-04 01:06:04 +00:00
if client . invalidSourceRef {
2026-07-07 18:34:23 +00:00
startUnitID = 999
2026-07-04 01:06:04 +00:00
}
2026-07-04 01:15:59 +00:00
payload := client . payload
if payload == nil {
payload = map [ string ] any {
"spell_casts" : [ ] map [ string ] any {
{
"caster" : "Aria" ,
"spell" : "Cure Wounds" ,
"effect" : "Heals a wounded ally." ,
"narrative_description" : "Aria casts Cure Wounds." ,
2026-07-07 18:34:23 +00:00
"source_refs" : [ ] map [ string ] any {
2026-07-04 01:15:59 +00:00
{
"source_id" : "session-alpha" ,
"start_unit_id" : startUnitID ,
2026-07-07 18:34:23 +00:00
"end_unit_id" : 1 ,
2026-07-04 01:15:59 +00:00
} ,
2026-07-04 01:06:04 +00:00
} ,
} ,
} ,
2026-07-04 01:15:59 +00:00
}
2026-07-04 01:06:04 +00:00
}
encoded , err := json . Marshal ( payload )
if err != nil {
return contracts . StructuredCompletionResponse { } , err
}
if err := json . Unmarshal ( encoded , out ) ; err != nil {
return contracts . StructuredCompletionResponse { } , err
}
return contracts . StructuredCompletionResponse { Content : encoded } , nil
}
func fakeLLMFactory ( client contracts . StructuredLLMClient , err error ) LLMClientFactory {
return func ( ctx context . Context , cfg config . Config , profileID string ) ( contracts . StructuredLLMClient , [ ] artifacts . LLMProfileManifest , error ) {
if err != nil {
return nil , nil , err
}
return client , [ ] artifacts . LLMProfileManifest { { ID : strings . TrimSpace ( profileID ) } } , nil
}
}
type recordingLLMFactory struct {
client contracts . StructuredLLMClient
profileIDs [ ] string
}
func ( factory * recordingLLMFactory ) build ( ctx context . Context , cfg config . Config , profileID string ) ( contracts . StructuredLLMClient , [ ] artifacts . LLMProfileManifest , error ) {
factory . profileIDs = append ( factory . profileIDs , strings . TrimSpace ( profileID ) )
return factory . client , [ ] artifacts . LLMProfileManifest { { ID : strings . TrimSpace ( profileID ) } } , nil
}
2026-07-04 01:11:57 +00:00
type unsafeOutputEncoder struct { }
func ( unsafeOutputEncoder ) Key ( ) string {
return "json"
}
func ( unsafeOutputEncoder ) Encode ( ctx context . Context , req contracts . OutputRequest ) ( contracts . OutputResult , error ) {
return contracts . OutputResult {
Files : [ ] contracts . OutputFile {
{
Name : "../escape.json" ,
ContentType : "application/json" ,
Bytes : [ ] byte ( "{}\n" ) ,
} ,
} ,
} , nil
}
type warningOutputEncoder struct { }
func ( warningOutputEncoder ) Key ( ) string {
return "json"
}
func ( warningOutputEncoder ) Encode ( ctx context . Context , req contracts . OutputRequest ) ( contracts . OutputResult , error ) {
result , err := jsonoutput . New ( ) . Encode ( ctx , req )
if err != nil {
return contracts . OutputResult { } , err
}
result . Warnings = append ( result . Warnings , contracts . Warning {
Scope : "output" ,
ReasonCode : "synthetic_warning" ,
Message : "synthetic output warning" ,
} )
return result , nil
}
func registriesWithOutput ( t * testing . T , encoder contracts . OutputEncoder ) pipeline . Registries {
t . Helper ( )
registries , err := productionRegistries ( )
if err != nil {
t . Fatalf ( "productionRegistries: %v" , err )
}
outputs := pipeline . NewOutputEncoderRegistry ( )
if err := outputs . RegisterWithSpec ( jsonoutput . ModuleSpec ( ) , func ( ) ( contracts . OutputEncoder , error ) {
return encoder , nil
} ) ; err != nil {
t . Fatalf ( "register test output encoder: %v" , err )
}
registries . Outputs = outputs
return registries
}
2026-07-05 14:44:44 +00:00
func fakeExecutionRegistries ( t * testing . T ) pipeline . Registries {
t . Helper ( )
inputs := pipeline . NewInputAdapterRegistry ( )
chunkers := pipeline . NewChunkerRegistry ( )
extractors := pipeline . NewExtractorRegistry ( )
mergers := pipeline . NewMergerRegistry ( )
normalizers := pipeline . NewNormalizerRegistry ( )
outputs := pipeline . NewOutputEncoderRegistry ( )
if err := inputs . RegisterWithSpec ( pipeline . ModuleSpec { Key : "fake/input" , Stage : pipeline . StageInput , Provides : [ ] string { "source" } } , func ( ) ( contracts . InputAdapter , error ) {
return fakeRunInputAdapter { } , nil
} ) ; err != nil {
t . Fatalf ( "register fake input: %v" , err )
}
if err := chunkers . RegisterWithSpec ( pipeline . ModuleSpec { Key : "generic" , Stage : pipeline . StageChunk , Requires : [ ] string { "source" } , Provides : [ ] string { "chunks" } } , func ( ) ( contracts . Chunker , error ) {
return fakeRunChunker { } , nil
} ) ; err != nil {
t . Fatalf ( "register fake chunker: %v" , err )
}
if err := extractors . RegisterWithSpec ( pipeline . ModuleSpec {
Key : "fake/extract" ,
Stage : pipeline . StageExtract ,
Requires : [ ] string { "chunks" } ,
Provides : [ ] string { "artifact" } ,
ReferenceSlots : [ ] contracts . ReferenceSlot {
{ Name : "roster" } ,
} ,
} , func ( ) ( contracts . Extractor , error ) {
return fakeRunExtractor { } , nil
} ) ; err != nil {
t . Fatalf ( "register fake extractor: %v" , err )
}
if err := mergers . RegisterWithSpec ( pipeline . ModuleSpec { Key : "appendorder" , Stage : pipeline . StageMerge , Requires : [ ] string { "artifact" } , Provides : [ ] string { "merged" } } , func ( ) ( contracts . Merger , error ) {
return fakeRunMerger { } , nil
} ) ; err != nil {
t . Fatalf ( "register fake merger: %v" , err )
}
if err := normalizers . RegisterWithSpec ( pipeline . ModuleSpec { Key : "noop" , Stage : pipeline . StageNormalize , Requires : [ ] string { "merged" } , Provides : [ ] string { "normalized" } } , func ( ) ( contracts . Normalizer , error ) {
return fakeRunNormalizer { } , nil
} ) ; err != nil {
t . Fatalf ( "register fake normalizer: %v" , err )
}
if err := jsonoutput . Register ( outputs ) ; err != nil {
t . Fatalf ( "register json output: %v" , err )
}
return pipeline . Registries {
Inputs : inputs ,
Chunkers : chunkers ,
Extractors : extractors ,
Mergers : mergers ,
Normalizers : normalizers ,
Outputs : outputs ,
}
}
type fakeRunInputAdapter struct { }
func ( fakeRunInputAdapter ) Key ( ) string {
return "fake/input"
}
func ( fakeRunInputAdapter ) Parse ( ctx context . Context , req contracts . ParseRequest ) ( * source . SourceDocument , error ) {
return & source . SourceDocument {
ID : "source" ,
Kind : "text" ,
Format : "test" ,
Digest : "sha256:source" ,
Units : [ ] source . SourceUnit {
2026-07-07 18:34:23 +00:00
{ ID : 1 , Kind : "text" , Text : string ( req . Raw ) } ,
2026-07-05 14:44:44 +00:00
} ,
} , nil
}
type fakeRunChunker struct { }
func ( fakeRunChunker ) Key ( ) string {
return "generic"
}
2026-07-05 16:06:47 +00:00
func ( fakeRunChunker ) ReferenceSlots ( ) [ ] contracts . ReferenceSlot {
return nil
}
2026-07-05 14:44:44 +00:00
func ( fakeRunChunker ) Chunk ( ctx context . Context , req contracts . ChunkRequest ) ( contracts . ChunkResult , error ) {
return contracts . ChunkResult {
Chunks : [ ] contracts . SourceChunk {
2026-07-07 18:34:23 +00:00
{ ID : "chunk-1" , SourceID : req . Source . ID , Index : 0 , StartUnitID : 1 , EndUnitID : 1 , Content : [ ] byte ( ` { "units":[1]} ` ) , MediaType : "application/json" , Units : req . Source . Units } ,
2026-07-05 14:44:44 +00:00
} ,
} , nil
}
type fakeRunExtractor struct { }
func ( fakeRunExtractor ) Key ( ) string {
return "fake/extract"
}
func ( fakeRunExtractor ) ReferenceSlots ( ) [ ] contracts . ReferenceSlot {
return [ ] contracts . ReferenceSlot { { Name : "roster" } }
}
func ( fakeRunExtractor ) Extract ( ctx context . Context , req contracts . ExtractionRequest ) ( contracts . ExtractionResult , error ) {
return contracts . ExtractionResult {
2026-07-07 18:58:23 +00:00
Output : contracts . ExtractOutput {
Schema : contracts . ResponseSchema { ID : "fake.artifact" , Name : "fake_artifact" , Version : "v1" } ,
Payload : contracts . RawPayload {
Content : [ ] byte ( ` { "value":true} ` ) ,
MediaType : "application/json" ,
2026-07-05 14:44:44 +00:00
} ,
} ,
} , nil
}
type fakeRunMerger struct { }
func ( fakeRunMerger ) Key ( ) string {
return "appendorder"
}
func ( fakeRunMerger ) Merge ( ctx context . Context , req contracts . MergeRequest ) ( contracts . MergeResult , error ) {
2026-07-07 18:58:23 +00:00
output := contracts . MergeOutput {
LaneID : req . LaneID ,
SourceID : req . Source . ID ,
Schema : contracts . ResponseSchema { ID : "fake.artifact" , Name : "fake_artifact" , Version : "v1" } ,
Payload : contracts . RawPayload {
Content : [ ] byte ( ` { "merged":true} ` ) ,
MediaType : "application/json" ,
} ,
}
if len ( req . ExtractOutputs ) > 0 {
output . Schema = req . ExtractOutputs [ 0 ] . Schema
output . Payload = req . ExtractOutputs [ 0 ] . Payload
2026-07-05 14:44:44 +00:00
}
2026-07-07 18:58:23 +00:00
return contracts . MergeResult { Output : output } , nil
2026-07-05 14:44:44 +00:00
}
type fakeRunNormalizer struct { }
func ( fakeRunNormalizer ) Key ( ) string {
return "noop"
}
2026-07-05 16:06:47 +00:00
func ( fakeRunNormalizer ) ReferenceSlots ( ) [ ] contracts . ReferenceSlot {
return nil
}
2026-07-05 14:44:44 +00:00
func ( fakeRunNormalizer ) Normalize ( ctx context . Context , req contracts . NormalizeRequest ) ( contracts . NormalizeResult , error ) {
2026-07-07 18:58:23 +00:00
return contracts . NormalizeResult {
Output : contracts . NormalizeOutput {
LaneID : req . LaneID ,
SourceID : req . MergeOutput . SourceID ,
Schema : req . MergeOutput . Schema ,
Payload : req . MergeOutput . Payload ,
} ,
} , nil
2026-07-05 14:44:44 +00:00
}
2026-07-04 01:11:57 +00:00
func onlyChildDir ( t * testing . T , root string ) string {
t . Helper ( )
children := childDirs ( t , root )
if len ( children ) != 1 {
t . Fatalf ( "child dirs under %q = %v, want one" , root , children )
}
return children [ 0 ]
}
func childDirs ( t * testing . T , root string ) [ ] string {
t . Helper ( )
entries , err := os . ReadDir ( root )
if err != nil {
if os . IsNotExist ( err ) {
return nil
}
t . Fatalf ( "read dir %q: %v" , root , err )
}
var dirs [ ] string
for _ , entry := range entries {
if entry . IsDir ( ) {
dirs = append ( dirs , filepath . Join ( root , entry . Name ( ) ) )
}
}
return dirs
}
func readFile ( t * testing . T , path string ) [ ] byte {
t . Helper ( )
data , err := os . ReadFile ( path )
if err != nil {
t . Fatalf ( "read %q: %v" , path , err )
}
return data
}
2026-07-04 01:15:59 +00:00
func readJSONFile ( t * testing . T , path string , out any ) {
t . Helper ( )
if err := json . Unmarshal ( readFile ( t , path ) , out ) ; err != nil {
t . Fatalf ( "unmarshal %q: %v" , path , err )
}
}
2026-07-05 14:27:23 +00:00
func readResolvedPipeline ( t * testing . T , diagnosticsDir string ) pipeline . ResolvedPipeline {
t . Helper ( )
runDir := onlyChildDir ( t , diagnosticsDir )
var resolved pipeline . ResolvedPipeline
readJSONFile ( t , filepath . Join ( runDir , diagnostics . ArtifactResolvedPipeline ) , & resolved )
return resolved
}
func resolvedArtifactLane ( t * testing . T , resolved pipeline . ResolvedPipeline , laneID string ) pipeline . ResolvedArtifactLane {
t . Helper ( )
for _ , lane := range resolved . ArtifactLanes {
if lane . ID == laneID {
return lane
}
}
t . Fatalf ( "lane %q not found in resolved pipeline" , laneID )
return pipeline . ResolvedArtifactLane { }
}
2026-07-04 01:11:57 +00:00
func assertNoTemporaryFiles ( t * testing . T , root string ) {
t . Helper ( )
if err := filepath . WalkDir ( root , func ( path string , entry os . DirEntry , err error ) error {
if err != nil {
return err
}
if strings . Contains ( entry . Name ( ) , ".tmp-" ) {
t . Fatalf ( "temporary file remains after success: %s" , path )
}
return nil
} ) ; err != nil {
t . Fatalf ( "walk output dir %q: %v" , root , err )
}
}
2026-07-05 14:27:23 +00:00
func fakeCatalog ( t * testing . T , overrides ... pipeline . ModuleSpec ) pipeline . ModuleCatalog {
2026-07-03 17:01:24 +00:00
t . Helper ( )
inputs := pipeline . NewInputAdapterRegistry ( )
chunkers := pipeline . NewChunkerRegistry ( )
extractors := pipeline . NewExtractorRegistry ( )
mergers := pipeline . NewMergerRegistry ( )
normalizers := pipeline . NewNormalizerRegistry ( )
validators := pipeline . NewValidatorRegistry ( )
outputs := pipeline . NewOutputEncoderRegistry ( )
2026-07-05 14:27:23 +00:00
specs := map [ string ] pipeline . ModuleSpec {
"fake/input" : { Key : "fake/input" , Stage : pipeline . StageInput , Provides : [ ] string { "source" } } ,
"generic" : { Key : "generic" , Stage : pipeline . StageChunk , Requires : [ ] string { "source" } , Provides : [ ] string { "chunks" } } ,
"fake/extract" : { Key : "fake/extract" , Stage : pipeline . StageExtract , Requires : [ ] string { "chunks" } , Provides : [ ] string { "artifact" } } ,
"appendorder" : { Key : "appendorder" , Stage : pipeline . StageMerge , Requires : [ ] string { "artifact" } , Provides : [ ] string { "merged" } } ,
"noop" : { Key : "noop" , Stage : pipeline . StageNormalize , Requires : [ ] string { "merged" } , Provides : [ ] string { "normalized" } } ,
"json" : { Key : "json" , Stage : pipeline . StageOutput , Requires : [ ] string { "normalized" } } ,
}
for _ , override := range overrides {
specs [ override . Key ] = override
}
mustRegisterInput ( t , inputs , specs [ "fake/input" ] )
mustRegisterChunker ( t , chunkers , specs [ "generic" ] )
mustRegisterExtractor ( t , extractors , specs [ "fake/extract" ] )
mustRegisterMerger ( t , mergers , specs [ "appendorder" ] )
mustRegisterNormalizer ( t , normalizers , specs [ "noop" ] )
mustRegisterOutput ( t , outputs , specs [ "json" ] )
2026-07-03 17:01:24 +00:00
return pipeline . ModuleCatalog {
Inputs : inputs ,
Chunkers : chunkers ,
Extractors : extractors ,
Mergers : mergers ,
Normalizers : normalizers ,
Validators : validators ,
Outputs : outputs ,
}
}
func mustRegisterInput ( t * testing . T , registry * pipeline . InputAdapterRegistry , spec pipeline . ModuleSpec ) {
t . Helper ( )
if err := registry . RegisterWithSpec ( spec , func ( ) ( contracts . InputAdapter , error ) { return nil , nil } ) ; err != nil {
t . Fatalf ( "register input: %v" , err )
}
}
func mustRegisterChunker ( t * testing . T , registry * pipeline . ChunkerRegistry , spec pipeline . ModuleSpec ) {
t . Helper ( )
if err := registry . RegisterWithSpec ( spec , func ( ) ( contracts . Chunker , error ) { return nil , nil } ) ; err != nil {
t . Fatalf ( "register chunker: %v" , err )
}
}
func mustRegisterExtractor ( t * testing . T , registry * pipeline . ExtractorRegistry , spec pipeline . ModuleSpec ) {
t . Helper ( )
if err := registry . RegisterWithSpec ( spec , func ( ) ( contracts . Extractor , error ) { return nil , nil } ) ; err != nil {
t . Fatalf ( "register extractor: %v" , err )
}
}
func mustRegisterMerger ( t * testing . T , registry * pipeline . MergerRegistry , spec pipeline . ModuleSpec ) {
t . Helper ( )
if err := registry . RegisterWithSpec ( spec , func ( ) ( contracts . Merger , error ) { return nil , nil } ) ; err != nil {
t . Fatalf ( "register merger: %v" , err )
}
}
func mustRegisterNormalizer ( t * testing . T , registry * pipeline . NormalizerRegistry , spec pipeline . ModuleSpec ) {
t . Helper ( )
if err := registry . RegisterWithSpec ( spec , func ( ) ( contracts . Normalizer , error ) { return nil , nil } ) ; err != nil {
t . Fatalf ( "register normalizer: %v" , err )
}
}
func mustRegisterOutput ( t * testing . T , registry * pipeline . OutputEncoderRegistry , spec pipeline . ModuleSpec ) {
t . Helper ( )
if err := registry . RegisterWithSpec ( spec , func ( ) ( contracts . OutputEncoder , error ) { return nil , nil } ) ; err != nil {
t . Fatalf ( "register output: %v" , err )
}
}
func mapLookup ( values map [ string ] string ) func ( string ) ( string , bool ) {
return func ( key string ) ( string , bool ) {
value , ok := values [ key ]
return value , ok
}
}