mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-05 13:29:44 +02:00
Review fix. DeleteRuntimeProfile previously guarded only on ACTIVE agents, but agent.runtime_id is ON DELETE RESTRICT — a profile whose runtimes had only ARCHIVED agents passed the guard, then DeleteAgentRuntimesByProfile hit the FK and the handler 500'd. Now it mirrors the mature runtime-delete cascade (DeleteAgentRuntime): in one transaction it enumerates the profile's runtime rows, refuses (409) any with active agents or active squads led by archived agents, then for each runtime pauses autopilots pinned to its archived agents, drops archived squads led by them, and hard-deletes the archived agents before removing the runtime rows and the profile. No code path can now fall through to a raw FK error. - queries: ListAgentRuntimeIDsByProfile (sqlc regen). Reuses the existing per-runtime teardown queries (CountActiveSquadsWithArchivedLeadersByRuntime, ListArchivedAgentIDsByRuntime, PauseAutopilotsByAgentAssignees, DeleteSquadsByArchivedAgentsOnRuntime, DeleteArchivedAgentsByRuntime). - tests: TestDeleteRuntimeProfile_ArchivedAgentCascade (archived-only profile deletes cleanly: 204, runtime + archived agent + profile gone) and TestDeleteRuntimeProfile_ActiveAgentBlocks (active agent → 409, survives). Verified against Postgres 17: both new tests pass; full handler suite, daemon tests, and agent lockstep test pass; go vet clean. Co-authored-by: multica-agent <github@multica.ai>
371 lines
11 KiB
Go
371 lines
11 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 countAgentsByProfile = `-- name: CountAgentsByProfile :one
|
|
SELECT count(*) FROM agent a
|
|
JOIN agent_runtime ar ON ar.id = a.runtime_id
|
|
WHERE ar.profile_id = $1 AND a.archived_at IS NULL
|
|
`
|
|
|
|
// Counts 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.
|
|
func (q *Queries) CountAgentsByProfile(ctx context.Context, profileID pgtype.UUID) (int64, error) {
|
|
row := q.db.QueryRow(ctx, countAgentsByProfile, profileID)
|
|
var count int64
|
|
err := row.Scan(&count)
|
|
return count, err
|
|
}
|
|
|
|
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
|
|
RETURNING id, workspace_id, owner_id, daemon_id, provider
|
|
`
|
|
|
|
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, profileID pgtype.UUID) ([]DeleteAgentRuntimesByProfileRow, error) {
|
|
rows, err := q.db.Query(ctx, deleteAgentRuntimesByProfile, profileID)
|
|
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 listAgentRuntimeIDsByProfile = `-- name: ListAgentRuntimeIDsByProfile :many
|
|
SELECT id FROM agent_runtime
|
|
WHERE profile_id = $1
|
|
`
|
|
|
|
// 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, profileID pgtype.UUID) ([]pgtype.UUID, error) {
|
|
rows, err := q.db.Query(ctx, listAgentRuntimeIDsByProfile, profileID)
|
|
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 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
|
|
}
|