mirror of
https://github.com/multica-ai/multica.git
synced 2026-06-17 03:38:32 +02:00
The list endpoint only selected the autopilot table, so the list UI could not answer "is this automation working" without N+1 detail calls. Each list row now carries trigger_kinds + next_run_at (enabled triggers only — the columns describe how it fires today) and last_run_status (most recent run). Fields are omitempty and absent from detail/create/update responses; clients must treat them as optional per the API compatibility rules. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1424 lines
43 KiB
Go
1424 lines
43 KiB
Go
// Code generated by sqlc. DO NOT EDIT.
|
|
// versions:
|
|
// sqlc v1.31.1
|
|
// source: autopilot.sql
|
|
|
|
package db
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/jackc/pgx/v5/pgtype"
|
|
)
|
|
|
|
const advanceTriggerNextRun = `-- name: AdvanceTriggerNextRun :exec
|
|
UPDATE autopilot_trigger
|
|
SET next_run_at = $2,
|
|
last_fired_at = now(),
|
|
updated_at = now()
|
|
WHERE id = $1
|
|
`
|
|
|
|
type AdvanceTriggerNextRunParams struct {
|
|
ID pgtype.UUID `json:"id"`
|
|
NextRunAt pgtype.Timestamptz `json:"next_run_at"`
|
|
}
|
|
|
|
func (q *Queries) AdvanceTriggerNextRun(ctx context.Context, arg AdvanceTriggerNextRunParams) error {
|
|
_, err := q.db.Exec(ctx, advanceTriggerNextRun, arg.ID, arg.NextRunAt)
|
|
return err
|
|
}
|
|
|
|
const claimDueScheduleTriggers = `-- name: ClaimDueScheduleTriggers :many
|
|
|
|
UPDATE autopilot_trigger t
|
|
SET next_run_at = NULL
|
|
FROM autopilot a
|
|
WHERE t.autopilot_id = a.id
|
|
AND t.kind = 'schedule'
|
|
AND t.enabled = true
|
|
AND t.next_run_at IS NOT NULL
|
|
AND t.next_run_at <= now()
|
|
AND a.status = 'active'
|
|
RETURNING t.id, t.autopilot_id, t.kind, t.enabled, t.cron_expression, t.timezone, t.next_run_at, t.webhook_token, t.label, t.last_fired_at, t.created_at, t.updated_at, t.provider, t.signing_secret, t.event_filters, a.workspace_id AS autopilot_workspace_id
|
|
`
|
|
|
|
type ClaimDueScheduleTriggersRow struct {
|
|
ID pgtype.UUID `json:"id"`
|
|
AutopilotID pgtype.UUID `json:"autopilot_id"`
|
|
Kind string `json:"kind"`
|
|
Enabled bool `json:"enabled"`
|
|
CronExpression pgtype.Text `json:"cron_expression"`
|
|
Timezone pgtype.Text `json:"timezone"`
|
|
NextRunAt pgtype.Timestamptz `json:"next_run_at"`
|
|
WebhookToken pgtype.Text `json:"webhook_token"`
|
|
Label pgtype.Text `json:"label"`
|
|
LastFiredAt pgtype.Timestamptz `json:"last_fired_at"`
|
|
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
|
Provider string `json:"provider"`
|
|
SigningSecret pgtype.Text `json:"signing_secret"`
|
|
EventFilters []byte `json:"event_filters"`
|
|
AutopilotWorkspaceID pgtype.UUID `json:"autopilot_workspace_id"`
|
|
}
|
|
|
|
// =====================
|
|
// Scheduler Queries
|
|
// =====================
|
|
// Atomically claim all due schedule triggers to prevent concurrent execution.
|
|
// Joins the autopilot table to ensure only active autopilots are fired.
|
|
func (q *Queries) ClaimDueScheduleTriggers(ctx context.Context) ([]ClaimDueScheduleTriggersRow, error) {
|
|
rows, err := q.db.Query(ctx, claimDueScheduleTriggers)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []ClaimDueScheduleTriggersRow{}
|
|
for rows.Next() {
|
|
var i ClaimDueScheduleTriggersRow
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.AutopilotID,
|
|
&i.Kind,
|
|
&i.Enabled,
|
|
&i.CronExpression,
|
|
&i.Timezone,
|
|
&i.NextRunAt,
|
|
&i.WebhookToken,
|
|
&i.Label,
|
|
&i.LastFiredAt,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.Provider,
|
|
&i.SigningSecret,
|
|
&i.EventFilters,
|
|
&i.AutopilotWorkspaceID,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const createAutopilot = `-- name: CreateAutopilot :one
|
|
INSERT INTO autopilot (
|
|
workspace_id, title, description, assignee_type, assignee_id,
|
|
status, execution_mode, issue_title_template, project_id,
|
|
created_by_type, created_by_id
|
|
) VALUES (
|
|
$1, $2, $9, $3, $4,
|
|
$5, $6, $10, $11,
|
|
$7, $8
|
|
) RETURNING id, workspace_id, title, description, assignee_id, status, execution_mode, issue_title_template, created_by_type, created_by_id, last_run_at, created_at, updated_at, assignee_type, project_id
|
|
`
|
|
|
|
type CreateAutopilotParams struct {
|
|
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
|
Title string `json:"title"`
|
|
AssigneeType string `json:"assignee_type"`
|
|
AssigneeID pgtype.UUID `json:"assignee_id"`
|
|
Status string `json:"status"`
|
|
ExecutionMode string `json:"execution_mode"`
|
|
CreatedByType string `json:"created_by_type"`
|
|
CreatedByID pgtype.UUID `json:"created_by_id"`
|
|
Description pgtype.Text `json:"description"`
|
|
IssueTitleTemplate pgtype.Text `json:"issue_title_template"`
|
|
ProjectID pgtype.UUID `json:"project_id"`
|
|
}
|
|
|
|
func (q *Queries) CreateAutopilot(ctx context.Context, arg CreateAutopilotParams) (Autopilot, error) {
|
|
row := q.db.QueryRow(ctx, createAutopilot,
|
|
arg.WorkspaceID,
|
|
arg.Title,
|
|
arg.AssigneeType,
|
|
arg.AssigneeID,
|
|
arg.Status,
|
|
arg.ExecutionMode,
|
|
arg.CreatedByType,
|
|
arg.CreatedByID,
|
|
arg.Description,
|
|
arg.IssueTitleTemplate,
|
|
arg.ProjectID,
|
|
)
|
|
var i Autopilot
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.WorkspaceID,
|
|
&i.Title,
|
|
&i.Description,
|
|
&i.AssigneeID,
|
|
&i.Status,
|
|
&i.ExecutionMode,
|
|
&i.IssueTitleTemplate,
|
|
&i.CreatedByType,
|
|
&i.CreatedByID,
|
|
&i.LastRunAt,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.AssigneeType,
|
|
&i.ProjectID,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const createAutopilotRun = `-- name: CreateAutopilotRun :one
|
|
|
|
INSERT INTO autopilot_run (
|
|
autopilot_id, trigger_id, source, status, trigger_payload, squad_id
|
|
) VALUES (
|
|
$1, $4, $2, $3, $5,
|
|
$6
|
|
) RETURNING id, autopilot_id, trigger_id, source, status, issue_id, task_id, triggered_at, completed_at, failure_reason, trigger_payload, result, created_at, squad_id
|
|
`
|
|
|
|
type CreateAutopilotRunParams struct {
|
|
AutopilotID pgtype.UUID `json:"autopilot_id"`
|
|
Source string `json:"source"`
|
|
Status string `json:"status"`
|
|
TriggerID pgtype.UUID `json:"trigger_id"`
|
|
TriggerPayload []byte `json:"trigger_payload"`
|
|
SquadID pgtype.UUID `json:"squad_id"`
|
|
}
|
|
|
|
// =====================
|
|
// Autopilot Run Management
|
|
// =====================
|
|
// squad_id is an attribution hook: set to the assignee squad when the
|
|
// parent autopilot has assignee_type='squad', NULL otherwise. The executing
|
|
// agent_id on agent_task_queue still records who actually ran the work
|
|
// (the squad leader); squad_id lets reports group by squad without a join.
|
|
func (q *Queries) CreateAutopilotRun(ctx context.Context, arg CreateAutopilotRunParams) (AutopilotRun, error) {
|
|
row := q.db.QueryRow(ctx, createAutopilotRun,
|
|
arg.AutopilotID,
|
|
arg.Source,
|
|
arg.Status,
|
|
arg.TriggerID,
|
|
arg.TriggerPayload,
|
|
arg.SquadID,
|
|
)
|
|
var i AutopilotRun
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.AutopilotID,
|
|
&i.TriggerID,
|
|
&i.Source,
|
|
&i.Status,
|
|
&i.IssueID,
|
|
&i.TaskID,
|
|
&i.TriggeredAt,
|
|
&i.CompletedAt,
|
|
&i.FailureReason,
|
|
&i.TriggerPayload,
|
|
&i.Result,
|
|
&i.CreatedAt,
|
|
&i.SquadID,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const createAutopilotTask = `-- name: CreateAutopilotTask :one
|
|
|
|
INSERT INTO agent_task_queue (agent_id, runtime_id, issue_id, status, priority, autopilot_run_id, trigger_summary)
|
|
VALUES ($1, $2, NULL, 'queued', $3, $4, $5)
|
|
RETURNING id, agent_id, issue_id, status, priority, dispatched_at, started_at, completed_at, result, error, created_at, context, runtime_id, session_id, work_dir, trigger_comment_id, chat_session_id, autopilot_run_id, attempt, max_attempts, parent_task_id, failure_reason, trigger_summary, force_fresh_session, is_leader_task, wait_reason, initiator_user_id
|
|
`
|
|
|
|
type CreateAutopilotTaskParams struct {
|
|
AgentID pgtype.UUID `json:"agent_id"`
|
|
RuntimeID pgtype.UUID `json:"runtime_id"`
|
|
Priority int32 `json:"priority"`
|
|
AutopilotRunID pgtype.UUID `json:"autopilot_run_id"`
|
|
TriggerSummary pgtype.Text `json:"trigger_summary"`
|
|
}
|
|
|
|
// =====================
|
|
// Task Queue (run_only mode)
|
|
// =====================
|
|
func (q *Queries) CreateAutopilotTask(ctx context.Context, arg CreateAutopilotTaskParams) (AgentTaskQueue, error) {
|
|
row := q.db.QueryRow(ctx, createAutopilotTask,
|
|
arg.AgentID,
|
|
arg.RuntimeID,
|
|
arg.Priority,
|
|
arg.AutopilotRunID,
|
|
arg.TriggerSummary,
|
|
)
|
|
var i AgentTaskQueue
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.AgentID,
|
|
&i.IssueID,
|
|
&i.Status,
|
|
&i.Priority,
|
|
&i.DispatchedAt,
|
|
&i.StartedAt,
|
|
&i.CompletedAt,
|
|
&i.Result,
|
|
&i.Error,
|
|
&i.CreatedAt,
|
|
&i.Context,
|
|
&i.RuntimeID,
|
|
&i.SessionID,
|
|
&i.WorkDir,
|
|
&i.TriggerCommentID,
|
|
&i.ChatSessionID,
|
|
&i.AutopilotRunID,
|
|
&i.Attempt,
|
|
&i.MaxAttempts,
|
|
&i.ParentTaskID,
|
|
&i.FailureReason,
|
|
&i.TriggerSummary,
|
|
&i.ForceFreshSession,
|
|
&i.IsLeaderTask,
|
|
&i.WaitReason,
|
|
&i.InitiatorUserID,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const createAutopilotTrigger = `-- name: CreateAutopilotTrigger :one
|
|
INSERT INTO autopilot_trigger (
|
|
autopilot_id, kind, enabled, cron_expression, timezone,
|
|
next_run_at, webhook_token, label, provider, event_filters
|
|
) VALUES (
|
|
$1, $2, $3, $4, $5,
|
|
$6, $7, $8,
|
|
COALESCE($9::text, 'generic'),
|
|
$10
|
|
) RETURNING id, autopilot_id, kind, enabled, cron_expression, timezone, next_run_at, webhook_token, label, last_fired_at, created_at, updated_at, provider, signing_secret, event_filters
|
|
`
|
|
|
|
type CreateAutopilotTriggerParams struct {
|
|
AutopilotID pgtype.UUID `json:"autopilot_id"`
|
|
Kind string `json:"kind"`
|
|
Enabled bool `json:"enabled"`
|
|
CronExpression pgtype.Text `json:"cron_expression"`
|
|
Timezone pgtype.Text `json:"timezone"`
|
|
NextRunAt pgtype.Timestamptz `json:"next_run_at"`
|
|
WebhookToken pgtype.Text `json:"webhook_token"`
|
|
Label pgtype.Text `json:"label"`
|
|
Provider pgtype.Text `json:"provider"`
|
|
EventFilters []byte `json:"event_filters"`
|
|
}
|
|
|
|
func (q *Queries) CreateAutopilotTrigger(ctx context.Context, arg CreateAutopilotTriggerParams) (AutopilotTrigger, error) {
|
|
row := q.db.QueryRow(ctx, createAutopilotTrigger,
|
|
arg.AutopilotID,
|
|
arg.Kind,
|
|
arg.Enabled,
|
|
arg.CronExpression,
|
|
arg.Timezone,
|
|
arg.NextRunAt,
|
|
arg.WebhookToken,
|
|
arg.Label,
|
|
arg.Provider,
|
|
arg.EventFilters,
|
|
)
|
|
var i AutopilotTrigger
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.AutopilotID,
|
|
&i.Kind,
|
|
&i.Enabled,
|
|
&i.CronExpression,
|
|
&i.Timezone,
|
|
&i.NextRunAt,
|
|
&i.WebhookToken,
|
|
&i.Label,
|
|
&i.LastFiredAt,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.Provider,
|
|
&i.SigningSecret,
|
|
&i.EventFilters,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const deleteAutopilot = `-- name: DeleteAutopilot :exec
|
|
DELETE FROM autopilot WHERE id = $1
|
|
`
|
|
|
|
func (q *Queries) DeleteAutopilot(ctx context.Context, id pgtype.UUID) error {
|
|
_, err := q.db.Exec(ctx, deleteAutopilot, id)
|
|
return err
|
|
}
|
|
|
|
const deleteAutopilotTrigger = `-- name: DeleteAutopilotTrigger :exec
|
|
DELETE FROM autopilot_trigger WHERE id = $1
|
|
`
|
|
|
|
func (q *Queries) DeleteAutopilotTrigger(ctx context.Context, id pgtype.UUID) error {
|
|
_, err := q.db.Exec(ctx, deleteAutopilotTrigger, id)
|
|
return err
|
|
}
|
|
|
|
const failAutopilotRunsByIssue = `-- name: FailAutopilotRunsByIssue :exec
|
|
UPDATE autopilot_run
|
|
SET status = 'failed', completed_at = now(), failure_reason = 'linked issue was deleted'
|
|
WHERE issue_id = $1
|
|
AND status IN ('issue_created', 'running')
|
|
`
|
|
|
|
// Fails active autopilot runs linked to a given issue.
|
|
// Must be called BEFORE issue deletion (ON DELETE SET NULL clears issue_id).
|
|
func (q *Queries) FailAutopilotRunsByIssue(ctx context.Context, issueID pgtype.UUID) error {
|
|
_, err := q.db.Exec(ctx, failAutopilotRunsByIssue, issueID)
|
|
return err
|
|
}
|
|
|
|
const getAutopilot = `-- name: GetAutopilot :one
|
|
SELECT id, workspace_id, title, description, assignee_id, status, execution_mode, issue_title_template, created_by_type, created_by_id, last_run_at, created_at, updated_at, assignee_type, project_id FROM autopilot
|
|
WHERE id = $1
|
|
`
|
|
|
|
func (q *Queries) GetAutopilot(ctx context.Context, id pgtype.UUID) (Autopilot, error) {
|
|
row := q.db.QueryRow(ctx, getAutopilot, id)
|
|
var i Autopilot
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.WorkspaceID,
|
|
&i.Title,
|
|
&i.Description,
|
|
&i.AssigneeID,
|
|
&i.Status,
|
|
&i.ExecutionMode,
|
|
&i.IssueTitleTemplate,
|
|
&i.CreatedByType,
|
|
&i.CreatedByID,
|
|
&i.LastRunAt,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.AssigneeType,
|
|
&i.ProjectID,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getAutopilotInWorkspace = `-- name: GetAutopilotInWorkspace :one
|
|
SELECT id, workspace_id, title, description, assignee_id, status, execution_mode, issue_title_template, created_by_type, created_by_id, last_run_at, created_at, updated_at, assignee_type, project_id FROM autopilot
|
|
WHERE id = $1 AND workspace_id = $2
|
|
`
|
|
|
|
type GetAutopilotInWorkspaceParams struct {
|
|
ID pgtype.UUID `json:"id"`
|
|
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
|
}
|
|
|
|
func (q *Queries) GetAutopilotInWorkspace(ctx context.Context, arg GetAutopilotInWorkspaceParams) (Autopilot, error) {
|
|
row := q.db.QueryRow(ctx, getAutopilotInWorkspace, arg.ID, arg.WorkspaceID)
|
|
var i Autopilot
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.WorkspaceID,
|
|
&i.Title,
|
|
&i.Description,
|
|
&i.AssigneeID,
|
|
&i.Status,
|
|
&i.ExecutionMode,
|
|
&i.IssueTitleTemplate,
|
|
&i.CreatedByType,
|
|
&i.CreatedByID,
|
|
&i.LastRunAt,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.AssigneeType,
|
|
&i.ProjectID,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getAutopilotRun = `-- name: GetAutopilotRun :one
|
|
SELECT id, autopilot_id, trigger_id, source, status, issue_id, task_id, triggered_at, completed_at, failure_reason, trigger_payload, result, created_at, squad_id FROM autopilot_run
|
|
WHERE id = $1
|
|
`
|
|
|
|
func (q *Queries) GetAutopilotRun(ctx context.Context, id pgtype.UUID) (AutopilotRun, error) {
|
|
row := q.db.QueryRow(ctx, getAutopilotRun, id)
|
|
var i AutopilotRun
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.AutopilotID,
|
|
&i.TriggerID,
|
|
&i.Source,
|
|
&i.Status,
|
|
&i.IssueID,
|
|
&i.TaskID,
|
|
&i.TriggeredAt,
|
|
&i.CompletedAt,
|
|
&i.FailureReason,
|
|
&i.TriggerPayload,
|
|
&i.Result,
|
|
&i.CreatedAt,
|
|
&i.SquadID,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getAutopilotRunByIssue = `-- name: GetAutopilotRunByIssue :one
|
|
|
|
SELECT id, autopilot_id, trigger_id, source, status, issue_id, task_id, triggered_at, completed_at, failure_reason, trigger_payload, result, created_at, squad_id FROM autopilot_run
|
|
WHERE issue_id = $1 AND status IN ('issue_created', 'running')
|
|
LIMIT 1
|
|
`
|
|
|
|
// =====================
|
|
// Run lookup by linked entities
|
|
// =====================
|
|
func (q *Queries) GetAutopilotRunByIssue(ctx context.Context, issueID pgtype.UUID) (AutopilotRun, error) {
|
|
row := q.db.QueryRow(ctx, getAutopilotRunByIssue, issueID)
|
|
var i AutopilotRun
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.AutopilotID,
|
|
&i.TriggerID,
|
|
&i.Source,
|
|
&i.Status,
|
|
&i.IssueID,
|
|
&i.TaskID,
|
|
&i.TriggeredAt,
|
|
&i.CompletedAt,
|
|
&i.FailureReason,
|
|
&i.TriggerPayload,
|
|
&i.Result,
|
|
&i.CreatedAt,
|
|
&i.SquadID,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getAutopilotTrigger = `-- name: GetAutopilotTrigger :one
|
|
SELECT id, autopilot_id, kind, enabled, cron_expression, timezone, next_run_at, webhook_token, label, last_fired_at, created_at, updated_at, provider, signing_secret, event_filters FROM autopilot_trigger
|
|
WHERE id = $1
|
|
`
|
|
|
|
func (q *Queries) GetAutopilotTrigger(ctx context.Context, id pgtype.UUID) (AutopilotTrigger, error) {
|
|
row := q.db.QueryRow(ctx, getAutopilotTrigger, id)
|
|
var i AutopilotTrigger
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.AutopilotID,
|
|
&i.Kind,
|
|
&i.Enabled,
|
|
&i.CronExpression,
|
|
&i.Timezone,
|
|
&i.NextRunAt,
|
|
&i.WebhookToken,
|
|
&i.Label,
|
|
&i.LastFiredAt,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.Provider,
|
|
&i.SigningSecret,
|
|
&i.EventFilters,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getWebhookTriggerByToken = `-- name: GetWebhookTriggerByToken :one
|
|
SELECT t.id, t.autopilot_id, t.kind, t.enabled, t.cron_expression, t.timezone, t.next_run_at, t.webhook_token, t.label, t.last_fired_at, t.created_at, t.updated_at, t.provider, t.signing_secret, t.event_filters, a.workspace_id AS autopilot_workspace_id
|
|
FROM autopilot_trigger t
|
|
JOIN autopilot a ON a.id = t.autopilot_id
|
|
WHERE t.kind = 'webhook'
|
|
AND t.webhook_token = $1
|
|
`
|
|
|
|
type GetWebhookTriggerByTokenRow struct {
|
|
ID pgtype.UUID `json:"id"`
|
|
AutopilotID pgtype.UUID `json:"autopilot_id"`
|
|
Kind string `json:"kind"`
|
|
Enabled bool `json:"enabled"`
|
|
CronExpression pgtype.Text `json:"cron_expression"`
|
|
Timezone pgtype.Text `json:"timezone"`
|
|
NextRunAt pgtype.Timestamptz `json:"next_run_at"`
|
|
WebhookToken pgtype.Text `json:"webhook_token"`
|
|
Label pgtype.Text `json:"label"`
|
|
LastFiredAt pgtype.Timestamptz `json:"last_fired_at"`
|
|
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
|
Provider string `json:"provider"`
|
|
SigningSecret pgtype.Text `json:"signing_secret"`
|
|
EventFilters []byte `json:"event_filters"`
|
|
AutopilotWorkspaceID pgtype.UUID `json:"autopilot_workspace_id"`
|
|
}
|
|
|
|
// Look up a webhook trigger by its public bearer token. Joined to autopilot
|
|
// so the webhook handler can derive the workspace from the trigger's parent
|
|
// without trusting any request header. The handler still re-loads the
|
|
// Autopilot via GetAutopilot and cross-checks WorkspaceID matches the row's
|
|
// autopilot_workspace_id.
|
|
func (q *Queries) GetWebhookTriggerByToken(ctx context.Context, webhookToken pgtype.Text) (GetWebhookTriggerByTokenRow, error) {
|
|
row := q.db.QueryRow(ctx, getWebhookTriggerByToken, webhookToken)
|
|
var i GetWebhookTriggerByTokenRow
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.AutopilotID,
|
|
&i.Kind,
|
|
&i.Enabled,
|
|
&i.CronExpression,
|
|
&i.Timezone,
|
|
&i.NextRunAt,
|
|
&i.WebhookToken,
|
|
&i.Label,
|
|
&i.LastFiredAt,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.Provider,
|
|
&i.SigningSecret,
|
|
&i.EventFilters,
|
|
&i.AutopilotWorkspaceID,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const listAutopilotRuns = `-- name: ListAutopilotRuns :many
|
|
SELECT id, autopilot_id, trigger_id, source, status, issue_id, task_id, triggered_at, completed_at, failure_reason, trigger_payload, result, created_at, squad_id FROM autopilot_run
|
|
WHERE autopilot_id = $1
|
|
ORDER BY created_at DESC
|
|
LIMIT $2 OFFSET $3
|
|
`
|
|
|
|
type ListAutopilotRunsParams struct {
|
|
AutopilotID pgtype.UUID `json:"autopilot_id"`
|
|
Limit int32 `json:"limit"`
|
|
Offset int32 `json:"offset"`
|
|
}
|
|
|
|
func (q *Queries) ListAutopilotRuns(ctx context.Context, arg ListAutopilotRunsParams) ([]AutopilotRun, error) {
|
|
rows, err := q.db.Query(ctx, listAutopilotRuns, arg.AutopilotID, arg.Limit, arg.Offset)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []AutopilotRun{}
|
|
for rows.Next() {
|
|
var i AutopilotRun
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.AutopilotID,
|
|
&i.TriggerID,
|
|
&i.Source,
|
|
&i.Status,
|
|
&i.IssueID,
|
|
&i.TaskID,
|
|
&i.TriggeredAt,
|
|
&i.CompletedAt,
|
|
&i.FailureReason,
|
|
&i.TriggerPayload,
|
|
&i.Result,
|
|
&i.CreatedAt,
|
|
&i.SquadID,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const listAutopilotTriggers = `-- name: ListAutopilotTriggers :many
|
|
|
|
SELECT id, autopilot_id, kind, enabled, cron_expression, timezone, next_run_at, webhook_token, label, last_fired_at, created_at, updated_at, provider, signing_secret, event_filters FROM autopilot_trigger
|
|
WHERE autopilot_id = $1
|
|
ORDER BY created_at ASC
|
|
`
|
|
|
|
// =====================
|
|
// Autopilot Trigger CRUD
|
|
// =====================
|
|
func (q *Queries) ListAutopilotTriggers(ctx context.Context, autopilotID pgtype.UUID) ([]AutopilotTrigger, error) {
|
|
rows, err := q.db.Query(ctx, listAutopilotTriggers, autopilotID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []AutopilotTrigger{}
|
|
for rows.Next() {
|
|
var i AutopilotTrigger
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.AutopilotID,
|
|
&i.Kind,
|
|
&i.Enabled,
|
|
&i.CronExpression,
|
|
&i.Timezone,
|
|
&i.NextRunAt,
|
|
&i.WebhookToken,
|
|
&i.Label,
|
|
&i.LastFiredAt,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.Provider,
|
|
&i.SigningSecret,
|
|
&i.EventFilters,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const listAutopilots = `-- name: ListAutopilots :many
|
|
|
|
SELECT
|
|
a.id, a.workspace_id, a.title, a.description, a.assignee_id, a.status, a.execution_mode, a.issue_title_template, a.created_by_type, a.created_by_id, a.last_run_at, a.created_at, a.updated_at, a.assignee_type, a.project_id,
|
|
(
|
|
SELECT array_agg(DISTINCT t.kind ORDER BY t.kind)
|
|
FROM autopilot_trigger t
|
|
WHERE t.autopilot_id = a.id AND t.enabled
|
|
)::text[] AS trigger_kinds,
|
|
(
|
|
SELECT min(t.next_run_at)
|
|
FROM autopilot_trigger t
|
|
WHERE t.autopilot_id = a.id AND t.enabled AND t.kind = 'schedule'
|
|
)::timestamptz AS next_run_at,
|
|
COALESCE((
|
|
SELECT r.status
|
|
FROM autopilot_run r
|
|
WHERE r.autopilot_id = a.id
|
|
ORDER BY r.triggered_at DESC
|
|
LIMIT 1
|
|
), '')::text AS last_run_status
|
|
FROM autopilot a
|
|
WHERE a.workspace_id = $1
|
|
AND ($2::text IS NULL OR a.status = $2)
|
|
ORDER BY a.created_at DESC
|
|
`
|
|
|
|
type ListAutopilotsParams struct {
|
|
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
|
Status pgtype.Text `json:"status"`
|
|
}
|
|
|
|
type ListAutopilotsRow struct {
|
|
Autopilot Autopilot `json:"autopilot"`
|
|
TriggerKinds []string `json:"trigger_kinds"`
|
|
NextRunAt pgtype.Timestamptz `json:"next_run_at"`
|
|
LastRunStatus string `json:"last_run_status"`
|
|
}
|
|
|
|
// =====================
|
|
// Autopilot CRUD
|
|
// =====================
|
|
// List rows carry three derived columns the list UI needs (trigger badges,
|
|
// next run, last-run outcome) so the page never has to N+1 into the detail
|
|
// endpoint. trigger_kinds/next_run_at only consider ENABLED triggers — the
|
|
// columns answer "how does this fire today", not "what is configured".
|
|
// last_run_status is COALESCEd to ” (never ran) because sqlc cannot infer
|
|
// nullability through a scalar subquery; the handler maps ” back to omitted.
|
|
func (q *Queries) ListAutopilots(ctx context.Context, arg ListAutopilotsParams) ([]ListAutopilotsRow, error) {
|
|
rows, err := q.db.Query(ctx, listAutopilots, arg.WorkspaceID, arg.Status)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []ListAutopilotsRow{}
|
|
for rows.Next() {
|
|
var i ListAutopilotsRow
|
|
if err := rows.Scan(
|
|
&i.Autopilot.ID,
|
|
&i.Autopilot.WorkspaceID,
|
|
&i.Autopilot.Title,
|
|
&i.Autopilot.Description,
|
|
&i.Autopilot.AssigneeID,
|
|
&i.Autopilot.Status,
|
|
&i.Autopilot.ExecutionMode,
|
|
&i.Autopilot.IssueTitleTemplate,
|
|
&i.Autopilot.CreatedByType,
|
|
&i.Autopilot.CreatedByID,
|
|
&i.Autopilot.LastRunAt,
|
|
&i.Autopilot.CreatedAt,
|
|
&i.Autopilot.UpdatedAt,
|
|
&i.Autopilot.AssigneeType,
|
|
&i.Autopilot.ProjectID,
|
|
&i.TriggerKinds,
|
|
&i.NextRunAt,
|
|
&i.LastRunStatus,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const recoverLostTriggers = `-- name: RecoverLostTriggers :many
|
|
|
|
SELECT t.id, t.autopilot_id, t.kind, t.enabled, t.cron_expression, t.timezone, t.next_run_at, t.webhook_token, t.label, t.last_fired_at, t.created_at, t.updated_at, t.provider, t.signing_secret, t.event_filters, a.workspace_id AS autopilot_workspace_id
|
|
FROM autopilot_trigger t
|
|
JOIN autopilot a ON t.autopilot_id = a.id
|
|
WHERE t.kind = 'schedule'
|
|
AND t.enabled = true
|
|
AND t.next_run_at IS NULL
|
|
AND t.cron_expression IS NOT NULL
|
|
AND a.status = 'active'
|
|
`
|
|
|
|
type RecoverLostTriggersRow struct {
|
|
ID pgtype.UUID `json:"id"`
|
|
AutopilotID pgtype.UUID `json:"autopilot_id"`
|
|
Kind string `json:"kind"`
|
|
Enabled bool `json:"enabled"`
|
|
CronExpression pgtype.Text `json:"cron_expression"`
|
|
Timezone pgtype.Text `json:"timezone"`
|
|
NextRunAt pgtype.Timestamptz `json:"next_run_at"`
|
|
WebhookToken pgtype.Text `json:"webhook_token"`
|
|
Label pgtype.Text `json:"label"`
|
|
LastFiredAt pgtype.Timestamptz `json:"last_fired_at"`
|
|
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
|
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
|
Provider string `json:"provider"`
|
|
SigningSecret pgtype.Text `json:"signing_secret"`
|
|
EventFilters []byte `json:"event_filters"`
|
|
AutopilotWorkspaceID pgtype.UUID `json:"autopilot_workspace_id"`
|
|
}
|
|
|
|
// =====================
|
|
// Scheduler Recovery
|
|
// =====================
|
|
// Finds schedule triggers that were claimed (next_run_at = NULL) but never
|
|
// advanced — typically due to a scheduler crash. Returns them so the scheduler
|
|
// can recompute next_run_at.
|
|
func (q *Queries) RecoverLostTriggers(ctx context.Context) ([]RecoverLostTriggersRow, error) {
|
|
rows, err := q.db.Query(ctx, recoverLostTriggers)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []RecoverLostTriggersRow{}
|
|
for rows.Next() {
|
|
var i RecoverLostTriggersRow
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.AutopilotID,
|
|
&i.Kind,
|
|
&i.Enabled,
|
|
&i.CronExpression,
|
|
&i.Timezone,
|
|
&i.NextRunAt,
|
|
&i.WebhookToken,
|
|
&i.Label,
|
|
&i.LastFiredAt,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.Provider,
|
|
&i.SigningSecret,
|
|
&i.EventFilters,
|
|
&i.AutopilotWorkspaceID,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const rotateAutopilotTriggerWebhookToken = `-- name: RotateAutopilotTriggerWebhookToken :one
|
|
UPDATE autopilot_trigger
|
|
SET webhook_token = $2,
|
|
updated_at = now()
|
|
WHERE id = $1
|
|
AND kind = 'webhook'
|
|
RETURNING id, autopilot_id, kind, enabled, cron_expression, timezone, next_run_at, webhook_token, label, last_fired_at, created_at, updated_at, provider, signing_secret, event_filters
|
|
`
|
|
|
|
type RotateAutopilotTriggerWebhookTokenParams struct {
|
|
ID pgtype.UUID `json:"id"`
|
|
WebhookToken pgtype.Text `json:"webhook_token"`
|
|
}
|
|
|
|
// Rotates the bearer token for a webhook trigger. Restricted to kind='webhook'
|
|
// so an accidental call against a schedule/api trigger is a no-op (returns no
|
|
// rows) rather than corrupting unrelated state.
|
|
func (q *Queries) RotateAutopilotTriggerWebhookToken(ctx context.Context, arg RotateAutopilotTriggerWebhookTokenParams) (AutopilotTrigger, error) {
|
|
row := q.db.QueryRow(ctx, rotateAutopilotTriggerWebhookToken, arg.ID, arg.WebhookToken)
|
|
var i AutopilotTrigger
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.AutopilotID,
|
|
&i.Kind,
|
|
&i.Enabled,
|
|
&i.CronExpression,
|
|
&i.Timezone,
|
|
&i.NextRunAt,
|
|
&i.WebhookToken,
|
|
&i.Label,
|
|
&i.LastFiredAt,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.Provider,
|
|
&i.SigningSecret,
|
|
&i.EventFilters,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const selectAutopilotsExceedingFailureThreshold = `-- name: SelectAutopilotsExceedingFailureThreshold :many
|
|
|
|
WITH stats AS (
|
|
SELECT autopilot_id,
|
|
count(*) FILTER (WHERE status IN ('completed', 'failed')) AS total,
|
|
count(*) FILTER (WHERE status = 'failed') AS failed
|
|
FROM autopilot_run
|
|
WHERE created_at >= $3::timestamptz
|
|
GROUP BY autopilot_id
|
|
)
|
|
SELECT a.id, a.workspace_id, a.title, a.assignee_id,
|
|
a.created_by_type, a.created_by_id,
|
|
s.total::bigint AS total_runs,
|
|
s.failed::bigint AS failed_runs
|
|
FROM autopilot a
|
|
JOIN stats s ON s.autopilot_id = a.id
|
|
WHERE a.status = 'active'
|
|
AND s.total >= $1::bigint
|
|
AND s.failed::float8 / NULLIF(s.total, 0)::float8 >= $2::float8
|
|
ORDER BY s.failed DESC, a.id ASC
|
|
`
|
|
|
|
type SelectAutopilotsExceedingFailureThresholdParams struct {
|
|
MinRuns int64 `json:"min_runs"`
|
|
FailRatioThreshold float64 `json:"fail_ratio_threshold"`
|
|
Since pgtype.Timestamptz `json:"since"`
|
|
}
|
|
|
|
type SelectAutopilotsExceedingFailureThresholdRow struct {
|
|
ID pgtype.UUID `json:"id"`
|
|
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
|
Title string `json:"title"`
|
|
AssigneeID pgtype.UUID `json:"assignee_id"`
|
|
CreatedByType string `json:"created_by_type"`
|
|
CreatedByID pgtype.UUID `json:"created_by_id"`
|
|
TotalRuns int64 `json:"total_runs"`
|
|
FailedRuns int64 `json:"failed_runs"`
|
|
}
|
|
|
|
// =====================
|
|
// Failure-rate auto-pause
|
|
// =====================
|
|
// Find active autopilots whose recent run failure rate exceeds the threshold.
|
|
// Counts only "real" terminal runs (completed | failed). 'skipped' is
|
|
// excluded from BOTH numerator and denominator: an admission-skipped run
|
|
// (e.g. assignee runtime offline at dispatch time, MUL-1899) is neither a
|
|
// success nor a failure, so it must not dilute the failure ratio (which
|
|
// would let a 100%-failing autopilot mask itself behind a wall of skips)
|
|
// nor inflate it. issue_created/running are still excluded so in-flight
|
|
// work isn't penalised.
|
|
// Used by the failure monitor to auto-pause sustained-failure autopilots
|
|
// (the canonical example from MUL-1336 was an autopilot scheduled every 5 min
|
|
// that 100% failed for days, burning ~1.5k useless tasks per week).
|
|
func (q *Queries) SelectAutopilotsExceedingFailureThreshold(ctx context.Context, arg SelectAutopilotsExceedingFailureThresholdParams) ([]SelectAutopilotsExceedingFailureThresholdRow, error) {
|
|
rows, err := q.db.Query(ctx, selectAutopilotsExceedingFailureThreshold, arg.MinRuns, arg.FailRatioThreshold, arg.Since)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []SelectAutopilotsExceedingFailureThresholdRow{}
|
|
for rows.Next() {
|
|
var i SelectAutopilotsExceedingFailureThresholdRow
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.WorkspaceID,
|
|
&i.Title,
|
|
&i.AssigneeID,
|
|
&i.CreatedByType,
|
|
&i.CreatedByID,
|
|
&i.TotalRuns,
|
|
&i.FailedRuns,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const setAutopilotTriggerSigningSecret = `-- name: SetAutopilotTriggerSigningSecret :one
|
|
UPDATE autopilot_trigger
|
|
SET signing_secret = $2,
|
|
updated_at = now()
|
|
WHERE id = $1
|
|
AND kind = 'webhook'
|
|
RETURNING id, autopilot_id, kind, enabled, cron_expression, timezone, next_run_at, webhook_token, label, last_fired_at, created_at, updated_at, provider, signing_secret, event_filters
|
|
`
|
|
|
|
type SetAutopilotTriggerSigningSecretParams struct {
|
|
ID pgtype.UUID `json:"id"`
|
|
SigningSecret pgtype.Text `json:"signing_secret"`
|
|
}
|
|
|
|
// Writes the signing secret for a webhook trigger. Kept as a dedicated query
|
|
// (not a field on UpdateAutopilotTrigger) so the request body for the
|
|
// write-only endpoint only ever carries the secret value, with no risk of an
|
|
// accidental log line leaking it alongside other fields. Restricted to
|
|
// webhook triggers to avoid corrupting unrelated state.
|
|
func (q *Queries) SetAutopilotTriggerSigningSecret(ctx context.Context, arg SetAutopilotTriggerSigningSecretParams) (AutopilotTrigger, error) {
|
|
row := q.db.QueryRow(ctx, setAutopilotTriggerSigningSecret, arg.ID, arg.SigningSecret)
|
|
var i AutopilotTrigger
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.AutopilotID,
|
|
&i.Kind,
|
|
&i.Enabled,
|
|
&i.CronExpression,
|
|
&i.Timezone,
|
|
&i.NextRunAt,
|
|
&i.WebhookToken,
|
|
&i.Label,
|
|
&i.LastFiredAt,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.Provider,
|
|
&i.SigningSecret,
|
|
&i.EventFilters,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const setAutopilotTriggerWebhookToken = `-- name: SetAutopilotTriggerWebhookToken :one
|
|
UPDATE autopilot_trigger
|
|
SET webhook_token = $2,
|
|
updated_at = now()
|
|
WHERE id = $1
|
|
RETURNING id, autopilot_id, kind, enabled, cron_expression, timezone, next_run_at, webhook_token, label, last_fired_at, created_at, updated_at, provider, signing_secret, event_filters
|
|
`
|
|
|
|
type SetAutopilotTriggerWebhookTokenParams struct {
|
|
ID pgtype.UUID `json:"id"`
|
|
WebhookToken pgtype.Text `json:"webhook_token"`
|
|
}
|
|
|
|
// Sets the webhook token at creation time. CreateAutopilotTrigger inserts the
|
|
// row first (using its full 8-arg signature), then this query attaches the
|
|
// token. Splitting the create + token-set keeps the existing CreateAutopilotTrigger
|
|
// query usable by the schedule path without forcing every caller to think
|
|
// about webhook_token.
|
|
func (q *Queries) SetAutopilotTriggerWebhookToken(ctx context.Context, arg SetAutopilotTriggerWebhookTokenParams) (AutopilotTrigger, error) {
|
|
row := q.db.QueryRow(ctx, setAutopilotTriggerWebhookToken, arg.ID, arg.WebhookToken)
|
|
var i AutopilotTrigger
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.AutopilotID,
|
|
&i.Kind,
|
|
&i.Enabled,
|
|
&i.CronExpression,
|
|
&i.Timezone,
|
|
&i.NextRunAt,
|
|
&i.WebhookToken,
|
|
&i.Label,
|
|
&i.LastFiredAt,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.Provider,
|
|
&i.SigningSecret,
|
|
&i.EventFilters,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const systemPauseAutopilot = `-- name: SystemPauseAutopilot :one
|
|
UPDATE autopilot
|
|
SET status = 'paused', updated_at = now()
|
|
WHERE id = $1 AND status = 'active'
|
|
RETURNING id, workspace_id, title, description, assignee_id, status, execution_mode, issue_title_template, created_by_type, created_by_id, last_run_at, created_at, updated_at, assignee_type, project_id
|
|
`
|
|
|
|
// Atomically pauses an autopilot only if it is currently active. Returns no
|
|
// rows when the autopilot was already paused/archived (or another worker
|
|
// raced first), letting the caller treat that as a benign no-op rather than
|
|
// an error.
|
|
func (q *Queries) SystemPauseAutopilot(ctx context.Context, id pgtype.UUID) (Autopilot, error) {
|
|
row := q.db.QueryRow(ctx, systemPauseAutopilot, id)
|
|
var i Autopilot
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.WorkspaceID,
|
|
&i.Title,
|
|
&i.Description,
|
|
&i.AssigneeID,
|
|
&i.Status,
|
|
&i.ExecutionMode,
|
|
&i.IssueTitleTemplate,
|
|
&i.CreatedByType,
|
|
&i.CreatedByID,
|
|
&i.LastRunAt,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.AssigneeType,
|
|
&i.ProjectID,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const touchAutopilotTriggerFiredAt = `-- name: TouchAutopilotTriggerFiredAt :exec
|
|
UPDATE autopilot_trigger
|
|
SET last_fired_at = now(),
|
|
updated_at = now()
|
|
WHERE id = $1
|
|
`
|
|
|
|
// Bumps last_fired_at after a webhook fires, regardless of whether the
|
|
// dispatch succeeded, was admission-skipped, or even if Autopilot status
|
|
// transitioned to paused/disabled at exactly the wrong moment. Disabled /
|
|
// paused early-return paths in the handler never call this.
|
|
func (q *Queries) TouchAutopilotTriggerFiredAt(ctx context.Context, id pgtype.UUID) error {
|
|
_, err := q.db.Exec(ctx, touchAutopilotTriggerFiredAt, id)
|
|
return err
|
|
}
|
|
|
|
const updateAutopilot = `-- name: UpdateAutopilot :one
|
|
UPDATE autopilot SET
|
|
title = COALESCE($2, title),
|
|
description = COALESCE($3, description),
|
|
assignee_type = COALESCE($4, assignee_type),
|
|
assignee_id = COALESCE($5::uuid, assignee_id),
|
|
status = COALESCE($6, status),
|
|
execution_mode = COALESCE($7, execution_mode),
|
|
issue_title_template = $8,
|
|
project_id = $9,
|
|
updated_at = now()
|
|
WHERE id = $1
|
|
RETURNING id, workspace_id, title, description, assignee_id, status, execution_mode, issue_title_template, created_by_type, created_by_id, last_run_at, created_at, updated_at, assignee_type, project_id
|
|
`
|
|
|
|
type UpdateAutopilotParams struct {
|
|
ID pgtype.UUID `json:"id"`
|
|
Title pgtype.Text `json:"title"`
|
|
Description pgtype.Text `json:"description"`
|
|
AssigneeType pgtype.Text `json:"assignee_type"`
|
|
AssigneeID pgtype.UUID `json:"assignee_id"`
|
|
Status pgtype.Text `json:"status"`
|
|
ExecutionMode pgtype.Text `json:"execution_mode"`
|
|
IssueTitleTemplate pgtype.Text `json:"issue_title_template"`
|
|
ProjectID pgtype.UUID `json:"project_id"`
|
|
}
|
|
|
|
func (q *Queries) UpdateAutopilot(ctx context.Context, arg UpdateAutopilotParams) (Autopilot, error) {
|
|
row := q.db.QueryRow(ctx, updateAutopilot,
|
|
arg.ID,
|
|
arg.Title,
|
|
arg.Description,
|
|
arg.AssigneeType,
|
|
arg.AssigneeID,
|
|
arg.Status,
|
|
arg.ExecutionMode,
|
|
arg.IssueTitleTemplate,
|
|
arg.ProjectID,
|
|
)
|
|
var i Autopilot
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.WorkspaceID,
|
|
&i.Title,
|
|
&i.Description,
|
|
&i.AssigneeID,
|
|
&i.Status,
|
|
&i.ExecutionMode,
|
|
&i.IssueTitleTemplate,
|
|
&i.CreatedByType,
|
|
&i.CreatedByID,
|
|
&i.LastRunAt,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.AssigneeType,
|
|
&i.ProjectID,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const updateAutopilotLastRunAt = `-- name: UpdateAutopilotLastRunAt :exec
|
|
UPDATE autopilot SET last_run_at = now(), updated_at = now()
|
|
WHERE id = $1
|
|
`
|
|
|
|
func (q *Queries) UpdateAutopilotLastRunAt(ctx context.Context, id pgtype.UUID) error {
|
|
_, err := q.db.Exec(ctx, updateAutopilotLastRunAt, id)
|
|
return err
|
|
}
|
|
|
|
const updateAutopilotRunCompleted = `-- name: UpdateAutopilotRunCompleted :one
|
|
UPDATE autopilot_run
|
|
SET status = 'completed', completed_at = now(), result = $2
|
|
WHERE id = $1
|
|
RETURNING id, autopilot_id, trigger_id, source, status, issue_id, task_id, triggered_at, completed_at, failure_reason, trigger_payload, result, created_at, squad_id
|
|
`
|
|
|
|
type UpdateAutopilotRunCompletedParams struct {
|
|
ID pgtype.UUID `json:"id"`
|
|
Result []byte `json:"result"`
|
|
}
|
|
|
|
func (q *Queries) UpdateAutopilotRunCompleted(ctx context.Context, arg UpdateAutopilotRunCompletedParams) (AutopilotRun, error) {
|
|
row := q.db.QueryRow(ctx, updateAutopilotRunCompleted, arg.ID, arg.Result)
|
|
var i AutopilotRun
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.AutopilotID,
|
|
&i.TriggerID,
|
|
&i.Source,
|
|
&i.Status,
|
|
&i.IssueID,
|
|
&i.TaskID,
|
|
&i.TriggeredAt,
|
|
&i.CompletedAt,
|
|
&i.FailureReason,
|
|
&i.TriggerPayload,
|
|
&i.Result,
|
|
&i.CreatedAt,
|
|
&i.SquadID,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const updateAutopilotRunFailed = `-- name: UpdateAutopilotRunFailed :one
|
|
UPDATE autopilot_run
|
|
SET status = 'failed', completed_at = now(), failure_reason = $2
|
|
WHERE id = $1
|
|
RETURNING id, autopilot_id, trigger_id, source, status, issue_id, task_id, triggered_at, completed_at, failure_reason, trigger_payload, result, created_at, squad_id
|
|
`
|
|
|
|
type UpdateAutopilotRunFailedParams struct {
|
|
ID pgtype.UUID `json:"id"`
|
|
FailureReason pgtype.Text `json:"failure_reason"`
|
|
}
|
|
|
|
func (q *Queries) UpdateAutopilotRunFailed(ctx context.Context, arg UpdateAutopilotRunFailedParams) (AutopilotRun, error) {
|
|
row := q.db.QueryRow(ctx, updateAutopilotRunFailed, arg.ID, arg.FailureReason)
|
|
var i AutopilotRun
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.AutopilotID,
|
|
&i.TriggerID,
|
|
&i.Source,
|
|
&i.Status,
|
|
&i.IssueID,
|
|
&i.TaskID,
|
|
&i.TriggeredAt,
|
|
&i.CompletedAt,
|
|
&i.FailureReason,
|
|
&i.TriggerPayload,
|
|
&i.Result,
|
|
&i.CreatedAt,
|
|
&i.SquadID,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const updateAutopilotRunIssueCreated = `-- name: UpdateAutopilotRunIssueCreated :one
|
|
UPDATE autopilot_run
|
|
SET status = 'issue_created', issue_id = $2
|
|
WHERE id = $1
|
|
RETURNING id, autopilot_id, trigger_id, source, status, issue_id, task_id, triggered_at, completed_at, failure_reason, trigger_payload, result, created_at, squad_id
|
|
`
|
|
|
|
type UpdateAutopilotRunIssueCreatedParams struct {
|
|
ID pgtype.UUID `json:"id"`
|
|
IssueID pgtype.UUID `json:"issue_id"`
|
|
}
|
|
|
|
func (q *Queries) UpdateAutopilotRunIssueCreated(ctx context.Context, arg UpdateAutopilotRunIssueCreatedParams) (AutopilotRun, error) {
|
|
row := q.db.QueryRow(ctx, updateAutopilotRunIssueCreated, arg.ID, arg.IssueID)
|
|
var i AutopilotRun
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.AutopilotID,
|
|
&i.TriggerID,
|
|
&i.Source,
|
|
&i.Status,
|
|
&i.IssueID,
|
|
&i.TaskID,
|
|
&i.TriggeredAt,
|
|
&i.CompletedAt,
|
|
&i.FailureReason,
|
|
&i.TriggerPayload,
|
|
&i.Result,
|
|
&i.CreatedAt,
|
|
&i.SquadID,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const updateAutopilotRunRunning = `-- name: UpdateAutopilotRunRunning :one
|
|
UPDATE autopilot_run
|
|
SET status = 'running', task_id = $2
|
|
WHERE id = $1
|
|
RETURNING id, autopilot_id, trigger_id, source, status, issue_id, task_id, triggered_at, completed_at, failure_reason, trigger_payload, result, created_at, squad_id
|
|
`
|
|
|
|
type UpdateAutopilotRunRunningParams struct {
|
|
ID pgtype.UUID `json:"id"`
|
|
TaskID pgtype.UUID `json:"task_id"`
|
|
}
|
|
|
|
func (q *Queries) UpdateAutopilotRunRunning(ctx context.Context, arg UpdateAutopilotRunRunningParams) (AutopilotRun, error) {
|
|
row := q.db.QueryRow(ctx, updateAutopilotRunRunning, arg.ID, arg.TaskID)
|
|
var i AutopilotRun
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.AutopilotID,
|
|
&i.TriggerID,
|
|
&i.Source,
|
|
&i.Status,
|
|
&i.IssueID,
|
|
&i.TaskID,
|
|
&i.TriggeredAt,
|
|
&i.CompletedAt,
|
|
&i.FailureReason,
|
|
&i.TriggerPayload,
|
|
&i.Result,
|
|
&i.CreatedAt,
|
|
&i.SquadID,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const updateAutopilotRunSkipped = `-- name: UpdateAutopilotRunSkipped :one
|
|
UPDATE autopilot_run
|
|
SET status = 'skipped', completed_at = now(), failure_reason = $2
|
|
WHERE id = $1
|
|
RETURNING id, autopilot_id, trigger_id, source, status, issue_id, task_id, triggered_at, completed_at, failure_reason, trigger_payload, result, created_at, squad_id
|
|
`
|
|
|
|
type UpdateAutopilotRunSkippedParams struct {
|
|
ID pgtype.UUID `json:"id"`
|
|
FailureReason pgtype.Text `json:"failure_reason"`
|
|
}
|
|
|
|
// Marks an autopilot_run as skipped without enqueueing any task. Used by the
|
|
// pre-flight admission check when the assignee agent's runtime is offline:
|
|
// creating an issue / task in that state would just pile a doomed job onto
|
|
// agent_task_queue (the canonical "持续给离线 local agent 入队" symptom from
|
|
// MUL-1899). Recording the skip + reason gives the UI / failure monitor / ops
|
|
// a paper trail without polluting the failure ratio.
|
|
func (q *Queries) UpdateAutopilotRunSkipped(ctx context.Context, arg UpdateAutopilotRunSkippedParams) (AutopilotRun, error) {
|
|
row := q.db.QueryRow(ctx, updateAutopilotRunSkipped, arg.ID, arg.FailureReason)
|
|
var i AutopilotRun
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.AutopilotID,
|
|
&i.TriggerID,
|
|
&i.Source,
|
|
&i.Status,
|
|
&i.IssueID,
|
|
&i.TaskID,
|
|
&i.TriggeredAt,
|
|
&i.CompletedAt,
|
|
&i.FailureReason,
|
|
&i.TriggerPayload,
|
|
&i.Result,
|
|
&i.CreatedAt,
|
|
&i.SquadID,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const updateAutopilotRunSkippedWithResult = `-- name: UpdateAutopilotRunSkippedWithResult :one
|
|
UPDATE autopilot_run
|
|
SET status = 'skipped',
|
|
completed_at = now(),
|
|
failure_reason = $2,
|
|
result = $3
|
|
WHERE id = $1
|
|
RETURNING id, autopilot_id, trigger_id, source, status, issue_id, task_id, triggered_at, completed_at, failure_reason, trigger_payload, result, created_at, squad_id
|
|
`
|
|
|
|
type UpdateAutopilotRunSkippedWithResultParams struct {
|
|
ID pgtype.UUID `json:"id"`
|
|
FailureReason pgtype.Text `json:"failure_reason"`
|
|
Result []byte `json:"result"`
|
|
}
|
|
|
|
func (q *Queries) UpdateAutopilotRunSkippedWithResult(ctx context.Context, arg UpdateAutopilotRunSkippedWithResultParams) (AutopilotRun, error) {
|
|
row := q.db.QueryRow(ctx, updateAutopilotRunSkippedWithResult, arg.ID, arg.FailureReason, arg.Result)
|
|
var i AutopilotRun
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.AutopilotID,
|
|
&i.TriggerID,
|
|
&i.Source,
|
|
&i.Status,
|
|
&i.IssueID,
|
|
&i.TaskID,
|
|
&i.TriggeredAt,
|
|
&i.CompletedAt,
|
|
&i.FailureReason,
|
|
&i.TriggerPayload,
|
|
&i.Result,
|
|
&i.CreatedAt,
|
|
&i.SquadID,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const updateAutopilotTrigger = `-- name: UpdateAutopilotTrigger :one
|
|
UPDATE autopilot_trigger SET
|
|
enabled = COALESCE($2::boolean, enabled),
|
|
cron_expression = COALESCE($3, cron_expression),
|
|
timezone = COALESCE($4, timezone),
|
|
next_run_at = $5,
|
|
label = COALESCE($6, label),
|
|
event_filters = COALESCE($7, event_filters),
|
|
updated_at = now()
|
|
WHERE id = $1
|
|
RETURNING id, autopilot_id, kind, enabled, cron_expression, timezone, next_run_at, webhook_token, label, last_fired_at, created_at, updated_at, provider, signing_secret, event_filters
|
|
`
|
|
|
|
type UpdateAutopilotTriggerParams struct {
|
|
ID pgtype.UUID `json:"id"`
|
|
Enabled pgtype.Bool `json:"enabled"`
|
|
CronExpression pgtype.Text `json:"cron_expression"`
|
|
Timezone pgtype.Text `json:"timezone"`
|
|
NextRunAt pgtype.Timestamptz `json:"next_run_at"`
|
|
Label pgtype.Text `json:"label"`
|
|
EventFilters []byte `json:"event_filters"`
|
|
}
|
|
|
|
func (q *Queries) UpdateAutopilotTrigger(ctx context.Context, arg UpdateAutopilotTriggerParams) (AutopilotTrigger, error) {
|
|
row := q.db.QueryRow(ctx, updateAutopilotTrigger,
|
|
arg.ID,
|
|
arg.Enabled,
|
|
arg.CronExpression,
|
|
arg.Timezone,
|
|
arg.NextRunAt,
|
|
arg.Label,
|
|
arg.EventFilters,
|
|
)
|
|
var i AutopilotTrigger
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.AutopilotID,
|
|
&i.Kind,
|
|
&i.Enabled,
|
|
&i.CronExpression,
|
|
&i.Timezone,
|
|
&i.NextRunAt,
|
|
&i.WebhookToken,
|
|
&i.Label,
|
|
&i.LastFiredAt,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.Provider,
|
|
&i.SigningSecret,
|
|
&i.EventFilters,
|
|
)
|
|
return i, err
|
|
}
|