package config import ( "bytes" "encoding/json" "errors" "fmt" "io" "os" "strings" ) // AgentEndpoint is deployment-only Dispatcher configuration. It is not a SaaS // command field and cannot be supplied by a tenant or a call task. type AgentEndpoint struct { AgentID string `json:"agent_id"` CellID string `json:"cell_id"` Address string `json:"address"` ServerName string `json:"server_name"` } // LoadAgentEndpoints reads the Dispatcher-owned endpoint inventory. Strict JSON // decoding prevents silently accepting misspelled authorization or identity // fields. func LoadAgentEndpoints(path string) ([]AgentEndpoint, error) { if strings.TrimSpace(path) == "" { return nil, nil } data, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("read Agent endpoint inventory: %w", err) } decoder := json.NewDecoder(bytes.NewReader(data)) decoder.DisallowUnknownFields() var endpoints []AgentEndpoint if err := decoder.Decode(&endpoints); err != nil { return nil, fmt.Errorf("decode Agent endpoint inventory: %w", err) } var trailing any if err := decoder.Decode(&trailing); !errors.Is(err, io.EOF) { if err == nil { return nil, errors.New("Agent endpoint inventory contains trailing JSON") } return nil, fmt.Errorf("read Agent endpoint inventory trailer: %w", err) } if len(endpoints) == 0 { return nil, errors.New("Agent endpoint inventory must not be empty") } seenAgents := make(map[string]struct{}, len(endpoints)) seenCells := make(map[string]struct{}, len(endpoints)) for index, endpoint := range endpoints { if strings.TrimSpace(endpoint.AgentID) == "" || strings.TrimSpace(endpoint.CellID) == "" || strings.TrimSpace(endpoint.Address) == "" || strings.TrimSpace(endpoint.ServerName) == "" { return nil, fmt.Errorf("Agent endpoint %d requires agent_id, cell_id, address, and server_name", index) } if _, exists := seenAgents[endpoint.AgentID]; exists { return nil, fmt.Errorf("duplicate Agent ID %q", endpoint.AgentID) } if _, exists := seenCells[endpoint.CellID]; exists { return nil, fmt.Errorf("duplicate Cell ID %q", endpoint.CellID) } seenAgents[endpoint.AgentID] = struct{}{} seenCells[endpoint.CellID] = struct{}{} } return endpoints, nil }