entity

package
v0.23.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 23, 2026 License: MIT Imports: 7 Imported by: 0

Documentation

Index

Constants

View Source
const (
	VisibilityPublic  = pkgentity.VisibilityPublic
	VisibilityPrivate = pkgentity.VisibilityPrivate
)
View Source
const (
	HomeViewCompact  = "compact"
	HomeViewDetailed = "detailed"
)
View Source
const RootParentID = uint(0)

RootParentID is the sentinel parent_id value for root-level items (direct children of an instance root). Using 0 instead of NULL avoids SQLite's NULL-inequality issue in unique indexes.

View Source
const SSOProviderGoogle = "google"

SSOProviderGoogle is the provider key for Google OAuth.

Variables

View Source
var StructToConfigs = pkgentity.StructToConfigs

StructToConfigs is re-exported from pkg/entity — see that package for the tag grammar and reflection rules.

Functions

This section is empty.

Types

type AgentChannel added in v0.9.0

type AgentChannel struct {
	ID        string  `gorm:"primaryKey;type:varchar(64)"`
	Type      string  `gorm:"type:varchar(32);not null;index"`
	Name      string  `gorm:"type:varchar(128);not null;default:'default'"`
	UserID    *string `gorm:"type:varchar(36);index"`
	Enabled   bool    `gorm:"not null;default:true"`
	Config    string  `gorm:"type:text;not null;default:'{}'"`
	CreatedAt time.Time
	UpdatedAt time.Time
}

func (AgentChannel) TableName added in v0.9.0

func (AgentChannel) TableName() string

type Bookmark

type Bookmark struct {
	UserID    string `gorm:"primaryKey;type:varchar(36)"`
	ToolPath  string `gorm:"primaryKey;type:varchar(255)"`
	CreatedAt time.Time
}

Bookmark marks a tool as a favorite for a specific user. Bookmarked tools appear in a dedicated "Bookmarks" group on the home page in addition to any group/category they already belong to.

type Config

type Config = pkgentity.Config

Config is re-exported from pkg/entity so existing internal callers keep working. Module authors should import pkg/entity directly.

type Connector added in v0.4.0

type Connector struct {
	ID       string `gorm:"type:varchar(36);primaryKey"`
	Key      string `gorm:"type:varchar(100);index;not null"`
	Label    string `gorm:"type:varchar(255);not null"`
	Disabled bool   `gorm:"default:false"`
	// RateLimitRPM caps how many times this connector instance may be
	// called per minute across all users. 0 means unlimited. Enforced
	// in-process via a sliding-window counter — not distributed.
	RateLimitRPM int    `gorm:"default:0"`
	CreatedBy    string `gorm:"type:varchar(36)"`
	// AllowOthersConfigure controls whether non-admin users who have tag
	// access to this instance can edit its credentials and settings.
	// Default false — only admins can configure. When true, any user
	// whose tags grant access to this row can also edit it.
	AllowOthersConfigure bool `gorm:"default:false"`
	// AllowOthersConnectSSO controls whether non-admin users who have tag
	// access to this instance can connect their own OAuth account.
	// Only meaningful when the connector's OAuthMeta is non-nil.
	// Default false — only admins can initiate the OAuth flow.
	AllowOthersConnectSSO bool `gorm:"default:false"`
	// EnableSSO controls whether this instance participates in the OAuth
	// flow at all. When false (default) the instance uses manually-entered
	// credentials (bot token, API key, service account). When true the
	// "Connect Account" button is shown and the OAuth flow is active.
	// Only meaningful when the connector's OAuthMeta is non-nil.
	EnableSSO bool `gorm:"default:false"`
	// MultiAccount controls whether each OAuth connect creates a new
	// instance row (true) or replaces the token on this row (false).
	// Only meaningful when EnableSSO=true.
	// false (default) — one row, one identity; reconnect replaces token.
	// true — each user that connects gets their own auto-created row
	// labelled "{Connector} – @{displayName}".
	MultiAccount bool `gorm:"default:false"`
	// AllowSessionConfig opts THIS instance into per-session cloning (the
	// Config tab + wick_session_workspace). Default false — admins flip it
	// per instance. Only meaningful when the connector's module declares
	// AllowSessionConfig=true (the capability); the UI only shows the
	// toggle for capable connectors. Effective gate: module-capable AND
	// this instance flag on.
	AllowSessionConfig bool `gorm:"default:false"`
	CreatedAt          time.Time
	UpdatedAt          time.Time
}

Connector is one row per running connector — the runtime pairing of a code-registered connector definition (identified by Key) with a credential set, label, and creator.

Connector definitions live in code (see pkg/connector and the internal/connectors registry); this entity is what the admin UI reads, writes, and duplicates. MCP exposes one tool per Connector row per enabled operation.

Key references the code definition's slug (e.g. "loki", "github") — it is NOT unique on this table. Multiple Connector rows share the same Key when admins duplicate a definition into multiple instances (Loki Prod, Loki Staging, Loki Dev). Uniqueness lives on ID; the admin UI distinguishes siblings by Label.

Code-registered Modules survive deletion: when bootstrap runs and finds zero rows for a registered Key, it auto-creates a fresh row (empty Configs, Label = Meta.Name). Admins who delete every row for a connector therefore get an empty-but-working row back on restart; duplicates and edits to existing rows are untouched.

Per-field credential / endpoint values live on the central configs table (owner = "connector:{id}"), one row per field declared on the connector's Creds struct. Wick reflects the typed Creds into rows at boot via entity.StructToConfigs and reconciles them on every connector instance create. Reading credentials goes through connectors.Service.LoadConfigs; the configs.Service cache makes the per-field shape as cheap as a JSON unmarshal but keeps each value query-able and individually editable.

Disabled hides the row from MCP tools/list and the admin UI list view (admins can re-enable from the manager). The tag-filter system (the existing ToolTag table, addressed by path "/connectors/{id}", joined against UserTag) gates which authenticated users see this row at all — Disabled is the orthogonal "off switch" for the whole row.

