mirror of
https://gh-proxy.org/https://github.com/klinsmaya/multica.git
synced 2026-11-10 02:16:45 +08:00
Deleting a profile-backed runtime instance is refused while its profile exists, and the refusal said "delete its runtime profile instead". For the case that produces it most often — a retired machine's leftover row in a profile other, healthy machines still use — that points at a workspace-wide delete which takes those machines' runtimes too, and which a bound agent refuses anyway with a second message naming no agent and no machine. Retention GC already deletes these rows after 7 days offline, ignoring profile_id, so the refusals now describe that instead of recommending the destructive route: - Instance refusal is status-aware, and only promises automatic cleanup when it matches the gate gcRuntime actually applies: the runtime's own undrained tasks plus those owned by every user agent bound to it, archived included. - Profile refusal names the blocking agents and the machine each sits on, with a per-class remedy — ordinary agents can be reassigned or archived; Mika cannot be archived but can be rebound; a Builder session is reachable only by its creator. Those clauses come from counts over the whole blocker set, so a class outside the 20-row sample is still reported. - Response bounded at 20 entries with an exact total, capping rows read inside the locked delete transaction. - CLI surfaces the server's sentence instead of dumping the raw JSON body. When a delete is allowed or refused is unchanged; the guard predicates and the instance-refusal error code are untouched. The remaining capability gap — retiring an offline instance on demand — is tracked separately. Fixes MUL-7409
577 lines
19 KiB
Go
577 lines
19 KiB
Go
// Code generated by sqlc. DO NOT EDIT.
|
|
// versions:
|
|
// sqlc v1.31.1
|
|
// source: runtime_profile.sql
|
|
|
|
package db
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/jackc/pgx/v5/pgtype"
|
|
)
|
|
|
|
const createRuntimeProfile = `-- name: CreateRuntimeProfile :one
|
|
|
|
INSERT INTO runtime_profile (
|
|
workspace_id,
|
|
display_name,
|
|
protocol_family,
|
|
command_name,
|
|
description,
|
|
fixed_args,
|
|
visibility,
|
|
created_by,
|
|
enabled
|
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
|
RETURNING id, workspace_id, display_name, protocol_family, command_name, description, fixed_args, visibility, created_by, enabled, created_at, updated_at
|
|
`
|
|
|
|
type CreateRuntimeProfileParams struct {
|
|
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
|
DisplayName string `json:"display_name"`
|
|
ProtocolFamily string `json:"protocol_family"`
|
|
CommandName string `json:"command_name"`
|
|
Description pgtype.Text `json:"description"`
|
|
FixedArgs []byte `json:"fixed_args"`
|
|
Visibility string `json:"visibility"`
|
|
CreatedBy pgtype.UUID `json:"created_by"`
|
|
Enabled bool `json:"enabled"`
|
|
}
|
|
|
|
// Custom Runtime profiles (MUL-3284). Workspace-level definitions of a custom
|
|
// runtime; see migration 120 for the table. Relational integrity (workspace,
|
|
// created_by) is enforced in the application layer — there are no DB FKs.
|
|
func (q *Queries) CreateRuntimeProfile(ctx context.Context, arg CreateRuntimeProfileParams) (RuntimeProfile, error) {
|
|
row := q.db.QueryRow(ctx, createRuntimeProfile,
|
|
arg.WorkspaceID,
|
|
arg.DisplayName,
|
|
arg.ProtocolFamily,
|
|
arg.CommandName,
|
|
arg.Description,
|
|
arg.FixedArgs,
|
|
arg.Visibility,
|
|
arg.CreatedBy,
|
|
arg.Enabled,
|
|
)
|
|
var i RuntimeProfile
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.WorkspaceID,
|
|
&i.DisplayName,
|
|
&i.ProtocolFamily,
|
|
&i.CommandName,
|
|
&i.Description,
|
|
&i.FixedArgs,
|
|
&i.Visibility,
|
|
&i.CreatedBy,
|
|
&i.Enabled,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const deleteAgentRuntimesByProfile = `-- name: DeleteAgentRuntimesByProfile :many
|
|
DELETE FROM agent_runtime
|
|
WHERE profile_id = $1 AND workspace_id = $2
|
|
RETURNING id, workspace_id, owner_id, daemon_id, provider
|
|
`
|
|
|
|
type DeleteAgentRuntimesByProfileParams struct {
|
|
ProfileID pgtype.UUID `json:"profile_id"`
|
|
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
|
}
|
|
|
|
type DeleteAgentRuntimesByProfileRow struct {
|
|
ID pgtype.UUID `json:"id"`
|
|
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
|
OwnerID pgtype.UUID `json:"owner_id"`
|
|
DaemonID pgtype.Text `json:"daemon_id"`
|
|
Provider string `json:"provider"`
|
|
}
|
|
|
|
// Application-layer cascade: migration 120 dropped the DB ON DELETE CASCADE, so
|
|
// the profile-delete path must remove the profile's registered runtime
|
|
// instances itself. Returns the deleted rows so the caller can broadcast /
|
|
// audit. Runs inside the same transaction as DeleteRuntimeProfile.
|
|
func (q *Queries) DeleteAgentRuntimesByProfile(ctx context.Context, arg DeleteAgentRuntimesByProfileParams) ([]DeleteAgentRuntimesByProfileRow, error) {
|
|
rows, err := q.db.Query(ctx, deleteAgentRuntimesByProfile, arg.ProfileID, arg.WorkspaceID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []DeleteAgentRuntimesByProfileRow{}
|
|
for rows.Next() {
|
|
var i DeleteAgentRuntimesByProfileRow
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.WorkspaceID,
|
|
&i.OwnerID,
|
|
&i.DaemonID,
|
|
&i.Provider,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const deleteRuntimeProfile = `-- name: DeleteRuntimeProfile :exec
|
|
DELETE FROM runtime_profile
|
|
WHERE id = $1 AND workspace_id = $2
|
|
`
|
|
|
|
type DeleteRuntimeProfileParams struct {
|
|
ID pgtype.UUID `json:"id"`
|
|
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
|
}
|
|
|
|
func (q *Queries) DeleteRuntimeProfile(ctx context.Context, arg DeleteRuntimeProfileParams) error {
|
|
_, err := q.db.Exec(ctx, deleteRuntimeProfile, arg.ID, arg.WorkspaceID)
|
|
return err
|
|
}
|
|
|
|
const getRuntimeProfile = `-- name: GetRuntimeProfile :one
|
|
SELECT id, workspace_id, display_name, protocol_family, command_name, description, fixed_args, visibility, created_by, enabled, created_at, updated_at FROM runtime_profile
|
|
WHERE id = $1
|
|
`
|
|
|
|
func (q *Queries) GetRuntimeProfile(ctx context.Context, id pgtype.UUID) (RuntimeProfile, error) {
|
|
row := q.db.QueryRow(ctx, getRuntimeProfile, id)
|
|
var i RuntimeProfile
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.WorkspaceID,
|
|
&i.DisplayName,
|
|
&i.ProtocolFamily,
|
|
&i.CommandName,
|
|
&i.Description,
|
|
&i.FixedArgs,
|
|
&i.Visibility,
|
|
&i.CreatedBy,
|
|
&i.Enabled,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getRuntimeProfileForWorkspace = `-- name: GetRuntimeProfileForWorkspace :one
|
|
SELECT id, workspace_id, display_name, protocol_family, command_name, description, fixed_args, visibility, created_by, enabled, created_at, updated_at FROM runtime_profile
|
|
WHERE id = $1 AND workspace_id = $2
|
|
`
|
|
|
|
type GetRuntimeProfileForWorkspaceParams struct {
|
|
ID pgtype.UUID `json:"id"`
|
|
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
|
}
|
|
|
|
func (q *Queries) GetRuntimeProfileForWorkspace(ctx context.Context, arg GetRuntimeProfileForWorkspaceParams) (RuntimeProfile, error) {
|
|
row := q.db.QueryRow(ctx, getRuntimeProfileForWorkspace, arg.ID, arg.WorkspaceID)
|
|
var i RuntimeProfile
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.WorkspaceID,
|
|
&i.DisplayName,
|
|
&i.ProtocolFamily,
|
|
&i.CommandName,
|
|
&i.Description,
|
|
&i.FixedArgs,
|
|
&i.Visibility,
|
|
&i.CreatedBy,
|
|
&i.Enabled,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const listActiveAgentsByProfile = `-- name: ListActiveAgentsByProfile :many
|
|
WITH blockers AS (
|
|
SELECT
|
|
a.id,
|
|
a.name,
|
|
a.kind,
|
|
a.system_key,
|
|
ar.id AS runtime_id,
|
|
ar.name AS runtime_name,
|
|
ar.custom_name AS runtime_custom_name,
|
|
ar.status AS runtime_status,
|
|
CASE
|
|
WHEN a.system_key IS NULL OR btrim(a.system_key) = '' THEN 'user'
|
|
WHEN btrim(a.system_key) = 'mika' THEN 'mika'
|
|
-- starts_with, not LIKE: '_' is a single-character wildcard in
|
|
-- LIKE, so 'agent_builder:%' also matches 'agent-builder:x' and
|
|
-- 'agentXbuilder:x'. Those would be classified here as carriers
|
|
-- and by the Go side as other_system, and the refusal would send
|
|
-- the user to an Agent Builder session that does not exist.
|
|
WHEN starts_with(btrim(a.system_key), 'agent_builder:') THEN 'agent_builder'
|
|
ELSE 'other_system'
|
|
END AS blocker_class
|
|
FROM agent a
|
|
JOIN agent_runtime ar ON ar.id = a.runtime_id
|
|
WHERE ar.profile_id = $1 AND ar.workspace_id = $2 AND a.archived_at IS NULL
|
|
)
|
|
SELECT
|
|
id,
|
|
name,
|
|
kind,
|
|
system_key,
|
|
runtime_id,
|
|
runtime_name,
|
|
runtime_custom_name,
|
|
runtime_status,
|
|
blocker_class,
|
|
count(*) OVER () AS total_count,
|
|
count(*) FILTER (WHERE blocker_class = 'user') OVER () AS user_count,
|
|
count(*) FILTER (WHERE blocker_class = 'mika') OVER () AS mika_count,
|
|
count(*) FILTER (WHERE blocker_class = 'agent_builder') OVER () AS agent_builder_count,
|
|
count(*) FILTER (WHERE blocker_class = 'other_system') OVER () AS other_system_count
|
|
FROM blockers
|
|
ORDER BY runtime_name ASC, name ASC
|
|
LIMIT $3::int
|
|
`
|
|
|
|
type ListActiveAgentsByProfileParams struct {
|
|
ProfileID pgtype.UUID `json:"profile_id"`
|
|
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
|
MaxRows int32 `json:"max_rows"`
|
|
}
|
|
|
|
type ListActiveAgentsByProfileRow struct {
|
|
ID pgtype.UUID `json:"id"`
|
|
Name string `json:"name"`
|
|
Kind string `json:"kind"`
|
|
SystemKey pgtype.Text `json:"system_key"`
|
|
RuntimeID pgtype.UUID `json:"runtime_id"`
|
|
RuntimeName string `json:"runtime_name"`
|
|
RuntimeCustomName pgtype.Text `json:"runtime_custom_name"`
|
|
RuntimeStatus string `json:"runtime_status"`
|
|
BlockerClass string `json:"blocker_class"`
|
|
TotalCount int64 `json:"total_count"`
|
|
UserCount int64 `json:"user_count"`
|
|
MikaCount int64 `json:"mika_count"`
|
|
AgentBuilderCount int64 `json:"agent_builder_count"`
|
|
OtherSystemCount int64 `json:"other_system_count"`
|
|
}
|
|
|
|
// Active (non-archived) agents bound to any runtime instance of this profile.
|
|
// The profile-delete path uses this to refuse deletion (409) while agents
|
|
// still depend on it, mirroring the runtime-delete guard.
|
|
//
|
|
// It returns the rows rather than a count because the refusal has to name
|
|
// them: a profile spans every machine that registered it, so the agents
|
|
// blocking the delete are routinely bound to a different machine than the one
|
|
// the user was trying to clean up, and a bare count gives them no way to tell
|
|
// (GH #8456). Carrying the runtime is what lets the message say which machine.
|
|
//
|
|
// Deliberately not filtered by kind: it defines when deletion is refused, and
|
|
// narrowing it to user agents here would let a profile with a bound builder
|
|
// carrier through, whereupon TeardownRuntime would hard-delete that carrier.
|
|
//
|
|
// system_key rides along because it, not kind, decides what the user can
|
|
// actually do about a blocker. Mika is kind='user' with system_key='mika' and
|
|
// can be neither archived nor moved; a builder carrier is kind='system' and is
|
|
// released by its Builder session, not from the agent list.
|
|
//
|
|
// Bounded on purpose. The caller only needs to know that blockers exist, name a
|
|
// few, and report how many there are — it never needs every row. This runs
|
|
// inside the delete transaction while profile, runtime and agent rows are
|
|
// locked, and the response is buffered whole before it is written, so an
|
|
// unbounded read here would make both the time under lock and the response body
|
|
// grow with the number of agents a profile has accumulated across machines.
|
|
//
|
|
// The per-class counts are what let the refusal stay correct while bounded.
|
|
// Rows are ordered by machine and name, so which blockers land inside the LIMIT
|
|
// is arbitrary with respect to class: twenty ordinary agents can push the one
|
|
// Mika to position 21. A message whose advice came from the visible rows would
|
|
// then tell the user to archive all of them — the unactionable instruction this
|
|
// whole change removes, just deferred. So the recovery paths are chosen from
|
|
// these counts and only the names come from the rows.
|
|
//
|
|
// Every count is a window function over the pre-LIMIT result, so they stay
|
|
// exact no matter how small max_rows is. blocker_class is emitted per row as
|
|
// well, giving the classification one definition that the Go classifier is
|
|
// pinned against in tests.
|
|
func (q *Queries) ListActiveAgentsByProfile(ctx context.Context, arg ListActiveAgentsByProfileParams) ([]ListActiveAgentsByProfileRow, error) {
|
|
rows, err := q.db.Query(ctx, listActiveAgentsByProfile, arg.ProfileID, arg.WorkspaceID, arg.MaxRows)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []ListActiveAgentsByProfileRow{}
|
|
for rows.Next() {
|
|
var i ListActiveAgentsByProfileRow
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.Name,
|
|
&i.Kind,
|
|
&i.SystemKey,
|
|
&i.RuntimeID,
|
|
&i.RuntimeName,
|
|
&i.RuntimeCustomName,
|
|
&i.RuntimeStatus,
|
|
&i.BlockerClass,
|
|
&i.TotalCount,
|
|
&i.UserCount,
|
|
&i.MikaCount,
|
|
&i.AgentBuilderCount,
|
|
&i.OtherSystemCount,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const listAgentRuntimeIDsByProfile = `-- name: ListAgentRuntimeIDsByProfile :many
|
|
SELECT id FROM agent_runtime
|
|
WHERE profile_id = $1 AND workspace_id = $2
|
|
ORDER BY id
|
|
FOR UPDATE
|
|
`
|
|
|
|
type ListAgentRuntimeIDsByProfileParams struct {
|
|
ProfileID pgtype.UUID `json:"profile_id"`
|
|
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
|
}
|
|
|
|
// Enumerates the runtime instance rows registered against a profile. The
|
|
// profile-delete cascade walks these so it can run the same archived-agent /
|
|
// archived-squad / autopilot teardown the runtime-delete path uses before
|
|
// removing each runtime row — agent.runtime_id is ON DELETE RESTRICT, so a
|
|
// bare delete would 500 whenever an archived agent still references the row.
|
|
func (q *Queries) ListAgentRuntimeIDsByProfile(ctx context.Context, arg ListAgentRuntimeIDsByProfileParams) ([]pgtype.UUID, error) {
|
|
rows, err := q.db.Query(ctx, listAgentRuntimeIDsByProfile, arg.ProfileID, arg.WorkspaceID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []pgtype.UUID{}
|
|
for rows.Next() {
|
|
var id pgtype.UUID
|
|
if err := rows.Scan(&id); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, id)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const listEnabledRuntimeProfilesForWorkspace = `-- name: ListEnabledRuntimeProfilesForWorkspace :many
|
|
SELECT id, workspace_id, display_name, protocol_family, command_name, description, fixed_args, visibility, created_by, enabled, created_at, updated_at FROM runtime_profile
|
|
WHERE workspace_id = $1 AND enabled = true
|
|
ORDER BY created_at ASC
|
|
`
|
|
|
|
// Daemon-facing list: only enabled profiles are candidates for a daemon to
|
|
// resolve on PATH and register. Ordered for stable output.
|
|
func (q *Queries) ListEnabledRuntimeProfilesForWorkspace(ctx context.Context, workspaceID pgtype.UUID) ([]RuntimeProfile, error) {
|
|
rows, err := q.db.Query(ctx, listEnabledRuntimeProfilesForWorkspace, workspaceID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []RuntimeProfile{}
|
|
for rows.Next() {
|
|
var i RuntimeProfile
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.WorkspaceID,
|
|
&i.DisplayName,
|
|
&i.ProtocolFamily,
|
|
&i.CommandName,
|
|
&i.Description,
|
|
&i.FixedArgs,
|
|
&i.Visibility,
|
|
&i.CreatedBy,
|
|
&i.Enabled,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const listRuntimeProfiles = `-- name: ListRuntimeProfiles :many
|
|
SELECT id, workspace_id, display_name, protocol_family, command_name, description, fixed_args, visibility, created_by, enabled, created_at, updated_at FROM runtime_profile
|
|
WHERE workspace_id = $1
|
|
ORDER BY created_at ASC
|
|
`
|
|
|
|
func (q *Queries) ListRuntimeProfiles(ctx context.Context, workspaceID pgtype.UUID) ([]RuntimeProfile, error) {
|
|
rows, err := q.db.Query(ctx, listRuntimeProfiles, workspaceID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []RuntimeProfile{}
|
|
for rows.Next() {
|
|
var i RuntimeProfile
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.WorkspaceID,
|
|
&i.DisplayName,
|
|
&i.ProtocolFamily,
|
|
&i.CommandName,
|
|
&i.Description,
|
|
&i.FixedArgs,
|
|
&i.Visibility,
|
|
&i.CreatedBy,
|
|
&i.Enabled,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const lockRuntimeProfileForDelete = `-- name: LockRuntimeProfileForDelete :one
|
|
SELECT id, workspace_id, display_name, protocol_family, command_name, description, fixed_args, visibility, created_by, enabled, created_at, updated_at FROM runtime_profile
|
|
WHERE id = $1 AND workspace_id = $2
|
|
FOR UPDATE
|
|
`
|
|
|
|
type LockRuntimeProfileForDeleteParams struct {
|
|
ID pgtype.UUID `json:"id"`
|
|
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
|
}
|
|
|
|
// See LockRuntimeProfileForRegistration. The stronger lock prevents a daemon
|
|
// from registering another instance between the delete plan and commit.
|
|
func (q *Queries) LockRuntimeProfileForDelete(ctx context.Context, arg LockRuntimeProfileForDeleteParams) (RuntimeProfile, error) {
|
|
row := q.db.QueryRow(ctx, lockRuntimeProfileForDelete, arg.ID, arg.WorkspaceID)
|
|
var i RuntimeProfile
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.WorkspaceID,
|
|
&i.DisplayName,
|
|
&i.ProtocolFamily,
|
|
&i.CommandName,
|
|
&i.Description,
|
|
&i.FixedArgs,
|
|
&i.Visibility,
|
|
&i.CreatedBy,
|
|
&i.Enabled,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const lockRuntimeProfileForRegistration = `-- name: LockRuntimeProfileForRegistration :one
|
|
SELECT id, workspace_id, display_name, protocol_family, command_name, description, fixed_args, visibility, created_by, enabled, created_at, updated_at FROM runtime_profile
|
|
WHERE id = $1 AND workspace_id = $2
|
|
FOR KEY SHARE
|
|
`
|
|
|
|
type LockRuntimeProfileForRegistrationParams struct {
|
|
ID pgtype.UUID `json:"id"`
|
|
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
|
}
|
|
|
|
// Serializes daemon registration with profile deletion. Registration holds a
|
|
// KEY SHARE lock until its runtime row is committed; profile deletion takes an
|
|
// UPDATE lock, then locks the profile's runtime rows. Whichever starts first
|
|
// wins, so deletion cannot miss a runtime inserted from a stale profile read.
|
|
func (q *Queries) LockRuntimeProfileForRegistration(ctx context.Context, arg LockRuntimeProfileForRegistrationParams) (RuntimeProfile, error) {
|
|
row := q.db.QueryRow(ctx, lockRuntimeProfileForRegistration, arg.ID, arg.WorkspaceID)
|
|
var i RuntimeProfile
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.WorkspaceID,
|
|
&i.DisplayName,
|
|
&i.ProtocolFamily,
|
|
&i.CommandName,
|
|
&i.Description,
|
|
&i.FixedArgs,
|
|
&i.Visibility,
|
|
&i.CreatedBy,
|
|
&i.Enabled,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const updateRuntimeProfile = `-- name: UpdateRuntimeProfile :one
|
|
UPDATE runtime_profile
|
|
SET display_name = COALESCE($1, display_name),
|
|
command_name = COALESCE($2, command_name),
|
|
description = COALESCE($3, description),
|
|
fixed_args = COALESCE($4, fixed_args),
|
|
visibility = COALESCE($5, visibility),
|
|
enabled = COALESCE($6, enabled),
|
|
updated_at = now()
|
|
WHERE id = $7 AND workspace_id = $8
|
|
RETURNING id, workspace_id, display_name, protocol_family, command_name, description, fixed_args, visibility, created_by, enabled, created_at, updated_at
|
|
`
|
|
|
|
type UpdateRuntimeProfileParams struct {
|
|
DisplayName pgtype.Text `json:"display_name"`
|
|
CommandName pgtype.Text `json:"command_name"`
|
|
Description pgtype.Text `json:"description"`
|
|
FixedArgs []byte `json:"fixed_args"`
|
|
Visibility pgtype.Text `json:"visibility"`
|
|
Enabled pgtype.Bool `json:"enabled"`
|
|
ID pgtype.UUID `json:"id"`
|
|
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
|
}
|
|
|
|
// Partial update via COALESCE: NULL args leave the column unchanged. The
|
|
// protocol_family is intentionally NOT updatable — changing the underlying
|
|
// backend of an existing profile would silently repoint every agent bound to
|
|
// it onto a different protocol; callers create a new profile instead.
|
|
func (q *Queries) UpdateRuntimeProfile(ctx context.Context, arg UpdateRuntimeProfileParams) (RuntimeProfile, error) {
|
|
row := q.db.QueryRow(ctx, updateRuntimeProfile,
|
|
arg.DisplayName,
|
|
arg.CommandName,
|
|
arg.Description,
|
|
arg.FixedArgs,
|
|
arg.Visibility,
|
|
arg.Enabled,
|
|
arg.ID,
|
|
arg.WorkspaceID,
|
|
)
|
|
var i RuntimeProfile
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.WorkspaceID,
|
|
&i.DisplayName,
|
|
&i.ProtocolFamily,
|
|
&i.CommandName,
|
|
&i.Description,
|
|
&i.FixedArgs,
|
|
&i.Visibility,
|
|
&i.CreatedBy,
|
|
&i.Enabled,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|