database

package
v0.8.11 Latest Latest
Warning

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

Go to latest
Published: Aug 7, 2026 License: AGPL-3.0 Imports: 22 Imported by: 0

Documentation

Overview

Package database — query tracer.

queryTracer implements the pgx v5 QueryTracer interface and records two metrics for every query the driver executes:

  • llmsafespaces_db_query_duration_seconds{operation}
  • llmsafespaces_db_errors_total{operation,error_type}

It is attached to both the *sql.DB pool (via stdlib.OpenDB + pgx.ConnConfig.Tracer) and the *pgxpool.Pool used by the secrets store (via pgxpool.Config.ConnConfig.Tracer). Every SQL statement issued by the API binary therefore flows through one tracer and one metric API, which is the long-term-correct alternative to wrapping every call site or registering a database/sql wrapper driver.

Index

Constants

This section is empty.

Variables

View Source
var ErrConflict = errors.New("conflict")

ErrConflict is returned for unique-constraint violations (e.g. rename collides with an existing name in the same scope).

View Source
var ErrNotFound = errors.New("not found")

ErrNotFound is the store's not-found sentinel, returned by GetX methods when no row matches. Callers use errors.Is(err, database.ErrNotFound). Exported so handler-layer code can distinguish 404 from 500 without fragile string matching.

View Source
var ErrTokenAlreadyConsumed = fmt.Errorf("token already consumed")

ErrTokenAlreadyConsumed is returned when ConsumeEmailToken affects 0 rows (the token was consumed by a concurrent request between Get and Consume — TOCTOU race). The handler maps this to 410 Gone.

Functions

func NewQueryTracer

func NewQueryTracer() pgx.QueryTracer

NewQueryTracer returns the QueryTracer used by the API's *sql.DB pool. Exported so callers (e.g. the secrets pgxpool initialized in internal/app) can attach the same tracer to their own pool, ensuring every query — regardless of which pool issues it — flows through one metrics path. The returned value implements pgx.QueryTracer.

Types

type ImageFactoryStore added in v0.7.1

type ImageFactoryStore interface {
	// ── Platform config (singleton row) ───────────────────────────────
	GetPlatformConfig(ctx context.Context) (imagefactory.PlatformConfig, error)
	SetPlatformConfig(ctx context.Context, pc imagefactory.PlatformConfig) error

	// ── Bases ─────────────────────────────────────────────────────────
	ListBases(ctx context.Context) ([]imagefactory.Base, error)
	GetBase(ctx context.Context, name, version string) (imagefactory.Base, error)
	UpsertBase(ctx context.Context, b imagefactory.Base) error
	DeleteBase(ctx context.Context, name, version string) error

	// ── Extensions ────────────────────────────────────────────────────
	ListExtensions(ctx context.Context, includeRetired bool) ([]imagefactory.Extension, error)
	GetExtension(ctx context.Context, id string) (imagefactory.Extension, error)
	PublishExtension(ctx context.Context, e imagefactory.Extension) error
	RetireExtension(ctx context.Context, id string) error
	SetExtensionReviewRequested(ctx context.Context, id string, v bool) error

	// ── Known failures ───────────────────────────────────────────────
	ListKnownFailures(ctx context.Context) ([]imagefactory.KnownFailure, error)
	GetKnownFailure(ctx context.Context, selectionHash, baseName string) (imagefactory.KnownFailure, error)
	RecordKnownFailure(ctx context.Context, kf imagefactory.KnownFailure) error
	SetKnownFailureRetriable(ctx context.Context, selectionHash, baseName string, retriable bool) error
	DeleteKnownFailure(ctx context.Context, selectionHash, baseName string) error
	ListRejectedConfigsForFailure(ctx context.Context, selectionHash, baseName string) ([]imagefactory.Config, error)

	// ── Configs ──────────────────────────────────────────────────────
	CreateConfig(ctx context.Context, c *imagefactory.Config) error
	CreateConfigAndBuild(ctx context.Context, c *imagefactory.Config, b *imagefactory.Build) error
	GetConfig(ctx context.Context, id string) (imagefactory.Config, error)
	GetConfigByHash(ctx context.Context, hash string, scope imagefactory.ConfigScope, ownerID, orgID *string) (imagefactory.Config, error)
	ListConfigs(ctx context.Context, scope imagefactory.ConfigScope, ownerID, orgID *string) ([]imagefactory.Config, error)
	ListVisibleConfigs(ctx context.Context, ownerID, orgID *string) ([]imagefactory.Config, error)
	SetConfigStatus(ctx context.Context, id string, status imagefactory.ConfigStatus) error
	DeleteConfig(ctx context.Context, id string) error
	RenameConfig(ctx context.Context, id, newName string) error
	// GetLaunchableConfigByHash returns a Ready config matching the hash
	// and scope/owner filter, together with the image_ref of its
	// successful build. Used by the workspace launch path to resolve a
	// user-selected config hash to a concrete, pre-built image. Returns
	// ErrNotFound if the config doesn't exist, isn't Ready, or has no
	// successful build (the normal "not launchable yet" case).
	GetLaunchableConfigByHash(ctx context.Context, hash string, scope imagefactory.ConfigScope, ownerID, orgID *string) (imagefactory.Config, string, error)

	// ── Builds ───────────────────────────────────────────────────────
	GetBuild(ctx context.Context, id string) (imagefactory.Build, error)
	GetInFlightOrSuccessfulBuild(ctx context.Context, hash, baseVersion string) (*imagefactory.Build, error)
	GetBuildByGHRunID(ctx context.Context, ghRunID int64) (imagefactory.Build, error)
	CreateBuild(ctx context.Context, b *imagefactory.Build) error
	MarkBuildSucceeded(ctx context.Context, id, imageRef, digest string) error
	MarkBuildFailed(ctx context.Context, id, failureReason, explanation string) error
	TransitionBuildSucceeded(ctx context.Context, buildID, configID, imageRef, digest string) error
	TransitionBuildFailed(ctx context.Context, buildID, configID string, kf imagefactory.KnownFailure) error
}

ImageFactoryStore is the data-access interface for the image factory (design/0046, design/0047). Methods are grouped: catalog (read), catalog admin (write), known failures, configs, builds.

All methods take ctx and return domain types from api/internal/imagefactory. Handlers depend on this interface (via the *database.Service concrete type) and tests inject fakes.

type OrgStore