Tag association reuses ToolTag (with ToolPath = "/connectors/{id}") rather than introducing a connector-specific link table; jobs do the same with "/jobs/{path}". A future rename of ToolTag/SetToolTags into a generic entity-tag API is tracked separately.

func (*Connector) BeforeCreate added in v0.4.0

func (c *Connector) BeforeCreate(tx *gorm.DB) error

type ConnectorAccount added in v0.16.0

type ConnectorAccount struct {
	ID             string `gorm:"type:varchar(36);primaryKey"`
	ConnectorID    string `gorm:"type:varchar(36);not null;index"`
	WickUserID     string `gorm:"type:varchar(36);index"`
	ExternalUserID string `gorm:"type:varchar(255);index"`
	DisplayName    string `gorm:"type:varchar(255);not null"`
	AccessToken    string `gorm:"type:text;not null"`
	// DisabledOps is a JSON array of operation keys disabled for this
	// account. Empty = all ops allowed. Example: ["send_message","delete_message"]
	DisabledOps string `gorm:"type:text;default:''"`
	CreatedAt   time.Time
	UpdatedAt   time.Time
}

ConnectorAccount stores one OAuth-connected user account per connector instance. Many accounts can live under one instance (when MultiAccount=true); at most one when MultiAccount=false (upsert by ConnectorID).

AccessToken is stored as-is — the configs encryption layer handles masking in the admin UI via the connector's Configs.UserToken secret field. The token here is the source of truth for execution; the connector reads it via c.Cfg("user_token") which resolves from this table at call time.

ConnectorAccount stores one OAuth-connected user account per connector instance. WickUserID is the wick platform user who initiated the OAuth flow. ExternalUserID is the provider-side user ID returned by GetUserIdentity (e.g. Slack U01ABCDEF, Google sub claim) — used for token lookup by the channel layer when routing inbound messages to sessions.

func (*ConnectorAccount) BeforeCreate added in v0.16.0

func (a *ConnectorAccount) BeforeCreate(tx *gorm.DB) error

type ConnectorOperation added in v0.4.0

type ConnectorOperation struct {
	ConnectorID  string `gorm:"primaryKey;type:varchar(36)"`
	OperationKey string `gorm:"primaryKey;type:varchar(100)"`
	Enabled      bool   `gorm:"default:true"`
	// AdminOnly restricts this operation to admin users only. Non-admin
	// MCP callers receive a 403-equivalent error before Execute runs.
	// Default: false (all authenticated users may call the operation).
	AdminOnly bool `gorm:"default:false"`
	// SystemDisabled is set by the health-check mechanism when the
	// configured credential lacks the upstream permissions an operation
	// needs (e.g. missing OAuth scope). It is orthogonal to Enabled —
	// effective availability is `Enabled AND NOT SystemDisabled`. The
	// admin UI locks the manual Enable/Disable toggle while this is
	// true; the only way to clear it is to fix the upstream permission
	// and re-run the health check. Default: false.
	SystemDisabled bool `gorm:"default:false"`
	// SystemDisabledReason is the human-readable explanation surfaced
	// alongside the lock — e.g. "needs scope: chat:write". Empty when
	// SystemDisabled is false.
	SystemDisabledReason string `gorm:"type:text"`
	UpdatedAt            time.Time
}

ConnectorOperation stores the enable state of one operation on one connector row. Operations are declared in code (Module.Operations); this table records whether the admin opted them in (or out) for a specific connector row.

Default rule applied when a connector row is created:

  • Operation.Destructive == false → Enabled = true
  • Operation.Destructive == true → Enabled = false (admin opt-in)

Rows for ops the admin has not touched yet may be missing; readers fall back to the default rule above when no row exists. Toggling in the UI inserts or updates a row.

type ConnectorRun added in v0.4.0

type ConnectorRun struct {
	// String columns are text, not varchar(n): in Postgres text and varchar
	// share storage/perf, so a length cap is pure constraint — and the caps
	// here kept biting (e.g. ConnectorID held "sw_"+uuid = 39 > varchar(36)).
	ID           string             `gorm:"type:text;primaryKey"`
	ConnectorID  string             `gorm:"type:text;not null;index:idx_run_connector_started,priority:1"`
	OperationKey string             `gorm:"type:text;not null"`
	UserID       string             `gorm:"type:text;index:idx_run_user_started,priority:1"`
	Source       ConnectorRunSource `gorm:"type:text;not null"`
	RequestJSON  string             `gorm:"type:text"`
	ResponseJSON string             `gorm:"type:text"`
	Status       ConnectorRunStatus `gorm:"type:text;not null;index:idx_run_status_started,priority:1"`
	ErrorMsg     string             `gorm:"type:text"`
	LatencyMs    int
	HTTPStatus   int
	IPAddress    string    `gorm:"type:text;index:idx_run_ip_started,priority:1"`
	UserAgent    string    `gorm:"type:text"`
	ParentRunID  *string   `gorm:"type:text;index"`
	StartedAt    time.Time `` /* 218-byte string literal not displayed */
	EndedAt      *time.Time
	CreatedAt    time.Time
}

ConnectorRun records one execution of one operation on one connector row. Written once per MCP tools/call, panel-test click, or retry, so admins can audit traffic, debug failures, and replay buggy calls.

RequestJSON stores the input arguments the caller (LLM or admin) passed in. Credentials are NOT in this column — they live on the Connector row itself, joined at exec time. Replaying a run rebuilds the call from RequestJSON + the current Connector.Configs, so a retry honors any cred edits the admin has made since the original.

ResponseJSON is the JSON-marshaled return value of ExecuteFunc. Large responses may be truncated by the writer (defense in depth); readers should treat the value as opaque.

