Ground location occurrences with contextual selectors
This commit is contained in:
@@ -14,19 +14,20 @@ import (
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
|
||||
locationcodec "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/codec/locationregistry"
|
||||
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/locations/identity"
|
||||
locationregistry "gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/locations/registry"
|
||||
)
|
||||
|
||||
func TestExtractMapsKindsOrdersOccurrencesAndPreservesIndependentFacts(t *testing.T) {
|
||||
locations := locationRegistry(t, "The Tavern", "The Tavern")
|
||||
first, second := locations.Locations[0], locations.Locations[1]
|
||||
client := &fakeOccurrencesLLMClient{response: extractionResponse{Occurrences: []occurrenceResponse{
|
||||
{LocationID: second.ID, Name: second.Name, Kind: "mentioned", SourceRefs: occurrenceRefs(30, 30)},
|
||||
{LocationID: first.ID, Name: first.Name, Kind: "mentioned", SourceRefs: occurrenceRefs(10, 10)},
|
||||
{LocationID: first.ID, Name: first.Name, Kind: "recalled", SourceRefs: occurrenceRefs(10, 10)},
|
||||
{LocationID: first.ID, Name: first.Name, Kind: "planned", SourceRefs: occurrenceRefs(10, 10)},
|
||||
{LocationID: first.ID, Name: first.Name, Kind: "visited", SourceRefs: append(occurrenceRefs(10, 10), occurrenceRefs(10, 10)...)},
|
||||
{LocationID: first.ID, Name: first.Name, Kind: "visited", SourceRefs: occurrenceRefs(20, 20)},
|
||||
{LocationID: first.ID, Name: first.Name, Kind: "visited", SourceRefs: occurrenceRefs(10, 10)},
|
||||
{Name: second.Name, RegistryRefs: registryRefs(second), Kind: "mentioned", SourceRefs: occurrenceRefs(30, 30)},
|
||||
{Name: first.Name, RegistryRefs: registryRefs(first), Kind: "mentioned", SourceRefs: occurrenceRefs(10, 10)},
|
||||
{Name: first.Name, RegistryRefs: registryRefs(first), Kind: "recalled", SourceRefs: occurrenceRefs(10, 10)},
|
||||
{Name: first.Name, RegistryRefs: registryRefs(first), Kind: "planned", SourceRefs: occurrenceRefs(10, 10)},
|
||||
{Name: first.Name, RegistryRefs: registryRefs(first), Kind: "visited", SourceRefs: append(occurrenceRefs(10, 10), occurrenceRefs(10, 10)...)},
|
||||
{Name: first.Name, RegistryRefs: registryRefs(first), Kind: "visited", SourceRefs: occurrenceRefs(20, 20)},
|
||||
{Name: first.Name, RegistryRefs: registryRefs(first), Kind: "visited", SourceRefs: occurrenceRefs(10, 10)},
|
||||
}}}
|
||||
references := registryReferences(t, locations)
|
||||
req := extractionRequest()
|
||||
@@ -50,11 +51,11 @@ func TestExtractMapsKindsOrdersOccurrencesAndPreservesIndependentFacts(t *testin
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractUsesIDsNamesAndCurrentTranscriptEvidenceOnly(t *testing.T) {
|
||||
func TestExtractResolvesContextualSelectorsAndUsesCurrentTranscriptEvidenceOnly(t *testing.T) {
|
||||
locations := locationRegistry(t, "The Tavern", "The Tavern")
|
||||
first, second := locations.Locations[0], locations.Locations[1]
|
||||
client := &fakeOccurrencesLLMClient{response: extractionResponse{Occurrences: []occurrenceResponse{{
|
||||
LocationID: second.ID, Name: second.Name, Kind: "visited", SourceRefs: occurrenceRefs(10, 10),
|
||||
Name: second.Name, RegistryRefs: registryRefs(second), Kind: "visited", SourceRefs: occurrenceRefs(10, 10),
|
||||
}}}}
|
||||
references := registryReferences(t, locations)
|
||||
req := extractionRequest()
|
||||
@@ -67,10 +68,10 @@ func TestExtractUsesIDsNamesAndCurrentTranscriptEvidenceOnly(t *testing.T) {
|
||||
t.Fatalf("occurrence = %#v", occurrence)
|
||||
}
|
||||
input := client.requests[0].Inputs[LocationRegistryReferenceSlot]
|
||||
if input.Name != LocationRegistryReferenceSlot || !strings.Contains(string(input.Content), first.ID) || !strings.Contains(string(input.Content), second.ID) {
|
||||
if input.Name != LocationRegistryReferenceSlot || !strings.Contains(string(input.Content), `"registry_refs":[{"start_unit_id":20,"end_unit_id":20}]`) {
|
||||
t.Fatalf("location prompt input = %#v", input)
|
||||
}
|
||||
for _, forbidden := range []string{"source_refs", "source_id", "other-session"} {
|
||||
for _, forbidden := range []string{"source_refs", "source_id", first.ID, second.ID} {
|
||||
if strings.Contains(string(input.Content), forbidden) {
|
||||
t.Fatalf("location prompt leaked %q: %s", forbidden, input.Content)
|
||||
}
|
||||
@@ -84,22 +85,46 @@ func TestExtractUsesIDsNamesAndCurrentTranscriptEvidenceOnly(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractPreservesUnknownOrMismatchedGroundingForValidators(t *testing.T) {
|
||||
func TestExtractRejectsUnknownMalformedOrMismatchedSelectorsAtomically(t *testing.T) {
|
||||
locations := locationRegistry(t, "The Mill")
|
||||
known := locations.Locations[0]
|
||||
client := &fakeOccurrencesLLMClient{response: extractionResponse{Occurrences: []occurrenceResponse{
|
||||
{LocationID: "location:sha256:unknown", Name: "The Mill", Kind: "mentioned", SourceRefs: occurrenceRefs(10, 10)},
|
||||
{LocationID: known.ID, Name: "A Different Mill", Kind: "mentioned", SourceRefs: occurrenceRefs(20, 20)},
|
||||
}}}
|
||||
references := registryReferences(t, locations)
|
||||
req := extractionRequest()
|
||||
req.References = references
|
||||
result, err := newExtractor(t, client, references).Extract(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
for _, occurrences := range [][]occurrenceResponse{
|
||||
{{Name: "Unknown", RegistryRefs: []locationregistry.RegistryRef{}, Kind: "mentioned", SourceRefs: occurrenceRefs(10, 10)}},
|
||||
{{Name: known.Name, RegistryRefs: []locationregistry.RegistryRef{{StartUnitID: 10, EndUnitID: 0}}, Kind: "mentioned", SourceRefs: occurrenceRefs(10, 10)}},
|
||||
{{Name: known.Name, RegistryRefs: []locationregistry.RegistryRef{{StartUnitID: 10, EndUnitID: 10}}, Kind: "mentioned", SourceRefs: occurrenceRefs(10, 10)}},
|
||||
{{Name: known.Name, RegistryRefs: []locationregistry.RegistryRef{}, Kind: "mentioned", SourceRefs: occurrenceRefs(10, 10)}, {Name: "Unknown", RegistryRefs: []locationregistry.RegistryRef{}, Kind: "mentioned", SourceRefs: occurrenceRefs(20, 20)}},
|
||||
} {
|
||||
client := &fakeOccurrencesLLMClient{response: extractionResponse{Occurrences: occurrences}}
|
||||
if result, err := newExtractor(t, client, references).Extract(context.Background(), req); err == nil || result.Value.Occurrences != nil || !strings.Contains(err.Error(), "location selector") {
|
||||
t.Fatalf("Extract() = %#v, %v", result, err)
|
||||
}
|
||||
}
|
||||
if result.Value.Occurrences[0].LocationID != "location:sha256:unknown" || result.Value.Occurrences[1].Name != "A Different Mill" {
|
||||
t.Fatalf("extractor repaired validator-owned grounding errors: %#v", result.Value.Occurrences)
|
||||
|
||||
sharedName := "The Tavern"
|
||||
firstRefs := []source.SourceRef{{SourceID: req.Source.ID, StartUnitID: 10, EndUnitID: 10}, {SourceID: req.Source.ID, StartUnitID: 20, EndUnitID: 20}}
|
||||
secondRefs := []source.SourceRef{{SourceID: req.Source.ID, StartUnitID: 30, EndUnitID: 30}}
|
||||
duplicateLocations := dnd.LocationRegistry{Locations: []dnd.Location{
|
||||
{ID: identity.DeriveID(sharedName, firstRefs), Name: sharedName, SourceRefs: firstRefs},
|
||||
{ID: identity.DeriveID(sharedName, secondRefs), Name: sharedName, SourceRefs: secondRefs},
|
||||
}}
|
||||
duplicateReferences := registryReferences(t, duplicateLocations)
|
||||
for _, selector := range []struct {
|
||||
name string
|
||||
refs []locationregistry.RegistryRef
|
||||
}{
|
||||
{name: sharedName, refs: []locationregistry.RegistryRef{}},
|
||||
{name: sharedName, refs: []locationregistry.RegistryRef{{StartUnitID: 10, EndUnitID: 10}}},
|
||||
{name: sharedName, refs: []locationregistry.RegistryRef{{StartUnitID: 20, EndUnitID: 20}, {StartUnitID: 10, EndUnitID: 10}}},
|
||||
} {
|
||||
client := &fakeOccurrencesLLMClient{response: extractionResponse{Occurrences: []occurrenceResponse{{Name: selector.name, RegistryRefs: selector.refs, Kind: "mentioned", SourceRefs: occurrenceRefs(10, 10)}}}}
|
||||
duplicateRequest := extractionRequest()
|
||||
duplicateRequest.References = duplicateReferences
|
||||
if _, err := newExtractor(t, client, duplicateReferences).Extract(context.Background(), duplicateRequest); err == nil || !strings.Contains(err.Error(), "location selector") {
|
||||
t.Fatalf("Extract(%#v) error = %v", selector, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,7 +150,7 @@ func TestExtractResolvesGeneratedRegistryAtOperationTimeAndDoesNotMutateResponse
|
||||
locations := locationRegistry(t, "The Mill")
|
||||
location := locations.Locations[0]
|
||||
client := &fakeOccurrencesLLMClient{response: extractionResponse{Occurrences: []occurrenceResponse{{
|
||||
LocationID: location.ID, Name: location.Name, Kind: "mentioned", SourceRefs: occurrenceRefs(30, 30),
|
||||
Name: location.Name, RegistryRefs: []locationregistry.RegistryRef{}, Kind: "mentioned", SourceRefs: occurrenceRefs(30, 30),
|
||||
}}}}
|
||||
references := registryReferences(t, locations)
|
||||
req := extractionRequest()
|
||||
@@ -135,7 +160,7 @@ func TestExtractResolvesGeneratedRegistryAtOperationTimeAndDoesNotMutateResponse
|
||||
if err != nil || result.Value.Occurrences[0].Name != "The Mill" {
|
||||
t.Fatalf("Extract() = %#v, %v", result, err)
|
||||
}
|
||||
if input := client.requests[0].Inputs[LocationRegistryReferenceSlot]; !strings.Contains(string(input.Content), location.ID) || input.OriginURI != "" {
|
||||
if input := client.requests[0].Inputs[LocationRegistryReferenceSlot]; strings.Contains(string(input.Content), location.ID) || input.OriginURI != "" {
|
||||
t.Fatalf("generated registry prompt input = %#v", input)
|
||||
}
|
||||
if _, ok := extractor.ManifestMetadata()["location_registry_digest"]; ok {
|
||||
@@ -215,21 +240,38 @@ func TestExtractorContractsMetadataAndFailures(t *testing.T) {
|
||||
t.Fatalf("metadata[%q] = %#v", key, metadata[key])
|
||||
}
|
||||
}
|
||||
if got := newExtractor(t, &fakeOccurrencesLLMClient{}, references).CheckpointFingerprints(); len(got) != 4 || got[3].Name != "location_registry" {
|
||||
if got := newExtractor(t, &fakeOccurrencesLLMClient{}, references).CheckpointFingerprints(); len(got) != 4 || got[3].Name != "location_registry" || got[3].Value != locationRegistryIdentityDigest(t, references) {
|
||||
t.Fatalf("fingerprints = %#v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func locationRegistryIdentityDigest(t *testing.T, references contracts.ReferenceSet) string {
|
||||
t.Helper()
|
||||
resolver, err := locationregistry.NewResolver(references)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return resolver.Seeded().IdentityDigest()
|
||||
}
|
||||
|
||||
func locationRegistry(t *testing.T, names ...string) dnd.LocationRegistry {
|
||||
t.Helper()
|
||||
locations := make([]dnd.Location, len(names))
|
||||
for index, name := range names {
|
||||
refs := []source.SourceRef{{SourceID: "other-session", StartUnitID: index + 1, EndUnitID: index + 1}}
|
||||
refs := []source.SourceRef{{SourceID: sourceDocument().ID, StartUnitID: (index + 1) * 10, EndUnitID: (index + 1) * 10}}
|
||||
locations[index] = dnd.Location{ID: identity.DeriveID(name, refs), Name: name, SourceRefs: refs}
|
||||
}
|
||||
return dnd.LocationRegistry{Locations: locations}
|
||||
}
|
||||
|
||||
func registryRefs(location dnd.Location) []locationregistry.RegistryRef {
|
||||
refs := make([]locationregistry.RegistryRef, len(location.SourceRefs))
|
||||
for index, ref := range location.SourceRefs {
|
||||
refs[index] = locationregistry.RegistryRef{StartUnitID: ref.StartUnitID, EndUnitID: ref.EndUnitID}
|
||||
}
|
||||
return refs
|
||||
}
|
||||
|
||||
func registryReferences(t *testing.T, locations dnd.LocationRegistry) contracts.ReferenceSet {
|
||||
t.Helper()
|
||||
content, err := locationcodec.New().Encode(locations)
|
||||
|
||||
Reference in New Issue
Block a user