From 14fd53cde6f10849686f72ec41c3adec88d1a75d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 31 Aug 2026 15:31:59 +0000 Subject: [PATCH 1/3] Initial plan From e54c7abb5574d56f8dc6b74f659d7d646f8cb1f3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 31 Aug 2026 15:35:22 +0000 Subject: [PATCH 2/3] Refactor duplicate validation helpers Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --- internal/config/agent_policy.go | 20 +++++++++----------- internal/config/validation_gateway.go | 8 ++------ internal/util/collections.go | 13 +++++++++++++ internal/util/collections_test.go | 25 +++++++++++++++++++++++++ 4 files changed, 49 insertions(+), 17 deletions(-) diff --git a/internal/config/agent_policy.go b/internal/config/agent_policy.go index 05dd4b477..c66dea053 100644 --- a/internal/config/agent_policy.go +++ b/internal/config/agent_policy.go @@ -165,7 +165,7 @@ func validateAgentPolicies(cfg *Config) error { // per-server tool allowlist, and any allow-only guard policy. func validateSingleAgentPolicy(policyID string, policy *AgentPolicy, servers map[string]*ServerConfig) error { formattedPolicyID := util.HashIdentifierForLog(policyID) - seenServers := make(map[string]struct{}, len(policy.Servers)) + serverSet := make(map[string]struct{}, len(policy.Servers)) for _, serverID := range policy.Servers { trimmed := strings.TrimSpace(serverID) if trimmed == "" { @@ -177,20 +177,19 @@ func validateSingleAgentPolicy(policyID string, policy *AgentPolicy, servers map if _, ok := servers[trimmed]; !ok { return fmt.Errorf("gateway.agent_policies[%q].servers references unknown server %q", formattedPolicyID, trimmed) } - if _, dup := seenServers[trimmed]; dup { - return fmt.Errorf("gateway.agent_policies[%q].servers must not contain duplicate server %q", formattedPolicyID, trimmed) - } - seenServers[trimmed] = struct{}{} + serverSet[trimmed] = struct{}{} + } + if duplicate, found := util.FindDuplicate(policy.Servers); found { + return fmt.Errorf("gateway.agent_policies[%q].servers must not contain duplicate server %q", formattedPolicyID, duplicate) } for serverID, tools := range policy.Tools { if strings.TrimSpace(serverID) != serverID { return fmt.Errorf("gateway.agent_policies[%q].tools server keys must not contain surrounding whitespace", formattedPolicyID) } - if _, ok := seenServers[serverID]; !ok { + if _, ok := serverSet[serverID]; !ok { return fmt.Errorf("gateway.agent_policies[%q].tools references server %q that is not in the policy's servers list", formattedPolicyID, serverID) } - seenTools := make(map[string]struct{}, len(tools)) for _, toolName := range tools { trimmed := strings.TrimSpace(toolName) if trimmed == "" { @@ -199,10 +198,9 @@ func validateSingleAgentPolicy(policyID string, policy *AgentPolicy, servers map if trimmed != toolName { return fmt.Errorf("gateway.agent_policies[%q].tools[%q] entries must not contain surrounding whitespace", formattedPolicyID, serverID) } - if _, dup := seenTools[trimmed]; dup { - return fmt.Errorf("gateway.agent_policies[%q].tools[%q] must not contain duplicate tool %q", formattedPolicyID, serverID, trimmed) - } - seenTools[trimmed] = struct{}{} + } + if duplicate, found := util.FindDuplicate(tools); found { + return fmt.Errorf("gateway.agent_policies[%q].tools[%q] must not contain duplicate tool %q", formattedPolicyID, serverID, duplicate) } } diff --git a/internal/config/validation_gateway.go b/internal/config/validation_gateway.go index 0021e9642..c08069988 100644 --- a/internal/config/validation_gateway.go +++ b/internal/config/validation_gateway.go @@ -133,12 +133,8 @@ func validateAgentIDs(agentIDs []string, defined bool, fieldName string) error { } // Reject duplicate agent IDs: each identity must be unique so per-agent policy, // session, and DIFC state can be attributed deterministically. - seen := make(map[string]struct{}, len(agentIDs)) - for _, id := range agentIDs { - if _, dup := seen[id]; dup { - return fmt.Errorf("gateway.%s must not contain duplicate agent ID %q", fieldName, util.HashIdentifierForLog(id)) - } - seen[id] = struct{}{} + if duplicate, found := util.FindDuplicate(agentIDs); found { + return fmt.Errorf("gateway.%s must not contain duplicate agent ID %q", fieldName, util.HashIdentifierForLog(duplicate)) } return nil } diff --git a/internal/util/collections.go b/internal/util/collections.go index a4cf048ca..ea88cb6e3 100644 --- a/internal/util/collections.go +++ b/internal/util/collections.go @@ -64,6 +64,19 @@ func DeduplicateStrings(input []string, sorted bool) []string { return out } +// FindDuplicate returns the first item that occurs more than once, if any. +func FindDuplicate[T comparable](items []T) (T, bool) { + seen := make(map[T]struct{}, len(items)) + for _, item := range items { + if _, exists := seen[item]; exists { + return item, true + } + seen[item] = struct{}{} + } + var zero T + return zero, false +} + // StringsToAny converts a []string to []any. func StringsToAny(input []string) []any { out := make([]any, len(input)) diff --git a/internal/util/collections_test.go b/internal/util/collections_test.go index 9b3d72095..8a2b3366b 100644 --- a/internal/util/collections_test.go +++ b/internal/util/collections_test.go @@ -204,6 +204,31 @@ func TestDeduplicateStrings(t *testing.T) { } } +func TestFindDuplicate(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + items []int + duplicate int + found bool + }{ + {name: "nil items"}, + {name: "unique items", items: []int{1, 2, 3}}, + {name: "finds first duplicate", items: []int{1, 2, 1, 2}, duplicate: 1, found: true}, + {name: "finds zero duplicate", items: []int{1, 0, 2, 0}, duplicate: 0, found: true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + duplicate, found := FindDuplicate(tt.items) + assert.Equal(t, tt.found, found) + assert.Equal(t, tt.duplicate, duplicate) + }) + } +} + func TestStringsToAny(t *testing.T) { t.Parallel() From e09014f8ce5a8d34e5aedc21fda81ba477e31aaa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 31 Aug 2026 15:39:56 +0000 Subject: [PATCH 3/3] Preserve normalized duplicate errors Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --- internal/config/agent_policy.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/internal/config/agent_policy.go b/internal/config/agent_policy.go index c66dea053..af70e7575 100644 --- a/internal/config/agent_policy.go +++ b/internal/config/agent_policy.go @@ -166,6 +166,7 @@ func validateAgentPolicies(cfg *Config) error { func validateSingleAgentPolicy(policyID string, policy *AgentPolicy, servers map[string]*ServerConfig) error { formattedPolicyID := util.HashIdentifierForLog(policyID) serverSet := make(map[string]struct{}, len(policy.Servers)) + serverIDs := make([]string, 0, len(policy.Servers)) for _, serverID := range policy.Servers { trimmed := strings.TrimSpace(serverID) if trimmed == "" { @@ -178,8 +179,9 @@ func validateSingleAgentPolicy(policyID string, policy *AgentPolicy, servers map return fmt.Errorf("gateway.agent_policies[%q].servers references unknown server %q", formattedPolicyID, trimmed) } serverSet[trimmed] = struct{}{} + serverIDs = append(serverIDs, trimmed) } - if duplicate, found := util.FindDuplicate(policy.Servers); found { + if duplicate, found := util.FindDuplicate(serverIDs); found { return fmt.Errorf("gateway.agent_policies[%q].servers must not contain duplicate server %q", formattedPolicyID, duplicate) } @@ -190,6 +192,7 @@ func validateSingleAgentPolicy(policyID string, policy *AgentPolicy, servers map if _, ok := serverSet[serverID]; !ok { return fmt.Errorf("gateway.agent_policies[%q].tools references server %q that is not in the policy's servers list", formattedPolicyID, serverID) } + toolNames := make([]string, 0, len(tools)) for _, toolName := range tools { trimmed := strings.TrimSpace(toolName) if trimmed == "" { @@ -198,8 +201,9 @@ func validateSingleAgentPolicy(policyID string, policy *AgentPolicy, servers map if trimmed != toolName { return fmt.Errorf("gateway.agent_policies[%q].tools[%q] entries must not contain surrounding whitespace", formattedPolicyID, serverID) } + toolNames = append(toolNames, trimmed) } - if duplicate, found := util.FindDuplicate(tools); found { + if duplicate, found := util.FindDuplicate(toolNames); found { return fmt.Errorf("gateway.agent_policies[%q].tools[%q] must not contain duplicate tool %q", formattedPolicyID, serverID, duplicate) } }