Files
multica/server/pkg/db/generated/runtime.sql.go
Jiang Bohan 97d292d89b feat(daemon): stabilize daemon_id with persistent UUID + legacy consolidation
Replaces the hostname-derived daemon_id with a UUID written to
~/.multica/<profile>/daemon.id on first start and reread forever after,
so macOS .local suffix drift, system rename, or profile switch can no
longer produce a second agent_runtime row for the same physical daemon.

At registration the daemon reports any hostname-based identifiers it may
have used historically (hostname, hostname.local, hostname-<profile>,
hostname.local-<profile>) as legacy_daemon_ids. The server reparents
agents and tasks from matching stale rows onto the new UUID row, deletes
them, and records the last value in a new legacy_daemon_id column for
audit. Scoped by (workspace, provider, owner) so another user's
runtimes on a shared hostname are never touched.

Removes MigrateAgentsToRuntime and its LIKE hostname-% logic; the UUID
makes runtime rebuild impossible, so the brittle profile-prefix heuristic
is no longer needed.

Refs MUL-975.
2026-04-17 02:30:40 +08:00

533 lines
15 KiB
Go

// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.30.0
// source: runtime.sql
package db
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
)
const countActiveAgentsByRuntime = `-- name: CountActiveAgentsByRuntime :one
SELECT count(*) FROM agent WHERE runtime_id = $1 AND archived_at IS NULL
`
func (q *Queries) CountActiveAgentsByRuntime(ctx context.Context, runtimeID pgtype.UUID) (int64, error) {
row := q.db.QueryRow(ctx, countActiveAgentsByRuntime, runtimeID)
var count int64
err := row.Scan(&count)
return count, err
}
const deleteAgentRuntime = `-- name: DeleteAgentRuntime :exec
DELETE FROM agent_runtime WHERE id = $1
`
func (q *Queries) DeleteAgentRuntime(ctx context.Context, id pgtype.UUID) error {
_, err := q.db.Exec(ctx, deleteAgentRuntime, id)
return err
}
const deleteArchivedAgentsByRuntime = `-- name: DeleteArchivedAgentsByRuntime :exec
DELETE FROM agent WHERE runtime_id = $1 AND archived_at IS NOT NULL
`
func (q *Queries) DeleteArchivedAgentsByRuntime(ctx context.Context, runtimeID pgtype.UUID) error {
_, err := q.db.Exec(ctx, deleteArchivedAgentsByRuntime, runtimeID)
return err
}
const deleteLegacyRuntime = `-- name: DeleteLegacyRuntime :execrows
DELETE FROM agent_runtime
WHERE workspace_id = $1
AND provider = $2
AND owner_id = $3
AND id != $4
AND daemon_id = $5
`
type DeleteLegacyRuntimeParams struct {
WorkspaceID pgtype.UUID `json:"workspace_id"`
Provider string `json:"provider"`
OwnerID pgtype.UUID `json:"owner_id"`
NewRuntimeID pgtype.UUID `json:"new_runtime_id"`
LegacyDaemonID pgtype.Text `json:"legacy_daemon_id"`
}
// Removes the stale hostname-derived runtime row once its agents and tasks
// have been reparented. legacy_daemon_id on the new row captures the last
// removed value as a breadcrumb.
func (q *Queries) DeleteLegacyRuntime(ctx context.Context, arg DeleteLegacyRuntimeParams) (int64, error) {
result, err := q.db.Exec(ctx, deleteLegacyRuntime,
arg.WorkspaceID,
arg.Provider,
arg.OwnerID,
arg.NewRuntimeID,
arg.LegacyDaemonID,
)
if err != nil {
return 0, err
}
return result.RowsAffected(), nil
}
const deleteStaleOfflineRuntimes = `-- name: DeleteStaleOfflineRuntimes :many
DELETE FROM agent_runtime
WHERE status = 'offline'
AND last_seen_at < now() - make_interval(secs => $1::double precision)
AND id NOT IN (SELECT DISTINCT runtime_id FROM agent)
RETURNING id, workspace_id
`
type DeleteStaleOfflineRuntimesRow struct {
ID pgtype.UUID `json:"id"`
WorkspaceID pgtype.UUID `json:"workspace_id"`
}
// Deletes runtimes that have been offline for longer than the TTL and have
// no agents bound (active or archived). The FK constraint on agent.runtime_id
// is ON DELETE RESTRICT, so we must exclude all agent references.
func (q *Queries) DeleteStaleOfflineRuntimes(ctx context.Context, staleSeconds float64) ([]DeleteStaleOfflineRuntimesRow, error) {
rows, err := q.db.Query(ctx, deleteStaleOfflineRuntimes, staleSeconds)
if err != nil {
return nil, err
}
defer rows.Close()
items := []DeleteStaleOfflineRuntimesRow{}
for rows.Next() {
var i DeleteStaleOfflineRuntimesRow
if err := rows.Scan(&i.ID, &i.WorkspaceID); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const failTasksForOfflineRuntimes = `-- name: FailTasksForOfflineRuntimes :many
UPDATE agent_task_queue
SET status = 'failed', completed_at = now(), error = 'runtime went offline'
WHERE status IN ('dispatched', 'running')
AND runtime_id IN (
SELECT id FROM agent_runtime WHERE status = 'offline'
)
RETURNING id, agent_id, issue_id
`
type FailTasksForOfflineRuntimesRow struct {
ID pgtype.UUID `json:"id"`
AgentID pgtype.UUID `json:"agent_id"`
IssueID pgtype.UUID `json:"issue_id"`
}
// Marks dispatched/running tasks as failed when their runtime is offline.
// This cleans up orphaned tasks after a daemon crash or network partition.
func (q *Queries) FailTasksForOfflineRuntimes(ctx context.Context) ([]FailTasksForOfflineRuntimesRow, error) {
rows, err := q.db.Query(ctx, failTasksForOfflineRuntimes)
if err != nil {
return nil, err
}
defer rows.Close()
items := []FailTasksForOfflineRuntimesRow{}
for rows.Next() {
var i FailTasksForOfflineRuntimesRow
if err := rows.Scan(&i.ID, &i.AgentID, &i.IssueID); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getAgentRuntime = `-- name: GetAgentRuntime :one
SELECT id, workspace_id, daemon_id, name, runtime_mode, provider, status, device_info, metadata, last_seen_at, created_at, updated_at, owner_id, legacy_daemon_id FROM agent_runtime
WHERE id = $1
`
func (q *Queries) GetAgentRuntime(ctx context.Context, id pgtype.UUID) (AgentRuntime, error) {
row := q.db.QueryRow(ctx, getAgentRuntime, id)
var i AgentRuntime
err := row.Scan(
&i.ID,
&i.WorkspaceID,
&i.DaemonID,
&i.Name,
&i.RuntimeMode,
&i.Provider,
&i.Status,
&i.DeviceInfo,
&i.Metadata,
&i.LastSeenAt,
&i.CreatedAt,
&i.UpdatedAt,
&i.OwnerID,
&i.LegacyDaemonID,
)
return i, err
}
const getAgentRuntimeForWorkspace = `-- name: GetAgentRuntimeForWorkspace :one
SELECT id, workspace_id, daemon_id, name, runtime_mode, provider, status, device_info, metadata, last_seen_at, created_at, updated_at, owner_id, legacy_daemon_id FROM agent_runtime
WHERE id = $1 AND workspace_id = $2
`
type GetAgentRuntimeForWorkspaceParams struct {
ID pgtype.UUID `json:"id"`
WorkspaceID pgtype.UUID `json:"workspace_id"`
}
func (q *Queries) GetAgentRuntimeForWorkspace(ctx context.Context, arg GetAgentRuntimeForWorkspaceParams) (AgentRuntime, error) {
row := q.db.QueryRow(ctx, getAgentRuntimeForWorkspace, arg.ID, arg.WorkspaceID)
var i AgentRuntime
err := row.Scan(
&i.ID,
&i.WorkspaceID,
&i.DaemonID,
&i.Name,
&i.RuntimeMode,
&i.Provider,
&i.Status,
&i.DeviceInfo,
&i.Metadata,
&i.LastSeenAt,
&i.CreatedAt,
&i.UpdatedAt,
&i.OwnerID,
&i.LegacyDaemonID,
)
return i, err
}
const listAgentRuntimes = `-- name: ListAgentRuntimes :many
SELECT id, workspace_id, daemon_id, name, runtime_mode, provider, status, device_info, metadata, last_seen_at, created_at, updated_at, owner_id, legacy_daemon_id FROM agent_runtime
WHERE workspace_id = $1
ORDER BY created_at ASC
`
func (q *Queries) ListAgentRuntimes(ctx context.Context, workspaceID pgtype.UUID) ([]AgentRuntime, error) {
rows, err := q.db.Query(ctx, listAgentRuntimes, workspaceID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []AgentRuntime{}
for rows.Next() {
var i AgentRuntime
if err := rows.Scan(
&i.ID,
&i.WorkspaceID,
&i.DaemonID,
&i.Name,
&i.RuntimeMode,
&i.Provider,
&i.Status,
&i.DeviceInfo,
&i.Metadata,
&i.LastSeenAt,
&i.CreatedAt,
&i.UpdatedAt,
&i.OwnerID,
&i.LegacyDaemonID,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listAgentRuntimesByOwner = `-- name: ListAgentRuntimesByOwner :many
SELECT id, workspace_id, daemon_id, name, runtime_mode, provider, status, device_info, metadata, last_seen_at, created_at, updated_at, owner_id, legacy_daemon_id FROM agent_runtime
WHERE workspace_id = $1 AND owner_id = $2
ORDER BY created_at ASC
`
type ListAgentRuntimesByOwnerParams struct {
WorkspaceID pgtype.UUID `json:"workspace_id"`
OwnerID pgtype.UUID `json:"owner_id"`
}
func (q *Queries) ListAgentRuntimesByOwner(ctx context.Context, arg ListAgentRuntimesByOwnerParams) ([]AgentRuntime, error) {
rows, err := q.db.Query(ctx, listAgentRuntimesByOwner, arg.WorkspaceID, arg.OwnerID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []AgentRuntime{}
for rows.Next() {
var i AgentRuntime
if err := rows.Scan(
&i.ID,
&i.WorkspaceID,
&i.DaemonID,
&i.Name,
&i.RuntimeMode,
&i.Provider,
&i.Status,
&i.DeviceInfo,
&i.Metadata,
&i.LastSeenAt,
&i.CreatedAt,
&i.UpdatedAt,
&i.OwnerID,
&i.LegacyDaemonID,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const markStaleRuntimesOffline = `-- name: MarkStaleRuntimesOffline :many
UPDATE agent_runtime
SET status = 'offline', updated_at = now()
WHERE status = 'online'
AND last_seen_at < now() - make_interval(secs => $1::double precision)
RETURNING id, workspace_id
`
type MarkStaleRuntimesOfflineRow struct {
ID pgtype.UUID `json:"id"`
WorkspaceID pgtype.UUID `json:"workspace_id"`
}
func (q *Queries) MarkStaleRuntimesOffline(ctx context.Context, staleSeconds float64) ([]MarkStaleRuntimesOfflineRow, error) {
rows, err := q.db.Query(ctx, markStaleRuntimesOffline, staleSeconds)
if err != nil {
return nil, err
}
defer rows.Close()
items := []MarkStaleRuntimesOfflineRow{}
for rows.Next() {
var i MarkStaleRuntimesOfflineRow
if err := rows.Scan(&i.ID, &i.WorkspaceID); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const migrateAgentsFromLegacyDaemon = `-- name: MigrateAgentsFromLegacyDaemon :execrows
UPDATE agent
SET runtime_id = $1
WHERE runtime_id IN (
SELECT ar.id FROM agent_runtime ar
WHERE ar.workspace_id = $2
AND ar.provider = $3
AND ar.owner_id = $4
AND ar.id != $1
AND ar.daemon_id = $5
)
`
type MigrateAgentsFromLegacyDaemonParams struct {
NewRuntimeID pgtype.UUID `json:"new_runtime_id"`
WorkspaceID pgtype.UUID `json:"workspace_id"`
Provider string `json:"provider"`
OwnerID pgtype.UUID `json:"owner_id"`
LegacyDaemonID pgtype.Text `json:"legacy_daemon_id"`
}
// Reparents agents from the legacy (hostname-derived) runtime row to the
// newly registered UUID row. Scoped to a single (workspace, provider,
// owner) triple so we never touch another user's runtimes even if they
// share a hostname on the same machine. Called once per legacy daemon_id
// candidate reported by the daemon at registration time.
func (q *Queries) MigrateAgentsFromLegacyDaemon(ctx context.Context, arg MigrateAgentsFromLegacyDaemonParams) (int64, error) {
result, err := q.db.Exec(ctx, migrateAgentsFromLegacyDaemon,
arg.NewRuntimeID,
arg.WorkspaceID,
arg.Provider,
arg.OwnerID,
arg.LegacyDaemonID,
)
if err != nil {
return 0, err
}
return result.RowsAffected(), nil
}
const migrateTasksFromLegacyDaemon = `-- name: MigrateTasksFromLegacyDaemon :execrows
UPDATE agent_task_queue
SET runtime_id = $1
WHERE runtime_id IN (
SELECT ar.id FROM agent_runtime ar
WHERE ar.workspace_id = $2
AND ar.provider = $3
AND ar.owner_id = $4
AND ar.id != $1
AND ar.daemon_id = $5
)
`
type MigrateTasksFromLegacyDaemonParams struct {
NewRuntimeID pgtype.UUID `json:"new_runtime_id"`
WorkspaceID pgtype.UUID `json:"workspace_id"`
Provider string `json:"provider"`
OwnerID pgtype.UUID `json:"owner_id"`
LegacyDaemonID pgtype.Text `json:"legacy_daemon_id"`
}
// Same scoping as MigrateAgentsFromLegacyDaemon. Must run before the DELETE
// below because agent_task_queue.runtime_id is ON DELETE CASCADE; deleting
// the legacy row first would silently drop in-flight tasks.
func (q *Queries) MigrateTasksFromLegacyDaemon(ctx context.Context, arg MigrateTasksFromLegacyDaemonParams) (int64, error) {
result, err := q.db.Exec(ctx, migrateTasksFromLegacyDaemon,
arg.NewRuntimeID,
arg.WorkspaceID,
arg.Provider,
arg.OwnerID,
arg.LegacyDaemonID,
)
if err != nil {
return 0, err
}
return result.RowsAffected(), nil
}
const setAgentRuntimeOffline = `-- name: SetAgentRuntimeOffline :exec
UPDATE agent_runtime
SET status = 'offline', updated_at = now()
WHERE id = $1
`
func (q *Queries) SetAgentRuntimeOffline(ctx context.Context, id pgtype.UUID) error {
_, err := q.db.Exec(ctx, setAgentRuntimeOffline, id)
return err
}
const setRuntimeLegacyDaemonID = `-- name: SetRuntimeLegacyDaemonID :exec
UPDATE agent_runtime
SET legacy_daemon_id = $1
WHERE id = $2
`
type SetRuntimeLegacyDaemonIDParams struct {
LegacyDaemonID pgtype.Text `json:"legacy_daemon_id"`
ID pgtype.UUID `json:"id"`
}
func (q *Queries) SetRuntimeLegacyDaemonID(ctx context.Context, arg SetRuntimeLegacyDaemonIDParams) error {
_, err := q.db.Exec(ctx, setRuntimeLegacyDaemonID, arg.LegacyDaemonID, arg.ID)
return err
}
const updateAgentRuntimeHeartbeat = `-- name: UpdateAgentRuntimeHeartbeat :one
UPDATE agent_runtime
SET status = 'online', last_seen_at = now(), updated_at = now()
WHERE id = $1
RETURNING id, workspace_id, daemon_id, name, runtime_mode, provider, status, device_info, metadata, last_seen_at, created_at, updated_at, owner_id, legacy_daemon_id
`
func (q *Queries) UpdateAgentRuntimeHeartbeat(ctx context.Context, id pgtype.UUID) (AgentRuntime, error) {
row := q.db.QueryRow(ctx, updateAgentRuntimeHeartbeat, id)
var i AgentRuntime
err := row.Scan(
&i.ID,
&i.WorkspaceID,
&i.DaemonID,
&i.Name,
&i.RuntimeMode,
&i.Provider,
&i.Status,
&i.DeviceInfo,
&i.Metadata,
&i.LastSeenAt,
&i.CreatedAt,
&i.UpdatedAt,
&i.OwnerID,
&i.LegacyDaemonID,
)
return i, err
}
const upsertAgentRuntime = `-- name: UpsertAgentRuntime :one
INSERT INTO agent_runtime (
workspace_id,
daemon_id,
name,
runtime_mode,
provider,
status,
device_info,
metadata,
owner_id,
last_seen_at
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, now())
ON CONFLICT (workspace_id, daemon_id, provider)
DO UPDATE SET
name = EXCLUDED.name,
runtime_mode = EXCLUDED.runtime_mode,
status = EXCLUDED.status,
device_info = EXCLUDED.device_info,
metadata = EXCLUDED.metadata,
owner_id = COALESCE(EXCLUDED.owner_id, agent_runtime.owner_id),
last_seen_at = now(),
updated_at = now()
RETURNING id, workspace_id, daemon_id, name, runtime_mode, provider, status, device_info, metadata, last_seen_at, created_at, updated_at, owner_id, legacy_daemon_id
`
type UpsertAgentRuntimeParams struct {
WorkspaceID pgtype.UUID `json:"workspace_id"`
DaemonID pgtype.Text `json:"daemon_id"`
Name string `json:"name"`
RuntimeMode string `json:"runtime_mode"`
Provider string `json:"provider"`
Status string `json:"status"`
DeviceInfo string `json:"device_info"`
Metadata []byte `json:"metadata"`
OwnerID pgtype.UUID `json:"owner_id"`
}
func (q *Queries) UpsertAgentRuntime(ctx context.Context, arg UpsertAgentRuntimeParams) (AgentRuntime, error) {
row := q.db.QueryRow(ctx, upsertAgentRuntime,
arg.WorkspaceID,
arg.DaemonID,
arg.Name,
arg.RuntimeMode,
arg.Provider,
arg.Status,
arg.DeviceInfo,
arg.Metadata,
arg.OwnerID,
)
var i AgentRuntime
err := row.Scan(
&i.ID,
&i.WorkspaceID,
&i.DaemonID,
&i.Name,
&i.RuntimeMode,
&i.Provider,
&i.Status,
&i.DeviceInfo,
&i.Metadata,
&i.LastSeenAt,
&i.CreatedAt,
&i.UpdatedAt,
&i.OwnerID,
&i.LegacyDaemonID,
)
return i, err
}