Files
multica/server/pkg/db/generated/squad.sql.go
Jiayuan Zhang fc8528d64d feat(autopilot): support assigning to a squad (MUL-2429) (#2888)
* feat(autopilot): support assigning autopilot to a squad (MUL-2429)

Path A (Squad-as-Leader) from the RFC: when an autopilot's assignee is a
squad, dispatch resolves to squad.leader_id and executes against the
leader's runtime — semantics match a human manually assigning the issue
to that squad, no fan-out.

Backend scope only; frontend picker change is a follow-up PR.

Changes:
- 096_autopilot_squad_assignee migration: drop agent FK on
  autopilot.assignee_id, add assignee_type column (default 'agent'),
  add autopilot_run.squad_id attribution column.
- service.AgentReadiness: single source of truth for archived /
  runtime-bound / runtime-online checks. Shared by autopilot
  admission gate, run_only dispatch, and isSquadLeaderReady.
- service.resolveAutopilotLeader: translates assignee_type/id to the
  agent that actually runs the work.
- dispatchCreateIssue: stamps issue with assignee_type='squad' for
  squad autopilots and enqueues via EnqueueTaskForSquadLeader.
- dispatchRunOnly: belt-and-braces readiness re-check after resolving
  squad → leader so a leader that went offline between admission and
  dispatch produces a clean failure instead of a doomed task.
- handler.CreateAutopilot / UpdateAutopilot: accept assignee_type with
  squad/agent existence + leader-archived validation. Backward-compatible
  default of "agent" preserves the contract for older clients.
- Analytics: AutopilotRunStarted/Completed/Failed events carry
  assignee_type and squad_id; PostHog can now group autopilot runs by
  squad without joining back to the autopilot row.

Co-authored-by: multica-agent <github@multica.ai>

* fix(autopilot): reject archived squads, route post-admission skips, cleanup dangling-agent autopilots (MUL-2429)

Addresses three review findings on PR #2888:

1. Archived squad handling: validateAutopilotAssignee now rejects squads
   with archived_at set; resolveAutopilotLeader returns errSquadArchived
   so the admission gate fails closed; DeleteSquad now mirrors the issue
   transfer for autopilot rows (TransferSquadAutopilotsToLeader) so
   surviving autopilots flip to assignee_type='agent' (leader) instead
   of dangling at the archived squad.

2. dispatchRunOnly post-admission readiness: introduces errDispatchSkipped
   sentinel, recognised by DispatchAutopilot via handleDispatchSkip so
   the run is recorded as `skipped` (not `failed`). Manual triggers no
   longer 500 when the leader's runtime goes offline between admission
   and task creation. New TestManualTriggerDoesNotErrorOnPostAdmissionSkip
   locks the behaviour in.

3. Dangling agent assignee after migration 096 dropped the FK:
   shouldSkipDispatch now distinguishes pgx.ErrNoRows / errSquadArchived
   (hard skip — retrying won't help) from transient DB errors
   (fail-open). DeleteAgentRuntime pauses autopilots that target agents
   about to be hard-deleted (ListArchivedAgentIDsByRuntime +
   PauseAutopilotsByAgentAssignees) so the breakage surfaces as a paused
   row in the UI instead of a quiet skip-burning loop.

Unit tests cover the sentinel unwrap contract and errSquadArchived
errors.Is behaviour. Integration test
TestAutopilotDispatchSkipsWhenRuntimeOffline re-verified against a fresh
DB with migration 096 applied.

Co-authored-by: multica-agent <github@multica.ai>

* fix(autopilot): bump last_run_at on post-admission skip (MUL-2429)

Match recordSkippedRun (pre-flight skip) and the success path so the
scheduler / "last seen" UI both reflect that this tick evaluated the
trigger, even when the post-admission readiness gate caught a late
regression.

Addresses Emacs review caveat #1 on PR #2888.

Co-authored-by: multica-agent <github@multica.ai>

* feat(autopilot): mixed agent/squad assignee picker in dialog (MUL-2429)

End-to-end UI for assigning an autopilot to a squad. Closes the PR #2888
backend gap: the squad-as-assignee feature was already wired in Go (Path A,
RFC §4) but the desktop dialog never offered the choice.

- core/types/autopilot: add `AutopilotAssigneeType`, surface
  `assignee_type` on `Autopilot` + Create/Update request payloads.
- views/autopilots/pickers/agent-picker: switch to a polymorphic
  AssigneeSelection (`{type, id}`); render agents and squads as two
  grouped sections with shared pinyin search.
- views/autopilots/autopilot-dialog: maintain `assigneeType` state, send
  it on create/update, render the trigger avatar / hover dot with
  `assignee.type`.
- views/autopilots/autopilots-page + autopilot-detail-page: render the
  assignee row using `autopilot.assignee_type` so squad-typed autopilots
  show the squad avatar + name, not a broken agent lookup.
- locales: add `agents_group` / `squads_group` / `select_assignee` keys
  (en + zh-Hans), keep legacy `select_agent` for callers that still
  reference it.

Co-authored-by: multica-agent <github@multica.ai>

---------

Co-authored-by: Lambda <lambda@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-05-20 05:30:13 +02:00

608 lines
17 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.30.0
// source: squad.sql
package db
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
)
const addSquadMember = `-- name: AddSquadMember :one
INSERT INTO squad_member (squad_id, member_type, member_id, role)
VALUES ($1, $2, $3, $4)
RETURNING id, squad_id, member_type, member_id, role, created_at
`
type AddSquadMemberParams struct {
SquadID pgtype.UUID `json:"squad_id"`
MemberType string `json:"member_type"`
MemberID pgtype.UUID `json:"member_id"`
Role string `json:"role"`
}
func (q *Queries) AddSquadMember(ctx context.Context, arg AddSquadMemberParams) (SquadMember, error) {
row := q.db.QueryRow(ctx, addSquadMember,
arg.SquadID,
arg.MemberType,
arg.MemberID,
arg.Role,
)
var i SquadMember
err := row.Scan(
&i.ID,
&i.SquadID,
&i.MemberType,
&i.MemberID,
&i.Role,
&i.CreatedAt,
)
return i, err
}
const archiveSquad = `-- name: ArchiveSquad :one
UPDATE squad SET archived_at = now(), archived_by = $2, updated_at = now()
WHERE id = $1
RETURNING id, workspace_id, name, description, leader_id, creator_id, created_at, updated_at, archived_at, archived_by, avatar_url, instructions
`
type ArchiveSquadParams struct {
ID pgtype.UUID `json:"id"`
ArchivedBy pgtype.UUID `json:"archived_by"`
}
func (q *Queries) ArchiveSquad(ctx context.Context, arg ArchiveSquadParams) (Squad, error) {
row := q.db.QueryRow(ctx, archiveSquad, arg.ID, arg.ArchivedBy)
var i Squad
err := row.Scan(
&i.ID,
&i.WorkspaceID,
&i.Name,
&i.Description,
&i.LeaderID,
&i.CreatorID,
&i.CreatedAt,
&i.UpdatedAt,
&i.ArchivedAt,
&i.ArchivedBy,
&i.AvatarUrl,
&i.Instructions,
)
return i, err
}
const countSquadMembers = `-- name: CountSquadMembers :one
SELECT count(*) FROM squad_member WHERE squad_id = $1
`
func (q *Queries) CountSquadMembers(ctx context.Context, squadID pgtype.UUID) (int64, error) {
row := q.db.QueryRow(ctx, countSquadMembers, squadID)
var count int64
err := row.Scan(&count)
return count, err
}
const createSquad = `-- name: CreateSquad :one
INSERT INTO squad (workspace_id, name, description, leader_id, creator_id, avatar_url)
VALUES ($1, $2, $3, $4, $5, $6)
RETURNING id, workspace_id, name, description, leader_id, creator_id, created_at, updated_at, archived_at, archived_by, avatar_url, instructions
`
type CreateSquadParams struct {
WorkspaceID pgtype.UUID `json:"workspace_id"`
Name string `json:"name"`
Description string `json:"description"`
LeaderID pgtype.UUID `json:"leader_id"`
CreatorID pgtype.UUID `json:"creator_id"`
AvatarUrl pgtype.Text `json:"avatar_url"`
}
func (q *Queries) CreateSquad(ctx context.Context, arg CreateSquadParams) (Squad, error) {
row := q.db.QueryRow(ctx, createSquad,
arg.WorkspaceID,
arg.Name,
arg.Description,
arg.LeaderID,
arg.CreatorID,
arg.AvatarUrl,
)
var i Squad
err := row.Scan(
&i.ID,
&i.WorkspaceID,
&i.Name,
&i.Description,
&i.LeaderID,
&i.CreatorID,
&i.CreatedAt,
&i.UpdatedAt,
&i.ArchivedAt,
&i.ArchivedBy,
&i.AvatarUrl,
&i.Instructions,
)
return i, err
}
const getSquad = `-- name: GetSquad :one
SELECT id, workspace_id, name, description, leader_id, creator_id, created_at, updated_at, archived_at, archived_by, avatar_url, instructions FROM squad WHERE id = $1
`
func (q *Queries) GetSquad(ctx context.Context, id pgtype.UUID) (Squad, error) {
row := q.db.QueryRow(ctx, getSquad, id)
var i Squad
err := row.Scan(
&i.ID,
&i.WorkspaceID,
&i.Name,
&i.Description,
&i.LeaderID,
&i.CreatorID,
&i.CreatedAt,
&i.UpdatedAt,
&i.ArchivedAt,
&i.ArchivedBy,
&i.AvatarUrl,
&i.Instructions,
)
return i, err
}
const getSquadByAssignee = `-- name: GetSquadByAssignee :one
SELECT s.id, s.workspace_id, s.name, s.description, s.leader_id, s.creator_id, s.created_at, s.updated_at, s.archived_at, s.archived_by, s.avatar_url, s.instructions FROM squad s WHERE s.id = $1 AND s.workspace_id = $2
`
type GetSquadByAssigneeParams struct {
ID pgtype.UUID `json:"id"`
WorkspaceID pgtype.UUID `json:"workspace_id"`
}
// Look up the squad when an issue is assigned to a squad.
func (q *Queries) GetSquadByAssignee(ctx context.Context, arg GetSquadByAssigneeParams) (Squad, error) {
row := q.db.QueryRow(ctx, getSquadByAssignee, arg.ID, arg.WorkspaceID)
var i Squad
err := row.Scan(
&i.ID,
&i.WorkspaceID,
&i.Name,
&i.Description,
&i.LeaderID,
&i.CreatorID,
&i.CreatedAt,
&i.UpdatedAt,
&i.ArchivedAt,
&i.ArchivedBy,
&i.AvatarUrl,
&i.Instructions,
)
return i, err
}
const getSquadInWorkspace = `-- name: GetSquadInWorkspace :one
SELECT id, workspace_id, name, description, leader_id, creator_id, created_at, updated_at, archived_at, archived_by, avatar_url, instructions FROM squad WHERE id = $1 AND workspace_id = $2
`
type GetSquadInWorkspaceParams struct {
ID pgtype.UUID `json:"id"`
WorkspaceID pgtype.UUID `json:"workspace_id"`
}
func (q *Queries) GetSquadInWorkspace(ctx context.Context, arg GetSquadInWorkspaceParams) (Squad, error) {
row := q.db.QueryRow(ctx, getSquadInWorkspace, arg.ID, arg.WorkspaceID)
var i Squad
err := row.Scan(
&i.ID,
&i.WorkspaceID,
&i.Name,
&i.Description,
&i.LeaderID,
&i.CreatorID,
&i.CreatedAt,
&i.UpdatedAt,
&i.ArchivedAt,
&i.ArchivedBy,
&i.AvatarUrl,
&i.Instructions,
)
return i, err
}
const isSquadMember = `-- name: IsSquadMember :one
SELECT EXISTS(
SELECT 1 FROM squad_member
WHERE squad_id = $1 AND member_type = $2 AND member_id = $3
) AS is_member
`
type IsSquadMemberParams struct {
SquadID pgtype.UUID `json:"squad_id"`
MemberType string `json:"member_type"`
MemberID pgtype.UUID `json:"member_id"`
}
func (q *Queries) IsSquadMember(ctx context.Context, arg IsSquadMemberParams) (bool, error) {
row := q.db.QueryRow(ctx, isSquadMember, arg.SquadID, arg.MemberType, arg.MemberID)
var is_member bool
err := row.Scan(&is_member)
return is_member, err
}
const listAllSquads = `-- name: ListAllSquads :many
SELECT id, workspace_id, name, description, leader_id, creator_id, created_at, updated_at, archived_at, archived_by, avatar_url, instructions FROM squad WHERE workspace_id = $1 ORDER BY created_at ASC
`
func (q *Queries) ListAllSquads(ctx context.Context, workspaceID pgtype.UUID) ([]Squad, error) {
rows, err := q.db.Query(ctx, listAllSquads, workspaceID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []Squad{}
for rows.Next() {
var i Squad
if err := rows.Scan(
&i.ID,
&i.WorkspaceID,
&i.Name,
&i.Description,
&i.LeaderID,
&i.CreatorID,
&i.CreatedAt,
&i.UpdatedAt,
&i.ArchivedAt,
&i.ArchivedBy,
&i.AvatarUrl,
&i.Instructions,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listSquadMemberStatusRows = `-- name: ListSquadMemberStatusRows :many
SELECT
sm.id AS squad_member_id,
sm.member_type AS member_type,
sm.member_id AS member_id,
a.archived_at AS agent_archived_at,
ar.status AS runtime_status,
ar.last_seen_at AS runtime_last_seen_at,
atq.id AS task_id,
atq.status AS task_status,
atq.issue_id AS task_issue_id,
atq.dispatched_at AS task_dispatched_at,
i.number AS issue_number,
i.title AS issue_title,
i.status AS issue_status
FROM squad_member sm
LEFT JOIN agent a
ON sm.member_type = 'agent' AND a.id = sm.member_id
LEFT JOIN agent_runtime ar
ON ar.id = a.runtime_id
LEFT JOIN agent_task_queue atq
ON sm.member_type = 'agent'
AND atq.agent_id = sm.member_id
AND atq.status IN ('dispatched', 'running')
LEFT JOIN issue i
ON i.id = atq.issue_id
WHERE sm.squad_id = $1
ORDER BY sm.created_at ASC, atq.dispatched_at DESC NULLS LAST
`
type ListSquadMemberStatusRowsRow struct {
SquadMemberID pgtype.UUID `json:"squad_member_id"`
MemberType string `json:"member_type"`
MemberID pgtype.UUID `json:"member_id"`
AgentArchivedAt pgtype.Timestamptz `json:"agent_archived_at"`
RuntimeStatus pgtype.Text `json:"runtime_status"`
RuntimeLastSeenAt pgtype.Timestamptz `json:"runtime_last_seen_at"`
TaskID pgtype.UUID `json:"task_id"`
TaskStatus pgtype.Text `json:"task_status"`
TaskIssueID pgtype.UUID `json:"task_issue_id"`
TaskDispatchedAt pgtype.Timestamptz `json:"task_dispatched_at"`
IssueNumber pgtype.Int4 `json:"issue_number"`
IssueTitle pgtype.Text `json:"issue_title"`
IssueStatus pgtype.Text `json:"issue_status"`
}
// Per-row join used to build the squad-members status view. One row per
// (squad_member × active_task); members with no active task return a
// single row with NULL task_* columns. Human members and agent members
// with no agent row also return one row with NULL agent_/runtime_ columns.
// The handler aggregates rows by member_id.
func (q *Queries) ListSquadMemberStatusRows(ctx context.Context, squadID pgtype.UUID) ([]ListSquadMemberStatusRowsRow, error) {
rows, err := q.db.Query(ctx, listSquadMemberStatusRows, squadID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListSquadMemberStatusRowsRow{}
for rows.Next() {
var i ListSquadMemberStatusRowsRow
if err := rows.Scan(
&i.SquadMemberID,
&i.MemberType,
&i.MemberID,
&i.AgentArchivedAt,
&i.RuntimeStatus,
&i.RuntimeLastSeenAt,
&i.TaskID,
&i.TaskStatus,
&i.TaskIssueID,
&i.TaskDispatchedAt,
&i.IssueNumber,
&i.IssueTitle,
&i.IssueStatus,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listSquadMembers = `-- name: ListSquadMembers :many
SELECT id, squad_id, member_type, member_id, role, created_at FROM squad_member WHERE squad_id = $1 ORDER BY created_at ASC
`
func (q *Queries) ListSquadMembers(ctx context.Context, squadID pgtype.UUID) ([]SquadMember, error) {
rows, err := q.db.Query(ctx, listSquadMembers, squadID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []SquadMember{}
for rows.Next() {
var i SquadMember
if err := rows.Scan(
&i.ID,
&i.SquadID,
&i.MemberType,
&i.MemberID,
&i.Role,
&i.CreatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listSquads = `-- name: ListSquads :many
SELECT id, workspace_id, name, description, leader_id, creator_id, created_at, updated_at, archived_at, archived_by, avatar_url, instructions FROM squad WHERE workspace_id = $1 AND archived_at IS NULL ORDER BY created_at ASC
`
func (q *Queries) ListSquads(ctx context.Context, workspaceID pgtype.UUID) ([]Squad, error) {
rows, err := q.db.Query(ctx, listSquads, workspaceID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []Squad{}
for rows.Next() {
var i Squad
if err := rows.Scan(
&i.ID,
&i.WorkspaceID,
&i.Name,
&i.Description,
&i.LeaderID,
&i.CreatorID,
&i.CreatedAt,
&i.UpdatedAt,
&i.ArchivedAt,
&i.ArchivedBy,
&i.AvatarUrl,
&i.Instructions,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listSquadsByMember = `-- name: ListSquadsByMember :many
SELECT s.id, s.workspace_id, s.name, s.description, s.leader_id, s.creator_id, s.created_at, s.updated_at, s.archived_at, s.archived_by, s.avatar_url, s.instructions FROM squad s
JOIN squad_member sm ON sm.squad_id = s.id
WHERE s.workspace_id = $1 AND sm.member_type = $2 AND sm.member_id = $3
ORDER BY s.created_at ASC
`
type ListSquadsByMemberParams struct {
WorkspaceID pgtype.UUID `json:"workspace_id"`
MemberType string `json:"member_type"`
MemberID pgtype.UUID `json:"member_id"`
}
// Find all squads a given entity belongs to in a workspace.
func (q *Queries) ListSquadsByMember(ctx context.Context, arg ListSquadsByMemberParams) ([]Squad, error) {
rows, err := q.db.Query(ctx, listSquadsByMember, arg.WorkspaceID, arg.MemberType, arg.MemberID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []Squad{}
for rows.Next() {
var i Squad
if err := rows.Scan(
&i.ID,
&i.WorkspaceID,
&i.Name,
&i.Description,
&i.LeaderID,
&i.CreatorID,
&i.CreatedAt,
&i.UpdatedAt,
&i.ArchivedAt,
&i.ArchivedBy,
&i.AvatarUrl,
&i.Instructions,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const removeSquadMember = `-- name: RemoveSquadMember :execrows
DELETE FROM squad_member
WHERE squad_id = $1 AND member_type = $2 AND member_id = $3
`
type RemoveSquadMemberParams struct {
SquadID pgtype.UUID `json:"squad_id"`
MemberType string `json:"member_type"`
MemberID pgtype.UUID `json:"member_id"`
}
func (q *Queries) RemoveSquadMember(ctx context.Context, arg RemoveSquadMemberParams) (int64, error) {
result, err := q.db.Exec(ctx, removeSquadMember, arg.SquadID, arg.MemberType, arg.MemberID)
if err != nil {
return 0, err
}
return result.RowsAffected(), nil
}
const transferSquadAssignees = `-- name: TransferSquadAssignees :exec
UPDATE issue SET assignee_type = 'agent', assignee_id = $2, updated_at = now()
WHERE assignee_type = 'squad' AND assignee_id = $1
`
type TransferSquadAssigneesParams struct {
AssigneeID pgtype.UUID `json:"assignee_id"`
AssigneeID_2 pgtype.UUID `json:"assignee_id_2"`
}
// Transfer all issues assigned to a squad to the squad's leader agent.
func (q *Queries) TransferSquadAssignees(ctx context.Context, arg TransferSquadAssigneesParams) error {
_, err := q.db.Exec(ctx, transferSquadAssignees, arg.AssigneeID, arg.AssigneeID_2)
return err
}
const transferSquadAutopilotsToLeader = `-- name: TransferSquadAutopilotsToLeader :exec
UPDATE autopilot
SET assignee_type = 'agent',
assignee_id = $2,
updated_at = now()
WHERE assignee_type = 'squad' AND assignee_id = $1
`
type TransferSquadAutopilotsToLeaderParams struct {
AssigneeID pgtype.UUID `json:"assignee_id"`
AssigneeID_2 pgtype.UUID `json:"assignee_id_2"`
}
// Mirrors TransferSquadAssignees for autopilot rows: when a squad is archived,
// any autopilot still pointing at the squad would otherwise dangle and the
// admission gate would skip every subsequent dispatch with "assignee squad
// cannot be resolved". Rewrite the assignee in place to the leader agent so
// the autopilot keeps firing under the same leader-only execution semantics
// it had a moment before the archive (Path A from MUL-2429).
func (q *Queries) TransferSquadAutopilotsToLeader(ctx context.Context, arg TransferSquadAutopilotsToLeaderParams) error {
_, err := q.db.Exec(ctx, transferSquadAutopilotsToLeader, arg.AssigneeID, arg.AssigneeID_2)
return err
}
const updateSquad = `-- name: UpdateSquad :one
UPDATE squad SET
name = COALESCE($2, name),
description = COALESCE($3, description),
leader_id = COALESCE($4, leader_id),
avatar_url = COALESCE($5, avatar_url),
instructions = COALESCE($6, instructions),
updated_at = now()
WHERE id = $1
RETURNING id, workspace_id, name, description, leader_id, creator_id, created_at, updated_at, archived_at, archived_by, avatar_url, instructions
`
type UpdateSquadParams struct {
ID pgtype.UUID `json:"id"`
Name pgtype.Text `json:"name"`
Description pgtype.Text `json:"description"`
LeaderID pgtype.UUID `json:"leader_id"`
AvatarUrl pgtype.Text `json:"avatar_url"`
Instructions pgtype.Text `json:"instructions"`
}
func (q *Queries) UpdateSquad(ctx context.Context, arg UpdateSquadParams) (Squad, error) {
row := q.db.QueryRow(ctx, updateSquad,
arg.ID,
arg.Name,
arg.Description,
arg.LeaderID,
arg.AvatarUrl,
arg.Instructions,
)
var i Squad
err := row.Scan(
&i.ID,
&i.WorkspaceID,
&i.Name,
&i.Description,
&i.LeaderID,
&i.CreatorID,
&i.CreatedAt,
&i.UpdatedAt,
&i.ArchivedAt,
&i.ArchivedBy,
&i.AvatarUrl,
&i.Instructions,
)
return i, err
}
const updateSquadMemberRole = `-- name: UpdateSquadMemberRole :one
UPDATE squad_member SET role = $4
WHERE squad_id = $1 AND member_type = $2 AND member_id = $3
RETURNING id, squad_id, member_type, member_id, role, created_at
`
type UpdateSquadMemberRoleParams struct {
SquadID pgtype.UUID `json:"squad_id"`
MemberType string `json:"member_type"`
MemberID pgtype.UUID `json:"member_id"`
Role string `json:"role"`
}
func (q *Queries) UpdateSquadMemberRole(ctx context.Context, arg UpdateSquadMemberRoleParams) (SquadMember, error) {
row := q.db.QueryRow(ctx, updateSquadMemberRole,
arg.SquadID,
arg.MemberType,
arg.MemberID,
arg.Role,
)
var i SquadMember
err := row.Scan(
&i.ID,
&i.SquadID,
&i.MemberType,
&i.MemberID,
&i.Role,
&i.CreatedAt,
)
return i, err
}