How do Sensu handler notification plugins determine the path of their JSON config files?
Okay, so it turns out this wasn't my problem, but I know the answer to the question I asked:

The file /opt/sensu/embedded/lib/ruby/gems/2.4.0/gems/sensu-plugins-microsoft-teams-2.0.0/bin/handler-microsoft-teams.rb defines a function get_setting, which looks like this:

def get_setting(name)
  settings[config[:json_config]][name]
end


config[:json_config] is the string passed with the -j switch.

On to config: the file /opt/sensu/embedded/lib/ruby/gems/2.4.0/gems/sensu-plugin-2.7.1/lib/sensu-handler.rb requires sensu-plugin/utils, which defines the following:

module Sensu
  module Plugin
    module Utils # rubocop:disable Metrics/ModuleLength
      def config_files
        if ENV['SENSU_LOADED_TEMPFILE'] &amp;&amp; File.file?(ENV['SENSU_LOADED_TEMPFILE'])
          IO.read(ENV['SENSU_LOADED_TEMPFILE']).split(':')
        elsif ENV['SENSU_CONFIG_FILES']
          ENV['SENSU_CONFIG_FILES'].split(':')
        else
          ['/etc/sensu/config.json'] + Dir['/etc/sensu/conf.d/**/*.json']
        end
      end

      def load_config(filename)
        JSON.parse(File.open(filename, 'r').read)
      rescue
        {}
      end

      def settings
        @settings ||= config_files.map { |f| load_config(f) }.reduce { |a, b| deep_merge(a, b) }
      end
...


So settings is going to load all JSON files nested anywhere under /etc/sensu/conf.d/ and deep-merge them. Beyond that, the path is not relevant to Sensu's ability to load it using the string slack; that's the name of the entry in the JSON payload. This mechanism is common to all Sensu config, not just handler plugins. Documentation is here: https://docs.sensu.io/sensu-core/1.8/reference/configuration/.

I looked at my log files, and it does seem to be finding and loading the files I had optimistically placed under /etc/sensu/conf.d/teams.d/, but it's not working.

Before I found the answer documented here, I took a stab and renamed the directory from teams.d to microsoft-teams.d, and the configuration object defined within from teams to microsoft-teams. I don't know why that would have had any effect, but it's working now. Maybe I had fat-fingered or copypasta'd something and resolved that inadvertently in the process of adding the microsoft prefix.