2026-08-10 00:24:02 +00:00
package artifacts
import (
"errors"
"os"
"path/filepath"
2026-08-10 22:06:48 +00:00
"runtime"
2026-09-02 14:09:32 +00:00
"strings"
2026-08-10 00:24:02 +00:00
"testing"
"time"
"gitea.maximumdirect.net/eric/narratio/internal/artifactmodel"
"gitea.maximumdirect.net/eric/narratio/internal/manifest"
)
func TestArtifactCatalogRegistersExtractionArtifactsDeterministically ( t * testing . T ) {
catalog := NewArtifactCatalog ( )
if err := catalog . RegisterConfiguredArtifacts ( map [ string ] ConfiguredArtifactDefinition {
"summary" : { Enabled : true } ,
} , nil ) ; err != nil {
t . Fatal ( err )
}
if err := catalog . RegisterExtractionArtifacts ( map [ string ] ExtractionArtifactDefinition {
"zeta" : { LaneID : "zeta" } ,
"summary" : { LaneID : "summary" } ,
"alpha" : { LaneID : "alpha" } ,
} ) ; err != nil {
t . Fatalf ( "RegisterExtractionArtifacts() error = %v" , err )
}
entries := catalog . ListExtraction ( )
if len ( entries ) != 3 || entries [ 0 ] . ExtractionKey != "alpha" || entries [ 1 ] . ExtractionKey != "summary" || entries [ 2 ] . ExtractionKey != "zeta" {
t . Fatalf ( "ListExtraction() = %#v, want alpha, summary, zeta" , entries )
}
extractionID , ok := catalog . SourceIDForExtractionKey ( "summary" )
if ! ok || extractionID != "narratio.extraction.summary" {
t . Fatalf ( "SourceIDForExtractionKey(summary) = %q, %v" , extractionID , ok )
}
configuredID , _ := catalog . SourceIDForConfiguredKey ( "summary" )
if configuredID == extractionID {
t . Fatalf ( "configured and extraction source families collided at %q" , extractionID )
}
entry , ok := catalog . Lookup ( extractionID )
if ! ok || entry . ProducerStage != "extract" || entry . OutputKind != "notarius_lane" || ! entry . Planned {
t . Fatalf ( "Lookup(%q) = %#v, %v" , extractionID , entry , ok )
}
}
func TestArtifactCatalogRejectsDuplicateExtractionRegistration ( t * testing . T ) {
catalog := NewArtifactCatalog ( )
definitions := map [ string ] ExtractionArtifactDefinition { "summary" : { LaneID : "summary" } }
if err := catalog . RegisterExtractionArtifacts ( definitions ) ; err != nil {
t . Fatal ( err )
}
if err := catalog . RegisterExtractionArtifacts ( definitions ) ; err == nil {
t . Fatal ( "second RegisterExtractionArtifacts() error = nil, want collision error" )
}
}
func TestHydrateExtractionArtifactsAcceptsOnlyCompleteCurrentBundle ( t * testing . T ) {
paths , currentManifest , definitions := validExtractionCatalogFixture ( t )
catalog := registeredExtractionCatalog ( t , definitions )
catalog . HydrateExtractionArtifacts ( paths , currentManifest , definitions )
entry , ok := catalog . Lookup ( ExtractionArtifactSourceID ( "encounters" ) )
if ! ok || ! entry . Available {
t . Fatalf ( "hydrated entry = %#v, %v; want available" , entry , ok )
}
if entry . Provenance != ArtifactProvenanceCurrentExtractManifest || entry . ProducerRunID != "extract-run-1" {
t . Fatalf ( "hydrated provenance = %#v" , entry )
}
2026-08-29 19:02:16 +00:00
wantOutput := currentManifest . Stages [ "extract" ] . Outputs [ 0 ]
info , err := os . Stat ( wantOutput . LocalPath )
if err != nil {
t . Fatal ( err )
}
if entry . Checksum != wantOutput . Checksum || entry . Size != info . Size ( ) {
t . Fatalf ( "hydrated content identity = (%q, %d), want (%q, %d)" , entry . Checksum , entry . Size , wantOutput . Checksum , info . Size ( ) )
}
if entry . Contract == nil || * entry . Contract != * wantOutput . Contract {
t . Fatalf ( "hydrated contract = %#v, want %#v" , entry . Contract , wantOutput . Contract )
}
2026-08-10 00:24:02 +00:00
resolved , err := ResolveSessionArtifactWithCatalog ( paths , currentManifest , entry . SourceID , catalog )
if err != nil {
t . Fatalf ( "ResolveSessionArtifactWithCatalog() error = %v" , err )
}
if resolved . Path != entry . Path || resolved . ProducerRunID != "extract-run-1" {
t . Fatalf ( "resolved = %#v" , resolved )
}
}
2026-08-10 22:06:48 +00:00
func TestInspectExtractionEvidenceClassifiesBundleStates ( t * testing . T ) {
tests := [ ] struct {
2026-08-10 22:15:53 +00:00
name string
state ExtractionEvidenceState
recomputeInputIdentity bool
2026-09-02 14:09:32 +00:00
wantInputUnavailable bool
2026-08-10 22:15:53 +00:00
mutate func ( * testing . T , * SessionPaths , * manifest . Manifest )
2026-08-10 22:06:48 +00:00
} {
{ name : "valid" , state : ExtractionEvidenceValid , mutate : func ( _ * testing . T , _ * SessionPaths , _ * manifest . Manifest ) { } } ,
{ name : "absent" , state : ExtractionEvidenceAbsent , mutate : func ( _ * testing . T , _ * SessionPaths , m * manifest . Manifest ) {
m . Stages [ "extract" ] . Status = manifest . StatusFailed
} } ,
{ name : "obsolete version" , state : ExtractionEvidenceObsolete , mutate : func ( _ * testing . T , _ * SessionPaths , m * manifest . Manifest ) {
m . Stages [ "extract" ] . Outputs [ 0 ] . Contract . SchemaVersion = "99"
} } ,
2026-09-02 14:09:32 +00:00
{ name : "changed direct transcript" , wantInputUnavailable : true , recomputeInputIdentity : true , mutate : func ( t * testing . T , _ * SessionPaths , m * manifest . Manifest ) {
2026-08-10 22:15:53 +00:00
writeExtractionFixtureFile ( t , m . Stages [ "trim" ] . Outputs [ 0 ] . LocalPath , ` { "segments":[ { "id":"changed"}]} ` )
} } ,
{ name : "changed direct producer" , state : ExtractionEvidenceObsolete , recomputeInputIdentity : true , mutate : func ( _ * testing . T , _ * SessionPaths , m * manifest . Manifest ) {
m . Stages [ "trim" ] . Outputs [ 0 ] . ProducerRunID = "trim-run-2"
} } ,
2026-08-10 22:06:48 +00:00
{ name : "incomplete" , state : ExtractionEvidenceObsolete , mutate : func ( _ * testing . T , _ * SessionPaths , m * manifest . Manifest ) {
m . Stages [ "extract" ] . Outputs = m . Stages [ "extract" ] . Outputs [ 1 : ]
} } ,
{ name : "unsafe root" , state : ExtractionEvidenceUnsafe , mutate : func ( t * testing . T , paths * SessionPaths , _ * manifest . Manifest ) {
paths . Root = t . TempDir ( )
} } ,
{ name : "unsafe symlink" , state : ExtractionEvidenceUnsafe , mutate : func ( t * testing . T , _ * SessionPaths , m * manifest . Manifest ) {
if runtime . GOOS == "windows" {
t . Skip ( "symlink creation requires privileges on Windows" )
}
path := m . Stages [ "extract" ] . Outputs [ 0 ] . LocalPath
outside := filepath . Join ( t . TempDir ( ) , "outside.json" )
writeExtractionFixtureFile ( t , outside , ` { "outside":true} ` )
if err := os . Remove ( path ) ; err != nil {
t . Fatal ( err )
}
if err := os . Symlink ( outside , path ) ; err != nil {
t . Fatal ( err )
}
} } ,
}
for _ , test := range tests {
t . Run ( test . name , func ( t * testing . T ) {
paths , currentManifest , definitions := validExtractionCatalogFixture ( t )
2026-08-10 22:15:53 +00:00
input , err := ResolveExtractionInputIdentity ( paths , currentManifest )
if err != nil {
t . Fatal ( err )
}
2026-08-10 22:06:48 +00:00
test . mutate ( t , & paths , currentManifest )
2026-08-10 22:15:53 +00:00
if test . recomputeInputIdentity {
input , err = ResolveExtractionInputIdentity ( paths , currentManifest )
2026-09-02 14:09:32 +00:00
if test . wantInputUnavailable {
if ! errors . Is ( err , ErrSessionArtifactNotFound ) && ( err == nil || ! strings . Contains ( err . Error ( ) , "checksum mismatch" ) ) {
t . Fatalf ( "ResolveExtractionInputIdentity() error = %v, want unavailable" , err )
}
return
}
2026-08-10 22:15:53 +00:00
if err != nil {
t . Fatal ( err )
}
}
proof := InspectExtractionEvidence ( paths , currentManifest , definitions , input )
2026-08-10 22:06:48 +00:00
if proof . State != test . state {
t . Fatalf ( "proof = %#v, want %q" , proof , test . state )
}
catalog := registeredExtractionCatalog ( t , definitions )
catalog . HydrateExtractionArtifacts ( paths , currentManifest , definitions )
entry , _ := catalog . Lookup ( ExtractionArtifactSourceID ( "encounters" ) )
if entry . Available != ( test . state == ExtractionEvidenceValid ) {
t . Fatalf ( "catalog availability for %s = %v" , test . state , entry . Available )
}
} )
}
}
2026-08-10 00:24:02 +00:00
func TestHydrateExtractionArtifactsRejectsUntrustedManifestState ( t * testing . T ) {
tests := [ ] struct {
name string
mutate func ( t * testing . T , paths SessionPaths , m * manifest . Manifest )
} {
{ name : "missing stage" , mutate : func ( _ * testing . T , _ SessionPaths , m * manifest . Manifest ) { delete ( m . Stages , "extract" ) } } ,
{ name : "skipped" , mutate : setExtractionStatus ( manifest . StatusSkipped ) } ,
{ name : "failed" , mutate : setExtractionStatus ( manifest . StatusFailed ) } ,
{ name : "stale" , mutate : setExtractionStatus ( manifest . StatusStale ) } ,
{ name : "interrupted" , mutate : setExtractionStatus ( manifest . StatusInterrupted ) } ,
{ name : "missing source" , mutate : func ( _ * testing . T , _ SessionPaths , m * manifest . Manifest ) {
m . Stages [ "extract" ] . Outputs = m . Stages [ "extract" ] . Outputs [ 1 : ]
} } ,
{ name : "inconsistent producer identity" , mutate : func ( _ * testing . T , _ SessionPaths , m * manifest . Manifest ) {
m . Stages [ "extract" ] . Outputs [ 0 ] . ProducerRunID = "another-run"
} } ,
{ name : "incompatible contract" , mutate : func ( _ * testing . T , _ SessionPaths , m * manifest . Manifest ) {
m . Stages [ "extract" ] . Outputs [ 0 ] . Contract . SchemaVersion = "99"
} } ,
{ name : "incompatible provenance" , mutate : func ( _ * testing . T , _ SessionPaths , m * manifest . Manifest ) {
m . Stages [ "extract" ] . Outputs [ 0 ] . ExternalProvenance . RunID = "another-notarius-run"
} } ,
{ name : "missing file" , mutate : func ( t * testing . T , _ SessionPaths , m * manifest . Manifest ) {
if err := os . Remove ( m . Stages [ "extract" ] . Outputs [ 0 ] . LocalPath ) ; err != nil {
t . Fatal ( err )
}
} } ,
{ name : "tampered checksum" , mutate : func ( t * testing . T , _ SessionPaths , m * manifest . Manifest ) {
if err := os . WriteFile ( m . Stages [ "extract" ] . Outputs [ 0 ] . LocalPath , [ ] byte ( ` { "tampered":true} ` ) , 0 o644 ) ; err != nil {
t . Fatal ( err )
}
} } ,
{ name : "unsafe path" , mutate : func ( t * testing . T , paths SessionPaths , m * manifest . Manifest ) {
outside := filepath . Join ( paths . Root , "incidental.json" )
writeExtractionFixtureFile ( t , outside , ` { "incidental":true} ` )
m . Stages [ "extract" ] . Outputs [ 0 ] . LocalPath = outside
m . Stages [ "extract" ] . Outputs [ 0 ] . Checksum = extractionFixtureChecksum ( t , outside )
} } ,
}
for _ , test := range tests {
t . Run ( test . name , func ( t * testing . T ) {
paths , currentManifest , definitions := validExtractionCatalogFixture ( t )
test . mutate ( t , paths , currentManifest )
catalog := registeredExtractionCatalog ( t , definitions )
catalog . HydrateExtractionArtifacts ( paths , currentManifest , definitions )
entry , _ := catalog . Lookup ( ExtractionArtifactSourceID ( "encounters" ) )
if entry . Available {
t . Fatalf ( "entry became available from %s manifest" , test . name )
}
} )
}
}
func TestResolveExtractionArtifactNeverDiscoversIncidentalBundleFile ( t * testing . T ) {
root := t . TempDir ( )
paths := SessionPaths { Root : root , ArtifactsDir : filepath . Join ( root , "artifacts" ) }
incidental := filepath . Join ( paths . ArtifactsDir , "notarius" , "incidental" , "lanes" , "encounters.json" )
writeExtractionFixtureFile ( t , incidental , ` { "encounters":[]} ` )
definitions := extractionFixtureDefinitions ( )
catalog := registeredExtractionCatalog ( t , definitions )
_ , err := ResolveSessionArtifactWithCatalog ( paths , manifest . New ( "session" , fixtureTime ) , ExtractionArtifactSourceID ( "encounters" ) , catalog )
if err == nil || ! errors . Is ( err , ErrSessionArtifactNotFound ) {
t . Fatalf ( "ResolveSessionArtifactWithCatalog() error = %v, want not found" , err )
}
}
var fixtureTime = mustFixtureTime ( )
func mustFixtureTime ( ) ( value time . Time ) {
return time . Date ( 2026 , 8 , 10 , 0 , 0 , 0 , 0 , time . UTC )
}
func setExtractionStatus ( status manifest . StageStatus ) func ( * testing . T , SessionPaths , * manifest . Manifest ) {
return func ( _ * testing . T , _ SessionPaths , m * manifest . Manifest ) { m . Stages [ "extract" ] . Status = status }
}
func extractionFixtureDefinitions ( ) map [ string ] ExtractionArtifactDefinition {
return map [ string ] ExtractionArtifactDefinition {
"encounters" : {
LaneID : "encounters" , PipelineID : "campaign.extract" , MediaType : "application/json" ,
SchemaID : "encounters" , SchemaVersion : "1" , ModuleKey : "encounters" ,
} ,
}
}
func registeredExtractionCatalog ( t * testing . T , definitions map [ string ] ExtractionArtifactDefinition ) * ArtifactCatalog {
t . Helper ( )
catalog := NewArtifactCatalog ( )
if err := catalog . RegisterExtractionArtifacts ( definitions ) ; err != nil {
t . Fatal ( err )
}
return catalog
}
func validExtractionCatalogFixture ( t * testing . T ) ( SessionPaths , * manifest . Manifest , map [ string ] ExtractionArtifactDefinition ) {
t . Helper ( )
root := t . TempDir ( )
paths := SessionPaths { Root : root , ArtifactsDir : filepath . Join ( root , "artifacts" ) }
2026-08-10 22:15:53 +00:00
trimmedPath := filepath . Join ( paths . Root , filepath . FromSlash ( TranscriptPathFinalTrimmed ) )
2026-08-10 00:24:02 +00:00
bundleRoot := filepath . Join ( paths . ArtifactsDir , "notarius" , "extract-run-1" )
lanePath := filepath . Join ( bundleRoot , "lanes" , "encounters.json" )
indexPath := filepath . Join ( bundleRoot , "index.json" )
writeExtractionFixtureFile ( t , lanePath , ` { "encounters":[]} ` )
writeExtractionFixtureFile ( t , indexPath , ` { "lanes":[]} ` )
2026-08-10 22:15:53 +00:00
writeExtractionFixtureFile ( t , trimmedPath , ` { "segments":[]} ` )
2026-08-10 00:24:02 +00:00
definitions := extractionFixtureDefinitions ( )
m := manifest . New ( "session" , fixtureTime )
2026-08-10 22:15:53 +00:00
m . Stages [ "trim" ] = & manifest . StageRecord {
Name : "trim" , Status : manifest . StatusSucceeded ,
Outputs : [ ] manifest . ArtifactRecord { {
2026-09-02 14:09:32 +00:00
Kind : TranscriptOutputKindFinalTrimmed , SourceID : ArtifactTranscriptFinalTrimmed , LocalPath : trimmedPath , ProducerRunID : "trim-run-1" , Checksum : extractionFixtureChecksum ( t , trimmedPath ) ,
2026-08-10 22:15:53 +00:00
} } ,
}
input , err := ResolveExtractionInputIdentity ( paths , m )
if err != nil {
t . Fatal ( err )
}
2026-08-10 00:24:02 +00:00
m . Stages [ "extract" ] = & manifest . StageRecord {
Name : "extract" , Status : manifest . StatusSucceeded ,
Metadata : map [ string ] any {
"narratio_run_id" : "extract-run-1" , "bundle_root" : bundleRoot ,
2026-08-10 22:15:53 +00:00
"receipt" : map [ string ] any { "run_id" : "notarius-run-1" , "pipeline_id" : "campaign.extract" } ,
extractionMetadataInput : input . Metadata ( ) ,
2026-08-10 00:24:02 +00:00
} ,
Outputs : [ ] manifest . ArtifactRecord {
{
Kind : "notarius_lane" , SourceID : ExtractionArtifactSourceID ( "encounters" ) , LocalPath : lanePath ,
ProducerRunID : "extract-run-1" , Checksum : extractionFixtureChecksum ( t , lanePath ) ,
Contract : & artifactmodel . ContractMetadata { MediaType : "application/json" , SchemaID : "encounters" , SchemaVersion : "1" , ModuleKey : "encounters" } ,
ExternalProvenance : & artifactmodel . ExternalProvenance { System : "notarius" , RunID : "notarius-run-1" , PipelineID : "campaign.extract" , ArtifactID : "encounters" } ,
} ,
{ Kind : "notarius_index" , LocalPath : indexPath , ProducerRunID : "extract-run-1" , Checksum : extractionFixtureChecksum ( t , indexPath ) } ,
} ,
}
return paths , m , definitions
}
func writeExtractionFixtureFile ( t * testing . T , path , body string ) {
t . Helper ( )
if err := os . MkdirAll ( filepath . Dir ( path ) , 0 o755 ) ; err != nil {
t . Fatal ( err )
}
if err := os . WriteFile ( path , [ ] byte ( body ) , 0 o644 ) ; err != nil {
t . Fatal ( err )
}
}
func extractionFixtureChecksum ( t * testing . T , path string ) string {
t . Helper ( )
checksum , err := SHA256File ( path )
if err != nil {
t . Fatal ( err )
}
return checksum
}