type OrgStore interface {
	CreateOrgWithAdmin(ctx context.Context, org *types.Organization, adminUserID string) (*types.Organization, error)
	GetOrg(ctx context.Context, orgID string) (*types.Organization, error)
	GetOrgBySlug(ctx context.Context, slug string) (*types.Organization, error)
	ListOrgsForUser(ctx context.Context, userID string) ([]*types.OrgResponse, error)
	UpdateOrg(ctx context.Context, orgID string, req types.UpdateOrgRequest) (*types.Organization, error)
	SoftDeleteOrg(ctx context.Context, orgID string) error

	AddOrgMember(ctx context.Context, orgID, userID string, role types.OrgRole) error
	GetOrgMember(ctx context.Context, orgID, userID string) (*types.OrgMember, error)
	ListOrgMembers(ctx context.Context, orgID string) ([]*types.OrgMember, error)
	// CountOrgAdmins returns the number of admin members in an active (non-
	// deleted) org. Used by the SSO login flow to avoid demoting the last admin
	// on an IdP-driven role change (org orphaning prevention, cf. D19).
	CountOrgAdmins(ctx context.Context, orgID string) (int, error)
	UpdateOrgMemberRole(ctx context.Context, orgID, userID string, role types.OrgRole) error
	RemoveOrgMember(ctx context.Context, orgID, userID string) error
	RemoveOrgAdminIfNotLast(ctx context.Context, orgID, targetUserID string) (bool, error)
	DemoteOrgAdminIfNotLast(ctx context.Context, orgID, targetUserID string) (bool, error)
	IsOrgMember(ctx context.Context, orgID, userID string) (bool, error)
	IsOrgAdmin(ctx context.Context, orgID, userID string) (bool, error)
	ListOrgWorkspaces(ctx context.Context, orgID string, limit, offset int) ([]*types.WorkspaceMetadata, *types.PaginationMetadata, error)
	// GetUserIDByEmail resolves an owner email to a user ID for admin-driven org
	// creation (design 0031 D1). Returns ("", nil) when no user matches. This is
	// a single targeted lookup, never a search/list endpoint, to prevent account
	// enumeration. Users are hard-deleted (no deleted_at column), so no soft-delete
	// filter is needed.
	GetUserIDByEmail(ctx context.Context, email string) (string, error)
	// GetUserEmail resolves a user ID to their email (inverse of GetUserIDByEmail).
	// Used by invitation acceptance to verify email binding.
	GetUserEmail(ctx context.Context, userID string) (string, error)
	// MarkUserEmailVerified sets users.email_verified=true for the given user,
	// bypassing the email-verification token flow. Used by the org-admin
	// "Verify" action when an admin has confirmed the member's identity
	// out-of-band. Idempotent.
	MarkUserEmailVerified(ctx context.Context, userID string) error
	// GetUserOrgID returns the user's single org ID (or "" if not in any org).
	// With single-org enforcement (D8), a user belongs to at most one org. Used
	// by invitation acceptance (S3 cross-org check) and workspace auto-attribution
	// (D4). Returns ("", nil) on no membership. S7 in 0034.
	GetUserOrgID(ctx context.Context, userID string) (string, error)

	// US-43.1: Stripe lifecycle. UpdateOrgStatus sets the operational status
	// (active/suspended) and/or subscription_status and/or plan_id. A nil/empty
	// argument leaves the column unchanged.
	UpdateOrgStatus(ctx context.Context, orgID string, status *types.OrgStatus, subStatus *types.OrgSubscriptionStatus, planID *types.OrgPlan) error
	// GetOrgIDByStripeCustomer resolves a Stripe customer ID to the owning org's
	// ID via billing_accounts. Returns ("", nil) when no row matches.
	GetOrgIDByStripeCustomer(ctx context.Context, stripeCustomerID string) (string, error)
	// GetStripeCustomerID resolves an org to its Stripe customer id via
	// billing_accounts. Returns ("", nil) when no billing account exists.
	GetStripeCustomerID(ctx context.Context, orgID string) (string, error)
	// --- US-43.2: Invitations ---
	CreateInvitation(ctx context.Context, inv *types.OrgInvitation) error
	ListPendingInvitations(ctx context.Context, orgID string) ([]*types.OrgInvitation, error)
	GetInvitationByTokenHash(ctx context.Context, tokenHash string) (*types.OrgInvitation, error)
	GetInvitationByID(ctx context.Context, invID string) (*types.OrgInvitation, error)
	// AcceptInvitation performs the accept flow atomically under FOR UPDATE:
	// locks the invitation row, re-checks it is still pending, inserts the
	// membership, and marks the invitation accepted. Returns
	// (membership, alreadyAccepted, error).
	AcceptInvitationTx(ctx context.Context, invID, userID string, role types.OrgRole) (*types.OrgMember, bool, error)
	DeclineInvitation(ctx context.Context, invID string) error
	DeleteInvitation(ctx context.Context, invID string) error
	CountInvitationsLastHour(ctx context.Context, orgID string) (int, error)

	// --- US-43.7: Policies ---
	GetOrgPolicies(ctx context.Context, orgID string) ([]*types.OrgPolicy, error)
	SetOrgPolicy(ctx context.Context, orgID string, key types.OrgPolicyKey, value json.RawMessage, updatedBy string) error
	DeleteOrgPolicy(ctx context.Context, orgID string, key types.OrgPolicyKey) error

	// --- US-43.13: Org-scoped audit log ---
	LogOrgEvent(ctx context.Context, orgID, actorID, action, targetID string, metadata map[string]any) error
	// LogAuditEvent is the general audit writer (US-43.19). domain must be one
	// of audit_log_domain_chk's allowed values; orgID is nil for platform-level
	// (non-org-scoped) events.
	LogAuditEvent(ctx context.Context, domain, actorID, action, targetID string, orgID *string, metadata map[string]any) error
	ListOrgAudit(ctx context.Context, orgID string, limit, offset int) ([]*types.AuditEntry, *types.PaginationMetadata, error)
	// US-43.20: cross-org audit. ListAllAudit returns audit_log rows across all
	// orgs, narrowed by the supplied filters (nil pointers ⇒ no filter). Limit
	// defaults to 100 and is clamped to [1, 500]; Offset defaults to 0.
	ListAllAudit(ctx context.Context, filters types.AuditFilters) ([]*types.AuditEntry, *types.PaginationMetadata, error)
	// US-43.19: last-admin deadlock prevention. Returns orgs where the given
	// user is the sole active admin — suspending them would orphan the org.
	OrgsWhereUserIsLastActiveAdmin(ctx context.Context, userID string) ([]types.LastAdminOrg, error)
	// US-43.18: platform-admin dashboard. ListAllOrgs returns every
	// non-deleted org with aggregated member + workspace counts, optionally
	// narrowed by status. statusFilter is applied only when non-nil/non-empty.
	// limit is clamped to [1, adminListMaxLimit]; offset defaults to 0.
	ListAllOrgs(ctx context.Context, limit, offset int, statusFilter *string) ([]types.OrgSummary, *types.PaginationMetadata, error)

	// --- US-43.10: OIDC SSO configuration ---
	// GetSSOConfig returns the org's SSO config or (nil, nil) when none exists.
	GetSSOConfig(ctx context.Context, orgID string) (*types.OrgSSOConfig, error)
	// UpsertSSOConfig inserts or replaces the org's SSO config. ClientSecret is
	// the already-encrypted blob (server KEK, D17-S4). VerifiedDomains is the
	// caller-computed subset of ClaimedDomains that remain verified after this
	// update (the service layer intersects existing verified with new claimed).
	// VerificationToken is generated on INSERT if empty; ON CONFLICT preserves
	// the existing token (rotation is via RotateVerificationToken).
	UpsertSSOConfig(ctx context.Context, config *types.OrgSSOConfig) error
	// DeleteSSOConfig removes the org's SSO config.
	DeleteSSOConfig(ctx context.Context, orgID string) error
	// FindSSOConfigByDomain resolves a claimed email domain (without leading
	// "@") to the owning org's SSO config. Returns (nil, nil) when no org has
	// claimed the domain.
	FindSSOConfigByDomain(ctx context.Context, domain string) (*types.OrgSSOConfig, error)
	// ListSSODomains returns every DNS-verified domain across all orgs, for
	// the login-page discovery endpoint. Unverified claimed domains are NOT
	// returned — they cannot auto-route until the org admin completes DNS
	// verification (D17 Q-S2).
	ListSSODomains(ctx context.Context) ([]types.SSODomain, error)
	// CountSSOConfigs returns the number of orgs with an SSO config. Used by
	// GET /auth/config to set the OIDCEnabled feature flag.
	CountSSOConfigs(ctx context.Context) (int, error)
	// SetDomainVerified atomically appends a domain to verified_domains. The
	// domain MUST already be in claimed_domains (enforced by the WHERE clause);
	// a domain not in claimed_domains is silently not added. Idempotent: adding
	// an already-verified domain is a no-op. Returns (true, nil) if the domain
	// was newly verified, (false, nil) if it was already verified or not claimed.
	SetDomainVerified(ctx context.Context, orgID, domain string) (bool, error)
	// RotateVerificationToken replaces the org's DNS verification token with a
	// fresh random value and returns it. Used both for initial token creation
	// (when verification_token is NULL) and for rotation. Old tokens stop
	// matching after rotation — admins must update their DNS TXT record.
	RotateVerificationToken(ctx context.Context, orgID string) (string, error)
}