IPAddress and UserAgent capture the calling client's network identity at the time of the run. These are recorded for security observability — feeding a future allowlist/blocklist surface — and for incident triage. They are best-effort: behind a proxy the IP is whatever X-Forwarded-For policy the deploy resolves to, and a PAT-using script may not send a recognizable UA at all.

ParentRunID is non-nil only when Source == ConnectorRunSourceRetry, pointing to the run this one re-played. There is no FK constraint — the parent may be deleted by retention, leaving the lineage dangling (the UI tolerates the gap).

Retention: rows older than the configured retention window are purged by a scheduled cleanup job (default 7 days). The single-column index on StartedAt keeps the purge query cheap.

Index strategy (composite, listed by query they serve):

  • (connector_id, started_at DESC) → "recent runs for this connector"
  • (user_id, started_at DESC) → "user activity timeline"
  • (status, started_at DESC) → "recent errors" filter
  • (ip_address, started_at DESC) → "activity from this IP" (future allow/block UX)
  • started_at → retention purge
  • parent_run_id → retry lineage trace

func (*ConnectorRun) BeforeCreate added in v0.4.0

func (r *ConnectorRun) BeforeCreate(tx *gorm.DB) error

type ConnectorRunSource added in v0.4.0

type ConnectorRunSource string

ConnectorRunSource describes how a ConnectorRun was triggered.

const (
	// ConnectorRunSourceMCP marks runs initiated by an LLM client through
	// the /mcp endpoint.
	ConnectorRunSourceMCP ConnectorRunSource = "mcp"
	// ConnectorRunSourceTest marks runs initiated from the panel-test
	// view in the admin UI (Postman-style manual exec).
	ConnectorRunSourceTest ConnectorRunSource = "test"
	// ConnectorRunSourceRetry marks runs that replay the request payload
	// of a previous run, identified by ConnectorRun.ParentRunID.
	ConnectorRunSourceRetry ConnectorRunSource = "retry"
)

type ConnectorRunStatus added in v0.4.0

type ConnectorRunStatus string

ConnectorRunStatus describes the outcome of a ConnectorRun.

const (
	ConnectorRunStatusRunning ConnectorRunStatus = "running"
	ConnectorRunStatusSuccess ConnectorRunStatus = "success"
	ConnectorRunStatusError   ConnectorRunStatus = "error"
)

type CustomConnector added in v0.17.0

type CustomConnector struct {
	ID          string `gorm:"type:varchar(36);primaryKey"`
	Key         string `gorm:"type:varchar(100);uniqueIndex;not null"`
	Name        string `gorm:"type:varchar(255);not null"`
	Description string `gorm:"type:text"`
	// Icon is an emoji, an inline <svg>, or a data:image/...;base64
	// payload (validated to 32KB) — rendered as text or <img> by the UI.
	Icon       string                `gorm:"type:text"`
	Source     CustomConnectorSource `gorm:"type:varchar(16);not null"`
	SourceMeta string                `gorm:"type:text"`
	Configs    string                `gorm:"type:text;not null;default:'[]'"`
	Ops        string                `gorm:"type:text;not null;default:'[]'"`
	CreatedBy  string                `gorm:"type:varchar(36)"`
	// SingleInstance locks the def to one row (Meta.Fixed). Default off:
	// custom connectors behave like built-ins — admins add/duplicate
	// instance rows, each with its own credentials.
	SingleInstance bool `gorm:"default:false"`
	// AllowSessionConfig is the capability flag mirrored onto
	// Module.AllowSessionConfig: when true this def's configs (base_url,
	// keys, …) may be overridden per agent session. Default off; an admin
	// still has to enable the per-instance toggle before any override is
	// accepted. Only meaningful for curl/manual API defs — leave off for
	// oauth/sso-backed MCP defs whose config is a user token.
	AllowSessionConfig bool `gorm:"default:false"`
	Disabled           bool `gorm:"default:false"`
	CreatedAt          time.Time
	UpdatedAt          time.Time
}

