2026-07-04 00:55:18 +00:00
package json
import (
"context"
stdjson "encoding/json"
"reflect"
"strings"
"testing"
"gitea.maximumdirect.net/eric/notarius/internal/core/artifacts"
2026-07-23 15:02:01 +00:00
"gitea.maximumdirect.net/eric/notarius/internal/core/source"
"gitea.maximumdirect.net/eric/notarius/internal/framework/chunkmap"
2026-07-04 00:55:18 +00:00
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
2026-07-27 18:23:48 +00:00
"gitea.maximumdirect.net/eric/notarius/internal/framework/evidencecontext"
2026-07-04 00:55:18 +00:00
"gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline"
)
func TestModuleSpecAndRegister ( t * testing . T ) {
want := pipeline . ModuleSpec {
2026-08-03 16:49:51 +00:00
Key : Key ,
Stage : pipeline . StageOutput ,
ExecutionClass : contracts . ExecutionClassDeterministic ,
Requires : [ ] string { "normalized" } ,
Provides : [ ] string { "encoded" } ,
2026-07-04 00:55:18 +00:00
}
if got := ModuleSpec ( ) ; ! reflect . DeepEqual ( got , want ) {
t . Fatalf ( "ModuleSpec() = %#v, want %#v" , got , want )
}
registry := pipeline . NewOutputEncoderRegistry ( )
if err := Register ( registry ) ; err != nil {
t . Fatalf ( "Register() error = %v, want nil" , err )
}
spec , ok := registry . Spec ( Key )
if ! ok {
t . Fatalf ( "Spec(%q) ok = false, want true" , Key )
}
if ! reflect . DeepEqual ( spec , want ) {
t . Fatalf ( "registered spec = %#v, want %#v" , spec , want )
2026-07-17 06:30:56 +00:00
}
if err := registry . ValidateOptions ( Key , map [ string ] any { "unexpected" : true } ) ; err == nil || ! strings . Contains ( err . Error ( ) , "unknown option" ) {
t . Fatalf ( "ValidateOptions() error = %v, want unknown option error" , err )
2026-07-04 00:55:18 +00:00
}
2026-07-23 15:02:01 +00:00
if err := registry . ValidateOptions ( Key , map [ string ] any { "include_chunk_map" : true } ) ; err != nil {
t . Fatalf ( "ValidateOptions() error = %v, want nil" , err )
}
}
func TestDecodeOptions ( t * testing . T ) {
for _ , test := range [ ] struct {
name string
options map [ string ] any
want Options
wantErr string
} {
{ name : "omitted" , want : Options { } } ,
{ name : "disabled" , options : map [ string ] any { "include_chunk_map" : false } , want : Options { } } ,
{ name : "enabled" , options : map [ string ] any { "include_chunk_map" : true } , want : Options { IncludeChunkMap : true } } ,
{ name : "wrong type" , options : map [ string ] any { "include_chunk_map" : "true" } , wantErr : "must be a boolean" } ,
{ name : "unknown" , options : map [ string ] any { "unknown" : true } , wantErr : "unknown option" } ,
} {
t . Run ( test . name , func ( t * testing . T ) {
got , err := DecodeOptions ( test . options )
if test . wantErr != "" {
if err == nil || ! strings . Contains ( err . Error ( ) , test . wantErr ) {
t . Fatalf ( "DecodeOptions() error = %v, want %q" , err , test . wantErr )
}
return
}
if err != nil {
t . Fatalf ( "DecodeOptions() error = %v, want nil" , err )
}
2026-07-27 18:04:12 +00:00
if ! reflect . DeepEqual ( got , test . want ) {
2026-07-23 15:02:01 +00:00
t . Fatalf ( "DecodeOptions() = %#v, want %#v" , got , test . want )
}
} )
}
2026-07-04 00:55:18 +00:00
}
2026-07-27 18:04:12 +00:00
func TestDecodeEvidenceContextOptions ( t * testing . T ) {
for _ , test := range [ ] struct {
name string
options map [ string ] any
want pipeline . EvidenceContextPolicy
wantErr string
} {
{ name : "disabled" , options : map [ string ] any { "evidence_context" : map [ string ] any { "enabled" : false } } } ,
{ name : "enabled default window" , options : map [ string ] any { "evidence_context" : map [ string ] any { "enabled" : true , "lanes" : [ ] any { "npcs" } } } , want : pipeline . EvidenceContextPolicy { Enabled : true , WindowUnits : 3 , LaneIDs : [ ] string { "npcs" } } } ,
{ name : "explicit zero window and normalized lanes" , options : map [ string ] any { "evidence_context" : map [ string ] any { "enabled" : true , "lanes" : [ ] any { " spells " , "npcs" } , "window_units" : 0 } } , want : pipeline . EvidenceContextPolicy { Enabled : true , WindowUnits : 0 , LaneIDs : [ ] string { "npcs" , "spells" } } } ,
2026-09-13 16:41:14 +00:00
{ name : "ConfigKit window integer" , options : map [ string ] any { "evidence_context" : map [ string ] any { "enabled" : true , "lanes" : [ ] any { "npcs" } , "window_units" : int64 ( 2 ) } } , want : pipeline . EvidenceContextPolicy { Enabled : true , WindowUnits : 2 , LaneIDs : [ ] string { "npcs" } } } ,
2026-07-27 18:04:12 +00:00
{ name : "duplicate lanes" , options : map [ string ] any { "evidence_context" : map [ string ] any { "enabled" : true , "lanes" : [ ] any { "npcs" , " npcs " } } } , wantErr : "duplicated" } ,
{ name : "unknown nested option" , options : map [ string ] any { "evidence_context" : map [ string ] any { "enabled" : false , "extra" : true } } , wantErr : "unknown option" } ,
{ name : "disabled nested fields" , options : map [ string ] any { "evidence_context" : map [ string ] any { "enabled" : false , "lanes" : [ ] any { "npcs" } } } , wantErr : "not allowed" } ,
{ name : "invalid object" , options : map [ string ] any { "evidence_context" : true } , wantErr : "must be an object" } ,
{ name : "invalid lane type" , options : map [ string ] any { "evidence_context" : map [ string ] any { "enabled" : true , "lanes" : "npcs" } } , wantErr : "must be an array" } ,
{ name : "invalid window type" , options : map [ string ] any { "evidence_context" : map [ string ] any { "enabled" : true , "lanes" : [ ] any { "npcs" } , "window_units" : "3" } } , wantErr : "must be an integer" } ,
} {
t . Run ( test . name , func ( t * testing . T ) {
got , err := DecodeOptions ( test . options )
if test . wantErr != "" {
if err == nil || ! strings . Contains ( err . Error ( ) , test . wantErr ) {
t . Fatalf ( "DecodeOptions() error = %v, want %q" , err , test . wantErr )
}
return
}
if err != nil {
t . Fatalf ( "DecodeOptions() error = %v, want nil" , err )
}
if ! reflect . DeepEqual ( got . EvidenceContext , test . want ) {
t . Fatalf ( "EvidenceContext = %#v, want %#v" , got . EvidenceContext , test . want )
}
} )
}
}
func TestProfileValidationRejectsUnknownEvidenceLaneAndCopiesInputs ( t * testing . T ) {
registry := pipeline . NewOutputEncoderRegistry ( )
if err := Register ( registry ) ; err != nil {
t . Fatal ( err )
}
options := map [ string ] any { "evidence_context" : map [ string ] any { "enabled" : true , "lanes" : [ ] any { "npcs" } } }
if err := registry . ValidateProfileOptions ( Key , pipeline . OutputProfileOptionContext { LaneIDs : [ ] string { "npcs" , "spells" } } , options ) ; err != nil {
t . Fatalf ( "ValidateProfileOptions() error = %v, want nil" , err )
}
if err := registry . ValidateProfileOptions ( Key , pipeline . OutputProfileOptionContext { LaneIDs : [ ] string { "spells" } } , options ) ; err == nil || ! strings . Contains ( err . Error ( ) , "not configured" ) {
t . Fatalf ( "ValidateProfileOptions() error = %v, want unknown lane failure" , err )
}
}
2026-07-07 18:58:23 +00:00
func TestEncodeReturnsLogicalFilesForNormalizedOutputs ( t * testing . T ) {
2026-07-04 00:55:18 +00:00
req := contracts . OutputRequest {
Manifest : artifacts . RunManifest { RunID : "run-1" , PipelineID : "pipeline-1" } ,
2026-07-17 07:33:49 +00:00
NormalizeOutputs : [ ] contracts . SerializedOutput {
2026-07-07 18:58:23 +00:00
normalizeOutput ( "spells" , ` { "spell_casts":[ { "spell":"Cure Wounds"}]} ` ) ,
normalizeOutput ( "notes/items" , ` { "items":[ { "name":"Torch"}]} ` ) ,
2026-07-04 00:55:18 +00:00
} ,
2026-07-07 18:58:23 +00:00
Rejected : [ ] contracts . RejectedOutput {
2026-07-04 00:55:18 +00:00
{
2026-07-07 18:58:23 +00:00
Stage : "extract" ,
LaneID : "spells" ,
ModuleKey : "dnd/spells" ,
ChunkID : "chunk-1" ,
ChunkIndex : 1 ,
2026-07-04 00:55:18 +00:00
ReasonCode : "invalid" ,
Message : "not accepted" ,
2026-07-07 18:58:23 +00:00
AttemptCount : 1 ,
ValidatorName : "validator" ,
2026-07-04 00:55:18 +00:00
} ,
} ,
2026-08-27 16:40:17 +00:00
Diagnostics : warningDiagnostics ( "warning" , "check source" ) ,
2026-07-04 00:55:18 +00:00
}
result , err := New ( ) . Encode ( context . Background ( ) , req )
if err != nil {
t . Fatalf ( "Encode() error = %v, want nil" , err )
}
wantNames := [ ] string {
2026-08-27 16:43:09 +00:00
"diagnostics.json" ,
2026-07-04 00:55:18 +00:00
"index.json" ,
2026-07-07 19:27:28 +00:00
"lanes/notes_items.json" ,
"lanes/spells.json" ,
2026-07-04 00:55:18 +00:00
"manifest.json" ,
"rejected.json" ,
"warnings.json" ,
}
if got := outputFileNames ( result . Files ) ; ! reflect . DeepEqual ( got , wantNames ) {
t . Fatalf ( "file names = %#v, want %#v" , got , wantNames )
}
2026-07-07 18:58:23 +00:00
2026-07-04 00:55:18 +00:00
for _ , file := range result . Files {
if ! strings . HasSuffix ( string ( file . Bytes ) , "\n" ) {
t . Fatalf ( "%s does not end with newline: %q" , file . Name , string ( file . Bytes ) )
}
2026-07-07 18:58:23 +00:00
if file . ContentType == contentTypeJSON && ! stdjson . Valid ( file . Bytes ) {
2026-07-04 00:55:18 +00:00
t . Fatalf ( "%s has invalid JSON: %s" , file . Name , file . Bytes )
}
}
2026-07-07 19:27:28 +00:00
spells := decodeObject ( t , fileBytes ( t , result . Files , "lanes/spells.json" ) )
2026-07-07 18:58:23 +00:00
spellCasts := spells [ "spell_casts" ] . ( [ ] any )
if spellCasts [ 0 ] . ( map [ string ] any ) [ "spell" ] != "Cure Wounds" {
t . Fatalf ( "spells output = %#v, want raw normalized content" , spells )
2026-07-04 00:55:18 +00:00
}
index := decodeObject ( t , fileBytes ( t , result . Files , "index.json" ) )
2026-07-07 18:58:23 +00:00
outputFiles := index [ "output_files" ] . ( [ ] any )
if len ( outputFiles ) != 2 {
t . Fatalf ( "len(index output_files) = %d, want 2" , len ( outputFiles ) )
}
firstIndex := outputFiles [ 0 ] . ( map [ string ] any )
secondIndex := outputFiles [ 1 ] . ( map [ string ] any )
if firstIndex [ "lane_id" ] != "notes/items" || secondIndex [ "lane_id" ] != "spells" {
t . Fatalf ( "output_files = %#v, want sorted by lane id" , outputFiles )
2026-07-04 00:55:18 +00:00
}
2026-07-07 18:58:23 +00:00
rejected := decodeObject ( t , fileBytes ( t , result . Files , "rejected.json" ) )
if got := rejected [ "rejected" ] . ( [ ] any ) ; len ( got ) != 1 {
t . Fatalf ( "rejected = %#v, want one rejected output" , got )
2026-07-04 00:55:18 +00:00
}
}
func TestEncodeIncludesRejectedAndWarningsWhenEmpty ( t * testing . T ) {
result , err := New ( ) . Encode ( context . Background ( ) , contracts . OutputRequest {
Manifest : artifacts . RunManifest { RunID : "run-1" } ,
} )
if err != nil {
t . Fatalf ( "Encode() error = %v, want nil" , err )
}
rejected := decodeObject ( t , fileBytes ( t , result . Files , "rejected.json" ) )
if got := rejected [ "rejected" ] . ( [ ] any ) ; len ( got ) != 0 {
t . Fatalf ( "rejected = %#v, want empty array" , got )
}
warnings := decodeObject ( t , fileBytes ( t , result . Files , "warnings.json" ) )
2026-08-27 16:40:17 +00:00
if got := warnings [ "groups" ] . ( [ ] any ) ; len ( got ) != 0 {
2026-07-04 00:55:18 +00:00
t . Fatalf ( "warnings = %#v, want empty array" , got )
}
}
2026-08-27 16:43:09 +00:00
func TestEncodePublishesVersionedDiagnosticPartitions ( t * testing . T ) {
diagnostics := contracts . DiagnosticCollection {
Groups : [ ] contracts . DiagnosticGroup {
{
Disposition : contracts . DiagnosticDispositionWarning ,
Category : contracts . DiagnosticCategoryDegradation ,
ReasonCode : "degraded" ,
Origin : contracts . DiagnosticOrigin { Stage : contracts . DiagnosticOriginStageChunk } ,
OccurrenceCount : 2 ,
Samples : [ ] contracts . DiagnosticSample { { Scope : "chunk" , Message : "degraded" } } ,
OmittedSampleCount : 1 ,
} ,
{
Disposition : contracts . DiagnosticDispositionAdvisory ,
Category : contracts . DiagnosticCategoryDataQuality ,
ReasonCode : "unrelated" ,
Origin : contracts . DiagnosticOrigin { Stage : contracts . DiagnosticOriginStageExtract } ,
OccurrenceCount : 3 ,
Samples : [ ] contracts . DiagnosticSample { { Scope : "items[0]" , Message : "unrelated" } } ,
OmittedSampleCount : 2 ,
} ,
} ,
Truncated : true ,
UnrepresentedOccurrenceCount : 5 ,
}
result , err := New ( ) . Encode ( context . Background ( ) , contracts . OutputRequest { Diagnostics : diagnostics } )
if err != nil {
t . Fatalf ( "Encode() error = %v" , err )
}
warnings := decodeObject ( t , fileBytes ( t , result . Files , "warnings.json" ) )
if warnings [ "schema_version" ] != warningsSchemaVersion || warnings [ "group_count" ] != float64 ( 1 ) || warnings [ "occurrence_count" ] != float64 ( 2 ) || len ( warnings [ "groups" ] . ( [ ] any ) ) != 1 {
t . Fatalf ( "warnings file = %#v" , warnings )
}
publishedDiagnostics := decodeObject ( t , fileBytes ( t , result . Files , "diagnostics.json" ) )
if publishedDiagnostics [ "schema_version" ] != diagnosticsSchemaVersion || publishedDiagnostics [ "group_count" ] != float64 ( 1 ) || publishedDiagnostics [ "occurrence_count" ] != float64 ( 8 ) || publishedDiagnostics [ "truncated" ] != true || publishedDiagnostics [ "unrepresented_occurrence_count" ] != float64 ( 5 ) || len ( publishedDiagnostics [ "groups" ] . ( [ ] any ) ) != 1 {
t . Fatalf ( "diagnostics file = %#v" , publishedDiagnostics )
}
index := decodeObject ( t , fileBytes ( t , result . Files , "index.json" ) )
if index [ "warnings_file" ] != "warnings.json" || index [ "diagnostics_file" ] != "diagnostics.json" {
t . Fatalf ( "index = %#v" , index )
}
}
2026-07-23 15:02:01 +00:00
func TestEncodeChunkMapExportIsOptIn ( t * testing . T ) {
artifact := acceptedChunkMapArtifact ( t )
request := contracts . OutputRequest {
Manifest : artifacts . RunManifest { RunID : "run-1" } ,
ChunkMap : & artifact ,
}
for _ , test := range [ ] struct {
name string
encoder * Encoder
} {
{ name : "default" , encoder : New ( ) } ,
{ name : "explicitly disabled" , encoder : NewWithOptions ( Options { IncludeChunkMap : false } ) } ,
} {
t . Run ( test . name , func ( t * testing . T ) {
result , err := test . encoder . Encode ( context . Background ( ) , request )
if err != nil {
t . Fatalf ( "Encode() error = %v, want nil" , err )
}
2026-08-27 16:43:09 +00:00
if got , want := outputFileNames ( result . Files ) , [ ] string { "diagnostics.json" , "index.json" , "manifest.json" , "rejected.json" , "warnings.json" } ; ! reflect . DeepEqual ( got , want ) {
2026-07-23 15:02:01 +00:00
t . Fatalf ( "file names = %#v, want %#v" , got , want )
}
index := decodeObject ( t , fileBytes ( t , result . Files , "index.json" ) )
if _ , ok := index [ "chunk_map" ] ; ok {
t . Fatalf ( "index = %#v, want no chunk map descriptor" , index )
}
} )
}
}
func TestEncodeIncludesValidatedChunkMap ( t * testing . T ) {
artifact := acceptedChunkMapArtifact ( t )
result , err := NewWithOptions ( Options { IncludeChunkMap : true } ) . Encode ( context . Background ( ) , contracts . OutputRequest {
Manifest : artifacts . RunManifest { RunID : "run-1" } ,
NormalizeOutputs : [ ] contracts . SerializedOutput {
normalizeOutput ( "spells" , ` { "spell_casts":[]} ` ) ,
} ,
ChunkMap : & artifact ,
} )
if err != nil {
t . Fatalf ( "Encode() error = %v, want nil" , err )
}
2026-08-27 16:43:09 +00:00
if got , want := outputFileNames ( result . Files ) , [ ] string { "chunk-map.json" , "diagnostics.json" , "index.json" , "lanes/spells.json" , "manifest.json" , "rejected.json" , "warnings.json" } ; ! reflect . DeepEqual ( got , want ) {
2026-07-23 15:02:01 +00:00
t . Fatalf ( "file names = %#v, want %#v" , got , want )
}
chunkMapFile := fileBytes ( t , result . Files , chunkMapFileName )
if ! stdjson . Valid ( chunkMapFile ) || ! strings . HasSuffix ( string ( chunkMapFile ) , "\n" ) || ! strings . Contains ( string ( chunkMapFile ) , "\n \"source_id\"" ) {
t . Fatalf ( "chunk map file = %q, want pretty valid newline-terminated JSON" , chunkMapFile )
}
if _ , err := chunkmap . New ( ) . Decode ( chunkMapFile ) ; err != nil {
t . Fatalf ( "Decode(chunk map file) error = %v, want nil" , err )
}
index := decodeObject ( t , fileBytes ( t , result . Files , "index.json" ) )
if got , want := index [ "chunk_map" ] , map [ string ] any {
"artifact_kind" : "source/chunk-map" ,
"file" : chunkMapFileName ,
"media_type" : chunkmap . MediaType ,
"schema_id" : chunkmap . SchemaID ,
"schema_name" : chunkmap . SchemaName ,
"schema_version" : chunkmap . SchemaVersion ,
} ; ! reflect . DeepEqual ( got , want ) {
t . Fatalf ( "chunk map descriptor = %#v, want %#v" , got , want )
}
for _ , entry := range index [ "output_files" ] . ( [ ] any ) {
if entry . ( map [ string ] any ) [ "file" ] == chunkMapFileName {
t . Fatalf ( "output_files = %#v, want no chunk map" , index [ "output_files" ] )
}
}
}
2026-07-23 17:38:04 +00:00
func TestEncodePreservesChunkMapAnnotationNumbers ( t * testing . T ) {
artifact := acceptedChunkMapArtifactWithPlanAnnotation ( t , stdjson . RawMessage ( ` { "decimal":1.0,"large":9007199254740993} ` ) )
result , err := NewWithOptions ( Options { IncludeChunkMap : true } ) . Encode ( context . Background ( ) , contracts . OutputRequest {
ChunkMap : & artifact ,
} )
if err != nil {
t . Fatalf ( "Encode() error = %v" , err )
}
value , err := chunkmap . New ( ) . Decode ( fileBytes ( t , result . Files , chunkMapFileName ) )
if err != nil {
t . Fatalf ( "Decode(emitted chunk map) error = %v" , err )
}
if got , want := string ( value . PlanAnnotations [ "test/numbers" ] ) , ` { "decimal":1.0,"large":9007199254740993} ` ; got != want {
t . Fatalf ( "numeric annotation = %s, want %s" , got , want )
}
}
2026-07-23 15:02:01 +00:00
func TestEncodeOmitsChunkMapWithoutAcceptedArtifact ( t * testing . T ) {
result , err := NewWithOptions ( Options { IncludeChunkMap : true } ) . Encode ( context . Background ( ) , contracts . OutputRequest {
Manifest : artifacts . RunManifest { RunID : "run-1" } ,
} )
if err != nil {
t . Fatalf ( "Encode() error = %v, want nil" , err )
}
if got := outputFileNames ( result . Files ) ; containsString ( got , chunkMapFileName ) {
t . Fatalf ( "file names = %#v, want no chunk map" , got )
}
index := decodeObject ( t , fileBytes ( t , result . Files , "index.json" ) )
if _ , ok := index [ "chunk_map" ] ; ok {
t . Fatalf ( "index = %#v, want no chunk map descriptor" , index )
}
}
func TestEncodeRejectsInvalidChunkMapArtifact ( t * testing . T ) {
artifact := acceptedChunkMapArtifact ( t )
for _ , test := range [ ] struct {
name string
mutate func ( * contracts . SerializedArtifact )
} {
{ name : "kind" , mutate : func ( artifact * contracts . SerializedArtifact ) { artifact . Kind = "other/chunk-map" } } ,
{ name : "schema" , mutate : func ( artifact * contracts . SerializedArtifact ) { artifact . Schema . Version = "v2" } } ,
{ name : "media type" , mutate : func ( artifact * contracts . SerializedArtifact ) { artifact . MediaType = "text/plain" } } ,
{ name : "content" , mutate : func ( artifact * contracts . SerializedArtifact ) { artifact . Content = [ ] byte ( ` { } ` ) } } ,
} {
t . Run ( test . name , func ( t * testing . T ) {
candidate := contracts . CloneSerializedArtifact ( artifact )
test . mutate ( & candidate )
_ , err := NewWithOptions ( Options { IncludeChunkMap : true } ) . Encode ( context . Background ( ) , contracts . OutputRequest { ChunkMap : & candidate } )
if err == nil {
t . Fatal ( "Encode() error = nil, want invalid chunk map error" )
}
} )
}
}
2026-07-27 18:23:48 +00:00
func TestEncodeIncludesValidatedEvidenceContext ( t * testing . T ) {
artifact := acceptedEvidenceContextArtifact ( t )
result , err := New ( ) . Encode ( context . Background ( ) , contracts . OutputRequest { EvidenceContext : & artifact } )
if err != nil {
t . Fatalf ( "Encode() error = %v" , err )
}
if got := string ( fileBytes ( t , result . Files , evidenceContextFileName ) ) ; ! strings . HasSuffix ( got , "\n" ) || ! stdjson . Valid ( [ ] byte ( got ) ) {
t . Fatalf ( "evidence context file = %q, want pretty valid newline-terminated JSON" , got )
}
value , err := evidencecontext . New ( ) . Decode ( fileBytes ( t , result . Files , evidenceContextFileName ) )
if err != nil {
t . Fatalf ( "Decode(evidence context file) error = %v" , err )
}
2026-08-09 19:45:21 +00:00
if len ( value ) != 1 || value [ 0 ] . ID != 7 || value [ 0 ] . Text != "Source content retained only in the evidence artifact." {
t . Fatalf ( "evidence context = %#v, want the published source-unit array" , value )
2026-07-27 18:23:48 +00:00
}
index := decodeObject ( t , fileBytes ( t , result . Files , "index.json" ) )
if got , want := index [ "evidence_context" ] , map [ string ] any {
"artifact_kind" : string ( evidencecontext . ArtifactKind ) ,
"file" : evidenceContextFileName ,
"media_type" : evidencecontext . MediaType ,
"schema_id" : evidencecontext . SchemaID ,
"schema_name" : evidencecontext . SchemaName ,
"schema_version" : evidencecontext . SchemaVersion ,
} ; ! reflect . DeepEqual ( got , want ) {
t . Fatalf ( "evidence context descriptor = %#v, want %#v" , got , want )
}
for _ , entry := range index [ "output_files" ] . ( [ ] any ) {
if entry . ( map [ string ] any ) [ "file" ] == evidenceContextFileName {
t . Fatalf ( "output_files = %#v, want no evidence context lane entry" , index [ "output_files" ] )
}
}
}
func TestEncodeRejectsInvalidEvidenceContextArtifactWithoutContentLeakage ( t * testing . T ) {
artifact := acceptedEvidenceContextArtifact ( t )
for _ , test := range [ ] struct {
name string
mutate func ( * contracts . SerializedArtifact )
} {
{ name : "kind" , mutate : func ( artifact * contracts . SerializedArtifact ) { artifact . Kind = "other/evidence" } } ,
{ name : "schema identity" , mutate : func ( artifact * contracts . SerializedArtifact ) { artifact . Schema . Version = "v2" } } ,
{ name : "schema digest" , mutate : func ( artifact * contracts . SerializedArtifact ) { artifact . Schema . JSONSchema [ 0 ] = '[' } } ,
{ name : "media type" , mutate : func ( artifact * contracts . SerializedArtifact ) { artifact . MediaType = "text/plain" } } ,
{ name : "payload" , mutate : func ( artifact * contracts . SerializedArtifact ) {
artifact . Content = [ ] byte ( ` { "source_id":"secret transcript text"} ` )
} } ,
} {
t . Run ( test . name , func ( t * testing . T ) {
candidate := contracts . CloneSerializedArtifact ( artifact )
test . mutate ( & candidate )
_ , err := New ( ) . Encode ( context . Background ( ) , contracts . OutputRequest { EvidenceContext : & candidate } )
if err == nil || err . Error ( ) != "json output encoder: evidence context artifact is invalid" {
t . Fatalf ( "Encode() error = %v, want fixed evidence artifact error" , err )
}
if strings . Contains ( err . Error ( ) , "secret transcript text" ) {
t . Fatalf ( "Encode() leaked artifact content: %v" , err )
}
} )
}
}
func TestEncodeOmitsEvidenceContextWhenArtifactIsAbsent ( t * testing . T ) {
result , err := New ( ) . Encode ( context . Background ( ) , contracts . OutputRequest { Manifest : artifacts . RunManifest { RunID : "run-1" } } )
if err != nil {
t . Fatalf ( "Encode() error = %v" , err )
}
if got := outputFileNames ( result . Files ) ; containsString ( got , evidenceContextFileName ) {
t . Fatalf ( "file names = %#v, want no evidence context" , got )
}
index := decodeObject ( t , fileBytes ( t , result . Files , "index.json" ) )
if _ , ok := index [ "evidence_context" ] ; ok {
t . Fatalf ( "index = %#v, want no evidence context descriptor" , index )
}
}
2026-07-23 15:02:01 +00:00
func TestEncodeChunkMapDoesNotMutateRequest ( t * testing . T ) {
artifact := acceptedChunkMapArtifact ( t )
artifact . Metadata = map [ string ] any { "owner" : "caller" }
request := contracts . OutputRequest { ChunkMap : & artifact }
before := contracts . CloneSerializedArtifact ( artifact )
result , err := NewWithOptions ( Options { IncludeChunkMap : true } ) . Encode ( context . Background ( ) , request )
if err != nil {
t . Fatalf ( "Encode() error = %v, want nil" , err )
}
if ! reflect . DeepEqual ( * request . ChunkMap , before ) {
t . Fatalf ( "chunk map artifact mutated:\nbefore: %#v\nafter: %#v" , before , * request . ChunkMap )
}
request . ChunkMap . Content [ 0 ] = '['
request . ChunkMap . Schema . JSONSchema [ 0 ] = '['
request . ChunkMap . Metadata [ "owner" ] = "changed"
if _ , err := chunkmap . New ( ) . Decode ( fileBytes ( t , result . Files , chunkMapFileName ) ) ; err != nil {
t . Fatalf ( "chunk map output changed after request mutation: %v" , err )
}
}
2026-07-04 00:55:18 +00:00
func TestEncodePrettyPrintsJSON ( t * testing . T ) {
result , err := New ( ) . Encode ( context . Background ( ) , contracts . OutputRequest {
Manifest : artifacts . RunManifest { RunID : "run-1" } ,
} )
if err != nil {
t . Fatalf ( "Encode() error = %v, want nil" , err )
}
manifest := string ( fileBytes ( t , result . Files , "manifest.json" ) )
if ! strings . Contains ( manifest , "\n \"run_id\": \"run-1\"\n" ) {
t . Fatalf ( "manifest JSON = %q, want two-space indentation" , manifest )
}
}
2026-07-30 02:18:14 +00:00
func TestEncodePublishesLLMProfileProvenance ( t * testing . T ) {
result , err := New ( ) . Encode ( context . Background ( ) , contracts . OutputRequest {
Manifest : artifacts . RunManifest {
RunID : "run-1" ,
LLMProfiles : [ ] artifacts . LLMProfileManifest {
{
ID : "endpoint-profile" ,
Provider : "promptkit" ,
Model : "endpoint-model" ,
ReasoningEffort : "low" ,
} ,
{
ID : "profile" ,
Provider : "promptkit" ,
Model : "model" ,
BackendID : "openrouter" ,
ReasoningEffort : "high" ,
} ,
} ,
} ,
} )
if err != nil {
t . Fatalf ( "Encode() error = %v, want nil" , err )
}
manifest := decodeObject ( t , fileBytes ( t , result . Files , "manifest.json" ) )
profiles := manifest [ "llm_profiles" ] . ( [ ] any )
if len ( profiles ) != 2 {
t . Fatalf ( "llm_profiles = %#v, want two entries" , profiles )
}
endpointProfile := profiles [ 0 ] . ( map [ string ] any )
if _ , exists := endpointProfile [ "backend_id" ] ; exists || endpointProfile [ "reasoning_effort" ] != "low" {
t . Fatalf ( "endpoint-only LLM profile = %#v, want omitted backend and published reasoning" , endpointProfile )
}
backendProfile := profiles [ 1 ] . ( map [ string ] any )
if backendProfile [ "backend_id" ] != "openrouter" || backendProfile [ "reasoning_effort" ] != "high" {
t . Fatalf ( "backend LLM profile = %#v, want published backend and reasoning fields" , backendProfile )
}
}
2026-07-05 14:44:44 +00:00
func TestEncodeIncludesManifestReferences ( t * testing . T ) {
result , err := New ( ) . Encode ( context . Background ( ) , contracts . OutputRequest {
Manifest : artifacts . RunManifest {
RunID : "run-1" ,
References : [ ] artifacts . ReferenceProvenance {
{
2026-07-05 16:12:38 +00:00
Stage : "extract" ,
2026-07-05 14:44:44 +00:00
LaneID : "events" ,
SlotName : "roster" ,
OriginType : "file" ,
OriginURI : "file:///tmp/roster.txt" ,
Digest : "sha256:reference" ,
MediaType : "text/plain; charset=utf-8" ,
SizeBytes : 12 ,
BindingSource : "config" ,
} ,
} ,
} ,
} )
if err != nil {
t . Fatalf ( "Encode() error = %v, want nil" , err )
}
manifest := decodeObject ( t , fileBytes ( t , result . Files , "manifest.json" ) )
references := manifest [ "references" ] . ( [ ] any )
if len ( references ) != 1 {
t . Fatalf ( "references = %#v, want one entry" , references )
}
reference := references [ 0 ] . ( map [ string ] any )
if reference [ "lane_id" ] != "events" || reference [ "slot_name" ] != "roster" || reference [ "digest" ] != "sha256:reference" {
t . Fatalf ( "reference manifest = %#v, want lane slot digest" , reference )
}
if _ , ok := reference [ "content" ] ; ok {
t . Fatalf ( "reference manifest = %#v, want no content field" , reference )
}
}
2026-07-07 19:27:28 +00:00
func TestEncodeIncludesManifestRawOutputProvenance ( t * testing . T ) {
result , err := New ( ) . Encode ( context . Background ( ) , contracts . OutputRequest {
Manifest : artifacts . RunManifest {
RunID : "run-1" ,
NormalizedOutputs : [ ] artifacts . NormalizedOutputManifest {
{
LaneID : "spells" ,
ModuleKey : "noop" ,
SourceID : "source-1" ,
MediaType : contentTypeJSON ,
Schema : artifacts . OutputSchemaProvenance {
ID : "schema-id" ,
Name : "schema-name" ,
Version : "v1" ,
} ,
} ,
} ,
RejectedOutputs : [ ] artifacts . RejectedOutputManifest {
{
Stage : "extract" ,
LaneID : "spells" ,
ModuleKey : "dnd/spells" ,
ChunkID : "chunk-0" ,
ReasonCode : "raw_output_rejected" ,
AttemptCount : 2 ,
} ,
} ,
} ,
} )
if err != nil {
t . Fatalf ( "Encode() error = %v, want nil" , err )
}
manifest := decodeObject ( t , fileBytes ( t , result . Files , "manifest.json" ) )
normalized := manifest [ "normalized_outputs" ] . ( [ ] any )
if len ( normalized ) != 1 {
t . Fatalf ( "normalized_outputs = %#v, want one entry" , normalized )
}
normalizedEntry := normalized [ 0 ] . ( map [ string ] any )
if normalizedEntry [ "lane_id" ] != "spells" || normalizedEntry [ "media_type" ] != contentTypeJSON {
t . Fatalf ( "normalized output manifest = %#v, want lane and media type" , normalizedEntry )
}
rejected := manifest [ "rejected_outputs" ] . ( [ ] any )
if len ( rejected ) != 1 {
t . Fatalf ( "rejected_outputs = %#v, want one entry" , rejected )
}
rejectedEntry := rejected [ 0 ] . ( map [ string ] any )
if rejectedEntry [ "attempt_count" ] != float64 ( 2 ) || rejectedEntry [ "chunk_id" ] != "chunk-0" {
t . Fatalf ( "rejected output manifest = %#v, want attempt count and chunk" , rejectedEntry )
}
}
2026-07-07 18:58:23 +00:00
func TestEncodeRejectsLaneIDWithoutSafeFileName ( t * testing . T ) {
2026-07-04 00:55:18 +00:00
_ , err := New ( ) . Encode ( context . Background ( ) , contracts . OutputRequest {
2026-07-17 07:33:49 +00:00
NormalizeOutputs : [ ] contracts . SerializedOutput { normalizeOutput ( "///" , ` { "value":true} ` ) } ,
2026-07-04 00:55:18 +00:00
} )
if err == nil {
2026-07-07 18:58:23 +00:00
t . Fatal ( "Encode() error = nil, want unsafe lane id error" )
2026-07-04 00:55:18 +00:00
}
if ! strings . Contains ( err . Error ( ) , "json output encoder" ) || ! strings . Contains ( err . Error ( ) , "safe file name" ) {
t . Fatalf ( "Encode() error = %q, want safe file name context" , err . Error ( ) )
}
}
func TestEncodeSanitizesParentPathSequences ( t * testing . T ) {
result , err := New ( ) . Encode ( context . Background ( ) , contracts . OutputRequest {
2026-07-17 07:33:49 +00:00
NormalizeOutputs : [ ] contracts . SerializedOutput { normalizeOutput ( "dnd..spell." , ` { "value":true} ` ) } ,
2026-07-04 00:55:18 +00:00
} )
if err != nil {
t . Fatalf ( "Encode() error = %v, want nil" , err )
}
2026-07-07 19:27:28 +00:00
if got := outputFileNames ( result . Files ) ; ! containsString ( got , "lanes/dnd__spell.json" ) {
2026-07-07 18:58:23 +00:00
t . Fatalf ( "file names = %#v, want sanitized output filename" , got )
2026-07-04 00:55:18 +00:00
}
}
2026-07-07 19:27:28 +00:00
func TestEncodeRejectsInvalidJSONAndUnsupportedMediaTypes ( t * testing . T ) {
tests := [ ] struct {
name string
2026-07-17 07:33:49 +00:00
output contracts . SerializedOutput
2026-07-07 19:27:28 +00:00
want string
} {
{
name : "invalid JSON" ,
output : normalizeOutput ( "spells" , ` { "spell_casts":[ ` ) ,
want : "invalid JSON" ,
} ,
{
name : "unsupported media type" ,
2026-07-17 07:33:49 +00:00
output : func ( ) contracts . SerializedOutput {
2026-07-07 19:27:28 +00:00
output := normalizeOutput ( "spells" , ` { "spell_casts":[]} ` )
2026-07-17 07:33:49 +00:00
output . Artifact . MediaType = "text/plain"
2026-07-07 19:27:28 +00:00
return output
} ( ) ,
want : "unsupported media type" ,
} ,
}
for _ , test := range tests {
t . Run ( test . name , func ( t * testing . T ) {
_ , err := New ( ) . Encode ( context . Background ( ) , contracts . OutputRequest {
2026-07-17 07:33:49 +00:00
NormalizeOutputs : [ ] contracts . SerializedOutput { test . output } ,
2026-07-07 19:27:28 +00:00
} )
if err == nil {
t . Fatal ( "Encode() error = nil, want error" )
}
if ! strings . Contains ( err . Error ( ) , test . want ) {
t . Fatalf ( "Encode() error = %q, want %q" , err . Error ( ) , test . want )
}
} )
}
}
2026-07-04 00:55:18 +00:00
func TestEncodeRejectsSanitizedFilenameCollisions ( t * testing . T ) {
_ , err := New ( ) . Encode ( context . Background ( ) , contracts . OutputRequest {
2026-07-17 07:33:49 +00:00
NormalizeOutputs : [ ] contracts . SerializedOutput {
2026-07-07 18:58:23 +00:00
normalizeOutput ( "a/b" , ` { "value":"slash"} ` ) ,
normalizeOutput ( "a?b" , ` { "value":"question"} ` ) ,
2026-07-04 00:55:18 +00:00
} ,
} )
if err == nil {
t . Fatal ( "Encode() error = nil, want duplicate file error" )
}
if ! strings . Contains ( err . Error ( ) , "duplicate output file" ) {
t . Fatalf ( "Encode() error = %q, want duplicate file context" , err . Error ( ) )
}
}
func TestEncodeDoesNotMutateInputs ( t * testing . T ) {
req := contracts . OutputRequest {
Manifest : artifacts . RunManifest { RunID : "run-1" } ,
2026-07-17 07:33:49 +00:00
NormalizeOutputs : [ ] contracts . SerializedOutput {
2026-07-07 18:58:23 +00:00
normalizeOutput ( "spells" , ` { "name":"original"} ` ) ,
2026-07-04 00:55:18 +00:00
} ,
2026-07-07 18:58:23 +00:00
Rejected : [ ] contracts . RejectedOutput {
{ Stage : "extract" , LaneID : "spells" , Message : "not accepted" } ,
2026-07-04 00:55:18 +00:00
} ,
2026-08-27 16:40:17 +00:00
Diagnostics : warningDiagnostics ( "warning" , "message" ) ,
2026-07-04 00:55:18 +00:00
}
before := mustMarshal ( t , req )
result , err := New ( ) . Encode ( context . Background ( ) , req )
if err != nil {
t . Fatalf ( "Encode() error = %v, want nil" , err )
}
after := mustMarshal ( t , req )
if before != after {
t . Fatalf ( "request mutated:\nbefore: %s\nafter: %s" , before , after )
}
2026-07-17 07:33:49 +00:00
req . NormalizeOutputs [ 0 ] . Artifact . Content [ 0 ] = '['
req . NormalizeOutputs [ 0 ] . Artifact . Metadata [ "name" ] = "changed"
2026-07-07 18:58:23 +00:00
req . Rejected [ 0 ] . Message = "changed"
2026-08-27 16:40:17 +00:00
req . Diagnostics . Groups [ 0 ] . Samples [ 0 ] . Message = "changed"
2026-07-04 00:55:18 +00:00
2026-07-07 19:27:28 +00:00
if ! stdjson . Valid ( fileBytes ( t , result . Files , "lanes/spells.json" ) ) {
2026-07-07 18:58:23 +00:00
t . Fatal ( "output changed after request mutation" )
2026-07-04 00:55:18 +00:00
}
warnings := decodeObject ( t , fileBytes ( t , result . Files , "warnings.json" ) )
2026-08-27 16:40:17 +00:00
gotWarnings := warnings [ "groups" ] . ( [ ] any )
if gotWarnings [ 0 ] . ( map [ string ] any ) [ "samples" ] . ( [ ] any ) [ 0 ] . ( map [ string ] any ) [ "message" ] != "message" {
2026-07-04 00:55:18 +00:00
t . Fatalf ( "warnings output changed after request mutation: %#v" , gotWarnings )
}
}
2026-07-07 18:58:23 +00:00
func TestOutputFilesDoNotContainWarnings ( t * testing . T ) {
2026-07-04 00:55:18 +00:00
result , err := New ( ) . Encode ( context . Background ( ) , contracts . OutputRequest {
2026-07-17 07:33:49 +00:00
NormalizeOutputs : [ ] contracts . SerializedOutput { normalizeOutput ( "spells" , ` { "spell":"Shield"} ` ) } ,
2026-08-27 16:40:17 +00:00
Diagnostics : warningDiagnostics ( "pipeline-warning" , "warning" ) ,
2026-07-04 00:55:18 +00:00
} )
if err != nil {
t . Fatalf ( "Encode() error = %v, want nil" , err )
}
2026-07-07 19:27:28 +00:00
outputFile := decodeObject ( t , fileBytes ( t , result . Files , "lanes/spells.json" ) )
2026-07-07 18:58:23 +00:00
if _ , ok := outputFile [ "warnings" ] ; ok {
t . Fatalf ( "output file contains warnings: %#v" , outputFile )
2026-07-04 00:55:18 +00:00
}
}
2026-08-27 16:40:17 +00:00
func warningDiagnostics ( reasonCode , message string ) contracts . DiagnosticCollection {
return contracts . DiagnosticCollection { Groups : [ ] contracts . DiagnosticGroup { {
Disposition : contracts . DiagnosticDispositionWarning ,
Category : contracts . DiagnosticCategoryDegradation ,
ReasonCode : reasonCode ,
Origin : contracts . DiagnosticOrigin { Stage : contracts . DiagnosticOriginStageChunk } ,
OccurrenceCount : 1 ,
Samples : [ ] contracts . DiagnosticSample { { Scope : "test" , Message : message } } ,
} } }
}
2026-07-17 07:33:49 +00:00
func normalizeOutput ( laneID string , content string ) contracts . SerializedOutput {
return contracts . SerializedOutput {
2026-07-07 18:58:23 +00:00
LaneID : laneID ,
NormalizerKey : "noop" ,
SourceID : "source-1" ,
2026-07-17 07:33:49 +00:00
Artifact : contracts . SerializedArtifact { Schema : contracts . ArtifactSchema {
2026-07-07 18:58:23 +00:00
ID : "schema-id" ,
Name : "schema-name" ,
Version : "v1" ,
2026-07-04 00:55:18 +00:00
} ,
2026-07-07 18:58:23 +00:00
Content : [ ] byte ( content ) ,
MediaType : contentTypeJSON ,
Metadata : map [ string ] any { "name" : laneID } ,
2026-07-04 00:55:18 +00:00
} ,
}
}
2026-07-23 15:02:01 +00:00
func acceptedChunkMapArtifact ( t * testing . T ) contracts . SerializedArtifact {
2026-07-23 17:38:04 +00:00
return acceptedChunkMapArtifactWithPlanAnnotation ( t , nil )
}
func acceptedChunkMapArtifactWithPlanAnnotation ( t * testing . T , annotation stdjson . RawMessage ) contracts . SerializedArtifact {
2026-07-23 15:02:01 +00:00
t . Helper ( )
document := & source . SourceDocument {
ID : "source-1" ,
Kind : "text" ,
Format : "text/plain" ,
Units : [ ] source . SourceUnit { {
ID : 1 ,
Kind : "text" ,
Text : "Accepted source content." ,
Ref : source . SourceRef { SourceID : "source-1" , StartUnitID : 1 , EndUnitID : 1 } ,
} } ,
}
digest , err := source . DigestDocument ( document )
if err != nil {
t . Fatal ( err )
}
document . Digest = digest
plan := source . ChunkPlan { SourceDigest : digest , Ranges : [ ] source . ChunkRange { { StartUnitID : 1 , EndUnitID : 1 } } }
2026-07-23 17:38:04 +00:00
if annotation != nil {
plan . Annotations = source . ChunkAnnotations { "test/numbers" : append ( stdjson . RawMessage ( nil ) , annotation ... ) }
}
2026-07-23 15:02:01 +00:00
chunks , err := source . MaterializeChunkPlan ( document , plan )
if err != nil {
t . Fatal ( err )
}
artifact , err := chunkmap . Serialize ( chunkmap . BuildRequest {
Source : document , Plan : plan , Chunks : chunks , RequestedChunker : "generic/units" ,
Producer : chunkmap . Producer { InputModule : "test/input" , ChunkModule : "generic/units" } ,
} )
if err != nil {
t . Fatal ( err )
}
return artifact
}
2026-07-27 18:23:48 +00:00
func acceptedEvidenceContextArtifact ( t * testing . T ) contracts . SerializedArtifact {
t . Helper ( )
document := & source . SourceDocument {
ID : "source-1" ,
Kind : "text" ,
Format : "text/plain" ,
Units : [ ] source . SourceUnit { {
ID : 7 ,
Kind : "text" ,
Text : "Source content retained only in the evidence artifact." ,
Ref : source . SourceRef { SourceID : "source-1" , StartUnitID : 7 , EndUnitID : 7 } ,
} } ,
}
digest , err := source . DigestDocument ( document )
if err != nil {
t . Fatal ( err )
}
document . Digest = digest
artifact , err := evidencecontext . Serialize ( evidencecontext . BuildRequest {
2026-08-09 19:45:21 +00:00
Source : document , WindowUnits : 3 ,
SourceRefs : [ ] source . SourceRef { { SourceID : "source-1" , StartUnitID : 7 , EndUnitID : 7 } } ,
2026-07-27 18:23:48 +00:00
} )
if err != nil {
t . Fatal ( err )
}
return artifact
}
2026-07-04 00:55:18 +00:00
func outputFileNames ( files [ ] contracts . OutputFile ) [ ] string {
names := make ( [ ] string , 0 , len ( files ) )
for _ , file := range files {
names = append ( names , file . Name )
}
return names
}
func containsString ( values [ ] string , want string ) bool {
for _ , value := range values {
if value == want {
return true
}
}
return false
}
func fileBytes ( t * testing . T , files [ ] contracts . OutputFile , name string ) [ ] byte {
t . Helper ( )
for _ , file := range files {
if file . Name == name {
return file . Bytes
}
}
t . Fatalf ( "file %q not found in %#v" , name , outputFileNames ( files ) )
return nil
}
func decodeObject ( t * testing . T , data [ ] byte ) map [ string ] any {
t . Helper ( )
var got map [ string ] any
if err := stdjson . Unmarshal ( data , & got ) ; err != nil {
t . Fatalf ( "Unmarshal() error = %v, want nil\n%s" , err , data )
}
return got
}
func mustMarshal ( t * testing . T , value any ) string {
t . Helper ( )
data , err := stdjson . Marshal ( value )
if err != nil {
t . Fatalf ( "Marshal() error = %v, want nil" , err )
}
return string ( data )
}