OrgStore is the data-access interface for organizations and their memberships.

type PendingOrgCleanup

type PendingOrgCleanup struct {
	OrgID            string
	Slug             string
	CreatedAt        time.Time
	StripeCustomerID string
}

PendingOrgCleanup describes a pending_activation org eligible for the cleanup cron. StripeCustomerID lets the cron verify checkout state with Stripe before deleting.

type PgEmailTokenStore

type PgEmailTokenStore struct {
	// contains filtered or unexported fields
}

PgEmailTokenStore implements the email-token CRUD against PostgreSQL.

func NewPgEmailTokenStore

func NewPgEmailTokenStore(db *sql.DB) *PgEmailTokenStore

func (*PgEmailTokenStore) ConsumeEmailToken

func (s *PgEmailTokenStore) ConsumeEmailToken(ctx context.Context, id string) error

func (*PgEmailTokenStore) CreateEmailToken

func (s *PgEmailTokenStore) CreateEmailToken(ctx context.Context, t *types.EmailToken) error

func (*PgEmailTokenStore) GetEmailTokenByHash

func (s *PgEmailTokenStore) GetEmailTokenByHash(ctx context.Context, hash string) (*types.EmailToken, error)

type PgOrgStore

type PgOrgStore struct {
	// contains filtered or unexported fields
}

PgOrgStore implements OrgStore using database/sql.

func NewPgOrgStore

func NewPgOrgStore(db *sql.DB) *PgOrgStore

NewPgOrgStore creates a new PgOrgStore.

func (*PgOrgStore) AcceptInvitationTx

func (s *PgOrgStore) AcceptInvitationTx(ctx context.Context, invID, userID string, role types.OrgRole) (*types.OrgMember, bool, error)

func (*PgOrgStore) AddOrgMember

func (s *PgOrgStore) AddOrgMember(ctx context.Context, orgID, userID string, role types.OrgRole) error

AddOrgMember inserts an org_memberships row AND migrates the new member's personal (NULL-org_id, non-deleted) workspaces to the org — mirroring CreateOrgWithAdmin (M1/D4) and AcceptInvitationTx (D4). Both operations run in a single transaction so the membership and the workspace migration commit atomically.

Callers (audited 2026-06-25):

  • admin "Add member" UI → POST /orgs/:id/members → orgs.go handler
  • SSO JIT provisioning during first login (sso.go)

Pre-fix: this function only INSERTed the membership row. The new member's existing workspaces stayed with org_id IS NULL forever, silently skipping org-credential auto-binding via BindCredentialToAllOrgWorkspaces (which filters w.org_id = $orgID). That produced the same orphan state migration 000044 was created to backfill. See the worklog `add-org-member-migrates-workspaces` for the discovery trail.

func (*PgOrgStore) ClearWorkspaceAgentRole

func (s *PgOrgStore) ClearWorkspaceAgentRole(ctx context.Context, workspaceID, userID string) error

ClearWorkspaceAgentRole removes the role assignment from a workspace (sets agent_role_id = NULL). Used by the "use platform default" action.

func (*PgOrgStore) CountInvitationsLastHour

func (s *PgOrgStore) CountInvitationsLastHour(ctx context.Context, orgID string) (int, error)

func (*PgOrgStore) CountOrgAdmins

func (s *PgOrgStore) CountOrgAdmins(ctx context.Context, orgID string) (int, error)

func (*PgOrgStore) CountSSOConfigs

func (s *PgOrgStore) CountSSOConfigs(ctx context.Context) (int, error)

func (*PgOrgStore) CreateAgentRole

func (s *PgOrgStore) CreateAgentRole(ctx context.Context, role *types.AgentRole, configJSON []byte) (*types.AgentRole, error)

CreateAgentRole inserts a new agent role.

func (*PgOrgStore) CreateInvitation

func (s *PgOrgStore) CreateInvitation(ctx context.Context, inv *types.OrgInvitation) error

func (*PgOrgStore) CreateOrgWithAdmin

func (s *PgOrgStore) CreateOrgWithAdmin(ctx context.Context, org *types.Organization, adminUserID string) (*types.Organization, error)

func (*PgOrgStore) DeclineInvitation

func (s *PgOrgStore) DeclineInvitation(ctx context.Context, invID string) error

func (*PgOrgStore) DeleteAgentRole

func (s *PgOrgStore) DeleteAgentRole(ctx context.Context, roleID string) error

DeleteAgentRole deletes a role. Caller must check dependents first.

func (*PgOrgStore) DeleteInvitation

func (s *PgOrgStore) DeleteInvitation(ctx context.Context, invID string) error

func (*PgOrgStore) DeleteOrgPolicy

func (s *PgOrgStore) DeleteOrgPolicy(ctx context.Context, orgID string, key types.OrgPolicyKey) error

func (*PgOrgStore) DeleteSSOConfig

func (s *PgOrgStore) DeleteSSOConfig(ctx context.Context, orgID string) error

func (*PgOrgStore) DeleteStripeEvent

func (s *PgOrgStore) DeleteStripeEvent(ctx context.Context, eventID string) error

func (*PgOrgStore) DeleteWorkspacePrompt

func (s *PgOrgStore) DeleteWorkspacePrompt(ctx context.Context, workspaceID string) error

DeleteWorkspacePrompt removes the user-level prompt override.

func (*PgOrgStore) DemoteOrgAdminIfNotLast

func (s *PgOrgStore) DemoteOrgAdminIfNotLast(ctx context.Context, orgID, targetUserID string) (bool, error)

func (*PgOrgStore) FindSSOConfigByDomain

func (s *PgOrgStore) FindSSOConfigByDomain(ctx context.Context, domain string) (*types.OrgSSOConfig, error)

