A plain Ruby policy object (no Pundit dependency) driven by the event_amigo_connectors join table, with a fallback so an event's creator is never locked out.

app/policies/event_policy.rb
Ruby
full file, 48 lines
# app/policies/event_policy.rb

class EventPolicy
  attr_reader :user, :record
  alias_method :amigo, :user

  def initialize(user, record)
    @user   = user
    @record = record
  end

  # --- top-level permissions (public) ---
  def create?  = user.present?
  def show?    = true
  def update?  = admin? || lead? || assistant?
  def destroy? = admin? || lead?

  # Connectors may be managed by admin, lead, or assistant
  def manage_connectors? = admin? || lead? || assistant?

  def manage_locations? = manage_connectors?

  # Roles may be changed only by admin or lead (NO assistant)
  def manage_roles? = admin? || lead?

  private

  def admin?
    amigo&.admin? || false
  end

  def connector
    return nil unless amigo && record
    @connector ||= record.event_amigo_connectors.find_by(amigo_id: amigo.id)
  end

  def lead?
    # Primary: check the connector join table (the authoritative source).
    # Fallback: compare directly against events.lead_coordinator_id so the
    # event creator is never locked out if their connector row is missing
    # (e.g. events created before the connector backfill).
    !!connector&.lead_coordinator? || record&.lead_coordinator_id == amigo&.id
  end

  def assistant?
    !!connector&.assistant_coordinator?
  end
end

One-liner endless-method definitions (Ruby 3) keep the permission matrix scannable at a glance.

# app/controllers/api/v1/events_controller.rb

create / update / destroy + authorize_event!
      # POST /api/v1/events
      def create
        policy = EventPolicy.new(current_amigo, nil)
        return render(json: { error: "Unauthorized" }, status: :unauthorized) unless policy.create?

        event = Events::CreateEvent.new.call(creator: current_amigo, attrs: event_params)
        render json: event, serializer: EventSerializer,
               include: [:primary_event_location], status: :created
      end

      # PATCH/PUT /api/v1/events/:id
      def update
        return unless authorize_event!(@event, :update?)

        Event.transaction do
          if params[:new_lead_coordinator_id].present?
            roles_policy = EventPolicy.new(current_amigo, @event)
            unless roles_policy.manage_roles?
              render json: { error: "Unauthorized" }, status: :unauthorized
              raise ActiveRecord::Rollback
            end
            # ...reassign lead_coordinator via the connector table
          end
          @event.update!(event_params)
        end
      end

      # DELETE /api/v1/events/:id
      def destroy
        return unless authorize_event!(@event, :destroy?)
        @event.destroy!
        render json: { message: "Event successfully deleted." }, status: :ok
      end

      private

      def authorize_event!(record, action)
        policy = EventPolicy.new(current_amigo, record)
        return true if policy.public_send(action)

        render json: { error: "Unauthorized" }, status: :unauthorized
        false
      end

A single authorize_event! helper checks the right predicate per action; reassigning the lead coordinator gets its own stricter check inline.