2026-07-17 14:05:21 +00:00
package pipeline
import (
"context"
2026-07-17 23:47:22 +00:00
"encoding/json"
2026-08-27 01:06:17 +00:00
"fmt"
2026-07-17 14:05:21 +00:00
"reflect"
"strings"
"testing"
2026-07-17 23:47:22 +00:00
"gitea.maximumdirect.net/eric/notarius/internal/core/source"
2026-07-17 14:05:21 +00:00
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
)
type extractCaptureRecorder struct {
CheckpointRecorder
checkpoint ExtractCheckpoint
}
2026-07-18 02:10:05 +00:00
type pipelineTypedMetadata map [ string ] any
type mutatingMetadataValidator struct {
seen string
}
func ( * mutatingMetadataValidator ) Name ( ) string { return "test/mutating-metadata" }
func ( * mutatingMetadataValidator ) ExecutionClass ( ) contracts . ExecutionClass {
return contracts . ExecutionClassDeterministic
}
func ( v * mutatingMetadataValidator ) Validate ( _ context . Context , request contracts . ChunkValidationRequest ) ( contracts . ValidationResult , error ) {
nested := request . Chunks [ 0 ] . Units [ 0 ] . Metadata [ "typed" ] . ( pipelineTypedMetadata )
v . seen = string ( nested [ "raw" ] . ( json . RawMessage ) )
nested [ "raw" ] . ( json . RawMessage ) [ 0 ] = '['
nested [ "changed" ] = true
return contracts . ValidationResult { Approved : true } , nil
}
2026-08-27 16:40:17 +00:00
func ( r * extractCaptureRecorder ) ExtractSucceeded ( _ string , _ string , _ [ ] CheckpointFingerprint , outputs [ ] CheckpointArtifact , rejected [ ] contracts . RejectedOutput ) error {
2026-07-17 14:05:21 +00:00
r . checkpoint = ExtractCheckpoint {
Outputs : cloneCheckpointArtifacts ( outputs ) ,
Rejected : cloneRejectedOutputs ( rejected ) ,
}
return nil
}
type extractResultLoader struct {
CheckpointLoader
checkpoint ExtractCheckpoint
decision CheckpointDecision
}
func ( l * extractResultLoader ) Enabled ( ) bool { return true }
func ( l * extractResultLoader ) Extract ( string , string , [ ] CheckpointFingerprint ) ( ExtractCheckpoint , CheckpointDecision ) {
return ExtractCheckpoint {
Outputs : cloneCheckpointArtifacts ( l . checkpoint . Outputs ) ,
Rejected : cloneRejectedOutputs ( l . checkpoint . Rejected ) ,
} , l . decision
}
func cloneCheckpointArtifacts ( values [ ] CheckpointArtifact ) [ ] CheckpointArtifact {
if len ( values ) == 0 {
return nil
}
cloned := make ( [ ] CheckpointArtifact , len ( values ) )
for i := range values {
cloned [ i ] = cloneCheckpointArtifact ( values [ i ] )
}
return cloned
}
2026-07-17 23:47:22 +00:00
func TestRunnerPassesIndependentAnnotationScopesToExtractors ( t * testing . T ) {
prepared := preparedAttemptDebugPipeline ( t )
chunker := prepared . chunker . ( * typedTestChunker )
2026-07-17 23:57:59 +00:00
chunker . plan . Ranges [ 0 ] . Annotations = source . ChunkAnnotations { "same" : json . RawMessage ( ` { "range":1} ` ) }
chunker . plan . Annotations = source . ChunkAnnotations { "same" : json . RawMessage ( ` { "plan":2} ` ) }
2026-07-17 23:47:22 +00:00
var gotRange , gotPlan string
installExtractOperation ( prepared , 0 , func ( _ context . Context , request contracts . TypedExtractionRequest ) ( erasedTypedResult , error ) {
gotRange = string ( request . Chunk . Annotations [ "same" ] )
gotPlan = string ( request . Chunk . PlanAnnotations [ "same" ] )
request . Chunk . Annotations [ "same" ] [ 0 ] = '['
request . Chunk . PlanAnnotations [ "same" ] [ 0 ] = '['
return erasedTypedResult { Value : typedValueForLane ( 0 , request . Chunk . Index ) } , nil
} )
if _ , err := New ( ) . Run ( context . Background ( ) , RunInput { Prepared : prepared , RawInput : [ ] byte ( "input" ) } ) ; err != nil {
t . Fatalf ( "Run() error = %v, want nil" , err )
}
if gotRange != ` { "range":1} ` || gotPlan != ` { "plan":2} ` {
t . Fatalf ( "extract annotations = %q / %q" , gotRange , gotPlan )
}
2026-07-17 23:57:59 +00:00
if string ( chunker . plan . Ranges [ 0 ] . Annotations [ "same" ] ) != ` { "range":1} ` || string ( chunker . plan . Annotations [ "same" ] ) != ` { "plan":2} ` {
2026-07-17 23:47:22 +00:00
t . Fatal ( "extract request shares annotation bytes with chunker output" )
}
}
2026-07-18 02:10:05 +00:00
func TestRunnerPassesIndependentConcreteMetadataToValidatorsAndExtractors ( t * testing . T ) {
prepared := preparedAttemptDebugPipeline ( t )
input := prepared . input . ( * typedTestInput )
input . doc . Units [ 0 ] . Metadata = map [ string ] any {
"typed" : pipelineTypedMetadata { "raw" : json . RawMessage ( ` { "value":true} ` ) } ,
}
var err error
input . doc . Digest , err = source . DigestDocument ( input . doc )
if err != nil {
t . Fatal ( err )
}
chunker := prepared . chunker . ( * typedTestChunker )
chunker . plan = typedTestPlan ( input . doc )
validator := & mutatingMetadataValidator { }
prepared . chunkValidators . validators = [ ] preparedValidator { {
resolved : ResolvedValidator { Binding : Binding ( validator . Name ( ) ) , Target : ValidatorTargetChunk } ,
chunk : validator ,
} }
var extractorSeen string
installExtractOperation ( prepared , 0 , func ( _ context . Context , request contracts . TypedExtractionRequest ) ( erasedTypedResult , error ) {
nested := request . Chunk . Units [ 0 ] . Metadata [ "typed" ] . ( pipelineTypedMetadata )
extractorSeen = string ( nested [ "raw" ] . ( json . RawMessage ) )
nested [ "raw" ] . ( json . RawMessage ) [ 0 ] = '['
nested [ "changed" ] = true
return erasedTypedResult { Value : typedValueForLane ( 0 , request . Chunk . Index ) } , nil
} )
if _ , err := New ( ) . Run ( context . Background ( ) , RunInput { Prepared : prepared , RawInput : [ ] byte ( "input" ) } ) ; err != nil {
t . Fatalf ( "Run() error = %v" , err )
}
if validator . seen != ` { "value":true} ` || extractorSeen != ` { "value":true} ` {
t . Fatalf ( "validator/extractor metadata = %q / %q" , validator . seen , extractorSeen )
}
original := input . doc . Units [ 0 ] . Metadata [ "typed" ] . ( pipelineTypedMetadata )
if _ , changed := original [ "changed" ] ; changed || string ( original [ "raw" ] . ( json . RawMessage ) ) != ` { "value":true} ` {
t . Fatalf ( "source metadata changed through runner handoff: %#v" , original )
}
}
2026-07-17 23:16:16 -05:00
func TestRunnerRejectsMetadataThatCannotBeCloned ( t * testing . T ) {
prepared := preparedAttemptDebugPipeline ( t )
cyclic := make ( map [ string ] any )
cyclic [ "self" ] = cyclic
_ , err := New ( ) . Run ( context . Background ( ) , RunInput {
Prepared : prepared ,
RawInput : [ ] byte ( "input" ) ,
Metadata : map [ string ] any { "cyclic" : cyclic } ,
} )
if err == nil || ! strings . Contains ( err . Error ( ) , "clone run metadata: metadata.cyclic.self contains a cycle" ) {
t . Fatalf ( "Run() error = %v, want contextual metadata clone failure" , err )
}
}
2026-07-17 14:05:21 +00:00
func TestRunnerContinuesFromFreshAndReusedExtractResults ( t * testing . T ) {
prepared := preparedAttemptDebugPipeline ( t )
extractCalls := 0
installExtractOperation ( prepared , 0 , func ( _ context . Context , request contracts . TypedExtractionRequest ) ( erasedTypedResult , error ) {
extractCalls ++
return erasedTypedResult {
2026-08-27 16:40:17 +00:00
Value : typedValueForLane ( 0 , request . Chunk . Index ) ,
2026-08-27 15:40:45 +00:00
Diagnostics : [ ] contracts . ProducerDiagnostic { {
Disposition : contracts . DiagnosticDispositionObservation ,
Category : contracts . DiagnosticCategoryNormalization ,
ReasonCode : "accepted_extract_normalized" ,
OccurrenceCount : 1 ,
Samples : [ ] contracts . DiagnosticSample { { Scope : "extract" , Message : "accepted extract" } } ,
} } ,
2026-07-17 14:05:21 +00:00
} , nil
} )
freshDebug := newCapturedDebugRecorder ( )
recorder := & extractCaptureRecorder { CheckpointRecorder : NoopCheckpointRecorder ( ) }
freshLoader := & extractResultLoader {
CheckpointLoader : NoopCheckpointLoader ( ) ,
decision : CheckpointDecision { Reason : "extract checkpoint not found" } ,
}
fresh , err := New ( ) . Run ( context . Background ( ) , RunInput {
Prepared : prepared ,
RawInput : [ ] byte ( "input" ) ,
Checkpoints : recorder ,
Checkpoint : freshLoader ,
Debug : freshDebug ,
} )
if err != nil {
t . Fatalf ( "fresh Run() error = %v, want nil" , err )
}
if extractCalls != 1 {
t . Fatalf ( "fresh extract calls = %d, want 1" , extractCalls )
}
assertExtractDecision ( t , fresh . CheckpointEvents , "executed" , "extract checkpoint not found" )
assertExtractDebugPaths ( t , freshDebug , true )
reusedDebug := newCapturedDebugRecorder ( )
reusedLoader := & extractResultLoader {
CheckpointLoader : NoopCheckpointLoader ( ) ,
checkpoint : recorder . checkpoint ,
decision : CheckpointDecision { Reused : true , Reason : "extract checkpoint matched" } ,
}
reused , err := New ( ) . Run ( context . Background ( ) , RunInput {
Prepared : prepared ,
RawInput : [ ] byte ( "input" ) ,
Checkpoint : reusedLoader ,
Debug : reusedDebug ,
} )
if err != nil {
t . Fatalf ( "reused Run() error = %v, want nil" , err )
}
if extractCalls != 1 {
t . Fatalf ( "extract calls after reuse = %d, want 1" , extractCalls )
}
assertExtractDecision ( t , reused . CheckpointEvents , "reused" , "extract checkpoint matched" )
assertExtractDebugPaths ( t , reusedDebug , false )
if ! reflect . DeepEqual ( reused . NormalizeOutputs , fresh . NormalizeOutputs ) {
t . Fatalf ( "reused normalize outputs = %#v, want fresh outputs %#v" , reused . NormalizeOutputs , fresh . NormalizeOutputs )
}
2026-08-27 15:40:45 +00:00
if ! reflect . DeepEqual ( reused . Diagnostics , fresh . Diagnostics ) {
t . Fatalf ( "reused diagnostics = %#v, want fresh diagnostics %#v" , reused . Diagnostics , fresh . Diagnostics )
}
2026-07-17 14:05:21 +00:00
}
2026-08-27 16:40:17 +00:00
func TestRunnerPromotesOnlyAcceptedExtractRetryDiagnostics ( t * testing . T ) {
2026-07-17 14:05:21 +00:00
prepared := preparedAttemptDebugPipeline ( t )
2026-07-21 21:19:00 +00:00
prepared . Steps [ 0 ] . lanes [ 0 ] . resolved . Extract . Retries = 1
2026-07-17 14:05:21 +00:00
attempts := 0
installExtractOperation ( prepared , 0 , func ( _ context . Context , request contracts . TypedExtractionRequest ) ( erasedTypedResult , error ) {
attempts ++
scope := "discarded"
if attempts == 2 {
scope = "accepted"
}
return erasedTypedResult {
2026-08-27 01:06:17 +00:00
Value : typedValueForLane ( 0 , request . Chunk . Index ) ,
2026-08-27 16:40:17 +00:00
Diagnostics : [ ] contracts . ProducerDiagnostic { producerDiagnostic ( scope , scope ) } ,
2026-08-27 01:06:17 +00:00
ModelCandidate : attemptCandidate ( t , fmt . Sprintf ( ` { "items":["%s"]} ` , scope ) ) ,
2026-07-17 14:05:21 +00:00
} , nil
} )
validatorCalls := 0
2026-07-21 21:19:00 +00:00
prepared . Steps [ 0 ] . lanes [ 0 ] . extractValidators . validators [ 0 ] . typedValidate = func ( context . Context , any , typedValidationTarget ) ( contracts . ValidationResult , error ) {
2026-07-17 14:05:21 +00:00
validatorCalls ++
2026-08-27 11:50:37 +00:00
return contracts . ValidationResult { Approved : validatorCalls == 2 , ReasonCode : "retry" , Message : "retry extract" , CorrectionGuidance : "return an acceptable extract" } , nil
2026-07-17 14:05:21 +00:00
}
debug := newCapturedDebugRecorder ( )
output , err := New ( ) . Run ( context . Background ( ) , RunInput { Prepared : prepared , RawInput : [ ] byte ( "input" ) , Debug : debug } )
if err != nil {
t . Fatalf ( "Run() error = %v, want nil" , err )
}
if attempts != 2 {
t . Fatalf ( "extract attempts = %d, want 2" , attempts )
}
2026-08-27 16:40:17 +00:00
if len ( output . Diagnostics . Groups ) != 1 || output . Diagnostics . Groups [ 0 ] . Samples [ 0 ] . Scope != "accepted" {
t . Fatalf ( "promoted diagnostics = %#v, want accepted attempt only" , output . Diagnostics )
2026-07-17 14:05:21 +00:00
}
2026-07-17 15:23:27 +00:00
assertAttemptEnvelopeSequence ( t , debug , "extract/notes/chunk-000001" , 1 , 2 )
first := debug . envelope ( t , "extract/notes/chunk-000001/attempt-01.json" )
if first . Error != "" || ! strings . Contains ( string ( debug . json [ "extract/notes/chunk-000001/attempt-01.json" ] ) , "rejection" ) {
t . Fatalf ( "first extract attempt = %#v, want rejection without error" , first )
}
2026-07-17 14:05:21 +00:00
name := "extract/notes/chunk-000001/attempt-02.json"
if ! debug . has ( name ) {
t . Fatalf ( "debug artifact %q is missing; names = %#v" , name , debug . names ( ) )
}
}
2026-07-22 02:13:50 +00:00
func assertExtractDecision ( t * testing . T , events [ ] CheckpointEvent , action CheckpointDecisionCategory , reason string ) {
2026-07-17 14:05:21 +00:00
t . Helper ( )
for _ , event := range events {
if event . Stage == string ( StageExtract ) {
if event . Action != action || event . Reason != reason {
t . Fatalf ( "extract checkpoint event = %#v, want action %q reason %q" , event , action , reason )
}
return
}
}
t . Fatalf ( "extract checkpoint event missing from %#v" , events )
}
func assertExtractDebugPaths ( t * testing . T , debug * capturedDebugRecorder , wantAttempt bool ) {
t . Helper ( )
for _ , name := range [ ] string { "extract/notes/input.json" , "extract/notes/output.json" } {
if ! debug . has ( name ) {
t . Fatalf ( "debug artifact %q is missing; names = %#v" , name , debug . names ( ) )
}
}
attemptPath := "extract/notes/chunk-000001/attempt-01.json"
if debug . has ( attemptPath ) != wantAttempt {
t . Fatalf ( "attempt debug path present = %t, want %t; names = %#v" , debug . has ( attemptPath ) , wantAttempt , debug . names ( ) )
}
input := debug . envelope ( t , "extract/notes/input.json" )
wantReuse := ! wantAttempt
if ! strings . Contains ( string ( debug . json [ "extract/notes/input.json" ] ) , ` "reused": ` + map [ bool ] string { true : "true" , false : "false" } [ wantReuse ] ) {
t . Fatalf ( "extract input payload = %#v, want reused %t" , input . Payload , wantReuse )
}
}