FindSSOConfigByDomain resolves a claimed email domain (without leading "@") to the owning org's SSO config. Returns (nil, nil) when no org has claimed the domain.

NOTE: this matches on claimed_domains (NOT verified_domains). It is intended for internal lookups where the org has been identified by other means and its full config is needed regardless of verification status. The login-page auto-routing path uses ListSSODomains (which filters on verified_domains). If you wire this into login routing, you MUST add a verified_domains check or you bypass the DNS verification gate this migration introduces.

func (*PgOrgStore) GetAgentRole

func (s *PgOrgStore) GetAgentRole(ctx context.Context, roleID string) (*types.AgentRole, error)

GetAgentRole retrieves a single agent role by ID.

func (*PgOrgStore) GetInvitationByID

func (s *PgOrgStore) GetInvitationByID(ctx context.Context, invID string) (*types.OrgInvitation, error)

func (*PgOrgStore) GetInvitationByTokenHash

func (s *PgOrgStore) GetInvitationByTokenHash(ctx context.Context, tokenHash string) (*types.OrgInvitation, error)

func (*PgOrgStore) GetOrg

func (s *PgOrgStore) GetOrg(ctx context.Context, orgID string) (*types.Organization, error)

func (*PgOrgStore) GetOrgBySlug

func (s *PgOrgStore) GetOrgBySlug(ctx context.Context, slug string) (*types.Organization, error)

func (*PgOrgStore) GetOrgIDByStripeCustomer

func (s *PgOrgStore) GetOrgIDByStripeCustomer(ctx context.Context, stripeCustomerID string) (string, error)

func (*PgOrgStore) GetOrgMember

func (s *PgOrgStore) GetOrgMember(ctx context.Context, orgID, userID string) (*types.OrgMember, error)

func (*PgOrgStore) GetOrgPolicies

func (s *PgOrgStore) GetOrgPolicies(ctx context.Context, orgID string) ([]*types.OrgPolicy, error)

func (*PgOrgStore) GetPlatformSetting

func (s *PgOrgStore) GetPlatformSetting(ctx context.Context, key types.PlatformSettingKey) (*types.PlatformSetting, error)

GetPlatformSetting retrieves a single platform-wide setting by key. Returns nil (not an error) when the key does not exist.

func (*PgOrgStore) GetRoleDependents

func (s *PgOrgStore) GetRoleDependents(ctx context.Context, roleID string) ([]*types.AgentRole, error)

GetRoleDependents returns roles that extend the given role.

func (*PgOrgStore) GetSSOConfig

func (s *PgOrgStore) GetSSOConfig(ctx context.Context, orgID string) (*types.OrgSSOConfig, error)

func (*PgOrgStore) GetStripeCustomerID

func (s *PgOrgStore) GetStripeCustomerID(ctx context.Context, orgID string) (string, error)

func (*PgOrgStore) GetUserEmail

func (s *PgOrgStore) GetUserEmail(ctx context.Context, userID string) (string, error)

func (*PgOrgStore) GetUserIDByEmail

func (s *PgOrgStore) GetUserIDByEmail(ctx context.Context, email string) (string, error)

func (*PgOrgStore) GetUserOrgID

func (s *PgOrgStore) GetUserOrgID(ctx context.Context, userID string) (string, error)

func (*PgOrgStore) GetUserPlan added in v0.7.0

func (s *PgOrgStore) GetUserPlan(ctx context.Context, userID string) (string, error)