CustomConnector is one admin-built custom connector definition — the whole definition in a single row. Built-in connectors live in Go code under internal/connectors/* and register through RegisterBuiltins; custom ones live here and are replayed into the same registry at boot (and on admin save) by internal/connectors/custom. From the MCP surface the two are indistinguishable — same tool_id shape, same audit trail, same encrypted-fields layer.

Key shares the namespace with built-in connector Meta.Keys; the save path validates uniqueness across both so a custom def can never shadow a built-in module. Instance rows, per-instance config values, per-op enable state, and run history all ride the existing tables (connectors, configs, connector_operations, connector_runs) exactly like a built-in.

Configs holds the per-instance field schema as a JSON array of custom.DefField ({key, label, widget, secret, required, default, desc}) — mirroring what entity.StructToConfigs produces so a connector.Module can be assembled without Go reflection. Ops holds the operations as a JSON array of custom.DefOp; array order is display order. SourceMeta keeps provenance (chosen category tag, MCP server id). Raw AI-parser pastes are never persisted.

func (*CustomConnector) BeforeCreate added in v0.17.0

func (d *CustomConnector) BeforeCreate(tx *gorm.DB) error

func (CustomConnector) TableName added in v0.17.0

func (CustomConnector) TableName() string

type CustomConnectorMCPServer added in v0.17.0

type CustomConnectorMCPServer struct {
	ID          string `gorm:"type:varchar(36);primaryKey"`
	Label       string `gorm:"type:varchar(255);not null"`
	Transport   string `gorm:"type:varchar(16);not null;default:'http'"`
	URL         string `gorm:"type:text;not null"`
	AuthScheme  string `gorm:"type:varchar(20);not null;default:'none'"`
	AuthSecret  string `gorm:"type:text"`
	AuthHeaders string `gorm:"type:text"`
	AuthExtra   string `gorm:"type:text"`
	Headers     string `gorm:"type:text"`
	// ExcludedTools is a JSON array of tool names the connector must NOT
	// expose. The exclusion model is opt-out: everything the server
	// lists is an operation unless its name is in here.
	ExcludedTools string `gorm:"type:text;not null;default:'[]'"`
	// ServerInfo is the JSON {name, version} the server reported on the
	// last successful initialize — admin-facing only (edit form,
	// def_get); deliberately never exposed to the LLM via wick_list.
	ServerInfo string `gorm:"type:text"`
	LastTestAt *time.Time
	LastTestOK bool `gorm:"default:false"`
	CreatedAt  time.Time
	UpdatedAt  time.Time
}

CustomConnectorMCPServer is one MCP server registered as a custom connector source (and runtime proxy target). One server row = one connector definition: every tool the server lists is exposed as an operation automatically, minus the names in ExcludedTools — nothing per-tool is persisted, so tools added on the server side appear after a module rebuild (boot / reload / server save) without any wick-side change. Wick is a forwarder only: it stores the streamable-HTTP URL plus auth material and fires per-call JSON-RPC (initialize / tools/list / tools/call) over the shared HTTP client. No process spawn, no lifecycle — stdio transports are out of scope (the Transport column is reserved so a future value doesn't need a migration). Tool catalogs are never cached: every module build re-hits tools/list live.

AuthScheme picks how the outbound call authenticates:

  • "none" — Content-Type/Accept only.
  • "bearer" — AuthSecret (stored encrypted under the master key, decrypted per request) as Authorization: Bearer.
  • "custom_header" — AuthHeaders JSON ([{key, value, secret}]); secret values stored encrypted.
  • "sso" — no stored secret; wick mints a short-lived ED25519 JWT for the calling user per request (AuthExtra JSON: {audience, ttl_seconds}) and sends it as X-Wick-User.

Headers carries extra non-auth rows (routing, tenancy) appended on top of the scheme's headers for every call, same JSON shape as AuthHeaders.

LastTestAt/LastTestOK gate the save flow: a row may only be created after at least one successful initialize + tools/list round-trip, so half-broken servers never pollute the table.

func (*CustomConnectorMCPServer) BeforeCreate added in v0.17.0

func (m *CustomConnectorMCPServer) BeforeCreate(tx *gorm.DB) error

func (CustomConnectorMCPServer) TableName added in v0.17.0

func (CustomConnectorMCPServer) TableName() string

type CustomConnectorSource added in v0.17.0

type CustomConnectorSource string

CustomConnectorSource describes where a custom connector definition came from. Display-only — the generic executor behaves identically for all three; each op's mcp_source decides the proxy path.

const (
	CustomConnectorSourceCurl   CustomConnectorSource = "curl"
	CustomConnectorSourceMCP    CustomConnectorSource = "mcp"
	CustomConnectorSourceManual CustomConnectorSource = "manual"
)

type DataTable added in v0.13.1

type DataTable struct {
	Slug        string `gorm:"type:varchar(80);primaryKey"`
	Name        string `gorm:"type:varchar(255);not null"`
	Description string `gorm:"type:text"`
	Mode        string `gorm:"type:varchar(20);not null;default:'strict'"` // strict | lax
	SchemaJSON  string `gorm:"type:jsonb;not null;default:'{}'"`
	AccessJSON  string `gorm:"type:jsonb;default:'{}'"`
	NextRowID   int64  `gorm:"not null;default:0"` // per-slug monotonic row id allocator
	CreatedBy   string `gorm:"type:varchar(36)"`
	CreatedAt   time.Time
	UpdatedAt   time.Time
}

DataTable is the schema metadata row, one per logical data table. SchemaJSON carries the full Schema body (columns with stable ids, next_col_id counter, access, retention) — see internal/agents/workflow/datatable for the typed shape.

All data rows live in DataTableRow keyed by Slug. There are no per-table physical tables — wick stays at two tables total (wick_data_tables + wick_data_table_rows) regardless of how many logical tables a user creates.

func (DataTable) TableName added in v0.13.1

func (DataTable) TableName() string

type DataTableRow added in v0.13.1

type DataTableRow struct {
	TableSlug string    `gorm:"type:varchar(80);primaryKey;not null;index:idx_dtr_slug_created,priority:1"`
	ID        int64     `gorm:"primaryKey;not null;autoIncrement:false"`
	Data      string    `gorm:"type:jsonb;not null;default:'{}'"`
	CreatedAt time.Time `gorm:"not null;index:idx_dtr_slug_created,priority:2,sort:desc"`
	UpdatedAt time.Time `gorm:"not null"`
}

DataTableRow is one row of one logical data table. Data is JSONB keyed by column id (the stable "cN" identifier from the schema), so renaming a column updates only the schema row, never the data.

Primary key is composite (TableSlug, ID). ID is allocated by the app from DataTable.NextRowID under SELECT FOR UPDATE so concurrent inserts never collide.

func (DataTableRow) TableName added in v0.13.1

func (DataTableRow) TableName() string

type HealthComponent

type HealthComponent struct {
	Database HealthState `json:"database"`
}

type HealthState

type HealthState string
const (
	HealthStateOK   HealthState = "ok"
	HealthStateFail HealthState = "fail"
)

type Job

type Job struct {
	ID            string    `gorm:"type:varchar(36);primaryKey"`
	Key           string    `gorm:"type:varchar(100);uniqueIndex;not null"`
	Name          string    `gorm:"type:varchar(255);not null"`
	Description   string    `gorm:"type:text"`
	Icon          string    `gorm:"type:varchar(10)"`
	Schedule      string    `gorm:"type:varchar(100)"` // cron expression
	Enabled       bool      `gorm:"default:false"`
	MaxRuns       int       `gorm:"default:0"`  // 0 = unlimited, admin-managed
	MaxTimeoutMin int       `gorm:"default:30"` // auto-cancel after N minutes; 0 = no timeout
	TotalRuns     int       `gorm:"default:0"`
	LastStatus    JobStatus `gorm:"type:varchar(20);default:'idle'"`
	LastRunAt     *time.Time
	CreatedBy     string `gorm:"type:varchar(36)"`
	CreatedAt     time.Time
	UpdatedAt     time.Time
}

Job is a background job definition whose schedule and lifecycle are managed via the DB. Code-defined jobs bootstrap a row on startup; admins can tweak the cron expression, enable/disable, and cap the run count.

func (*Job) BeforeCreate

func (j *Job) BeforeCreate(tx *gorm.DB) error

type JobRun

type JobRun struct {
	ID          string     `gorm:"type:varchar(36);primaryKey"`
	JobID       string     `gorm:"type:varchar(36);index;not null"`
	Status      RunStatus  `gorm:"type:varchar(20);not null"`
	Result      string     `gorm:"type:text"`
	TriggeredBy RunTrigger `gorm:"type:varchar(20);not null"`
	UserID      string     `gorm:"type:varchar(36)"`
	StartedAt   time.Time
	EndedAt     *time.Time
	CreatedAt   time.Time
}

JobRun stores the result of a single execution of a Job.

func (*JobRun) BeforeCreate

func (r *JobRun) BeforeCreate(tx *gorm.DB) error

type JobStatus

type JobStatus string

JobStatus represents the current state of a job.

const (
	JobStatusIdle    JobStatus = "idle"
	JobStatusRunning JobStatus = "running"
)

type OAuthAuthorizationCode added in v0.4.0

type OAuthAuthorizationCode struct {
	ID                  string `gorm:"type:varchar(36);primaryKey"`
	Code                string `gorm:"type:varchar(64);uniqueIndex;not null"`
	ClientID            string `gorm:"type:varchar(64);index;not null"`
	UserID              string `gorm:"type:varchar(36);not null"`
	RedirectURI         string `gorm:"type:varchar(512);not null"`
	Scope               string `gorm:"type:varchar(255)"`
	CodeChallenge       string `gorm:"type:varchar(128);not null"`
	CodeChallengeMethod string `gorm:"type:varchar(10);not null"`
	Used                bool   `gorm:"default:false"`
	ExpiresAt           time.Time
	CreatedAt           time.Time
}

OAuthAuthorizationCode is the short-lived PKCE authorization code minted at /oauth/authorize and consumed at /oauth/token.

Code is the opaque string sent in the redirect query string. It's indexed for the lookup at /token but not unique — we let the database flag duplicates if two requests collide (statistically near impossible with 32 random bytes).

CodeChallenge / Method come from the original /authorize request; /token verifies the client's code_verifier against them per RFC 7636.

Used flips to true on first /token redemption to prevent replay. We keep the row for audit instead of deleting; PurgeExpired sweeps later.

func (*OAuthAuthorizationCode) BeforeCreate added in v0.4.0

func (c *OAuthAuthorizationCode) BeforeCreate(tx *gorm.DB) error

func (OAuthAuthorizationCode) TableName added in v0.4.0

func (OAuthAuthorizationCode) TableName() string

type OAuthClient added in v0.4.0

type OAuthClient struct {
	ID           string `gorm:"type:varchar(36);primaryKey"`
	ClientID     string `gorm:"type:varchar(64);uniqueIndex;not null"`
	Name         string `gorm:"type:varchar(255)"`
	RedirectURIs string `gorm:"type:text;not null"` // JSON array
	CreatedBy    string `gorm:"type:varchar(36)"`
	CreatedAt    time.Time
}

OAuthClient is one Dynamic Client Registration record (RFC 7591).

MCP clients (Claude.ai web, Claude Desktop, Cursor) call POST /oauth/register without prior coordination, hand wick a name + redirect_uris, and receive back the ClientID. There is no client secret in this flow — every client is treated as a public client using PKCE per RFC 7636 (which the MCP authorization spec mandates).

RedirectURIs is a JSON-encoded array of allowed redirect URIs; /oauth/authorize verifies the requested URI is one of them. We store as JSON to keep gorm migrations simple — the list is small and read whole every time.

CreatedBy is non-nil only when the registration happened while a user was logged in (rare — DCR usually fires before any wick session exists). Useful for admin auditing.

func (*OAuthClient) BeforeCreate added in v0.4.0

func (c *OAuthClient) BeforeCreate(tx *gorm.DB) error

func (OAuthClient) TableName added in v0.4.0

func (OAuthClient) TableName() string

TableName pins the table name. GORM's default naming would lower- case + snake_case "OAuth" into "o_auth", giving "o_auth_clients". We override so raw SQL in oauth.Repo.ListGrantsByUser stays readable ("oauth_clients", not "o_auth_clients").

type OAuthToken added in v0.4.0

type OAuthToken struct {
	ID            string  `gorm:"type:varchar(36);primaryKey"`
	TokenHash     string  `gorm:"type:varchar(64);uniqueIndex;not null"`
	Kind          string  `gorm:"type:varchar(10);not null"` // access | refresh
	ClientID      string  `gorm:"type:varchar(64);index;not null"`
	UserID        string  `gorm:"type:varchar(36);index;not null"`
	Scope         string  `gorm:"type:varchar(255)"`
	ParentTokenID *string `gorm:"type:varchar(36);index"` // refresh chain ancestor
	ExpiresAt     time.Time
	RevokedAt     *time.Time `gorm:"index"`
	LastUsedAt    *time.Time
	CreatedAt     time.Time
}

OAuthToken is one issued access or refresh token. Stored opaque (32 hex chars), hashed at rest just like PersonalAccessToken — the plaintext only crosses the wire on the /token response.

Kind is "access" or "refresh". A code redemption mints both: the access has a short TTL (~1h), the refresh a long one (~30d) and is rotated on every use (RFC 6749 §6 + best-current-practice).

ParentTokenID chains refresh-token rotation: when a refresh redeems, the new refresh row carries the previous row's ID here. RevokedAt stamps a row when its child is minted, so reuse of an old refresh is detectable (and the whole chain should be revoked — a sign the token was leaked).

func (*OAuthToken) BeforeCreate added in v0.4.0

func (t *OAuthToken) BeforeCreate(tx *gorm.DB) error

func (OAuthToken) TableName added in v0.4.0

func (OAuthToken) TableName() string

type PersonalAccessToken added in v0.4.0

type PersonalAccessToken struct {
	ID         string `gorm:"type:varchar(36);primaryKey"`
	UserID     string `gorm:"type:varchar(36);not null;index"`
	Name       string `gorm:"type:varchar(120);not null"`
	TokenHash  string `gorm:"type:varchar(64);not null;uniqueIndex"`
	Last4      string `gorm:"type:varchar(8);not null"`
	CreatedAt  time.Time
	LastUsedAt *time.Time
	RevokedAt  *time.Time `gorm:"index"`
}

PersonalAccessToken is a static bearer credential a user generates from /profile/mcp. The plaintext token is shown to the user exactly once at creation time; only the SHA-256 hash is persisted so it can be looked up on incoming MCP requests but never reconstructed.

Token wire format: "wick_pat_" + 32 hex chars. Last4 stores the last 4 characters of the random suffix so the UI can render a stable "wick_pat_****abcd" preview without keeping the secret around.

LastUsedAt is best-effort — written by the MCP middleware on successful auth. RevokedAt nil means active; non-nil hides the row from active-token queries while keeping the audit trail intact.

func (*PersonalAccessToken) BeforeCreate added in v0.4.0

func (t *PersonalAccessToken) BeforeCreate(tx *gorm.DB) error

func (*PersonalAccessToken) Masked added in v0.4.0

func (t *PersonalAccessToken) Masked() string

Masked returns the display form for list views: prefix + asterisks + last 4 characters. Never exposes the secret.

type ProviderStorage added in v0.11.0

type ProviderStorage struct {
	ID            uint   `gorm:"primaryKey;autoIncrement"`
	ProviderType  string `gorm:"type:varchar(32);not null;uniqueIndex:idx_provider_path"`
	InstanceName  string `gorm:"type:varchar(128);not null;uniqueIndex:idx_provider_path"`
	RelPath       string `gorm:"type:varchar(1024);not null;uniqueIndex:idx_provider_path"` // absolute filesystem path
	ParentID      uint   `gorm:"default:0;index"`                                           // 0 = root (RootParentID)
	Name          string `gorm:"type:varchar(512)"`                                         // basename only
	IsDir         bool   `gorm:"default:false"`
	Content       []byte
	Size          int       `gorm:"default:0"`                 // byte length of Content; lets listings skip loading the blob
	ContentHash   string    `gorm:"type:varchar(64);not null"` // SHA-256 hex; "" for dirs
	SyncedAt      time.Time `gorm:"not null"`
	RetentionDays int       `gorm:"not null;default:0"` // 0 = never purge
}

ProviderStorage holds one synced file or folder entry for a provider instance. Mode "folder" produces multiple rows (one per file); mode "single" produces exactly one. content_hash gates writes — only changed files are re-upserted.

Adjacency-list layout: each row knows its parent via ParentID (0 = root). Folder rows have IsDir=true, Content=nil, ContentHash="".

func (ProviderStorage) TableName added in v0.11.0

func (ProviderStorage) TableName() string

type ProviderStorageSource added in v0.11.0

type ProviderStorageSource struct {
	ID           uint   `gorm:"primaryKey;autoIncrement"`
	ProviderType string `gorm:"type:varchar(32);not null;index:idx_source_provider"`
	InstanceName string `gorm:"type:varchar(128);not null;index:idx_source_provider"`
	Label        string `gorm:"type:varchar(128);not null"` // e.g. "claude workspace", "credentials"
	SyncPath     string `gorm:"type:varchar(1024);not null"`
	// Mode is one of:
	//   "folder"  — include the folder tree at SyncPath
	//   "single"  — include the single file at SyncPath
	//   "exclude" — skip paths matching SyncPath (literal abs path or
	//               glob with * / ** / ?). Stops walks for any include
	//               source on the same instance and purges DB rows.
	Mode          string    `gorm:"type:varchar(16);not null;default:'folder'"`
	RetentionDays int       `gorm:"not null;default:0"` // 0 = never purge (include only)
	Enabled       bool      `gorm:"not null;default:true"`
	CreatedAt     time.Time `gorm:"not null"`
	UpdatedAt     time.Time `gorm:"not null"`
}

ProviderStorageSource is one configured sync source per provider instance. Multiple sources can exist per instance (e.g. credentials folder + sessions folder). The Manager reads this table at boot (RestoreAll) and at Start (background tickers).

func (ProviderStorageSource) TableName added in v0.11.0

func (ProviderStorageSource) TableName() string

type PushSubscription added in v0.15.0

type PushSubscription struct {
	ID          string `gorm:"type:varchar(36);primaryKey"`
	UserID      string `gorm:"type:varchar(36);not null;index"`
	User        User   `gorm:"foreignKey:UserID"`
	Endpoint    string `gorm:"not null;uniqueIndex;type:text"`
	P256dh      string `gorm:"not null;type:text"`
	Auth        string `gorm:"not null;type:text"`
	UserAgent   string `gorm:"type:text"`
	DeviceLabel string `gorm:"type:varchar(160)"`
	CreatedAt   time.Time
	UpdatedAt   time.Time
	LastSeenAt  *time.Time
	DisabledAt  *time.Time `gorm:"index"`
}

PushSubscription is one browser/device endpoint for notifications. A single user can have many rows: desktop Chrome, Android Chrome, Safari, Firefox, and so on. Endpoint is globally unique because vendors issue it per subscription.

func (*PushSubscription) BeforeCreate added in v0.15.0

func (s *PushSubscription) BeforeCreate(tx *gorm.DB) error

type RunStatus

type RunStatus string

RunStatus represents the outcome of a single job execution.

const (
	RunStatusRunning RunStatus = "running"
	RunStatusSuccess RunStatus = "success"
	RunStatusError   RunStatus = "error"
)

type RunTrigger

type RunTrigger string

RunTrigger describes how a run was initiated.

const (
	RunTriggerManual RunTrigger = "manual"
	RunTriggerCron   RunTrigger = "cron"
)

type SSOProvider

type SSOProvider struct {
	ID           uint   `gorm:"primaryKey"`
	Provider     string `gorm:"uniqueIndex;type:varchar(32);not null"` // "google"
	ClientID     string `gorm:"type:varchar(255)"`
	ClientSecret string `gorm:"type:varchar(255)"`
	Enabled      bool   `gorm:"default:false"`
	// AllowedDomains is a comma-separated list of email domains allowed
	// to sign in through this provider (e.g. "abc.com,abc.net").
	// Empty string means no restriction — any email from the provider is
	// accepted. Matching is case-insensitive.
	AllowedDomains string `gorm:"type:text"`
	CreatedAt      time.Time
	UpdatedAt      time.Time
}

SSOProvider holds one OAuth/SSO provider's configuration. The callback URL is never stored — it's derived at runtime from app_variables.app_url + "/auth/callback".

func (SSOProvider) TableName

func (SSOProvider) TableName() string

type Session

type Session struct {
	Token     string    `gorm:"primaryKey;type:varchar(64)"`
	UserID    string    `gorm:"type:uuid;not null;index"`
	User      User      `gorm:"foreignKey:UserID"`
	ExpiresAt time.Time `gorm:"not null"`
	CreatedAt time.Time
}

type Skill added in v0.17.0

type Skill struct {
	ID        string  `gorm:"type:varchar(36);primaryKey"`
	Name      string  `gorm:"uniqueIndex;not null"`
	IsSystem  bool    `gorm:"not null;default:false"`
	CreatedBy *string `gorm:"column:created_by;type:varchar(36)"`
	FilePath  string  `gorm:"column:file_path"`
	CreatedAt time.Time
	UpdatedAt time.Time
}

func (*Skill) BeforeCreate added in v0.17.0

func (s *Skill) BeforeCreate(tx *gorm.DB) error

type Tag

type Tag struct {
	ID          string `gorm:"type:varchar(36);primaryKey"`
	Name        string `gorm:"uniqueIndex;type:varchar(100);not null"`
	Description string `gorm:"type:varchar(500)"`
	IsGroup     bool   `gorm:"default:false"`
	IsFilter    bool   `gorm:"default:false"`
	IsSystem    bool   `gorm:"default:false"`
	SortOrder   int    `gorm:"default:0"`
	CreatedAt   time.Time
	DisplayName string `gorm:"-"`
}

Tag is a first-class label that can be attached to users and tools. Renaming a Tag propagates automatically because associations store TagID, not the name.

A Tag has two orthogonal-but-combinable purposes:

  • Access filter: when IsFilter is true and the tag is attached to a Private tool, only users who carry the same tag may access it. Tool-tags without IsFilter are purely cosmetic for access (they don't restrict who can enter).
  • Group on home: when IsGroup is true, tools carrying the tag are rendered together on the home page under Name. A tool with multiple group tags appears in each group.

A tag can set any combination of IsGroup and IsFilter independently.

IsSystem marks a tag as code-owned: it can only be assigned to tool/job/connector entities by code (via DefaultTags seeding), never by an admin from the UI to a user. The intent is to gate built-in maintenance items (e.g. the connector-runs-purge job) behind a tag no end user can carry — combined with IsFilter=true, this hides the item from /manager/* for everyone except admin (who bypasses the tag-filter rule wholesale).

func (*Tag) BeforeCreate

func (t *Tag) BeforeCreate(tx *gorm.DB) error

type ToolPermission

type ToolPermission struct {
	ToolPath   string         `gorm:"primaryKey;type:varchar(255)"`
	Visibility ToolVisibility `gorm:"type:varchar(50);default:'private'"`
	// Disabled hides the tool from every user (including admins) and makes
	// direct hits to /tools/{slug}/* return 404. Admins re-enable from
	// /admin/tools.
	Disabled  bool `gorm:"default:false"`
	UpdatedAt time.Time
}

ToolPermission stores the per-tool visibility override set by an admin. If no row exists for a tool path the code falls back to the tool's declared default visibility.

type ToolTag

type ToolTag struct {
	ToolPath string `gorm:"primaryKey;type:varchar(255)"`
	TagID    string `gorm:"primaryKey;type:varchar(36);index"`
}

ToolTag links a tool to a Tag. When a tool is Private and at least one ToolTag exists, only users carrying one of those tags may access it.

type ToolVisibility

type ToolVisibility = pkgentity.ToolVisibility

ToolVisibility is re-exported from pkg/entity so existing callers continue to work after the public contract moved out of internal.

type User

type User struct {
	ID           string `gorm:"type:varchar(36);primaryKey"`
	Email        string `gorm:"uniqueIndex;not null"`
	Name         string `gorm:"not null"`
	Avatar       string
	Role         UserRole     `gorm:"type:varchar(50);default:'user'"`
	IsOwner      bool         `gorm:"column:is_owner;not null;default:false"`
	Approved     bool         `gorm:"default:false"`
	PasswordHash string       `gorm:"type:varchar(255)"`
	Metadata     UserMetadata `gorm:"type:jsonb"`
	CreatedAt    time.Time
	UpdatedAt    time.Time
}

func (*User) BeforeCreate

func (u *User) BeforeCreate(tx *gorm.DB) error

func (*User) CanSeeAllSessions added in v0.17.0

func (u *User) CanSeeAllSessions() bool

func (*User) IsAdmin

func (u *User) IsAdmin() bool

type UserMetadata

type UserMetadata struct {
	// HomeView picks the tool grid density: "compact" (icon+name) or
	// "detailed" (wider cards with description). Empty means compact.
	HomeView string `json:"home_view,omitempty"`

	// Theme picks the UI color palette. Values are Theme.ID from
	// internal/pkg/ui/theme.go ("light", "dark", "dracula", …).
	// Empty means "no preference" — guests follow the device
	// `prefers-color-scheme`, logged-in users can pick in the navbar.
	Theme string `json:"theme,omitempty"`

	// LightTheme / DarkTheme remember the last light- and dark-mode
	// theme the user picked from the dropdown, so the navbar toggle
	// can switch straight back to that variant instead of the generic
	// "light"/"dark" defaults. Values are Theme.ID.
	LightTheme string `json:"light_theme,omitempty"`
	DarkTheme  string `json:"dark_theme,omitempty"`

	// PinnedAgentProjectID is the agents Project this user pinned as
	// their personal default. One per user. When set, opening the agents
	// tool lands scoped to this project. Empty = unpinned. See
	// internal/planning/archive/project/design.md.
	PinnedAgentProjectID string `json:"pinned_agent_project_id,omitempty"`

	// PushPermission stores the last browser notification permission
	// state reported by the browser prompt ("granted" or "denied").
	PushPermission   string     `json:"push_permission,omitempty"`
	PushPermissionAt *time.Time `json:"push_permission_at,omitempty"`
}

UserMetadata is the free-form preferences bag stored as JSON on the user row. Add fields here when a new per-user preference is needed — all consumers should default to the zero value when a field is unset so existing rows (NULL metadata) keep working without a backfill.

func (UserMetadata) HomeViewOrDefault

func (m UserMetadata) HomeViewOrDefault() string

HomeViewOrDefault returns a valid HomeView value, falling back to compact when unset or unrecognized.

func (*UserMetadata) Scan

func (m *UserMetadata) Scan(value any) error

func (UserMetadata) Value

func (m UserMetadata) Value() (driver.Value, error)

type UserRole

type UserRole string
const (
	RoleAdmin UserRole = "admin"
	RoleUser  UserRole = "user"
)

type UserTag

type UserTag struct {
	UserID string `gorm:"primaryKey;type:uuid"`
	TagID  string `gorm:"primaryKey;type:varchar(36);index"`
}

UserTag assigns a Tag to a user. Only approved users may carry tags.

type Workflow added in v0.14.20

type Workflow struct {
	// ID is the stable UUID minted by the canvas / MCP create flow.
	// Matches Workflow.ID on the in-memory side.
	ID      string `gorm:"primaryKey;type:varchar(64)"`
	Name    string `gorm:"type:varchar(256);not null;default:''"`
	Enabled bool   `gorm:"not null;default:false"`
	Version int    `gorm:"not null;default:0"`
	// BodyPublished is the last-published JSON body. Mutated only by
	// the publish path. Empty until first publish.
	BodyPublished string `gorm:"type:text;not null;default:''"`
	// BodyDraft is the in-progress edit. Mutated on every save. Empty
	// when there is no draft (cleared on publish + discard).
	BodyDraft string `gorm:"type:text;not null;default:''"`
	HasDraft  bool   `gorm:"not null;default:false"`
	// EnvValues is the runtime config blob: a JSON object of
	// {key: value} where secrets are stored as wick_enc_ ciphertext and
	// kvlist/picker values as JSON strings — same shape as configs.value.
	// Current-only: NOT snapshotted into workflow_versions, so changing a
	// channel target or rotating a secret never bloats the history. The
	// env SCHEMA (which fields exist, their widgets) lives in the body;
	// only the VALUES live here. See internal/docs/workflow/11-env-secrets.md.
	EnvValues string `gorm:"type:text;not null;default:''"`
	CreatedBy string `gorm:"type:varchar(128);default:''"`
	CreatedAt time.Time
	UpdatedAt time.Time
}

Workflow is the DB representation of a workflow document. Body columns hold the canonical JSON; metadata columns (Name, Enabled) duplicate body fields so the list page can render without parsing every row.

func (Workflow) TableName added in v0.14.20

func (Workflow) TableName() string

type WorkflowTestCase added in v0.14.20

type WorkflowTestCase struct {
	ID         uint   `gorm:"primaryKey;autoIncrement"`
	WorkflowID string `gorm:"type:varchar(64);not null;index:idx_wtc_workflow_name,unique,priority:1"`
	Name       string `gorm:"type:varchar(256);not null;index:idx_wtc_workflow_name,unique,priority:2"`
	Body       string `gorm:"type:text;not null"`
	UpdatedAt  time.Time
}

WorkflowTestCase mirrors the file-based `__tests__/<name>.json` fixtures. Migrated alongside the body so workflow tests survive the move and stay editable through the SPA.

func (WorkflowTestCase) TableName added in v0.14.20

func (WorkflowTestCase) TableName() string

type WorkflowVersion added in v0.14.20

type WorkflowVersion struct {
	ID         uint      `gorm:"primaryKey;autoIncrement" json:"id"`
	WorkflowID string    `gorm:"type:varchar(64);not null;index" json:"workflow_id"`
	Kind       string    `gorm:"type:varchar(16);not null;index" json:"kind"` // "draft" | "published"
	Body       string    `gorm:"type:text;not null" json:"body"`
	Message    string    `gorm:"type:varchar(512);default:''" json:"message"`
	CreatedBy  string    `gorm:"type:varchar(128);default:''" json:"created_by"`
	CreatedAt  time.Time `json:"created_at"`
}

WorkflowVersion captures one immutable snapshot of a workflow at a point in time. Two flavours:

  • Kind = "draft" — written on every save while the user is editing. Retention policy: keep the last N per workflow (default 50, configurable).
  • Kind = "published" — written on Publish. Retained forever; this is the audit trail the History UI surfaces as restorable revisions.

`Message` is an optional human-readable label users attach when they publish ("fix slack template", "add retry"). `CreatedBy` is the authenticated user id captured by the handler.

func (WorkflowVersion) TableName added in v0.14.20

func (WorkflowVersion) TableName() string

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL