access-engine/internal/adapters/directory/enrichment.go
tegwick f758381cea
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 6s
Build and Publish Container Image / build-and-push (push) Successful in 1m23s
FLEX-WP-0036: Go module is github.com/netkingdom/access-engine; T02 consumer inventory
- module path renamed (no external importers); golangci local-prefixes
- ACCESS_ENGINE_RUN_TOPAZ_INTEGRATION replaces FLEX_AUTH_RUN_TOPAZ_INTEGRATION
- README title and identity paragraph reflect the landed renames
- docs/runtime-rename-inventory.md: per-consumer references by kind and order
- chart name, image package and /opt paths deliberately left for T05

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 735967@bnt-lap001
Assistant-Session: 5971967f-77f8-4ced-9e02-c6ef67040ffc
2026-10-10 15:29:00 +02:00

96 lines
2.6 KiB
Go

package directory
import (
"sort"
"github.com/netkingdom/access-engine/pkg/api"
)
// MergeResults combines resolver results while keeping source metadata.
func MergeResults(subject api.SubjectRef, results ...ResolveResult) SubjectEnrichment {
groups := map[string]struct{}{}
roles := map[api.CanonicalRole]struct{}{}
metadata := map[string]any{}
enrichment := SubjectEnrichment{
Subject: subject,
Metadata: metadata,
}
for _, result := range results {
for _, group := range result.Groups {
groups[group.ID] = struct{}{}
if group.Descriptor != nil {
enrichment.Descriptors = append(enrichment.Descriptors, *group.Descriptor)
}
}
for _, role := range result.Roles {
roles[role.Role] = struct{}{}
}
if !result.Freshness.RetrievedAt.IsZero() || result.Freshness.Source != "" {
enrichment.Freshness = append(enrichment.Freshness, result.Freshness)
}
if result.Overage.Detected {
enrichment.Overage = append(enrichment.Overage, result.Overage)
}
if result.Source != "" {
metadata["source:"+string(result.Source)] = true
}
}
enrichment.Groups = sortedStringKeys(groups)
enrichment.Roles = sortedRoleKeys(roles)
return enrichment
}
// ApplyToSubject returns a subject with resolved groups, roles, and metadata.
func ApplyToSubject(subject api.Subject, enrichment SubjectEnrichment) api.Subject {
out := subject
out.Groups = mergeStrings(out.Groups, enrichment.Groups)
out.Roles = mergeRoles(out.Roles, enrichment.Roles)
if out.Metadata == nil {
out.Metadata = map[string]any{}
}
out.Metadata["directory_freshness"] = enrichment.Freshness
out.Metadata["directory_overage"] = enrichment.Overage
return out
}
func mergeStrings(a, b []string) []string {
items := map[string]struct{}{}
for _, value := range a {
items[value] = struct{}{}
}
for _, value := range b {
items[value] = struct{}{}
}
return sortedStringKeys(items)
}
func mergeRoles(a, b []api.CanonicalRole) []api.CanonicalRole {
items := map[api.CanonicalRole]struct{}{}
for _, value := range a {
items[value] = struct{}{}
}
for _, value := range b {
items[value] = struct{}{}
}
return sortedRoleKeys(items)
}
func sortedStringKeys(items map[string]struct{}) []string {
keys := make([]string, 0, len(items))
for key := range items {
keys = append(keys, key)
}
sort.Strings(keys)
return keys
}
func sortedRoleKeys(items map[api.CanonicalRole]struct{}) []api.CanonicalRole {
keys := make([]api.CanonicalRole, 0, len(items))
for key := range items {
keys = append(keys, key)
}
sort.Slice(keys, func(i, j int) bool { return keys[i] < keys[j] })
return keys
}