2026-08-04 00:11:31 +00:00
package registry
import (
"bytes"
2026-08-08 14:49:08 +00:00
"encoding/json"
"reflect"
2026-08-04 00:11:31 +00:00
"strings"
"testing"
"gitea.maximumdirect.net/eric/notarius/internal/core/source"
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
2026-08-05 19:13:37 +00:00
locationcodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/locationregistry"
2026-08-04 00:11:31 +00:00
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/locations/identity"
)
2026-08-08 14:56:43 +00:00
func TestResolveUnboundRegistryHasEmptyIdentity ( t * testing . T ) {
2026-08-04 00:11:31 +00:00
registry , err := Resolve ( contracts . ReferenceSet { } )
if err != nil {
t . Fatal ( err )
}
2026-08-08 14:56:43 +00:00
if registry . Bound ( ) || registry . Digest ( ) != "" || registry . Count ( ) != 0 || string ( registry . CanonicalBytes ( ) ) != emptyRegistryContent {
t . Fatalf ( "unbound registry = %#v" , registry )
2026-08-04 00:11:31 +00:00
}
2026-08-08 14:56:43 +00:00
if registry . IdentityDigest ( ) == "" {
t . Fatal ( "unbound registry identity digest is empty" )
2026-08-04 00:11:31 +00:00
}
}
2026-08-08 14:56:43 +00:00
func TestResolveRetainsSeparateDurableAndIdentityDigests ( t * testing . T ) {
2026-08-04 00:11:31 +00:00
registry := resolveList ( t , registryFixture ( ) )
if ! registry . Bound ( ) || registry . Count ( ) != 2 || registry . Digest ( ) == "" {
t . Fatalf ( "registry identity = bound %t count %d digest %q" , registry . Bound ( ) , registry . Count ( ) , registry . Digest ( ) )
}
2026-08-08 14:56:43 +00:00
if registry . IdentityDigest ( ) == "" || registry . Digest ( ) == registry . IdentityDigest ( ) {
t . Fatalf ( "full/identity digests = %q/%q" , registry . Digest ( ) , registry . IdentityDigest ( ) )
2026-08-04 00:11:31 +00:00
}
}
func TestRegistryLookupUsesIDAndReturnsDefensiveCopies ( t * testing . T ) {
registry := resolveList ( t , registryFixture ( ) )
first := registry . Locations ( ) [ 0 ]
if got , ok := registry . Lookup ( first . ID ) ; ! ok || got . Name != first . Name || ! registry . Matches ( first . ID , first . Name ) || registry . Matches ( first . ID , "Other Tavern" ) {
t . Fatalf ( "ID lookup/match = %#v, %t" , got , ok )
}
if _ , ok := registry . Lookup ( "The Tavern" ) ; ok {
t . Fatal ( "Lookup accepted a name as an ID" )
}
locations := registry . Locations ( )
locations [ 0 ] . Name = "changed"
locations [ 0 ] . SourceRefs [ 0 ] . SourceID = "changed"
canonical := registry . CanonicalBytes ( )
canonical [ 0 ] = '['
if next , ok := registry . Lookup ( first . ID ) ; ! ok || next . Name != first . Name || next . SourceRefs [ 0 ] . SourceID != "session-alpha" {
t . Fatalf ( "registry mutated through accessor: %#v, %t" , next , ok )
}
2026-08-08 14:56:43 +00:00
if registry . CanonicalBytes ( ) [ 0 ] != '{' {
2026-08-04 00:11:31 +00:00
t . Fatal ( "registry bytes mutated through accessor" )
}
}
func TestResolveRejectsInvalidReferenceInputs ( t * testing . T ) {
valid := registryFixture ( )
content , err := locationcodec . New ( ) . Encode ( valid )
if err != nil {
t . Fatal ( err )
}
invalidIdentity := append ( [ ] byte ( nil ) , content ... )
invalidIdentity = bytes . Replace ( invalidIdentity , [ ] byte ( valid . Locations [ 0 ] . ID ) , [ ] byte ( "location:sha256:0000000000000000000000000000000000000000000000000000000000000000" ) , 1 )
for _ , test := range [ ] struct {
name string
set contracts . ReferenceSet
want string
} {
{ "empty bound slot" , referenceSet ( ) , "exactly one item" } ,
{ "multiple items" , referenceSet ( item ( content ) , item ( content ) ) , "exactly one item" } ,
{ "malformed JSON" , referenceSet ( item ( [ ] byte ( ` { "locations":[ ` ) ) ) , "invalid approved" } ,
{ "wrong media type" , referenceSet ( contracts . ReferenceItem { MediaType : "text/plain" , Content : content } ) , "media type must be" } ,
{ "oversized" , referenceSet ( item ( make ( [ ] byte , MaxBytes + 1 ) ) ) , "limit" } ,
{ "invalid identity" , referenceSet ( item ( invalidIdentity ) ) , "id_mismatch" } ,
} {
t . Run ( test . name , func ( t * testing . T ) {
if _ , err := Resolve ( test . set ) ; err == nil || ! strings . Contains ( err . Error ( ) , test . want ) {
t . Fatalf ( "Resolve() error = %v, want %q" , err , test . want )
}
} )
}
}
2026-08-09 01:54:43 +00:00
func TestResolveRejectsReversedEvidenceInDirectRegistryReference ( t * testing . T ) {
value := registryFixture ( )
value . Locations [ 0 ] . SourceRefs [ 0 ] = source . SourceRef { SourceID : "session-alpha" , StartUnitID : 2 , EndUnitID : 1 }
content , err := locationcodec . New ( ) . EncodeCandidate ( value )
if err != nil {
t . Fatalf ( "EncodeCandidate() error = %v" , err )
}
if _ , err := Resolve ( referenceSet ( item ( content ) ) ) ; err == nil || ! strings . Contains ( err . Error ( ) , "invalid approved" ) {
t . Fatalf ( "Resolve() error = %v, want direct-reference rejection" , err )
}
}
2026-08-04 18:29:17 +00:00
func TestResolverHandlesConstructionAndOperationReferences ( t * testing . T ) {
2026-08-04 13:30:12 +00:00
placeholder , err := NewResolver ( referenceSet ( ) )
if err != nil || placeholder . Seeded ( ) . Bound ( ) {
t . Fatalf ( "generated placeholder = %#v, %v; want unbound seed" , placeholder , err )
2026-08-04 00:11:31 +00:00
}
2026-08-04 13:30:12 +00:00
validContent := encodeList ( t , registryFixture ( ) )
2026-08-04 18:29:17 +00:00
malformed := referenceSet ( item ( [ ] byte ( ` { "locations":[ ` ) ) )
if _ , err := NewResolver ( malformed ) ; err == nil {
t . Fatal ( "NewResolver(malformed) error = nil" )
}
if _ , err := placeholder . Resolve ( malformed ) ; err == nil {
t . Fatal ( "Resolve(malformed) error = nil" )
2026-08-04 13:30:12 +00:00
}
staticContent := append ( [ ] byte ( nil ) , validContent ... )
staticReferences := referenceSet ( item ( staticContent ) )
seeded , err := NewResolver ( staticReferences )
if err != nil {
t . Fatal ( err )
}
resolved , err := seeded . Resolve ( staticReferences )
if err != nil || resolved != seeded . Seeded ( ) {
t . Fatalf ( "seed reuse = %p / %p, %v" , resolved , seeded . Seeded ( ) , err )
}
staticContent [ 0 ] = '['
delete ( staticReferences . Slots , ReferenceSlot )
if seeded . Seeded ( ) . Count ( ) != 2 || seeded . Seeded ( ) . CanonicalBytes ( ) [ 0 ] != '{' {
t . Fatalf ( "seeded registry retained construction references: %#v" , seeded . Seeded ( ) )
2026-08-04 00:11:31 +00:00
}
2026-08-04 18:29:17 +00:00
if fallback , err := seeded . Resolve ( contracts . ReferenceSet { } ) ; err != nil || fallback != seeded . Seeded ( ) {
t . Fatalf ( "fallback = %#v, %v; want seeded registry" , fallback , err )
}
2026-08-04 00:11:31 +00:00
}
2026-08-04 18:29:17 +00:00
func TestResolverReusesEquivalentCanonicalRegistries ( t * testing . T ) {
2026-08-04 13:30:12 +00:00
resolver , err := NewResolver ( referenceSet ( ) )
2026-08-04 00:11:31 +00:00
if err != nil {
t . Fatal ( err )
}
content , err := locationcodec . New ( ) . Encode ( registryFixture ( ) )
if err != nil {
t . Fatal ( err )
}
firstSet := referenceSet ( item ( content ) )
first , err := resolver . Resolve ( firstSet )
if err != nil {
t . Fatal ( err )
}
spaced := append ( [ ] byte ( "\n " ) , content ... )
spaced = append ( spaced , '\n' )
2026-08-04 18:29:17 +00:00
second , err := resolver . Resolve ( referenceSet ( contracts . ReferenceItem { MediaType : "APPLICATION/JSON; charset=utf-8" , Content : spaced } ) )
if err != nil || second != first {
t . Fatalf ( "equivalent canonical registry = %p / %p, %v" , first , second , err )
2026-08-04 13:30:12 +00:00
}
2026-08-04 00:11:31 +00:00
}
2026-08-05 19:05:42 +00:00
func registryFixture ( ) dnd . LocationRegistry {
2026-08-04 00:11:31 +00:00
firstRefs := [ ] source . SourceRef { { SourceID : "session-alpha" , StartUnitID : 1 , EndUnitID : 1 } }
secondRefs := [ ] source . SourceRef { { SourceID : "session-alpha" , StartUnitID : 2 , EndUnitID : 2 } }
2026-08-05 19:05:42 +00:00
return dnd . LocationRegistry { Locations : [ ] dnd . Location {
2026-08-04 00:11:31 +00:00
{ ID : identity . DeriveID ( "The Tavern" , firstRefs ) , Name : "The Tavern" , SourceRefs : firstRefs } ,
{ ID : identity . DeriveID ( "The Tavern" , secondRefs ) , Name : "The Tavern" , SourceRefs : secondRefs } ,
} }
}
2026-08-05 19:05:42 +00:00
func resolveList ( t * testing . T , list dnd . LocationRegistry ) * Registry {
2026-08-04 00:11:31 +00:00
t . Helper ( )
registry , err := Resolve ( referenceSet ( item ( encodeList ( t , list ) ) ) )
if err != nil {
t . Fatalf ( "Resolve() error = %v" , err )
}
return registry
}
2026-08-05 19:05:42 +00:00
func encodeList ( t * testing . T , list dnd . LocationRegistry ) [ ] byte {
2026-08-04 00:11:31 +00:00
t . Helper ( )
content , err := locationcodec . New ( ) . Encode ( list )
if err != nil {
t . Fatalf ( "Encode() error = %v" , err )
}
return content
}
func item ( content [ ] byte ) contracts . ReferenceItem {
return contracts . ReferenceItem { SlotName : ReferenceSlot , MediaType : locationcodec . MediaType , Content : content }
}
func referenceSet ( items ... contracts . ReferenceItem ) contracts . ReferenceSet {
return contracts . ReferenceSet { Slots : map [ string ] contracts . ResolvedReferenceSlot { ReferenceSlot : { Items : items } } }
}
2026-08-08 14:49:08 +00:00
func TestGroundingProjectsAndResolvesUniqueAndSameNameLocations ( t * testing . T ) {
doc := groundingDocument ( )
sharedName := "The Tavern"
firstRefs := [ ] source . SourceRef { { SourceID : doc . ID , StartUnitID : 20 , EndUnitID : 20 } , { SourceID : doc . ID , StartUnitID : 30 , EndUnitID : 30 } }
secondRefs := [ ] source . SourceRef { { SourceID : doc . ID , StartUnitID : 10 , EndUnitID : 10 } }
marketRefs := [ ] source . SourceRef { { SourceID : doc . ID , StartUnitID : 40 , EndUnitID : 40 } }
list := dnd . LocationRegistry { Locations : [ ] dnd . Location {
location ( sharedName , firstRefs ) ,
location ( sharedName , secondRefs ) ,
location ( "Market" , marketRefs ) ,
} }
registry := resolveList ( t , list )
grounding , err := NewGrounding ( registry , doc )
if err != nil {
t . Fatal ( err )
}
projection := decodeGroundingProjection ( t , grounding . PromptInput ( ) . Content )
if len ( projection . Locations ) != 3 {
t . Fatalf ( "projection = %#v" , projection )
}
first := projection . Locations [ 0 ]
if first . Name != sharedName || ! reflect . DeepEqual ( first . RegistryRefs , [ ] RegistryRef { { StartUnitID : 30 , EndUnitID : 30 } , { StartUnitID : 20 , EndUnitID : 20 } } ) || ! reflect . DeepEqual ( first . Context , [ ] ContextUnit { { UnitID : 30 , Text : "Tavern entrance" } , { UnitID : 20 , Text : "Tavern cellar" } } ) {
t . Fatalf ( "first contextual projection = %#v" , first )
}
second := projection . Locations [ 1 ]
if ! reflect . DeepEqual ( second . RegistryRefs , [ ] RegistryRef { { StartUnitID : 10 , EndUnitID : 10 } } ) || ! reflect . DeepEqual ( second . Context , [ ] ContextUnit { { UnitID : 10 , Text : "Tavern common room" } } ) {
t . Fatalf ( "second contextual projection = %#v" , second )
}
market := projection . Locations [ 2 ]
if market . Name != "Market" || len ( market . RegistryRefs ) != 0 || len ( market . Context ) != 0 {
t . Fatalf ( "unique-name projection = %#v" , market )
}
if bytes . Contains ( grounding . PromptInput ( ) . Content , [ ] byte ( "location:sha256:" ) ) || bytes . Contains ( grounding . PromptInput ( ) . Content , [ ] byte ( ` "source_id" ` ) ) {
t . Fatalf ( "grounding projection exposed durable identity: %s" , grounding . PromptInput ( ) . Content )
}
2026-08-08 15:47:41 +00:00
if grounding . PromptInput ( ) . Digest == "" || grounding . PromptInput ( ) . Digest == registry . IdentityDigest ( ) {
t . Fatalf ( "grounding/identity digests = %q/%q" , grounding . PromptInput ( ) . Digest , registry . IdentityDigest ( ) )
2026-08-08 14:49:08 +00:00
}
resolved , ok := grounding . Resolve ( Selector { Name : " the tavern " , RegistryRefs : [ ] RegistryRef { { StartUnitID : 30 , EndUnitID : 30 } , { StartUnitID : 20 , EndUnitID : 20 } } } )
if ! ok || resolved . ID != list . Locations [ 0 ] . ID || resolved . Name != sharedName {
t . Fatalf ( "Resolve(same name) = %#v, %t" , resolved , ok )
}
if resolved , ok = grounding . Resolve ( Selector { Name : "MARKET" , RegistryRefs : [ ] RegistryRef { } } ) ; ! ok || resolved . ID != list . Locations [ 2 ] . ID {
t . Fatalf ( "Resolve(unique name) = %#v, %t" , resolved , ok )
}
for _ , selector := range [ ] Selector {
{ Name : "Market" , RegistryRefs : [ ] RegistryRef { { StartUnitID : 40 , EndUnitID : 40 } } } ,
{ Name : sharedName , RegistryRefs : [ ] RegistryRef { } } ,
{ Name : sharedName , RegistryRefs : [ ] RegistryRef { { StartUnitID : 30 , EndUnitID : 30 } } } ,
{ Name : sharedName , RegistryRefs : [ ] RegistryRef { { StartUnitID : 20 , EndUnitID : 20 } , { StartUnitID : 30 , EndUnitID : 30 } } } ,
{ Name : "Unknown" , RegistryRefs : [ ] RegistryRef { } } ,
} {
if _ , ok := grounding . Resolve ( selector ) ; ok {
t . Fatalf ( "Resolve(%#v) accepted an unsupported selector" , selector )
}
}
input := grounding . PromptInput ( )
input . Content [ 0 ] = '['
resolved . SourceRefs [ 0 ] . SourceID = "changed"
if grounding . PromptInput ( ) . Content [ 0 ] != '{' {
t . Fatal ( "grounding prompt input was mutable" )
}
if next , ok := grounding . Resolve ( Selector { Name : sharedName , RegistryRefs : [ ] RegistryRef { { StartUnitID : 30 , EndUnitID : 30 } , { StartUnitID : 20 , EndUnitID : 20 } } } ) ; ! ok || next . SourceRefs [ 0 ] . SourceID != doc . ID {
t . Fatalf ( "grounding resolved location was mutable: %#v, %t" , next , ok )
}
}
func TestGroundingHandlesEmptyRegistriesAndIdentityFingerprints ( t * testing . T ) {
doc := groundingDocument ( )
empty := resolveList ( t , dnd . LocationRegistry { Locations : [ ] dnd . Location { } } )
grounding , err := NewGrounding ( empty , doc )
if err != nil {
t . Fatal ( err )
}
if projection := decodeGroundingProjection ( t , grounding . PromptInput ( ) . Content ) ; projection . Locations == nil || len ( projection . Locations ) != 0 {
t . Fatalf ( "empty projection = %#v" , projection )
}
if _ , ok := grounding . Resolve ( Selector { Name : "Market" , RegistryRefs : [ ] RegistryRef { } } ) ; ok {
t . Fatal ( "empty grounding resolved a location" )
}
baseRefs := [ ] source . SourceRef { { SourceID : doc . ID , StartUnitID : 10 , EndUnitID : 10 } }
base := resolveList ( t , dnd . LocationRegistry { Locations : [ ] dnd . Location { location ( "Market" , baseRefs ) } } )
expandedRefs := append ( append ( [ ] source . SourceRef ( nil ) , baseRefs ... ) , source . SourceRef { SourceID : doc . ID , StartUnitID : 40 , EndUnitID : 40 } )
expanded := resolveList ( t , dnd . LocationRegistry { Locations : [ ] dnd . Location { location ( "Market" , expandedRefs ) } } )
baseGrounding , err := NewGrounding ( base , doc )
if err != nil {
t . Fatal ( err )
}
expandedGrounding , err := NewGrounding ( expanded , doc )
if err != nil {
t . Fatal ( err )
}
2026-08-08 15:47:41 +00:00
if base . IdentityDigest ( ) != expanded . IdentityDigest ( ) || baseGrounding . PromptInput ( ) . Digest != expandedGrounding . PromptInput ( ) . Digest {
t . Fatalf ( "identity/model fingerprints = %q/%q and %q/%q" , base . IdentityDigest ( ) , expanded . IdentityDigest ( ) , baseGrounding . PromptInput ( ) . Digest , expandedGrounding . PromptInput ( ) . Digest )
2026-08-08 14:49:08 +00:00
}
}
func TestGroundingRejectsUnsafeContextualConstruction ( t * testing . T ) {
doc := groundingDocument ( )
validRefs := [ ] source . SourceRef { { SourceID : doc . ID , StartUnitID : 10 , EndUnitID : 10 } }
valid := resolveList ( t , dnd . LocationRegistry { Locations : [ ] dnd . Location { location ( "Market" , validRefs ) } } )
if _ , err := NewGrounding ( valid , nil ) ; err == nil {
t . Fatal ( "NewGrounding(nil source) error = nil" )
}
for _ , test := range [ ] struct {
name string
registry * Registry
} {
{
name : "foreign reference" ,
registry : resolveList ( t , dnd . LocationRegistry { Locations : [ ] dnd . Location {
location ( "The Tavern" , [ ] source . SourceRef { { SourceID : doc . ID , StartUnitID : 10 , EndUnitID : 10 } } ) ,
location ( "The Tavern" , [ ] source . SourceRef { { SourceID : "other-source" , StartUnitID : 10 , EndUnitID : 10 } } ) ,
} } ) ,
} ,
{
name : "invalid reference" ,
registry : resolveList ( t , dnd . LocationRegistry { Locations : [ ] dnd . Location {
location ( "The Tavern" , [ ] source . SourceRef { { SourceID : doc . ID , StartUnitID : 10 , EndUnitID : 10 } } ) ,
location ( "The Tavern" , [ ] source . SourceRef { { SourceID : doc . ID , StartUnitID : 999 , EndUnitID : 999 } } ) ,
} } ) ,
} ,
{
name : "selector collision" ,
registry : & Registry { list : dnd . LocationRegistry { Locations : [ ] dnd . Location {
{ ID : "first" , Name : "The Tavern" , SourceRefs : validRefs } ,
{ ID : "second" , Name : "The Tavern" , SourceRefs : validRefs } ,
} } } ,
} ,
{
name : "empty comparison name" ,
registry : & Registry { list : dnd . LocationRegistry { Locations : [ ] dnd . Location { { ID : "first" , Name : " " , SourceRefs : validRefs } } } } ,
} ,
} {
t . Run ( test . name , func ( t * testing . T ) {
if _ , err := NewGrounding ( test . registry , doc ) ; err == nil || strings . Contains ( err . Error ( ) , "Tavern entrance" ) {
t . Fatalf ( "NewGrounding() error = %v, want content-safe failure" , err )
}
} )
}
}
type decodedGroundingProjection struct {
Locations [ ] struct {
Name string ` json:"name" `
RegistryRefs [ ] RegistryRef ` json:"registry_refs" `
Context [ ] ContextUnit ` json:"context" `
} ` json:"locations" `
}
func decodeGroundingProjection ( t * testing . T , content [ ] byte ) decodedGroundingProjection {
t . Helper ( )
var projection decodedGroundingProjection
if err := json . Unmarshal ( content , & projection ) ; err != nil {
t . Fatal ( err )
}
return projection
}
func groundingDocument ( ) * source . SourceDocument {
return & source . SourceDocument { ID : "session-alpha" , Units : [ ] source . SourceUnit {
{ ID : 30 , Text : "Tavern entrance" } ,
{ ID : 10 , Text : "Tavern common room" } ,
{ ID : 20 , Text : "Tavern cellar" } ,
{ ID : 40 , Text : "Market square" } ,
} }
}
func location ( name string , refs [ ] source . SourceRef ) dnd . Location {
return dnd . Location { ID : identity . DeriveID ( name , refs ) , Name : name , SourceRefs : refs }
}