GetUserPlan reads users.plan_id for an individual user. Used by the MCP-servers user-scope handler to resolve the MaxPersonalMcpServers quota (Epic 53 D12). Returns PlanFree on any error (fail-safe: a solo user whose plan can't be read gets the free-tier quota, not unlimited).

func (*PgOrgStore) GetWorkspaceAgentRole

func (s *PgOrgStore) GetWorkspaceAgentRole(ctx context.Context, workspaceID string) (*types.AgentRole, error)

GetWorkspaceAgentRole retrieves the role assigned to a workspace.

func (*PgOrgStore) GetWorkspaceOrgID

func (s *PgOrgStore) GetWorkspaceOrgID(ctx context.Context, workspaceID string) (string, error)

GetWorkspaceOrgID returns the org_id for a workspace, or "" if the workspace has no org (standalone user).

func (*PgOrgStore) GetWorkspacePrompt

func (s *PgOrgStore) GetWorkspacePrompt(ctx context.Context, workspaceID string) (*types.WorkspacePrompt, error)

GetWorkspacePrompt retrieves the user-level prompt override for a workspace. Returns nil (not an error) when no override exists.

func (*PgOrgStore) HardDeleteOrg

func (s *PgOrgStore) HardDeleteOrg(ctx context.Context, orgID string) error

func (*PgOrgStore) HasRoleWorkspaceUsage

func (s *PgOrgStore) HasRoleWorkspaceUsage(ctx context.Context, roleID string) (bool, error)

HasRoleWorkspaceUsage checks if any workspace references this role.

func (*PgOrgStore) IsOrgAdmin

func (s *PgOrgStore) IsOrgAdmin(ctx context.Context, orgID, userID string) (bool, error)

func (*PgOrgStore) IsOrgMember

func (s *PgOrgStore) IsOrgMember(ctx context.Context, orgID, userID string) (bool, error)

func (*PgOrgStore) ListAgentRoles

func (s *PgOrgStore) ListAgentRoles(ctx context.Context, scope string, orgID string) ([]*types.AgentRole, error)

ListAgentRoles lists roles by scope and optional org_id.

func (*PgOrgStore) ListAllAudit

func (s *PgOrgStore) ListAllAudit(ctx context.Context, filters types.AuditFilters) ([]*types.AuditEntry, *types.PaginationMetadata, error)

ListAllAudit returns audit_log rows across every org, narrowed by filters. The WHERE clause is built from conditional ANDs over a parameterised args slice — no user input is ever interpolated into the SQL text.

func (*PgOrgStore) ListAllOrgs

func (s *PgOrgStore) ListAllOrgs(ctx context.Context, limit, offset int, statusFilter *string) ([]types.OrgSummary, *types.PaginationMetadata, error)

ListAllOrgs returns every non-deleted organization with aggregated member and workspace counts for the platform-admin dashboard. The optional statusFilter narrows the result to a single OrgStatus (e.g. "suspended"); an empty/nil filter returns all statuses. Results are ordered by created_at DESC.

The two counts are correlated subqueries on the same row, so a single round trip returns the full summary without an N+1 fan-out. The COUNT(*) total is fetched first so an empty page short-circuits the SELECT.

func (*PgOrgStore) ListOrgAudit

func (s *PgOrgStore) ListOrgAudit(ctx context.Context, orgID string, limit, offset int) ([]*types.AuditEntry, *types.PaginationMetadata, error)

func (*PgOrgStore) ListOrgMembers

func (s *PgOrgStore) ListOrgMembers(ctx context.Context, orgID string) ([]*types.OrgMember, error)

func (*PgOrgStore) ListOrgWorkspaces

func (s *PgOrgStore) ListOrgWorkspaces(ctx context.Context, orgID string, limit, offset int) ([]*types.WorkspaceMetadata, *types.PaginationMetadata, error)

func (*PgOrgStore) ListOrgsForUser

func (s *PgOrgStore) ListOrgsForUser(ctx context.Context, userID string) ([]*types.OrgResponse, error)

func (*PgOrgStore) ListPendingInvitations

func (s *PgOrgStore) ListPendingInvitations(ctx context.Context, orgID string) ([]*types.OrgInvitation, error)

func (*PgOrgStore) ListPendingOrgsOlderThan

func (s *PgOrgStore) ListPendingOrgsOlderThan(ctx context.Context, maxAge time.Duration) ([]PendingOrgCleanup, error)

func (*PgOrgStore) ListSSODomains

func (s *PgOrgStore) ListSSODomains(ctx context.Context) ([]types.SSODomain, error)

func (*PgOrgStore) LogAuditEvent

func (s *PgOrgStore) LogAuditEvent(ctx context.Context, domain, actorID, action, targetID string, orgID *string, metadata map[string]any) error

LogAuditEvent inserts a row into audit_log with an explicit domain and an optional org scope. It is the general audit writer used by both org-scoped events (domain='org', orgID non-nil) and platform-admin events (domain='admin', orgID nil). The domain must be one of the values allowed by the audit_log_domain_chk CHECK constraint (billing/secrets/admin/org).

func (*PgOrgStore) LogOrgEvent

func (s *PgOrgStore) LogOrgEvent(ctx context.Context, orgID, actorID, action, targetID string, metadata map[string]any) error

func (*PgOrgStore) MarkUserEmailVerified

func (s *PgOrgStore) MarkUserEmailVerified(ctx context.Context, userID string) error

MarkUserEmailVerified sets users.email_verified=true for the given user, bypassing the email-verification token flow. Used by the org-admin "Verify" action (POST /orgs/:id/members/:userID/verify) when an admin has confirmed the member's identity out-of-band. The membership is verified by the caller (OrgAdminGuard + GetOrgMember) before this is invoked, so a bare userID is safe here. Idempotent: re-verifying an already-verified user is a no-op.

func (*PgOrgStore) OrgsWhereUserIsLastActiveAdmin

func (s *PgOrgStore) OrgsWhereUserIsLastActiveAdmin(ctx context.Context, userID string) ([]types.LastAdminOrg, error)

OrgsWhereUserIsLastActiveAdmin returns every organization where the given user is an admin AND no OTHER active admin exists. Suspending such a user (D19) would orphan the org — no remaining admin could manage it (promote members, change policies, manage billing). The user-suspend path refuses with 409 when this returns a non-empty slice (unless force=true).

func (*PgOrgStore) RecordStripeEvent

func (s *PgOrgStore) RecordStripeEvent(ctx context.Context, eventID, eventType string) (bool, error)

func (*PgOrgStore) RemoveOrgAdminIfNotLast

func (s *PgOrgStore) RemoveOrgAdminIfNotLast(ctx context.Context, orgID, targetUserID string) (bool, error)

func (*PgOrgStore) RemoveOrgMember

func (s *PgOrgStore) RemoveOrgMember(ctx context.Context, orgID, userID string) error

func (*PgOrgStore) RotateVerificationToken

func (s *PgOrgStore) RotateVerificationToken(ctx context.Context, orgID string) (string, error)

RotateVerificationToken replaces the org's verification token with a fresh random 32-hex value and returns it. Used for both initial creation (when verification_token is NULL) and rotation. Returns the new token.

func (*PgOrgStore) SetBillingAccountSubscription

func (s *PgOrgStore) SetBillingAccountSubscription(ctx context.Context, ownerID, ownerType, provider, subscriptionID string) error

func (*PgOrgStore) SetDomainVerified

func (s *PgOrgStore) SetDomainVerified(ctx context.Context, orgID, domain string) (bool, error)

SetDomainVerified atomically appends a domain to verified_domains. The domain must already be in claimed_domains (the WHERE clause enforces this); a domain not claimed is a no-op. Idempotent: re-verifying an already- verified domain returns (false, nil) without error. Returns (true, nil) only when the domain was newly promoted.

func (*PgOrgStore) SetOrgDefaultRole

func (s *PgOrgStore) SetOrgDefaultRole(ctx context.Context, orgID, roleID string) error

SetOrgDefaultRole atomically sets one role as default and clears all others.

func (*PgOrgStore) SetOrgPolicy

func (s *PgOrgStore) SetOrgPolicy(ctx context.Context, orgID string, key types.OrgPolicyKey, value json.RawMessage, updatedBy string) error

func (*PgOrgStore) SetPlatformSetting

func (s *PgOrgStore) SetPlatformSetting(ctx context.Context, key types.PlatformSettingKey, value json.RawMessage, updatedBy string) error

SetPlatformSetting upserts a platform-wide setting.

func (*PgOrgStore) SetWorkspaceAgentRole

func (s *PgOrgStore) SetWorkspaceAgentRole(ctx context.Context, workspaceID, roleID, userID string) error

SetWorkspaceAgentRole sets the role for a workspace.

func (*PgOrgStore) SetWorkspacePrompt

func (s *PgOrgStore) SetWorkspacePrompt(ctx context.Context, workspaceID string, prompt string, updatedBy string) error

SetWorkspacePrompt upserts the user-level prompt override for a workspace.

func (*PgOrgStore) SoftDeleteOrg

func (s *PgOrgStore) SoftDeleteOrg(ctx context.Context, orgID string) error

func (*PgOrgStore) SuspendUserGuardedByLastAdmin

func (s *PgOrgStore) SuspendUserGuardedByLastAdmin(ctx context.Context, userID string, force bool) (*types.LastAdminOrg, error)

SuspendUserGuardedByLastAdmin atomically refuses to suspend the user when they are the sole active admin of any org (unless force), and otherwise sets the user's status to suspended. The SELECT … FOR UPDATE on the admin membership rows of every org the user administers plus the UPDATE on users run in a single transaction, closing the TOCTOU window of the prior read-then-write sequence (F7, US-43.19): two concurrent admin suspensions or a suspend racing a demote can no longer both pass the last-admin check and leave the org adminless. `active` is mirrored to `false` so the legacy column cannot drift from `status` (F6).

Returns a non-nil *LastAdminOrg when the suspend was refused (last admin); the caller surfaces this as 409. Returns (nil, nil) on a successful suspension.

func (*PgOrgStore) UpdateAgentRole

func (s *PgOrgStore) UpdateAgentRole(ctx context.Context, roleID string, role *types.AgentRole, configJSON []byte) (*types.AgentRole, error)

UpdateAgentRole updates an agent role.

func (*PgOrgStore) UpdateOrg

func (s *PgOrgStore) UpdateOrg(ctx context.Context, orgID string, req types.UpdateOrgRequest) (*types.Organization, error)

func (*PgOrgStore) UpdateOrgMemberRole

func (s *PgOrgStore) UpdateOrgMemberRole(ctx context.Context, orgID, userID string, role types.OrgRole) error

func (*PgOrgStore) UpdateOrgStatus

func (s *PgOrgStore) UpdateOrgStatus(ctx context.Context, orgID string, status *types.OrgStatus, subStatus *types.OrgSubscriptionStatus, planID *types.OrgPlan) error

func (*PgOrgStore) UpsertSSOConfig

func (s *PgOrgStore) UpsertSSOConfig(ctx context.Context, config *types.OrgSSOConfig) error

type Service

type Service struct {
	Logger *logger.Logger
	Config *config.Config
	DB     *sql.DB
}

Service handles database operations

func New

func New(cfg *config.Config, log *logger.Logger) (*Service, error)

func (*Service) BeginTx

func (s *Service) BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error)

