Ground NPC occurrences by canonical names
This commit is contained in:
@@ -31,8 +31,8 @@ type Registry struct {
|
||||
canonical []byte
|
||||
digest string
|
||||
projectionDigest string
|
||||
identityDigest string
|
||||
promptInput contracts.LLMInputMaterial
|
||||
identityInput contracts.LLMInputMaterial
|
||||
lookupByKey map[string]int
|
||||
lookupByID map[string]int
|
||||
}
|
||||
@@ -110,8 +110,8 @@ func emptyRegistry() *Registry {
|
||||
list: dnd.NPCRegistry{NPCs: []dnd.NPC{}},
|
||||
canonical: append([]byte(nil), content...),
|
||||
projectionDigest: projectionDigest,
|
||||
identityDigest: projectionDigest,
|
||||
promptInput: contracts.NewLLMInputMaterial(ReferenceSlot, npccodec.MediaType, content, projectionDigest, ""),
|
||||
identityInput: contracts.NewLLMInputMaterial(ReferenceSlot, npccodec.MediaType, content, projectionDigest, ""),
|
||||
lookupByKey: map[string]int{},
|
||||
lookupByID: map[string]int{},
|
||||
}
|
||||
@@ -155,9 +155,9 @@ func loadRegistry(referenceContent []byte) (*Registry, error) {
|
||||
canonical: append([]byte(nil), content...),
|
||||
digest: digest,
|
||||
projectionDigest: projectionDigest,
|
||||
identityDigest: identityProjectionDigest,
|
||||
promptInput: contracts.NewLLMInputMaterial(ReferenceSlot, npccodec.MediaType, projection, projectionDigest, ""),
|
||||
lookupByKey: lookupByKey,
|
||||
identityInput: contracts.NewLLMInputMaterial(ReferenceSlot, npccodec.MediaType, identityProjection, identityProjectionDigest, ""),
|
||||
lookupByID: lookupByID,
|
||||
}, nil
|
||||
}
|
||||
@@ -207,6 +207,15 @@ func (r *Registry) ProjectionDigest() string {
|
||||
return r.projectionDigest
|
||||
}
|
||||
|
||||
// IdentityDigest returns the SHA-256 digest of the ordered ID/name identity
|
||||
// projection. It binds durable identities without exposing them to the model.
|
||||
func (r *Registry) IdentityDigest() string {
|
||||
if r == nil {
|
||||
return ""
|
||||
}
|
||||
return r.identityDigest
|
||||
}
|
||||
|
||||
// Count returns the number of validated NPC records.
|
||||
func (r *Registry) Count() int {
|
||||
if r == nil {
|
||||
@@ -224,15 +233,6 @@ func (r *Registry) PromptInput() contracts.LLMInputMaterial {
|
||||
return r.promptInput.Clone()
|
||||
}
|
||||
|
||||
// IdentityPromptInput returns the ordered ID/name projection for consumers
|
||||
// that must bind output records to exact registry identities.
|
||||
func (r *Registry) IdentityPromptInput() contracts.LLMInputMaterial {
|
||||
if r == nil {
|
||||
return contracts.LLMInputMaterial{}
|
||||
}
|
||||
return r.identityInput.Clone()
|
||||
}
|
||||
|
||||
// Lookup returns the canonical NPC for an exact canonical-name match under the
|
||||
// NPC identity comparison policy.
|
||||
func (r *Registry) Lookup(value string) (dnd.NPC, bool) {
|
||||
|
||||
@@ -19,7 +19,7 @@ func TestResolveUnboundRegistryHasExactEmptyProjection(t *testing.T) {
|
||||
t.Fatalf("Resolve() error = %v", err)
|
||||
}
|
||||
input := registry.PromptInput()
|
||||
if registry.Bound() || registry.Digest() != "" || registry.Count() != 0 || string(input.Content) != emptyPrompt || string(registry.IdentityPromptInput().Content) != emptyPrompt {
|
||||
if registry.Bound() || registry.Digest() != "" || registry.Count() != 0 || string(input.Content) != emptyPrompt || registry.IdentityDigest() != registry.ProjectionDigest() {
|
||||
t.Fatalf("registry = %#v input = %#v, want unbound empty registry", registry, input)
|
||||
}
|
||||
if registry.ProjectionDigest() == "" || input.Digest != registry.ProjectionDigest() || input.OriginURI != "" {
|
||||
@@ -46,12 +46,15 @@ func TestResolveKeepsDurableProvenanceAndProjectsOnlyOrderedNames(t *testing.T)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIdentityPromptInputProjectsOrderedIDsAndNames(t *testing.T) {
|
||||
func TestIdentityDigestTracksOrderedIDsAndNames(t *testing.T) {
|
||||
registry := resolveList(t, registryFixture())
|
||||
input := registry.IdentityPromptInput()
|
||||
projection, err := identityProjection(registryFixture())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := `{"npcs":[{"id":"` + identity.DeriveID("Mira Thorn") + `","name":"Mira Thorn"},{"id":"` + identity.DeriveID("Captain Vale") + `","name":"Captain Vale"}]}`
|
||||
if string(input.Content) != want || input.Digest == registry.ProjectionDigest() {
|
||||
t.Fatalf("identity projection = %#v, want %s", input, want)
|
||||
if string(projection) != want || registry.IdentityDigest() != semanticDigest(projection) || registry.IdentityDigest() == registry.ProjectionDigest() {
|
||||
t.Fatalf("identity digest = %q, want digest for %s", registry.IdentityDigest(), want)
|
||||
}
|
||||
if npc, ok := registry.LookupID(identity.DeriveID("Mira Thorn")); !ok || npc.Name != "Mira Thorn" {
|
||||
t.Fatalf("LookupID() = %#v, %t", npc, ok)
|
||||
@@ -213,7 +216,7 @@ func TestProjectionIsStableForEquivalentNormalizedRegistries(t *testing.T) {
|
||||
secondList := registryFixture()
|
||||
secondList.NPCs[0].SourceRefs = append(secondList.NPCs[0].SourceRefs, source.SourceRef{SourceID: "session-beta", StartUnitID: 8, EndUnitID: 8})
|
||||
second := resolveList(t, secondList)
|
||||
if !reflect.DeepEqual(first.PromptInput().Content, second.PromptInput().Content) || first.ProjectionDigest() != second.ProjectionDigest() || first.Digest() == second.Digest() {
|
||||
if !reflect.DeepEqual(first.PromptInput().Content, second.PromptInput().Content) || first.ProjectionDigest() != second.ProjectionDigest() || first.IdentityDigest() != second.IdentityDigest() || first.Digest() == second.Digest() {
|
||||
t.Fatalf("projection/full identity mismatch: %#v %#v", first, second)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user