Derive independent glossary context and protection

This commit is contained in:
2026-11-10 17:25:08 +00:00
parent d42e2095d7
commit 89de2a7014
23 changed files with 364 additions and 173 deletions

View File

@@ -0,0 +1,39 @@
// Package glossary derives consumer views from resolved configuration.
package glossary
import "gitea.maximumdirect.net/eric/audita/v3/internal/core/config"
// Entry is the known glossary content and its collection-scoped identity.
type Entry struct {
Collection string `json:"-"`
ID string `json:"id"`
Name string `json:"name"`
Category string `json:"category"`
Description string `json:"description,omitempty"`
Aliases []string `json:"aliases,omitempty"`
Plural *string `json:"plural,omitempty"`
}
type identity struct{ collection, id string }
// Select retains the first occurrence of each selected collection entry.
func Select(collections map[string]config.GlossaryCollection, names []string) []Entry {
seen := make(map[identity]struct{})
var selected []Entry
for _, name := range names {
for _, item := range collections[name].Entries {
key := identity{collection: name, id: item.ID}
if _, exists := seen[key]; exists {
continue
}
seen[key] = struct{}{}
entry := Entry{Collection: name, ID: item.ID, Name: item.Name, Category: item.Category, Description: item.Description, Aliases: append([]string(nil), item.Aliases...)}
if item.Plural != nil {
plural := *item.Plural
entry.Plural = &plural
}
selected = append(selected, entry)
}
}
return selected
}

View File

@@ -0,0 +1,33 @@
package glossary
import (
"encoding/json"
"strings"
"testing"
"gitea.maximumdirect.net/eric/audita/v3/internal/core/config"
)
func TestSelectionRetainsDistinctIdentitiesAndDoesNotExposeExtensions(t *testing.T) {
plural := "Shields"
collections := map[string]config.GlossaryCollection{
"spells": {Entries: []config.GlossaryEntry{{ID: "shield", Name: "Shield", Category: "spell", Aliases: []string{"Ward"}, Plural: &plural, Extra: map[string]any{"private": "do not forward"}}}},
"items": {Entries: []config.GlossaryEntry{{ID: "shield", Name: "Shield", Category: "item"}}},
}
result := Select(collections, []string{"spells", "items", "spells"})
if len(result) != 2 || result[0].Collection != "spells" || result[1].Collection != "items" {
t.Fatalf("entries=%+v", result)
}
raw, err := json.Marshal(result)
if err != nil {
t.Fatal(err)
}
if strings.Contains(string(raw), "do not forward") {
t.Fatal("extension leaked into prompt projection")
}
result[0].Aliases[0] = "changed"
*result[0].Plural = "changed"
if collections["spells"].Entries[0].Aliases[0] != "Ward" || *collections["spells"].Entries[0].Plural != "Shields" {
t.Fatal("projection changed configuration")
}
}

View File

@@ -0,0 +1,71 @@
package glossary
import "strings"
// Identity identifies an entry within its configured collection.
type Identity struct {
Collection string
ID string
}
// Term retains every entry that contributes a recognized spelling.
type Term struct {
Spelling string
Identities []Identity
}
// Vocabulary derives recognized spellings in selection order. Excluded entries
// remove matching spellings regardless of which protected entries supplied them.
func Vocabulary(entries, excluded []Entry) []Term {
exclusions := make(map[string]struct{})
for _, entry := range excluded {
for _, spelling := range spellings(entry) {
exclusions[strings.ToLower(spelling)] = struct{}{}
}
}
indices := make(map[string]int)
var terms []Term
for _, entry := range entries {
id := Identity{Collection: entry.Collection, ID: entry.ID}
for _, spelling := range spellings(entry) {
key := strings.ToLower(spelling)
if _, excluded := exclusions[key]; excluded {
continue
}
index, exists := indices[key]
if !exists {
index = len(terms)
indices[key] = index
terms = append(terms, Term{Spelling: spelling})
}
identities := terms[index].Identities
found := false
for _, existing := range identities {
if existing == id {
found = true
break
}
}
if !found {
terms[index].Identities = append(identities, id)
}
}
}
return terms
}
func spellings(entry Entry) []string {
var result []string
for _, value := range append([]string{entry.Name}, entry.Aliases...) {
value = strings.TrimSpace(value)
if value != "" {
result = append(result, value, value+"s")
}
}
if entry.Plural != nil {
if plural := strings.TrimSpace(*entry.Plural); plural != "" {
result = append(result, plural)
}
}
return result
}

View File

@@ -0,0 +1,30 @@
package glossary
import (
"reflect"
"testing"
)
func TestVocabularyKeepsContributorsAndExemptsOnlyActiveSpellings(t *testing.T) {
plural := "Aegides"
active := []Entry{{Collection: "spells", ID: "shield", Name: "Shield", Aliases: []string{"Ward"}}}
protected := []Entry{
active[0],
{Collection: "items", ID: "shield", Name: "shield", Aliases: []string{"Aegis"}, Plural: &plural},
}
full := Vocabulary(protected, nil)
if !reflect.DeepEqual(full[0].Identities, []Identity{{Collection: "spells", ID: "shield"}, {Collection: "items", ID: "shield"}}) {
t.Fatalf("shared spelling contributors: %+v", full[0])
}
reduced := Vocabulary(protected, active)
var got []string
for _, term := range reduced {
got = append(got, term.Spelling)
}
if !reflect.DeepEqual(got, []string{"Aegis", "Aegiss", "Aegides"}) {
t.Fatalf("remaining spellings: %v", got)
}
if !reflect.DeepEqual(full, Vocabulary(protected, nil)) || protected[0].Aliases[0] != "Ward" {
t.Fatal("deriving an exemption changed another view or its inputs")
}
}