BeginTx starts a new database transaction. Used by handlers that need multi-statement atomicity (e.g., AgentReloadHandler's SELECT FOR UPDATE + UPSERT).

func (*Service) CheckPermission

func (s *Service) CheckPermission(ctx context.Context, userID, resourceType, resourceID, action string) (bool, error)

CheckPermission checks if a user has permission to perform an action on a resource

func (*Service) CheckResourceOwnership

func (s *Service) CheckResourceOwnership(ctx context.Context, userID, resourceType, resourceID string) (bool, error)

CheckResourceOwnership checks if a user owns a resource

func (*Service) CountActiveWorkspacesByUserAndOrg

func (s *Service) CountActiveWorkspacesByUserAndOrg(ctx context.Context, userID, orgID string) (int, error)

func (*Service) CountUsers

func (s *Service) CountUsers(ctx context.Context) (int, error)

CountUsers returns the total number of users in the system. Used by the auth Register flow to detect a fresh installation (count == 0) and auto-promote the first user to admin so a brand-new install has at least one administrator.

func (*Service) CountWorkspacesByUserAndOrg

func (s *Service) CountWorkspacesByUserAndOrg(ctx context.Context, userID, orgID string) (int, error)

func (*Service) CreateAPIKey

func (s *Service) CreateAPIKey(ctx context.Context, apiKey *types.APIKey) error

func (*Service) CreateBuild added in v0.7.1

func (s *Service) CreateBuild(ctx context.Context, b *imagefactory.Build) error

func (*Service) CreateConfig added in v0.7.1

func (s *Service) CreateConfig(ctx context.Context, c *imagefactory.Config) error

func (*Service) CreateConfigAndBuild added in v0.7.1

func (s *Service) CreateConfigAndBuild(ctx context.Context, c *imagefactory.Config, b *imagefactory.Build) error

CreateConfigAndBuild inserts both rows in a single transaction so a failure in either insert rolls back the other — no orphaned config at 'building' with no build, no build row with no config. The handler calls this after a successful dispatch (design/0046 #17).

func (*Service) CreateUser

func (s *Service) CreateUser(ctx context.Context, user *types.User) error

CreateUser creates a new user

func (*Service) CreateWorkspace

func (s *Service) CreateWorkspace(ctx context.Context, workspace *types.WorkspaceMetadata) error

CreateWorkspace inserts a new workspace record.

func (*Service) DeleteAPIKey

func (s *Service) DeleteAPIKey(ctx context.Context, userID, keyID string) error

func (*Service) DeleteBase added in v0.7.1

func (s *Service) DeleteBase(ctx context.Context, name, version string) error

func (*Service) DeleteConfig added in v0.8.8

func (s *Service) DeleteConfig(ctx context.Context, id string) error

DeleteConfig deletes a config row and its build history. Returns ErrNotFound if the config doesn't exist. Builds are deleted first (the FK has no ON DELETE CASCADE), then the config — both in a single tx so a partial delete can't leave orphaned rows.

func (*Service) DeleteKnownFailure added in v0.7.1

func (s *Service) DeleteKnownFailure(ctx context.Context, selectionHash, baseName string) error

func (*Service) DeleteSessionIndex

func (s *Service) DeleteSessionIndex(ctx context.Context, workspaceID string) error

func (*Service) DeleteSessionTree

func (s *Service) DeleteSessionTree(ctx context.Context, workspaceID, sessionID string) error

func (*Service) DeleteUser

func (s *Service) DeleteUser(ctx context.Context, userID string) error

DeleteUser deletes a user

func (*Service) DeleteWorkspace

func (s *Service) DeleteWorkspace(ctx context.Context, workspaceID string) error

DeleteWorkspace removes a workspace record.

func (*Service) GetAPIKey

func (s *Service) GetAPIKey(ctx context.Context, userID, keyID string) (*types.APIKey, error)

func (*Service) GetAPIKeyRecordByHash

func (s *Service) GetAPIKeyRecordByHash(ctx context.Context, keyHash string) (*types.APIKey, error)

func (*Service) GetAllInstanceSettings

func (s *Service) GetAllInstanceSettings(ctx context.Context) (map[string]json.RawMessage, error)

GetAllInstanceSettings returns all rows from instance_settings.

func (*Service) GetAllUserSettings

func (s *Service) GetAllUserSettings(ctx context.Context, userID string) (map[string]json.RawMessage, error)

GetAllUserSettings returns all settings for a specific user.

func (*Service) GetBase added in v0.7.1

func (s *Service) GetBase(ctx context.Context, name, version string) (imagefactory.Base, error)

func (*Service) GetBuild added in v0.7.1

func (s *Service) GetBuild(ctx context.Context, id string) (imagefactory.Build, error)

func (*Service) GetBuildByGHRunID added in v0.7.1

func (s *Service) GetBuildByGHRunID(ctx context.Context, ghRunID int64) (imagefactory.Build, error)

func (*Service) GetConfig added in v0.7.1

func (s *Service) GetConfig(ctx context.Context, id string) (imagefactory.Config, error)

func (*Service) GetConfigByHash added in v0.7.1

func (s *Service) GetConfigByHash(ctx context.Context, hash string, scope imagefactory.ConfigScope, ownerID, orgID *string) (imagefactory.Config, error)

func (*Service) GetDefaultModel

func (s *Service) GetDefaultModel(ctx context.Context, workspaceID string) (string, error)

GetDefaultModel returns the workspace's configured default model, or "" if unset.

func (*Service) GetExtension added in v0.7.1

func (s *Service) GetExtension(ctx context.Context, id string) (imagefactory.Extension, error)

func (*Service) GetInFlightOrSuccessfulBuild added in v0.7.1

func (s *Service) GetInFlightOrSuccessfulBuild(ctx context.Context, hash, baseVersion string) (*imagefactory.Build, error)

GetInFlightOrSuccessfulBuild is the coalescing probe (design/0046 #16). Returns a successful build if one exists for (hash, base_version); otherwise an in-flight (dispatched) one; otherwise nil. Prefers success so a new config immediately links to a Ready build rather than waiting on an in-flight one that might fail.

func (*Service) GetKnownFailure added in v0.7.1

func (s *Service) GetKnownFailure(ctx context.Context, selectionHash, baseName string) (imagefactory.KnownFailure, error)

func (*Service) GetLastCredentialChangedAt

func (s *Service) GetLastCredentialChangedAt(ctx context.Context, workspaceID string) (time.Time, error)

GetLastCredentialChangedAt returns the most recent credential-changed timestamp for the workspace, or the zero time if no row exists.

func (*Service) GetLaunchableConfigByHash added in v0.8.5

func (s *Service) GetLaunchableConfigByHash(ctx context.Context, hash string, scope imagefactory.ConfigScope, ownerID, orgID *string) (imagefactory.Config, string, error)

GetLaunchableConfigByHash implements ImageFactoryStore.GetLaunchableConfigByHash. It joins image_factory_configs with its successful build to return both the config and the image_ref in one round-trip, and enforces the Ready + scope constraints needed by the workspace launch path. The query filters:

  • config.status = 'ready' (design/0046 #15 — only Ready configs are launchable)
  • scope/owner/org match (authorization: caller must own the config)
  • a joined build row with status='succeeded' AND image_ref <> ” exists

All selected columns are qualified with `c.` (or `b.`) because config and build tables share column names (hash, base_name, base_version, etc.) — an unqualified SELECT would be ambiguous and fail at query time.

The build's image_ref is what the controller's runtime_resolver will use verbatim as the pod image (any '/'-containing runtime value is a passthrough).

func (*Service) GetPlatformConfig added in v0.7.1

func (s *Service) GetPlatformConfig(ctx context.Context) (imagefactory.PlatformConfig, error)

func (*Service) GetUser

func (s *Service) GetUser(ctx context.Context, userID string) (*types.User, error)

GetUser gets a user by ID

func (*Service) GetUserByAPIKey

func (s *Service) GetUserByAPIKey(ctx context.Context, apiKey string) (*types.User, error)

GetUserByAPIKey gets the user associated with an API key

func (*Service) GetUserByEmail

func (s *Service) GetUserByEmail(ctx context.Context, email string) (*types.User, error)

GetUserByEmail gets a user by email address

func (*Service) GetWorkspace

func (s *Service) GetWorkspace(ctx context.Context, workspaceID string) (*types.WorkspaceMetadata, error)

GetWorkspace gets a workspace by ID.

func (*Service) InsertInstanceSettingIfMissing

func (s *Service) InsertInstanceSettingIfMissing(ctx context.Context, key string, value json.RawMessage) (bool, error)

InsertInstanceSettingIfMissing inserts a setting only if the key doesn't exist. Returns true if inserted, false if already existed.

func (*Service) ListAPIKeys

func (s *Service) ListAPIKeys(ctx context.Context, userID string) ([]*types.APIKey, error)

func (*Service) ListAPIKeysWithDecrypt

func (s *Service) ListAPIKeysWithDecrypt(ctx context.Context, userID string) ([]*types.APIKey, error)

func (*Service) ListAllUsers

func (s *Service) ListAllUsers(ctx context.Context, limit, offset int, statusFilter *string) ([]types.UserListEntry, *types.PaginationMetadata, error)

ListAllUsers returns every user for the platform-admin dashboard (US-43.18). The optional statusFilter narrows to a single UserStatus; an empty/nil filter returns all users. Each entry carries the user's single org membership (org_id/org_name) resolved via a LEFT JOIN — under single-org enforcement (D8) a user belongs to at most one org, so this adds no row fan-out.

Password hashes and other sensitive columns are never selected. limit is clamped to [1, adminListMaxLimit]; offset defaults to 0.

func (*Service) ListAllWorkspaceOwners

func (s *Service) ListAllWorkspaceOwners(ctx context.Context) (map[string]string, error)

func (*Service) ListAllWorkspacesForBilling

func (s *Service) ListAllWorkspacesForBilling(ctx context.Context) ([]WorkspaceBillingRecord, error)

func (*Service) ListBases added in v0.7.1

func (s *Service) ListBases(ctx context.Context) ([]imagefactory.Base, error)

func (*Service) ListConfigs added in v0.7.1

func (s *Service) ListConfigs(ctx context.Context, scope imagefactory.ConfigScope, ownerID, orgID *string) ([]imagefactory.Config, error)

func (*Service) ListExtensions added in v0.7.1

func (s *Service) ListExtensions(ctx context.Context, includeRetired bool) ([]imagefactory.Extension, error)

func (*Service) ListKnownFailures added in v0.7.1

func (s *Service) ListKnownFailures(ctx context.Context) ([]imagefactory.KnownFailure, error)

func (*Service) ListPendingReloadWorkspaces

func (s *Service) ListPendingReloadWorkspaces(ctx context.Context, userID string) ([]*types.WorkspaceMetadata, error)

ListPendingReloadWorkspaces returns workspaces with pending_refresh=TRUE for the given user.

func (*Service) ListRejectedConfigsForFailure added in v0.7.1

func (s *Service) ListRejectedConfigsForFailure(ctx context.Context, selectionHash, baseName string) ([]imagefactory.Config, error)

func (*Service) ListSessionIndex

func (s *Service) ListSessionIndex(ctx context.Context, workspaceID string) ([]types.SessionListItem, error)

func (*Service) ListVisibleConfigs added in v0.7.1

func (s *Service) ListVisibleConfigs(ctx context.Context, ownerID, orgID *string) ([]imagefactory.Config, error)

ListVisibleConfigs returns the configs a member can see: their own member-scope, their org's org-scope (if any), plus platform-scope.

func (*Service) ListWorkspaces

func (s *Service) ListWorkspaces(ctx context.Context, userID string, limit, offset int) ([]*types.WorkspaceMetadata, *types.PaginationMetadata, error)

ListWorkspaces lists workspaces owned by the user with pagination.

Per Epic 43 decision D6, this returns only workspaces the user created (w.user_id = $1). The prior LEFT JOIN org_memberships + OR clause that let any org member see every other member's org workspace has been removed: members now see only their own workspaces. Org admins who need to see all org workspaces use the dedicated GET /orgs/:id/workspaces endpoint (OrgStore.ListOrgWorkspaces).

func (*Service) MarkAgentReloaded

func (s *Service) MarkAgentReloaded(ctx context.Context, tx *sql.Tx, workspaceID string, priorChangedAt time.Time) (time.Time, error)

MarkAgentReloaded clears pending_refresh after a successful dispose. Uses SELECT FOR UPDATE to serialize against concurrent MarkCredentialChanged. priorChangedAt is captured BEFORE dispose; if a new credential was staged during the dispose window, pending_refresh stays true. Returns the DB-clock timestamp written to last_agent_disposed_at.

func (*Service) MarkBuildFailed added in v0.7.1

func (s *Service) MarkBuildFailed(ctx context.Context, id, failureReason, explanation string) error

func (*Service) MarkBuildSucceeded added in v0.7.1

func (s *Service) MarkBuildSucceeded(ctx context.Context, id, imageRef, digest string) error

func (*Service) MarkCredentialChanged

func (s *Service) MarkCredentialChanged(ctx context.Context, workspaceID string) error

MarkCredentialChanged flips a workspace into "credentials staged, reload needed" state. Uses a single auto-commit UPSERT (no external transaction parameter) because the binding write (PgSecretStore, pgxpool) and this write (*sql.DB) use incompatible connection pools — cross-pool transactions are impossible.

func (*Service) MarkWorkspaceDeleted

func (s *Service) MarkWorkspaceDeleted(ctx context.Context, workspaceID string)

MarkWorkspaceDeleted soft-deletes a workspace by setting deleted_at and purges any user_secret_bindings rows pointing at it within a single transaction. The bindings table has no FK to workspaces.id (the column types differ historically) so a soft delete leaves orphan binding rows behind unless we clean up here explicitly. See Bug 11 in worklog 0085.

The two writes are wrapped in a single transaction so an API-process crash between them cannot leave a soft-deleted workspace with orphan bindings (validator finding on Bug 11 follow-up).

func (*Service) Ping

func (s *Service) Ping(ctx context.Context) error

Ping checks the database connection

func (*Service) PublishExtension added in v0.7.1

func (s *Service) PublishExtension(ctx context.Context, e imagefactory.Extension) error

func (*Service) PurgeUserSecrets

func (s *Service) PurgeUserSecrets(ctx context.Context, userID string) error

PurgeUserSecrets deletes every user-owned secret row for a user: provider_credentials (LLM provider keys) and user_secrets. It is called from the email password-reset flow to make the "your saved keys will be deleted" guarantee literal.

The DEK reinitialisation that precedes this call already makes the old ciphertext cryptographically undecryptable; deleting the rows removes them outright and guarantees no future materialization can resurrect them. Both tables' dependents (workspace_credential_bindings, user_secret_bindings) reference the parent with ON DELETE CASCADE, so no orphaned binding rows remain. Rows deleted before this call by the DEK reinit's UPSERT (user_keys) are unaffected.

Best-effort at the caller: a failure here does not undo the reset because the cryptographic erasure has already happened.

func (*Service) RecordKnownFailure added in v0.7.1

func (s *Service) RecordKnownFailure(ctx context.Context, kf imagefactory.KnownFailure) error

func (*Service) RenameConfig added in v0.8.8

func (s *Service) RenameConfig(ctx context.Context, id, newName string) error

RenameConfig updates the friendly name. Returns ErrNotFound if the config doesn't exist, or ErrConflict (via pq unique violation) if the name collides within the same scope.

func (*Service) RetireExtension added in v0.7.1

func (s *Service) RetireExtension(ctx context.Context, id string) error

func (*Service) SetConfigStatus added in v0.7.1

func (s *Service) SetConfigStatus(ctx context.Context, id string, status imagefactory.ConfigStatus) error

func (*Service) SetExtensionReviewRequested added in v0.7.1

func (s *Service) SetExtensionReviewRequested(ctx context.Context, id string, v bool) error

func (*Service) SetInstanceSetting

func (s *Service) SetInstanceSetting(ctx context.Context, key string, value json.RawMessage) error

SetInstanceSetting upserts a single instance setting.

func (*Service) SetKnownFailureRetriable added in v0.7.1

func (s *Service) SetKnownFailureRetriable(ctx context.Context, selectionHash, baseName string, retriable bool) error

func (*Service) SetPlatformConfig added in v0.7.1

func (s *Service) SetPlatformConfig(ctx context.Context, pc imagefactory.PlatformConfig) error

func (*Service) SetUserSetting

func (s *Service) SetUserSetting(ctx context.Context, userID, key string, value json.RawMessage) error

SetUserSetting upserts a single user setting.

func (*Service) SetUserStatus

func (s *Service) SetUserStatus(ctx context.Context, userID string, status types.UserStatus) error

SetUserStatus sets the authoritative operational status of a user account (D19). status='suspended' blocks the user across all contexts via the auth middleware; 'active' restores access.

F6 (US-43.19): the legacy `active` boolean is mirrored from `status` (active = (status='active')) so the two columns cannot drift apart. The auth middleware authorizes on `status`; Login historically checks `active`. Before this fix, any path that wrote `active` independently of `status` would leave the user blocked at Login but not at the middleware (or vice-versa). Keeping them in lockstep removes that divergence vector.

func (*Service) Start

func (s *Service) Start() error

Start starts the database service

func (*Service) Stop

func (s *Service) Stop() error

Stop stops the database service

func (*Service) SyncWorkspaceVersionInfo

func (s *Service) SyncWorkspaceVersionInfo(ctx context.Context, workspaceID, imageTag, agentVersion string)

SyncWorkspaceVersionInfo updates image_tag and/or agent_version in the DB. Only non-empty values are written; passing an empty string for either field leaves the existing DB value untouched. This allows the CRD watcher to sync imageTag without clobbering agentVersion, which is sourced separately from agentd health checks.

func (*Service) TransitionBuildFailed added in v0.7.1

func (s *Service) TransitionBuildFailed(ctx context.Context, buildID, configID string, kf imagefactory.KnownFailure) error

TransitionBuildFailed atomically marks a build failed, records the known failure, and flips the config to rejected. Single tx — no partial state.

func (*Service) TransitionBuildSucceeded added in v0.7.1

func (s *Service) TransitionBuildSucceeded(ctx context.Context, buildID, configID, imageRef, digest string) error

TransitionBuildSucceeded atomically marks a build succeeded and its config ready. Single tx — no partial state if one write fails.

func (*Service) UpdateAPIKeyDEK

func (s *Service) UpdateAPIKeyDEK(ctx context.Context, keyID string, wrappedDEK, kekSalt []byte, synced bool) error

func (*Service) UpdateSessionLastSeen

func (s *Service) UpdateSessionLastSeen(ctx context.Context, workspaceID, sessionID string) error

func (*Service) UpdateUser

func (s *Service) UpdateUser(ctx context.Context, userID string, updates types.UserUpdates) error

UpdateUser updates specific fields on a user record. Only non-nil fields are applied.

func (*Service) UpdateWorkspace

func (s *Service) UpdateWorkspace(ctx context.Context, workspaceID string, updates types.WorkspaceUpdates) error

UpdateWorkspace updates specific fields on a workspace record.

func (*Service) UpsertBase added in v0.7.1

func (s *Service) UpsertBase(ctx context.Context, b imagefactory.Base) error

func (*Service) UpsertSessionContextUsed

func (s *Service) UpsertSessionContextUsed(ctx context.Context, workspaceID, sessionID string, contextUsed int64) error

UpsertSessionContextUsed persists the prompt token count for the most recent LLM step in this session. Called by the API proxy on every session.next.step.ended SSE event. Idempotent: concurrent writes of the same value are safe (both replicas receive the same event and write the same data).

func (*Service) UpsertSessionMessage

func (s *Service) UpsertSessionMessage(ctx context.Context, workspaceID, sessionID string, at time.Time) error

func (*Service) UpsertSessionParent

func (s *Service) UpsertSessionParent(ctx context.Context, workspaceID, sessionID, parentID string) error

UpsertSessionParent records (or refreshes) the parent_session_id for a session. Used to mirror opencode subagent (subtask) parent links into the sidebar's session_index so the UI can render the hierarchy without round-tripping the agent.

Idempotent: passing the same parentID is a no-op. We deliberately do not guard against parentID changes — opencode never re-parents a session in practice, and an UPDATE-on-conflict path costs less than a SELECT-then- UPDATE round trip.

func (*Service) UpsertSessionTitle

func (s *Service) UpsertSessionTitle(ctx context.Context, workspaceID, sessionID, title string) error

type WorkspaceBillingRecord

type WorkspaceBillingRecord struct {
	ID          string
	UserID      string
	StorageSize string
}

Jump to

Keyboard shortcuts

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