Documentation
¶
Overview ¶
Package store is the bex control plane: the Postgres-backed source of truth for the product's business entities (tenants, apps, domains + their mappings to Ory and Stripe) and the minimal API over them. It projects `apps` rows into App CRs (app.bex.co/v1alpha1) for the operator to execute — policy/intent lives here, mechanism stays in the operator (docs/ADR003-control-plane.md).
Index ¶
- Constants
- Variables
- func CRName(tenant, app string) string
- func CanAddMember(plan string, currentMembers int) bool
- func CanTransitionDeploy(from, to string) bool
- func CancelRelease(ctx context.Context, c client.Client, a *appv1alpha1.App, generation int64, ...) error
- func CheckOwnership(ctx context.Context, pool *pgxpool.Pool) error
- func CronRunNameFromStartedKey(key string) (string, bool)
- func DeployFailureStatus(status string) bool
- func DeployStatusStampsDispatch(status string) bool
- func DeployStatusStartsExecution(status string) bool
- func DeployStatuses() []string
- func GitWebhookSigningEpoch(keyClass, secret string) string
- func ImageRepository(v string) (string, error)
- func IsOpenDeployStatus(status string) bool
- func IsTerminalDeployStatus(status string) bool
- func ManagedAppID(labels map[string]string) string
- func MapError(err error) error
- func Migrate(uri string) error
- func NormalizePlan(plan string) (string, error)
- func NormalizeResourceKind(kind string) string
- func OwnerWorkspaces(workspace string, err error) ([]string, error)
- func RedactRepoURL(v string) string
- func RenderDeployStatus(status string) string
- func ResourceDisplayNameKey(kind, id string) string
- func RoleAllowedOnPlan(plan, role string) bool
- func SandboxNamespace(workspaceID string) string
- func TailFeed[T any](ctx context.Context, cur *FeedCursor, pass FeedPass[T]) error
- func ValidGitRef(v string) bool
- func ValidGlob(v string) bool
- func ValidRepo(v string) bool
- func ValidRootDir(v string) bool
- func ValidateImage(v string) error
- func ValidatePushNotification(notification PushNotification) error
- func WorkspaceNamespace(workspaceID string) string
- type API
- type AbandonedBlueprintSync
- type AccountDeletion
- type AccountDeletionBlockedError
- type AccountWorkspaceDisposition
- type ActivePushSubscription
- type AgentDispatch
- type AgentSession
- type AgentSessionFailure
- type AgentSessionListQuery
- type AgentSessionTranscriptPart
- type AgentSessionTurn
- type App
- type AppPlacement
- type ArchivedFilter
- type AuditFilter
- type AuditRow
- type AutoDeployFilter
- type BillingAdmin
- type BillingEnforcement
- type BillingExportIssue
- type BillingExportReport
- type BillingExportRow
- type BillingExportStats
- type BillingLifecycle
- type BillingNotification
- type BillingOperations
- type BillingOverrideRequest
- type BillingProviderMapping
- type Blueprint
- type BlueprintAutoSyncIntent
- type BlueprintClaimKind
- type BlueprintResourceClaim
- type BlueprintResourceKey
- type BlueprintSync
- type CLITelemetryEvent
- type CloneSecreter
- type CommitInfo
- type CreateAppRequest
- type CreateDomainRequest
- type CreateTenantRequest
- type DatastoreEventFact
- type DatastoreEventFactType
- type Deploy
- type DeployFilter
- type DeployNotification
- type DeployNotifier
- type DesiredApp
- type DevicePushSubscription
- type Disk
- type DiskUsageRow
- type Domain
- type DomainDeclaration
- type DuePushDelivery
- type DueWebhookAttempt
- type EnsureWatermark
- type EnvGroup
- type EnvGroupCommit
- type EnvGroupContent
- type EnvGroupOp
- type EnvGroupRelease
- type Environment
- type EventFactWriter
- type FeedCursor
- type FeedPass
- type GitConnection
- type GitConnectionLimitError
- type GitHubClaimCandidate
- type GitHubClaimSelection
- type GitHubConnectTransaction
- type GitInstallationWorkspaceLimitError
- type GitWebhookReplayClaim
- type GitWebhookReplayEpoch
- type GroupingPair
- type GroupingStore
- type HourlyRow
- type Invite
- type Job
- type JobListFilter
- type MembershipGranter
- type NamespaceReconciler
- type NotificationSettings
- type NotifyRecipient
- type ObservedDatastoreState
- type ObservedServiceState
- type PGStore
- func (s *PGStore) AbandonAgentDispatch(ctx context.Context, d AgentDispatch, now time.Time, ...) (TerminalTurnFact, error)
- func (s *PGStore) AbandonBlueprintSync(ctx context.Context, runID string, now time.Time, reason string) (bool, error)
- func (s *PGStore) AbortRehydrate(ctx context.Context, id string) (AgentSession, error)
- func (s *PGStore) AcceptInviteByToken(ctx context.Context, token, subject string) (Invite, error)
- func (s *PGStore) AcceptInvitesForEmail(ctx context.Context, email, subject string) ([]Invite, error)
- func (s *PGStore) AcceptPushDelivery(ctx context.Context, delivery DuePushDelivery, ticketID string, ...) (bool, error)
- func (s *PGStore) AccountDeletionTombstoned(ctx context.Context, subject string) (bool, error)
- func (s *PGStore) AddDomain(ctx context.Context, appID, host, redirectForName string) error
- func (s *PGStore) AddDomainClaim(ctx context.Context, appID, host, redirectForName string) (Domain, bool, error)
- func (s *PGStore) AddMember(ctx context.Context, subject, tenantID, role string) error
- func (s *PGStore) AdmitBlueprintCreate(ctx context.Context, b Blueprint, run BlueprintSync) (Blueprint, BlueprintSync, bool, error)
- func (s *PGStore) AdmitBlueprintSyncRun(ctx context.Context, blueprintID, tenantID string, run BlueprintSync) (Blueprint, BlueprintSync, error)
- func (s *PGStore) AdvanceAccountDeletion(ctx context.Context, subject, from, to string) error
- func (s *PGStore) AdvanceEnvGroupOp(ctx context.Context, id, opID, phase string, leaseUntil time.Time) error
- func (s *PGStore) AgentSessionSSHActivity(ctx context.Context, resourceID string, freshSince time.Time) (hasFreshOpen bool, lastEnded *time.Time, err error)
- func (s *PGStore) AgentSessionTranscript(ctx context.Context, sessionID string, afterSeq int64, maxBytes int64, ...) ([]AgentSessionTranscriptPart, error)
- func (s *PGStore) AgentSessionTranscriptBytes(ctx context.Context, sessionID string) (int64, error)
- func (s *PGStore) AgentSessionTranscriptMaxSeq(ctx context.Context, sessionID string) (int64, bool, error)
- func (s *PGStore) AgentSessionTranscriptProgress(ctx context.Context, sessionID string, turn int, indexes []int64) (int64, []int64, error)
- func (s *PGStore) AgentSessionTranscriptTurnMaxIndex(ctx context.Context, sessionID string, turn int) (int64, bool, error)
- func (s *PGStore) AgentSessionTurns(ctx context.Context, sessionID string) ([]AgentSessionTurn, error)
- func (s *PGStore) AppendAgentSessionTranscript(ctx context.Context, sessionID string, parts []AgentSessionTranscriptPart) error
- func (s *PGStore) AssertBlueprintExecution(ctx context.Context, id, tenantID string, generation int64, runID string) error
- func (s *PGStore) BackfillAppType(ctx context.Context, id, serviceType string) (bool, error)
- func (s *PGStore) BeginAccountDeletion(ctx context.Context, subject, email string, machineSubjects []string) (AccountDeletion, error)
- func (s *PGStore) BeginAgentSessionTurn(ctx context.Context, id, prompt, deliveryMode, phase, status string) (AgentSession, error)
- func (s *PGStore) BeginRehydrate(ctx context.Context, id, prompt, deliveryMode string) (AgentSession, error)
- func (s *PGStore) BillingCustomerID(ctx context.Context, workspaceID string, livemode bool) (string, bool, error)
- func (s *PGStore) BillingExportReport(ctx context.Context, workspaceID string, start, end time.Time) (BillingExportReport, error)
- func (s *PGStore) BillingExportStats(ctx context.Context, floor, sealBefore, now time.Time, ...) (BillingExportStats, error)
- func (s *PGStore) BindClient(ctx context.Context, clientID, tenantID string) error
- func (s *PGStore) BindGitConnection(ctx context.Context, c GitConnection, maxConnections, maxWorkspaces int) (GitConnection, error)
- func (s *PGStore) BlueprintResourceOwners(ctx context.Context, tenantID string) (map[BlueprintResourceKey]string, error)
- func (s *PGStore) BlueprintWorkspace(ctx context.Context, id string) (string, error)
- func (s *PGStore) BuiltDeployIDs(ctx context.Context, appID string, limit int) (map[string]bool, error)
- func (s *PGStore) BumpOAuthRevocation(ctx context.Context, subject, clientID string) error
- func (s *PGStore) CancelWorkspaceCreationAttempt(ctx context.Context, id, subject string) error
- func (s *PGStore) CheckBillingMutationAllowed(ctx context.Context, workspaceID string) error
- func (s *PGStore) ClaimAccountDeletions(ctx context.Context, limit int) ([]AccountDeletion, error)
- func (s *PGStore) ClaimAgentSessionForHibernation(ctx context.Context, id string) (AgentSession, error)
- func (s *PGStore) ClaimBillingNotifications(ctx context.Context, now time.Time, lease time.Duration, limit int) ([]BillingNotification, error)
- func (s *PGStore) ClaimBlueprintAutoSyncIntents(ctx context.Context, limit int, lease time.Duration) ([]BlueprintAutoSyncIntent, error)
- func (s *PGStore) ClaimBlueprintResource(ctx context.Context, tenantID string, kind BlueprintClaimKind, ...) error
- func (s *PGStore) ClaimDueBillingLifecycle(ctx context.Context, now time.Time, lease time.Duration) (BillingLifecycle, bool, error)
- func (s *PGStore) ClaimDuePushDeliveries(ctx context.Context, now, leaseUntil time.Time, limit int) ([]DuePushDelivery, error)
- func (s *PGStore) ClaimDuePushReceipts(ctx context.Context, now, leaseUntil time.Time, limit int) ([]DuePushDelivery, error)
- func (s *PGStore) ClaimDueWebhookAttempts(ctx context.Context, now, leaseUntil time.Time, limit int) ([]DueWebhookAttempt, error)
- func (s *PGStore) ClaimEnvGroupContent(ctx context.Context, id string, expectedVersion int64, op EnvGroupOp) (int64, error)
- func (s *PGStore) ClaimGitWebhookDelivery(ctx context.Context, claim GitWebhookReplayClaim) (bool, error)
- func (s *PGStore) ClaimRoleReconciliations(ctx context.Context, limit int) ([]RoleReconciliation, error)
- func (s *PGStore) ClaimShellNonce(ctx context.Context, nonce string, expiresAt time.Time) (bool, error)
- func (s *PGStore) ClaimUnboundBillingCustomers(ctx context.Context, livemode bool, horizon time.Duration, limit int) ([]BillingProviderMapping, error)
- func (s *PGStore) ClaimWebhookFailureNotice(ctx context.Context, endpointID string, now, threshold time.Time) (bool, error)
- func (s *PGStore) CleanupAccountSubject(ctx context.Context, subject, marker string) error
- func (s *PGStore) ClearAgentSessionSandbox(ctx context.Context, id string) error
- func (s *PGStore) ClearPreviousSandboxTeardown(ctx context.Context, sessionID string, turn int) error
- func (s *PGStore) CloseDeploy(ctx context.Context, id, status, resolvedImage string) (bool, error)
- func (s *PGStore) CloseUsageSourceStreams(ctx context.Context, ref UsageResourceRef, through time.Time) error
- func (s *PGStore) CompactUsage(ctx context.Context, before time.Time) (UsageCompaction, error)
- func (s *PGStore) CompleteAgentSessionTurn(ctx context.Context, sessionID string, turn int, complete, truncated bool, ...) error
- func (s *PGStore) CompleteAppCreation(ctx context.Context, id string) error
- func (s *PGStore) CompleteBillingLifecycleWork(ctx context.Context, workspaceID string, expectedVersion int64, status string, ...) (BillingLifecycle, error)
- func (s *PGStore) CompleteBillingNotification(ctx context.Context, workspaceID string, version int64, at time.Time) error
- func (s *PGStore) CompleteBlueprintAutoSyncIntent(ctx context.Context, id string) error
- func (s *PGStore) CompleteBlueprintSync(ctx context.Context, id, tenantID, runID string, generation int64, ...) (Blueprint, error)
- func (s *PGStore) CompletePushDelivery(ctx context.Context, delivery DuePushDelivery, at time.Time) (bool, error)
- func (s *PGStore) CompleteRoleReconciliation(ctx context.Context, tenantID, subject, role string) error
- func (s *PGStore) CompleteWebhookAttempt(ctx context.Context, completion WebhookAttemptCompletion) (bool, error)
- func (s *PGStore) ConsumeGitHubClaimSelection(ctx context.Context, id string) (GitHubClaimSelection, error)
- func (s *PGStore) ConsumeGitHubConnectTransaction(ctx context.Context, nonce string) (GitHubConnectTransaction, error)
- func (s *PGStore) CountAppsForTenant(ctx context.Context, tenantID string) (int, error)
- func (s *PGStore) CountEnvGroups(ctx context.Context, tenantID string) (int, error)
- func (s *PGStore) CountGitConnections(ctx context.Context, workspaceID string) (int, error)
- func (s *PGStore) CountInvites(ctx context.Context, tenantID string) (int, error)
- func (s *PGStore) CountLiveAgentSessionSandboxes(ctx context.Context, workspaceID string, phases []string) (int, error)
- func (s *PGStore) CountPinnedAgentSessions(ctx context.Context, workspaceID string) (int, error)
- func (s *PGStore) CountRegistryCredentials(ctx context.Context, workspaceID string) (int, error)
- func (s *PGStore) CountTenantAdmins(ctx context.Context, tenantID string) (int, error)
- func (s *PGStore) CountTenantMembers(ctx context.Context, tenantID string) (int, error)
- func (s *PGStore) CountUnreadPushNotifications(ctx context.Context, tenantID, subject string, excludeEvents []string) (int64, error)
- func (s *PGStore) CountWorkspaceDomainClaims(ctx context.Context, workspaceID string) (int, error)
- func (s *PGStore) CountWorkspaceGroupings(ctx context.Context, tenantID string) (int, int, error)
- func (s *PGStore) CountWorkspacesForSubjectPlan(ctx context.Context, subject, plan string) (int, error)
- func (s *PGStore) CreateAgentSession(ctx context.Context, in AgentSession) (AgentSession, error)
- func (s *PGStore) CreateApp(ctx context.Context, a App) (App, error)
- func (s *PGStore) CreateDeploy(ctx context.Context, appID, trigger, image string, generation int64, ...) (Deploy, error)
- func (s *PGStore) CreateDisk(ctx context.Context, tenantID, appID, name, mountPath string, sizeGB int32) (Disk, error)
- func (s *PGStore) CreateDomain(ctx context.Context, appID, host string, primary bool) (Domain, error)
- func (s *PGStore) CreateEnvironment(ctx context.Context, projectID, tenantID, name string) (Environment, error)
- func (s *PGStore) CreateGitHubClaimSelection(ctx context.Context, sel GitHubClaimSelection) error
- func (s *PGStore) CreateGitHubConnectTransaction(ctx context.Context, t GitHubConnectTransaction) error
- func (s *PGStore) CreateInvite(ctx context.Context, tenantID, email, role, token, invitedBy string, ...) (Invite, error)
- func (s *PGStore) CreateProject(ctx context.Context, tenantID, name string) (Project, error)
- func (s *PGStore) CreateRegistryCredential(ctx context.Context, workspaceID, name, host, username, createdBy string, ...) (RegistryCredential, error)
- func (s *PGStore) CreateRollbackDeploy(ctx context.Context, appID, image, rollbackOf string, generation int64, ...) (Deploy, error)
- func (s *PGStore) CreateSSHKey(ctx context.Context, key SSHKey) (SSHKey, error)
- func (s *PGStore) CreateTenant(ctx context.Context, name, plan string) (Tenant, error)
- func (s *PGStore) CreateTenantWithMember(ctx context.Context, identityID, plan string) (Tenant, error)
- func (s *PGStore) CreateWebhookEndpoint(ctx context.Context, tenantID, name, url, secret string, eventTypes []string, ...) (WebhookEndpoint, error)
- func (s *PGStore) CreateWorkspace(ctx context.Context, name, plan, ownerSubject string) (Tenant, error)
- func (s *PGStore) CreateWorkspaceCreationAttempt(ctx context.Context, subject, name, plan, billingEmail string, ...) (WorkspaceCreationAttempt, error)
- func (s *PGStore) CurrentUsageCoverage(ctx context.Context, workspaceID string, now time.Time) (UsageCoverage, error)
- func (s *PGStore) DeferAgentDispatchCleanup(ctx context.Context, d AgentDispatch, next time.Time) error
- func (s *PGStore) DeferPreviousSandboxTeardown(ctx context.Context, sessionID string, turn int, next time.Time) error
- func (s *PGStore) DeleteAgentSession(ctx context.Context, id string) error
- func (s *PGStore) DeleteApp(ctx context.Context, id string) error
- func (s *PGStore) DeleteDisk(ctx context.Context, id string) error
- func (s *PGStore) DeleteDomain(ctx context.Context, appID, host string) error
- func (s *PGStore) DeleteEnvGroup(ctx context.Context, id string) (EnvGroup, error)
- func (s *PGStore) DeleteEnvironment(ctx context.Context, id string) error
- func (s *PGStore) DeleteGitConnection(ctx context.Context, workspaceID string, installationID int64) error
- func (s *PGStore) DeleteInvite(ctx context.Context, tenantID, id string) error
- func (s *PGStore) DeleteProject(ctx context.Context, id string) error
- func (s *PGStore) DeleteRegistryCredential(ctx context.Context, workspaceID, id string) error
- func (s *PGStore) DeleteSSHKey(ctx context.Context, subject, id string) error
- func (s *PGStore) DeleteTenant(ctx context.Context, id string) error
- func (s *PGStore) DeleteUnboundBillingProviderMapping(ctx context.Context, workspaceID, customerID string) error
- func (s *PGStore) DeleteWebhookEndpoint(ctx context.Context, tenantID, id string) error
- func (s *PGStore) DeployIDByGeneration(ctx context.Context, appID string, generation int64) (string, error)
- func (s *PGStore) DevicePushSubscriptionWorkspaces(ctx context.Context, subject, deviceID string) ([]string, error)
- func (s *PGStore) DisconnectBlueprint(ctx context.Context, id, tenantID string) error
- func (s *PGStore) DiskUsageForWindow(ctx context.Context, from, to time.Time) ([]DiskUsageRow, error)
- func (s *PGStore) DomainHostsClaimed(ctx context.Context, hosts []string) ([]string, error)
- func (s *PGStore) EndSSHSession(ctx context.Context, sessionID, result string, endedAt time.Time) error
- func (s *PGStore) EnqueueBlueprintAutoSyncIntent(ctx context.Context, intent BlueprintAutoSyncIntent) (bool, error)
- func (s *PGStore) EnqueuePushNotifications(ctx context.Context, items []PushNotificationBatchItem, at time.Time, ...) error
- func (s *PGStore) EnqueueWebhookDeliveries(ctx context.Context, deliveries []WebhookDelivery, at time.Time, key string, ...) (WebhookEnqueueResult, error)
- func (s *PGStore) EnsureBillingEnforcement(ctx context.Context, e BillingEnforcement) (BillingEnforcement, error)
- func (s *PGStore) EnsureBillingLifecycle(ctx context.Context, workspaceID string) (BillingLifecycle, error)
- func (s *PGStore) EnsurePushWatermark(ctx context.Context, at time.Time) (time.Time, string, error)
- func (s *PGStore) EnsureWebhookWatermark(ctx context.Context, at time.Time) (time.Time, string, error)
- func (s *PGStore) EnvGroupNameTaken(ctx context.Context, tenantID, name string) (bool, error)
- func (s *PGStore) ExpireHibernatedAgentSession(ctx context.Context, id, snapshotRef string) (AgentSession, error)
- func (s *PGStore) ExpireWorkspaceCreationAttempts(ctx context.Context, limit int) ([]WorkspaceCreationAttempt, error)
- func (s *PGStore) ExtendBillingGrace(ctx context.Context, workspaceID string, extension time.Duration, ...) (BillingLifecycle, error)
- func (s *PGStore) FailAccountDeletion(ctx context.Context, subject, message string) error
- func (s *PGStore) FailAdmittedSync(ctx context.Context, id, tenantID, runID string, generation int64, ...) error
- func (s *PGStore) FailBillingLifecycleWork(ctx context.Context, workspaceID string, expectedVersion int64, message string, ...) error
- func (s *PGStore) FailBillingNotification(ctx context.Context, workspaceID string, version int64, message string, ...) error
- func (s *PGStore) FailBlueprintAutoSyncIntent(ctx context.Context, id, errMsg string) error
- func (s *PGStore) FailRoleReconciliation(ctx context.Context, tenantID, subject, role, message string) error
- func (s *PGStore) FinalizeAgentSession(ctx context.Context, id, phase, headSHA, prURL string, prNumber int, ...) (AgentSession, TerminalTurnFact, error)
- func (s *PGStore) FinalizeWorkspaceCreation(ctx context.Context, id, subject string, boundAt time.Time) (Tenant, error)
- func (s *PGStore) FinishWorkspaceCreationCleanup(ctx context.Context, id string, success bool) error
- func (s *PGStore) ForceBillingRecovery(ctx context.Context, workspaceID, actor, reason string, at time.Time) (BillingLifecycle, error)
- func (s *PGStore) GetAgentSession(ctx context.Context, id string) (AgentSession, error)
- func (s *PGStore) GetApp(ctx context.Context, id string) (App, error)
- func (s *PGStore) GetAppPlacements(ctx context.Context, appIDs []string) (map[string]AppPlacement, error)
- func (s *PGStore) GetAppProtectedStatus(ctx context.Context, appID string) (string, error)
- func (s *PGStore) GetBillingLifecycle(ctx context.Context, workspaceID string) (BillingLifecycle, error)
- func (s *PGStore) GetBlueprint(ctx context.Context, id, tenantID string) (Blueprint, error)
- func (s *PGStore) GetBlueprintByRepo(ctx context.Context, tenantID, repo, branch string) (Blueprint, error)
- func (s *PGStore) GetBlueprintResourceOwner(ctx context.Context, tenantID string, kind BlueprintClaimKind, name string) (string, error)
- func (s *PGStore) GetDeploy(ctx context.Context, appID, deployID string) (Deploy, error)
- func (s *PGStore) GetDisk(ctx context.Context, id string) (Disk, error)
- func (s *PGStore) GetDomainClaim(ctx context.Context, appID, host string) (Domain, error)
- func (s *PGStore) GetEnvGroup(ctx context.Context, id string) (EnvGroup, error)
- func (s *PGStore) GetEnvGroupByName(ctx context.Context, tenantID, name string) (EnvGroup, error)
- func (s *PGStore) GetEnvironment(ctx context.Context, id string) (Environment, error)
- func (s *PGStore) GetEnvironmentProtectedStatus(ctx context.Context, environmentID string) (string, error)
- func (s *PGStore) GetGitConnection(ctx context.Context, workspaceID string) (GitConnection, error)
- func (s *PGStore) GetGitConnectionByOwner(ctx context.Context, workspaceID, accountLogin string) (GitConnection, error)
- func (s *PGStore) GetGitHubClaimSelection(ctx context.Context, id string) (GitHubClaimSelection, error)
- func (s *PGStore) GetInvite(ctx context.Context, tenantID, id string) (Invite, error)
- func (s *PGStore) GetInviteByToken(ctx context.Context, token string) (Invite, error)
- func (s *PGStore) GetJob(ctx context.Context, serviceName, tenantID, jobID string) (Job, error)
- func (s *PGStore) GetNotificationSettings(ctx context.Context, tenantID, subject string) (NotificationSettings, error)
- func (s *PGStore) GetProject(ctx context.Context, id string) (Project, error)
- func (s *PGStore) GetRegistryCredential(ctx context.Context, workspaceID, id string) (RegistryCredential, error)
- func (s *PGStore) GetRegistryCredentialByHost(ctx context.Context, workspaceID, host string) (RegistryCredential, error)
- func (s *PGStore) GetRegistryCredentialByID(ctx context.Context, id string) (RegistryCredential, error)
- func (s *PGStore) GetRegistryCredentialsByIDs(ctx context.Context, workspaceID string, ids []string) ([]RegistryCredential, error)
- func (s *PGStore) GetServiceEvent(ctx context.Context, workspaceID, eventID string) (ServiceEventLookup, error)
- func (s *PGStore) GetTenant(ctx context.Context, id string) (Tenant, error)
- func (s *PGStore) GetTenantMember(ctx context.Context, tenantID, subject string) (TenantMember, error)
- func (s *PGStore) GetWebhookEndpoint(ctx context.Context, tenantID, id string) (WebhookEndpoint, error)
- func (s *PGStore) GetWorkspaceCreationAttempt(ctx context.Context, id, subject string) (WorkspaceCreationAttempt, error)
- func (s *PGStore) GitConnectionsByInstallation(ctx context.Context, installationID int64) ([]GitConnection, error)
- func (s *PGStore) HasPendingRoleReconciliation(ctx context.Context, tenantID, subject string) (bool, error)
- func (s *PGStore) HibernateAgentSession(ctx context.Context, id, snapshotRef string, snapshotBytes int64, ...) (AgentSession, error)
- func (s *PGStore) IdempotentCLIRefresh(ctx context.Context, tokenHash [sha256.Size]byte, ttl time.Duration, ...) ([]byte, int, error)
- func (s *PGStore) InsertBlueprintSync(ctx context.Context, run BlueprintSync) (BlueprintSync, error)
- func (s *PGStore) InsertCLITelemetryEvent(ctx context.Context, ev CLITelemetryEvent) error
- func (s *PGStore) InsertEnvGroup(ctx context.Context, g EnvGroup) (EnvGroup, error)
- func (s *PGStore) InsertServiceEventFact(ctx context.Context, fact ServiceEventFact) (bool, error)
- func (s *PGStore) InsertServiceEventFacts(ctx context.Context, facts []ServiceEventFact) error
- func (s *PGStore) IsMember(ctx context.Context, subject, tenantID string) (bool, error)
- func (s *PGStore) LastDatastoreHealthyTransitionAt(ctx context.Context, datastoreID string) (time.Time, error)
- func (s *PGStore) LastHealthyTransitionAt(ctx context.Context, appID string) (time.Time, error)
- func (s *PGStore) LatestDeployCommit(ctx context.Context, appID string) (CommitInfo, error)
- func (s *PGStore) LatestUsageWindow(ctx context.Context, resourceKind, serviceID, kind string) (time.Time, error)
- func (s *PGStore) LatestUsageWindowForKind(ctx context.Context, kind string) (time.Time, bool, error)
- func (s *PGStore) ListAbandonedBlueprintSyncs(ctx context.Context, before time.Time, limit int) ([]AbandonedBlueprintSync, error)
- func (s *PGStore) ListActiveBillingEnforcements(ctx context.Context, workspaceID string) ([]BillingEnforcement, error)
- func (s *PGStore) ListActivePushSubscriptions(ctx context.Context) ([]ActivePushSubscription, error)
- func (s *PGStore) ListAgentDispatchesDue(ctx context.Context, now time.Time) ([]AgentDispatch, error)
- func (s *PGStore) ListAgentSessions(ctx context.Context, workspaceID string, q AgentSessionListQuery) ([]AgentSession, error)
- func (s *PGStore) ListAgentSessionsByPhases(ctx context.Context, phases []string) ([]AgentSession, error)
- func (s *PGStore) ListApps(ctx context.Context) ([]App, error)
- func (s *PGStore) ListAuditEvents(ctx context.Context, workspaceID string, filter AuditFilter) ([]AuditRow, error)
- func (s *PGStore) ListAutoSyncBlueprints(ctx context.Context, branch, tenantScope string) ([]Blueprint, error)
- func (s *PGStore) ListBillingExportIssues(ctx context.Context, openOnly bool, limit int) ([]BillingExportIssue, error)
- func (s *PGStore) ListBillingOwnerSubjects(ctx context.Context, workspaceID string) ([]string, error)
- func (s *PGStore) ListBillingProviderMappings(ctx context.Context, livemode bool, limit int) ([]BillingProviderMapping, error)
- func (s *PGStore) ListBlueprintResourceClaims(ctx context.Context, tenantID, blueprintID string) ([]BlueprintResourceClaim, error)
- func (s *PGStore) ListBlueprintSyncs(ctx context.Context, blueprintID, cursor string, limit int) ([]BlueprintSync, error)
- func (s *PGStore) ListBlueprints(ctx context.Context, tenantID string) ([]Blueprint, error)
- func (s *PGStore) ListDeploys(ctx context.Context, appID string, filter DeployFilter) ([]Deploy, error)
- func (s *PGStore) ListDesiredApps(ctx context.Context) ([]DesiredApp, error)
- func (s *PGStore) ListDisks(ctx context.Context, tenantID, appID string) ([]Disk, error)
- func (s *PGStore) ListDomainClaims(ctx context.Context, appID string) ([]Domain, error)
- func (s *PGStore) ListEnvGroups(ctx context.Context, tenantID string) ([]EnvGroup, error)
- func (s *PGStore) ListEnvironmentServices(ctx context.Context, environmentID, projectID string) ([]string, error)
- func (s *PGStore) ListEnvironments(ctx context.Context, projectID string) ([]Environment, error)
- func (s *PGStore) ListEnvironmentsForProjects(ctx context.Context, projectIDs []string) ([]Environment, error)
- func (s *PGStore) ListGitConnections(ctx context.Context, workspaceID string) ([]GitConnection, error)
- func (s *PGStore) ListHibernatedForRetention(ctx context.Context, now time.Time, limit int) ([]AgentSession, error)
- func (s *PGStore) ListInvites(ctx context.Context, tenantID string) ([]Invite, error)
- func (s *PGStore) ListJobs(ctx context.Context, serviceName, tenantID string, filter JobListFilter) ([]Job, error)
- func (s *PGStore) ListNotifyRecipients(ctx context.Context, tenantID string) ([]NotifyRecipient, error)
- func (s *PGStore) ListOpenDeploys(ctx context.Context) ([]Deploy, error)
- func (s *PGStore) ListOwnDevicePushSubscriptions(ctx context.Context, tenantID, subject string) ([]DevicePushSubscription, error)
- func (s *PGStore) ListOwnPushNotifications(ctx context.Context, tenantID, subject string, limit int, ...) ([]PushNotification, error)
- func (s *PGStore) ListOwnWebPushSubscriptions(ctx context.Context, tenantID, subject string) ([]WebPushSubscription, error)
- func (s *PGStore) ListPreviousSandboxTeardownsDue(ctx context.Context, now time.Time) ([]AgentDispatch, error)
- func (s *PGStore) ListProjects(ctx context.Context, tenantID string) ([]Project, error)
- func (s *PGStore) ListRegistryCredentials(ctx context.Context, workspaceID string) ([]RegistryCredential, error)
- func (s *PGStore) ListRetiredUsageResources(ctx context.Context, since time.Time) ([]RetiredUsageResource, error)
- func (s *PGStore) ListSSHKeys(ctx context.Context, subject string) ([]SSHKey, error)
- func (s *PGStore) ListSandboxTenantKeys(ctx context.Context) ([]SandboxTenantKey, error)
- func (s *PGStore) ListServiceEvents(ctx context.Context, appID, ownerWorkspace string, f ServiceEventFilter) ([]ServiceEventRow, error)
- func (s *PGStore) ListServicesForProjects(ctx context.Context, projectIDs []string) (map[string][]string, error)
- func (s *PGStore) ListTenantMembers(ctx context.Context, tenantID string) ([]TenantMember, error)
- func (s *PGStore) ListTenants(ctx context.Context) ([]Tenant, error)
- func (s *PGStore) ListTenantsForSubject(ctx context.Context, subject string) ([]Tenant, error)
- func (s *PGStore) ListTerminalAgentSessionsForPush(ctx context.Context, since time.Time) ([]AgentSession, error)
- func (s *PGStore) ListTerminalAgentSessionsWithSandbox(ctx context.Context, now time.Time) ([]AgentSession, error)
- func (s *PGStore) ListWebhookAttempts(ctx context.Context, filter WebhookAttemptFilter) ([]WebhookAttempt, error)
- func (s *PGStore) ListWebhookDispatchEndpoints(ctx context.Context, since time.Time) ([]WebhookEndpoint, error)
- func (s *PGStore) ListWebhookEndpoints(ctx context.Context, tenantIDs []string, afterAt time.Time, afterKey string, ...) ([]WebhookEndpoint, error)
- func (s *PGStore) ListWebhookEvents(ctx context.Context, afterAt time.Time, afterKey string, until time.Time, ...) ([]WebhookEventRow, error)
- func (s *PGStore) ListWorkspaceEnvironmentServices(ctx context.Context, tenantID string) (map[string][]string, error)
- func (s *PGStore) ListWorkspaceEnvironments(ctx context.Context, tenantID string) ([]Environment, error)
- func (s *PGStore) MaintainGitWebhookReplayEpochs(ctx context.Context, epochs []GitWebhookReplayEpoch, now time.Time) (int64, error)
- func (s *PGStore) MarkBillingEnforcementRecovered(ctx context.Context, workspaceID, kind, name string, at time.Time) error
- func (s *PGStore) MarkCheckoutStarted(ctx context.Context, workspaceID string, at time.Time) error
- func (s *PGStore) MarkMachineMemberships(ctx context.Context, clientIDs []string) (int64, error)
- func (s *PGStore) MarkOwnPushNotificationRead(ctx context.Context, tenantID, subject, eventID string, at time.Time) (bool, error)
- func (s *PGStore) MarkUsageAttempted(ctx context.Context, attempts []UsageExportAttempt, at time.Time) error
- func (s *PGStore) MarkUsageEmitted(ctx context.Context, rows []HourlyRow, at time.Time) error
- func (s *PGStore) MarkWorkspaceCreationSetupSucceeded(ctx context.Context, id, subject, paymentMethodID string) (WorkspaceCreationAttempt, error)
- func (s *PGStore) OAuthRevokedAt(ctx context.Context, subject, clientID string) (time.Time, bool, error)
- func (s *PGStore) ObserveSandboxMeter(ctx context.Context, obs SandboxMeterObservation) error
- func (s *PGStore) OwnerIDForSubject(ctx context.Context, subject string) (string, error)
- func (s *PGStore) OwnerIDsForSubjects(ctx context.Context, subjects []string) (map[string]string, error)
- func (s *PGStore) PaymentEligibility(ctx context.Context, workspaceID string) (PaymentEligibility, error)
- func (s *PGStore) PaymentMethodBound(ctx context.Context, workspaceID string) (bool, error)
- func (s *PGStore) Ping(ctx context.Context) error
- func (s *PGStore) PreviewAccountDeletion(ctx context.Context, subject string, machineSubjects []string) ([]AccountWorkspaceDisposition, error)
- func (s *PGStore) PromoteDomainClaim(ctx context.Context, appID, id, expectedChallenge string, at time.Time) (Domain, error)
- func (s *PGStore) PruneRetiredUsageResources(ctx context.Context, before time.Time) (int64, error)
- func (s *PGStore) PruneWebhookEligibility(ctx context.Context, before time.Time) (int64, error)
- func (s *PGStore) PurgeAuditEvents(ctx context.Context, before time.Time) (int64, error)
- func (s *PGStore) PurgeCLITelemetryEvents(ctx context.Context, before time.Time) (int64, error)
- func (s *PGStore) PurgeProductAnalytics(ctx context.Context, before time.Time) (int64, error)
- func (s *PGStore) PurgeSSHSessions(ctx context.Context, before time.Time) (int64, error)
- func (s *PGStore) PurgeStripeBillingEvents(ctx context.Context, before time.Time) (int64, error)
- func (s *PGStore) PushDeliveryStats(ctx context.Context) (PushQueueStats, error)
- func (s *PGStore) PushNotificationWorkspaces(ctx context.Context, subject, eventID string) ([]string, error)
- func (s *PGStore) QuarantineOldUsageAttempts(ctx context.Context, before, at time.Time) (int64, error)
- func (s *PGStore) QueueWebhookResend(ctx context.Context, request WebhookResendRequest) (WebhookAttempt, error)
- func (s *PGStore) ReclaimEmptyBlueprintGroupings(ctx context.Context, tenantID string, pairs []GroupingPair, ...) (removedEnvironments, removedProjects []string, err error)
- func (s *PGStore) ReconcileUsageSourceStreams(ctx context.Context, active []UsageResourceRef, through time.Time) error
- func (s *PGStore) Record(ctx context.Context, ev core.AuditEvent) error
- func (s *PGStore) RecordAgentSessionDispatch(ctx context.Context, id, sandboxID, phase, status, deliveryMode string, ...) (AgentSession, error)
- func (s *PGStore) RecordBillingNotificationMailed(ctx context.Context, workspaceID string, version int64, subject string) error
- func (s *PGStore) RecordDomainVerificationAttempt(ctx context.Context, appID, id string, at time.Time) error
- func (s *PGStore) RecordObservedDatastoreState(ctx context.Context, obs ObservedDatastoreState) ([]DatastoreEventFact, error)
- func (s *PGStore) RecordObservedServiceState(ctx context.Context, obs ObservedServiceState) ([]ServiceEventFact, error)
- func (s *PGStore) RecordProductActivity(ctx context.Context, e core.ProductActivity) error
- func (s *PGStore) RecordProductDomainTLS(ctx context.Context, domain string, ready bool, at time.Time) error
- func (s *PGStore) RecordProductHosting(ctx context.Context, workspace, resource, kind string, live bool, at time.Time) (bool, error)
- func (s *PGStore) RecordPushReceipt(ctx context.Context, d DuePushDelivery, code string, at, next time.Time, ...) (bool, error)
- func (s *PGStore) RecordPushSendFailure(ctx context.Context, delivery DuePushDelivery, code string, at, next time.Time, ...) (bool, error)
- func (s *PGStore) RecordResourceDisplayNames(ctx context.Context, tenantID string, records []ResourceDisplayName) error
- func (s *PGStore) RecordRetiredUsageResource(ctx context.Context, r RetiredUsageResource) error
- func (s *PGStore) RecordStripeBillingEvent(ctx context.Context, e StripeBillingEvent, grace time.Duration) (BillingLifecycle, bool, bool, error)
- func (s *PGStore) RecordUsageExportResult(ctx context.Context, accepted []UsageExportAttempt, ...) error
- func (s *PGStore) RecordUsageSourceHealth(ctx context.Context, records []UsageSourceRecord) error
- func (s *PGStore) RefreshInvite(ctx context.Context, tenantID, id, token string, expiresAt time.Time) (Invite, error)
- func (s *PGStore) RehydrateAgentSession(ctx context.Context, id, sandboxID, phase, status, deliveryMode string) (AgentSession, error)
- func (s *PGStore) ReleaseBlueprintResourceClaim(ctx context.Context, tenantID string, kind BlueprintClaimKind, ...) error
- func (s *PGStore) ReleaseBlueprintResourceClaims(ctx context.Context, tenantID, blueprintID string) error
- func (s *PGStore) ReleaseEnvGroupContent(ctx context.Context, id string, expectedVersion int64, rel EnvGroupRelease) error
- func (s *PGStore) ReleaseGitWebhookDelivery(ctx context.Context, claim GitWebhookReplayClaim) error
- func (s *PGStore) ReleasePushDelivery(ctx context.Context, delivery DuePushDelivery) (bool, error)
- func (s *PGStore) RemoveAccountMember(ctx context.Context, tenantID, subject string) error
- func (s *PGStore) RemoveDomain(ctx context.Context, appID, host string) error
- func (s *PGStore) RemoveMember(ctx context.Context, tenantID, subject string) error
- func (s *PGStore) RenameEnvironment(ctx context.Context, id, name string) error
- func (s *PGStore) RenameProject(ctx context.Context, id, name string) error
- func (s *PGStore) RenameTenant(ctx context.Context, id, name string) (Tenant, error)
- func (s *PGStore) ReplaceDomainClaims(ctx context.Context, appID string, declarations []DomainDeclaration) ([]Domain, error)
- func (s *PGStore) ReplaceDomains(ctx context.Context, appID, primary string, hosts []string) error
- func (s *PGStore) ResolveBillingExportIssue(ctx context.Context, transactionID, action, actor, reason string, at time.Time) (BillingExportIssue, error)
- func (s *PGStore) ResourceDisplayNames(ctx context.Context, tenantID string, refs []ResourceDisplayName) (map[string]string, error)
- func (s *PGStore) RevokeAllDevicePushSubscriptions(ctx context.Context, tenantID, subject string) (int64, error)
- func (s *PGStore) RevokeAllWebPushSubscriptions(ctx context.Context, tenantID, subject string) (int64, error)
- func (s *PGStore) RevokeDevicePushSubscription(ctx context.Context, tenantID, subject, deviceID string) (bool, error)
- func (s *PGStore) RevokeExactPushSubscription(ctx context.Context, d DuePushDelivery) (bool, error)
- func (s *PGStore) RevokeWebPushSubscription(ctx context.Context, tenantID, subject, browserID string) (bool, error)
- func (s *PGStore) RollbackAppCreation(ctx context.Context, appID string) error
- func (s *PGStore) RunGitWebhookReplayMaintenance(ctx context.Context, epochs []GitWebhookReplayEpoch)
- func (s *PGStore) RunGroupingTx(ctx context.Context, fn func(GroupingStore) error) error
- func (s *PGStore) SSHKeyByFingerprint(ctx context.Context, fingerprint string) (SSHKey, error)
- func (s *PGStore) SandboxKeyForWorkspace(ctx context.Context, workspaceID string) (string, error)
- func (s *PGStore) SandboxKeyLookup(ctx context.Context, workspaceID string) (string, bool, error)
- func (s *PGStore) SandboxUsageMetadata(ctx context.Context, tenantID string, sandboxIDs []string) (map[string]SandboxUsageMetadata, error)
- func (s *PGStore) ScheduleAgentSessionSandboxReap(ctx context.Context, id string, now time.Time) error
- func (s *PGStore) SelectUnemittedUsage(ctx context.Context, floor, sealBefore time.Time, limit int, ...) ([]HourlyRow, error)
- func (s *PGStore) ServiceEventWorkspaces(ctx context.Context, eventID string) ([]string, error)
- func (s *PGStore) ServiceNameTaken(ctx context.Context, tenantID, selfID, name string) (bool, error)
- func (s *PGStore) SetAgentSessionArchived(ctx context.Context, id string, archived bool) (AgentSession, error)
- func (s *PGStore) SetAgentSessionCanceled(ctx context.Context, id string) (AgentSession, TerminalTurnFact, error)
- func (s *PGStore) SetAgentSessionFailure(ctx context.Context, id, sandboxID, reason string) (AgentSession, error)
- func (s *PGStore) SetAgentSessionLifecycle(ctx context.Context, id, sandboxID, phase, status string, canceled bool) (AgentSession, error)
- func (s *PGStore) SetAgentSessionPinned(ctx context.Context, id string, pinned bool, retainUntil *time.Time) (AgentSession, error)
- func (s *PGStore) SetAppDisplayName(ctx context.Context, id string, displayName string) error
- func (s *PGStore) SetAppEnvironment(ctx context.Context, id, projectID, environmentID string) error
- func (s *PGStore) SetAppIdleTTL(ctx context.Context, id string, seconds int32) error
- func (s *PGStore) SetAppImage(ctx context.Context, id string, image string) error
- func (s *PGStore) SetAppPort(ctx context.Context, id string, port int32, mode string) error
- func (s *PGStore) SetAppReplicas(ctx context.Context, id string, replicas int32) error
- func (s *PGStore) SetAppSource(ctx context.Context, id, repo, image, branch string, ...) error
- func (s *PGStore) SetAppSuspended(ctx context.Context, id string, suspended bool) error
- func (s *PGStore) SetAppTier(ctx context.Context, id string, tier string) error
- func (s *PGStore) SetBillingException(ctx context.Context, workspaceID, exception string, enabled bool, ...) (bool, BillingLifecycle, error)
- func (s *PGStore) SetBlueprintSyncNote(ctx context.Context, runID, note string) error
- func (s *PGStore) SetDeployPreDeployStatus(ctx context.Context, id, status string) (bool, error)
- func (s *PGStore) SetDeployStallReason(ctx context.Context, id, reason string) error
- func (s *PGStore) SetEnvironmentACL(ctx context.Context, id, protectedStatus string, networkIsolationEnabled bool, ...) error
- func (s *PGStore) SetEnvironmentServices(ctx context.Context, environmentID, projectID, tenantID string, ...) ([]core.ServicePlacementChange, error)
- func (s *PGStore) SetPaymentMethodBound(ctx context.Context, workspaceID string, at time.Time) error
- func (s *PGStore) SetProjectServices(ctx context.Context, projectID, tenantID string, serviceIDs []string) ([]core.ServicePlacementChange, error)
- func (s *PGStore) SetTenantBillingExcluded(ctx context.Context, tenantID string, excluded bool, actor string, ...) (bool, error)
- func (s *PGStore) SetWebhookEndpointEnabled(ctx context.Context, tenantID, id string, enabled bool, reason string) (WebhookEndpoint, error)
- func (s *PGStore) SetWorkspaceCreationSetup(ctx context.Context, id, subject, customerID, setupIntentID string, ...) (WorkspaceCreationAttempt, error)
- func (s *PGStore) SetWorkspaceCreationSubscription(ctx context.Context, id, subject, subscriptionID string) error
- func (s *PGStore) SettleCanceledPreDeploy(ctx context.Context, appID string, generation int64, status string, ...) (bool, error)
- func (s *PGStore) StageBlueprintManifest(ctx context.Context, id, tenantID string, generation int64, ...) (Blueprint, error)
- func (s *PGStore) StartSSHSession(ctx context.Context, session SSHSessionAudit) error
- func (s *PGStore) SubjectIsWorkspaceAdmin(ctx context.Context, tenantID, subject string) (bool, error)
- func (s *PGStore) SweepPushRetention(ctx context.Context, revokedBefore, terminalBefore time.Time) (PushSweepResult, error)
- func (s *PGStore) SweepWebhookDeliveries(ctx context.Context, before time.Time, keepPerEndpoint, limit int) (int64, error)
- func (s *PGStore) TenantContactSubjects(ctx context.Context, tenantIDs []string) (map[string]string, error)
- func (s *PGStore) TenantForIdentity(ctx context.Context, subject string) (Tenant, error)
- func (s *PGStore) TenantForOwner(ctx context.Context, identityID string) (Tenant, error)
- func (s *PGStore) TenantOwnerSubject(ctx context.Context, tenantID string) (string, error)
- func (s *PGStore) TerminateMissingSandboxMeters(ctx context.Context, workspaceID string, seen []string, observedAt time.Time) error
- func (s *PGStore) TouchBillingProviderMapping(ctx context.Context, workspaceID string, at time.Time) error
- func (s *PGStore) TouchProject(ctx context.Context, id string) error
- func (s *PGStore) TouchRegistryCredential(ctx context.Context, workspaceID, id string) error
- func (s *PGStore) TransitionDeploy(ctx context.Context, ...) (bool, error)
- func (s *PGStore) UnbindClient(ctx context.Context, clientID string) error
- func (s *PGStore) UpdateBlueprint(ctx context.Context, id, tenantID string, name *string, autoSync *bool, ...) (Blueprint, error)
- func (s *PGStore) UpdateBlueprintSync(ctx context.Context, id, state string, completedAt *time.Time, errMsg *string) (BlueprintSync, error)
- func (s *PGStore) UpdateDisk(ctx context.Context, id string, name, mountPath *string, sizeGB *int32) (Disk, error)
- func (s *PGStore) UpdateEnvGroupMeta(ctx context.Context, g EnvGroup) (EnvGroup, error)
- func (s *PGStore) UpdateJobStatus(ctx context.Context, jobID, status string) (Job, error)
- func (s *PGStore) UpdateMemberRole(ctx context.Context, tenantID, subject, role string) error
- func (s *PGStore) UpdateRegistryCredential(ctx context.Context, workspaceID, id, name, username string, ...) (RegistryCredential, error)
- func (s *PGStore) UpdateTenantPlan(ctx context.Context, id, plan string) (Tenant, error)
- func (s *PGStore) UpdateWebhookEndpoint(ctx context.Context, tenantID, id, name, url string, eventTypes []string, ...) (WebhookEndpoint, error)
- func (s *PGStore) UpsertBillingProviderMapping(ctx context.Context, m BillingProviderMapping) error
- func (s *PGStore) UpsertBlueprint(ctx context.Context, b Blueprint) (Blueprint, error)
- func (s *PGStore) UpsertDevicePushSubscription(ctx context.Context, sub DevicePushSubscription) (DevicePushSubscription, error)
- func (s *PGStore) UpsertNotificationPushPolicy(ctx context.Context, tenantID, subject string, policy json.RawMessage) (NotificationSettings, error)
- func (s *PGStore) UpsertNotificationSettings(ctx context.Context, tenantID, subject string, ...) (NotificationSettings, error)
- func (s *PGStore) UpsertUsageHourly(ctx context.Context, row HourlyRow) error
- func (s *PGStore) UpsertWebPushSubscription(ctx context.Context, sub WebPushSubscription) (WebPushSubscription, error)
- func (s *PGStore) UsageMonthToDate(ctx context.Context, workspaceID string, now time.Time) ([]UsageSummaryRow, error)
- func (s *PGStore) WebhookEndpointWorkspace(ctx context.Context, id string) (string, error)
- func (s *PGStore) WithAppAdvisoryLock(ctx context.Context, appID string, fn func() error) error
- func (s *PGStore) WithEnvGroupContentLock(ctx context.Context, id string, fn func(context.Context) error) (err error)
- func (s *PGStore) WithTenantAdvisoryLock(ctx context.Context, tenantID string, fn func() error) error
- func (s *PGStore) WorkspaceForSandboxKey(ctx context.Context, apiKey string) (string, error)
- type PaymentEligibility
- type PlanLimits
- type ProductInventoryCollector
- type Project
- type PushNotification
- type PushNotificationBatchItem
- type PushQueueStats
- type PushSweepResult
- type QuotaCaps
- type Reconciler
- type ReconcilerMetrics
- type RegistryCredential
- type ResolveBillingExportIssueRequest
- type ResourceDisplayName
- type RetiredUsageResource
- type RoleReconciliation
- type SSHKey
- type SSHSessionAudit
- type SandboxMeterObservation
- type SandboxTenantKey
- type SandboxTenantResolver
- type SandboxUsageMetadata
- type ServiceEventFact
- type ServiceEventFactType
- type ServiceEventFilter
- type ServiceEventLookup
- type ServiceEventRow
- type SetBillingExcludedRequest
- type Store
- type StripeBillingEvent
- type Tenant
- type TenantMember
- type TerminalTurnFact
- type UsageCompaction
- type UsageCoverage
- type UsageExportAttempt
- type UsageExportReject
- type UsageResourceRef
- type UsageRetirementRecorder
- type UsageSourceObservation
- type UsageSourceRecord
- type UsageSummaryRow
- type WebPushSubscription
- type WebhookAttempt
- type WebhookAttemptCompletion
- type WebhookAttemptFilter
- type WebhookDelivery
- type WebhookEligibility
- type WebhookEndpoint
- type WebhookEnqueueResult
- type WebhookEventRow
- type WebhookResendRequest
- type WorkspaceCreationAttempt
Constants ¶
const ( AccountDeletionPending = "pending" AccountDeletionCleaning = "cleaning" AccountDeletionIdentity = "identity" AccountDeletionDone = "done" AccountWorkspaceDelete = "delete" AccountWorkspaceLeave = "leave" AccountWorkspaceBlocked = "blocked" )
const ( DefaultAuditPageSize = 20 MaxAuditPageSize = 1000 )
DefaultAuditPageSize/MaxAuditPageSize bound ListAuditEvents' page — Render's audit-specific limit param (default 20, maximum 1000; captured from the public OpenAPI's auditLogLimitParam, w4/m26 — note the cap is audit-specific, NOT the general core.MaxPageLimit of 100).
const ( BillingHealthy = "healthy" BillingGrace = "grace" BillingEnforcing = "enforcing" BillingEnforced = "enforced" BillingRecovering = "recovering" BillingExcluded = "excluded" BillingComped = "comped" BillingOutcomeFailure = "failure" BillingOutcomeSuccess = "success" )
const ( BlueprintAutoSyncIntentPending = "pending" BlueprintAutoSyncIntentClaimed = "claimed" BlueprintAutoSyncIntentCompleted = "completed" BlueprintAutoSyncIntentFailed = "failed" )
Intent state constants for blueprint_auto_sync_intents.
const ( BlueprintSyncStateCreated = "created" BlueprintSyncStateRunning = "running" BlueprintSyncStateSuccess = "success" BlueprintSyncStateError = "error" )
Blueprint sync state constants (Render's vocabulary).
const ( BlueprintStatusCreated = "created" BlueprintStatusPaused = "paused" BlueprintStatusInSync = "in_sync" BlueprintStatusSyncing = "syncing" BlueprintStatusError = "error" )
Blueprint status constants (Render's vocabulary). The legacy 'active' value stored in pre-m62 rows is mapped to 'in_sync' at the service layer.
const ( DatastoreKindPostgres = "postgres" DatastoreKindKeyValue = "keyvalue" )
DatastoreKind separates the two managed datastore products. It is the CHECK constraint's vocabulary, and it decides which availability names an edge takes — Render says a Postgres is "unavailable" and a Key Value is "unhealthy", and bex matches that rather than normalizing it away.
const ( // A member normally has one phone and perhaps a tablet/test install. Ten // leaves ample headroom while bounding the persistent dimension one viewer // can project into every push event. MaxActivePushDevicesPerSubject = 10 // The workspace cap is a second, race-safe ceiling across many subjects. It // protects the global worker even if a workspace legitimately has a large // membership or several compromised members. MaxActivePushDevicesPerWorkspace = 1000 )
const ( EnvGroupIdle = "idle" EnvGroupBusy = "busy" EnvGroupRepairRequired = "repair_required" )
Content states of an environment group (env_groups.content_state).
const ( EventStatusSucceeded = "succeeded" EventStatusFailed = "failed" EventStatusCanceled = "canceled" )
Closed lifecycle-step outcomes for a *_ended fact's Status column — the same structural discipline as reason_code: a step outcome is never an arbitrary string. Mirror service_event_facts' status CHECK (migration 0057).
const ( EventReasonImagePullBackoff = "image_pull_backoff" EventReasonReadinessFailed = "readiness_failed" EventReasonRootDirectory = "root_directory" EventReasonBuildFilter = "build_filter" EventReasonSkipPhrase = "skip_phrase" // EventReasonSuperseded is a deploy canceled because a newer release // replaced it (w4/089) — distinct from a user-initiated cancel, which // carries no reason code. EventReasonSuperseded = "superseded" // Why a cron run failed (w4/m114), one per field of the reason object on // Render's "Cron Job Run Ended" event. EventReasonNonZeroExit = "non_zero_exit" EventReasonOOMKilled = "oom_killed" EventReasonEvicted = "evicted" EventReasonTimedOut = "timed_out" )
const ( EventSourceDeploy = "deploy" EventSourceAudit = "audit" EventSourceFact = "fact" )
Event source discriminators — which table an event row was projected from.
const ( EventPhaseStarted = "started" EventPhaseEnded = "ended" )
Deploy-event phases: one deploys row yields a started event at created_at and, once terminal, an ended event at finished_at.
const ( GitWebhookReplayKeyManual = "manual" GitWebhookReplayKeyGitHub = "github" // MaxGitWebhookReplayClaimsPerScope bounds the durable rows one tenant can // allocate across all signing epochs that live API replicas still accept. // At roughly one row per push, 100k leaves years of ordinary headroom while // imposing a finite per-tenant storage cost. Reaching it fails closed until // a retired signing epoch is reclaimed or an operator intervenes. MaxGitWebhookReplayClaimsPerScope = 100_000 // GitWebhookReplayMaintenanceInterval is each replica's epoch heartbeat and // retired-epoch sweep cadence. GitWebhookReplayMaintenanceInterval = time.Minute // GitWebhookReplayLeaseTTL is the failure-detection window before a crashed // or disconnected replica's signing epoch can be retired. GitWebhookReplayLeaseTTL = 5 * time.Minute )
const ( JobPending = "pending" JobRunning = "running" JobSucceeded = "succeeded" JobFailed = "failed" JobCanceled = "canceled" )
Job status constants — the Render job status enum.
const ( // RegimeLabel distinguishes a workspace's hosting namespace from its sandbox // namespace (ADR043 D2 — both untrusted, opposite default network postures). // RegimeLabel also rides OpenSandbox create metadata, selecting the resulting // BatchSandbox and Pod for sandbox-only network policy. RegimeLabel = "app.bex.co/regime" RegimeHosting = "hosting" RegimeSandbox = "sandbox" // AppsQuotaCountKey is the per-namespace ResourceQuota count key that // enforces the per-workspace service cap (ADR043 D3), replacing the // retired BEX_MAX_SERVICES app-code check. Exported so the create path // (internal/apps) can map a quota-exceeded admission error back onto the // Render-shaped cap message (core.QuotaCapError). AppsQuotaCountKey = "count/apps.app.bex.co" // DatabasesQuotaCountKey / KeyValuesQuotaCountKey are the datastore siblings. // Live only since ADR043 D8 put the CRs in the namespace being charged // (w7/m77); before that these dimensions existed but counted nothing. The // postgres/keyvalue services map a rejection on these keys back to the same // Render-shaped message the service cap uses. DatabasesQuotaCountKey = "count/databases.app.bex.co" KeyValuesQuotaCountKey = "count/keyvalues.app.bex.co" )
Per-tenant namespace isolation (ADR043). A workspace maps unconditionally to a hosting namespace `<ws>` and a `<ws>-sandbox` namespace (pillar 5, m32). The NamespaceReconciler is the lifecycle mechanism: it is level-triggered exactly like the App Reconciler — a full resync plus a Kick after workspace writes — so the set of tenant namespaces is a rebuildable projection of the `tenants` table, never imperative drift. Every namespace gets its identity labels, a plan-scaled ResourceQuota/LimitRange, and a default-deny NetworkPolicy; App workloads land in `<ws>`, per-`<ws>` RBAC binds there, and same-workspace/platform NetworkPolicy allows apply on top. The transitional BEX_TENANT_NAMESPACES/BEX_TENANT_SANDBOX_NAMESPACES rollout gates that used to make this opt-in were retired in w3/m34 once the fleet was fully migrated.
const ( PlanHobby = "hobby" PlanPro = "pro" PlanScale = "scale" PlanEnterprise = "enterprise" )
Workspace plans mirror Render's post-2026-04-23 capability lineup (verified in .pm/w6/RESEARCH-workspaces.md): Hobby is capped, while higher plans lift member/service caps and add roles. Monthly workspace fees live in lego/backend/internal/pricing (ADR030): Render's flat subscriptions × 0.70 (Hobby $0, Pro $17.50, Scale $349.30; Enterprise custom). tenants.plan is still the capability vehicle; resource-tier usage is billed separately (ADR040, ADR046).
const ( LabelManagedBy = "app.kubernetes.io/managed-by" ManagedByValue = "bex-controlplane" // ControlPlaneLabel refines LabelManagedBy from "a bex control plane owns // this" to "WHICH one owns it" (w6/m39). Both projectors here prune by // ABSENCE from their own database using a CLUSTER-scoped List, so without an // instance identity every bex-api holding a BEX_CP_DB_URI deletes every // managed object it cannot account for — including another control plane's. // That is not hypothetical: two `dev-N` harnesses sharing the CAPD mock // cluster deleted each other's tenant namespaces within one resync, observed // live in both directions (.pm/w3/017.md). Read it through ownedBy, never // directly, so the deliberate unlabeled-legacy asymmetry stays in one place. ControlPlaneLabel = "app.bex.co/control-plane" // DefaultControlPlaneIdentity is the identity an unconfigured control plane // runs under, so production behaves exactly as it did before w6/m39 and only // a deliberately-configured harness narrows its own prune scope. DefaultControlPlaneIdentity = "production" LabelAppID = core.LabelAppID // LabelTenant aliases core.LabelTenant — one label, one constant, so the // stamp (here) and the gate (core.Base.GetApp) can never drift apart. LabelTenant = core.LabelTenant // LabelWorkspace aliases core.LabelWorkspace so the stamp (here) and the // operator's propagation to pod templates share one canonical value. LabelWorkspace = core.LabelWorkspace )
Labels stamped on every projected App CR. LabelAppID ties the CR back to its apps row (the reconciler's join key); LabelTenant carries the owning tenant's id (tea-<id>) — core.Base.GetApp's single source of truth for gating a fetched App against the caller's tenant, so every feature that fetches by name (apps/logs/metrics/secrets) inherits the same check; the managed-by label scopes list/delete so hand-applied Apps are never touched.
ManagedByValue is PERSISTED DATA — it lives on every projected App CR in live clusters. The service was renamed bex-backend, but changing this value would blind the projection to existing CRs (List filters on it) and make it re-Create them into name conflicts. Flip it only with a relabel migration. LabelTenant is re-stamped on every resync (stampLabels), so a value change (it once carried the tenant name) migrates existing CRs without a relabel.
const ( DeployCreated = "created" DeployQueued = "queued" DeployBuildInProgress = "build_in_progress" DeployBuildFailed = "build_failed" DeployPreDeployInProgress = "pre_deploy_in_progress" DeployPreDeployFailed = "pre_deploy_failed" DeployUpdateInProgress = "update_in_progress" DeployLive = "live" DeployUpdateFailed = "update_failed" DeployDeactivated = "deactivated" DeployCanceled = "canceled" )
Deploy status vocabulary — Render's complete eleven-state enum. The backend persists only states supported by current App/Job evidence; it never invents an unobserved intermediate phase merely to make the sequence look complete.
const ( PreDeployRunning = "running" PreDeploySucceeded = "succeeded" PreDeployFailed = "failed" PreDeployCanceled = "canceled" )
Pre-deploy step status vocabulary — the deploy row's pre_deploy_status column (w1/m33). The lowercase projection of the App CR's status.preDeploy.Status, distinct from the overall deploy status: a deploy can be update_failed with pre_deploy_status 'failed' (its migration failed) or ” (its health check failed). Empty means no pre-deploy step ran. Canceled is the close's own verdict on a step still running when its deploy ended without going live (superseded or canceled, w4/187): no later observation can settle it. A pre_deploy_failed close settles such a step failed instead (w5/m114), and migration 0142's CHECK keeps any closed row from reading running.
const ( TriggerCreate = "create" // the app's first deploy, opened by CreateApp TriggerAPI = "api" // an authenticated POST .../deploys TriggerDeployHook = "deploy_hook" // the App's unauthenticated secret-URL trigger TriggerRollback = "rollback" // a deploy created by Rollback (w2/m10), restoring an earlier image TriggerNewCommit = "new_commit" // a git-push redeploy via the HMAC webhook (Render's spelling) // TriggerConfigChange is a rollout a configuration write forced rather than // an explicit deploy verb: a Settings-page field, an env var or secret file, // an env-group link/unlink. bex cannot apply any of these without rolling a // new release, so each is a real deploy the user must be able to see, retry, // and roll back — spelled distinctly so the history still tells a settings // save apart from a manual deploy (w6/m51). TriggerConfigChange = "config_change" )
The deploy `trigger` vocabulary — what caused a rollout. One place, since both the writers (CreateApp, deploys.Trigger/Rollback) and the readers (the events feed's deploy_started trigger flags, internal/events) spell it.
const ( UsageKindInstanceSeconds = "instance_seconds" UsageKindEgressBytes = "egress_bytes" UsageKindBuildSeconds = "build_seconds" UsageKindStorageGBSeconds = "storage_gb_seconds" // UsageKindSandboxComputeSeconds is measured in milli-vCPU-equivalent // seconds. Memory is folded into the weight at the AgentCore reference // ratio ($0.00945/GB-hour ÷ $0.0895/vCPU-hour); see ADR047 D6. UsageKindSandboxComputeSeconds = "sandbox_compute_seconds" // UsageKindDiskGBSeconds is a service disk's PROVISIONED capacity in GB // multiplied by the seconds it was provisioned for — deliberately a // different dimension from UsageKindStorageGBSeconds, which averages USED // bytes on a datastore volume. Disks bill on what was reserved, running or // not, because that is what Render charges for and what Hetzner charges // bex for; see docs/ADR082-persistent-disks.md D8/D9. UsageKindDiskGBSeconds = "disk_gb_seconds" )
UsageKind names the metering dimensions. storage_gb_seconds is the average used datastore volume size in decimal GB multiplied by window seconds.
const ( UsageSourceInstance = "instance" UsageSourceBuild = "build" UsageSourceStorage = "storage" UsageSourceHTTP = "http" UsageSourceWebSocket = "websocket" UsageSourceDirect = "direct" UsageSourcePostgres = "postgres" UsageSourceKeyValue = "key_value" // UsageSourceSandbox is response-only for now: sandbox compute uses its // own durable lifecycle cursor rather than usage.Service's hourly source // observations, so any current-month sandbox total conservatively degrades // otherwise-known workspace coverage. UsageSourceSandbox = "sandbox" // UsageSourceDisk covers the provisioned-disk meter. Unlike every source // above it, it reads the control-plane's own disk lifecycle rows rather // than the cluster, so it is the one source that stays healthy while // Prometheus or the app cluster is unreachable. UsageSourceDisk = "disk" UsageSourceHealthy = "healthy" UsageSourceDegraded = "degraded" )
Usage source-health vocabulary. Keep this closed and presentation-safe: the values are returned to clients when a current-month total is partial, so they must never contain provider errors, metric selectors, or tenant data.
const ( ResourceKindService = "service" // App (web/worker/cron/static) ResourceKindPostgres = "postgres" // Database CR → CNPG Cluster ResourceKindKeyValue = "key_value" // KeyValue CR → Valkey StatefulSet ResourceKindSandbox = "sandbox" // hosted OpenSandbox execution environment )
ResourceKind identifies what type of resource a usage row belongs to. "service" is the default (App CRs); "postgres" and "key_value" are the managed-datastore kinds added in w8/m5.
const ( WebhookAttemptPending = "pending" WebhookAttemptDelivered = "delivered" WebhookAttemptFailed = "failed" WebhookAttemptAutomatic = "automatic" WebhookAttemptManual = "manual" )
const ( MaxActiveWebPushBrowsersPerSubject = 10 MaxActiveWebPushBrowsersPerWorkspace = 1000 )
const ( WorkspaceCreationPrepared = "prepared" WorkspaceCreationSetupPending = "setup_pending" WorkspaceCreationSetupSucceeded = "setup_succeeded" WorkspaceCreationFinalized = "finalized" WorkspaceCreationCleanupPending = "cleanup_pending" WorkspaceCreationExpired = "expired" )
const ( MemberKindUser = "user" MemberKindMachine = "machine" )
The two kinds of tenant_members row. A machine row is what makes a bound API key resolve to its workspace and authorize there — it is not a member of the workspace in any sense the Team surface or the seat cap should mean.
const BlueprintRunInterruptedReason = "" /* 143-byte string literal not displayed */
BlueprintRunInterruptedReason is the stored error_message for an abandoned run: an actionable retry instruction, never an automatic replay.
const BlueprintRunRecoveryBound = 30 * time.Minute
BlueprintRunRecoveryBound is the documented staleness bound: a running sync with no terminal state older than this is abandoned as interrupted, and a disconnect may reap an active claim older than this inline. Healthy applies are expected to finish well inside it; a live run past the bound is settled as interrupted anyway (its late completion is then fenced, never applied as success), which bounds the busy window after process loss.
const DefaultFeedPark = time.Minute
DefaultFeedPark bounds how far the durable watermark may lag the read window before an otherwise-quiet pass persists it forward.
It lives here, like FeedCommitLag, because it is a property of the shared watermark row rather than a per-consumer tunable: parking per tick would be a Postgres write transaction every poll interval forever on a platform where nothing is happening, and parking never makes a restart re-read the whole empty window. A minute makes a quiet-but-subscribed platform cost one small write a minute and re-read at most a minute of already-empty window — the same trade for every tailer. Page size, by contrast, is genuinely per-consumer (the push worker does per-row lookups that the webhook worker does not) and stays in each package.
const DefaultPlan = PlanHobby
DefaultPlan is the plan a new workspace gets when the caller names none — the free tier, matching Render's create flow default.
const FeedCommitLag = 3 * time.Second
FeedCommitLag is how far behind now a tailer of ListWebhookEvents must read. The feed's timestamps are assigned before their rows commit (Go's clock for audit rows, the statement's now() for deploys), so a row can appear under an already-advanced watermark; reading only rows older than this leaves in-flight commits time to land. A transaction slower than this can still slip under — accepted, documented, and bounded (audit inserts time out at 2s, core/audit.go).
It lives with the query rather than with either consumer because it is a property of THIS feed's write path, not a per-consumer tunable: every tailer of ListWebhookEvents (internal/webhooks, internal/notifications) is exposed to the same in-flight commits and must use the same lag. Page size and park interval are genuinely per-consumer and stay in their own packages.
const FirstDeployGeneration int64 = 1
FirstDeployGeneration is the release generation a service's create-triggered deploy runs under: a freshly created Kubernetes object always starts at metadata.generation 1, and the reconciler projects this row into exactly such an object. bex-api stamps the same value on the CR's release-generation annotation, and the two MUST agree — a row pinned to a release the operator never reports reads as superseded and closes canceled (w6/m46 t004).
const MaxAgentSessionPromptBytes = 8 << 20
MaxAgentSessionPromptBytes caps all durable user intent for one session. Individual prompts are already limited to 100 KiB at the API and schema; this aggregate bound prevents an unbounded number of valid turns from growing Postgres independently of the assistant-transcript quota.
const MaxAgentSessionTranscriptBytes = 64 << 20
MaxAgentSessionTranscriptBytes caps the total payload bytes one session's transcript may hold (w1/m65 F10), bounding Postgres growth from tenant-controlled agent output and replay memory. Every write path (gateway live splice, gateway prompt turn, Completer harvest) seeds its byte counter from the already-stored total (AgentSessionTranscriptBytes) and stops appending at this cap; the replay read (AgentSessionTranscript) is budgeted by the same bound, so it never materializes more than the cap.
const MaxBillingNotificationAttempts = 24
MaxBillingNotificationAttempts caps how often a billing notice is claimed. With the worker's backoff (doubling to 128 minutes) the last attempt falls about 36 hours after the first. A notice whose last attempt fails stays undelivered with its last_error, due at infinity, so the claim scan never reaches it again (w5/m136).
const MaxReplicas = appv1alpha1.MaxReplicas
MaxReplicas is the shared upper bound on an App's replica count, enforced by both the create path (store/api.go) and the apps scale verb so the two can't disagree about what a valid App is. The lower bounds legitimately differ (create treats 0 as "default 1"; scale rejects 0 — see apps.Service.Scale). The value is the CRD/operator ceiling (types.MaxReplicas) so autoscaling, explicit scale, and create cannot drift from what Kubernetes will admit.
const MaxWebhookEndpointsPerWorkspace = 25
MaxWebhookEndpointsPerWorkspace bounds how many endpoints one workspace may register (w1/m67 F2). Every event is fanned out across every enabled endpoint of its workspace by a worker SHARED with every other tenant, so an unbounded endpoint count is an unbounded multiplier on shared memory and database work — growable persistently, by an ordinary workspace admin, at no cost to them. The value is far above any real integration topology (Render's own docs describe a handful of endpoints per workspace); it exists to stop the pathological case, not to shape normal use.
const MaxWebhookURLBytes = 2048
MaxWebhookURLBytes is the destination length cap (codex round-15 #2). The shared worker reloads every enabled endpoint every two seconds, so an unbounded HTTPS URL (the global 2 MiB body cap) is a persistent memory and query-byte multiplier across tenants. 2048 is far above any real webhook destination (Slack/PagerDuty/GitHub are hundreds of bytes) and matches the practical URL length most intermediaries accept.
Variables ¶
var ( ErrAgentSessionPromptQuota = fmt.Errorf("agent session prompt byte quota reached: %w", ErrConflict) ErrAgentSessionTurnState = fmt.Errorf("agent session cannot accept another turn in its current state: %w", ErrConflict) )
var ( ErrPushDeviceSubjectLimit = fmt.Errorf("member has the maximum number of active push devices: %w", ErrConflict) ErrPushDeviceWorkspaceLimit = fmt.Errorf("workspace has the maximum number of active push devices: %w", ErrConflict) )
var ( // ErrEnvGroupNameTaken is the (tenant_id, name) unique violation: another // group in the workspace already uses the name. ErrEnvGroupNameTaken = errors.New("environment group name taken") // ErrEnvGroupChanged is a lost compare-and-set: the row exists but its // version moved, or the operation it names is no longer the active one. ErrEnvGroupChanged = errors.New("environment group changed") )
var ( ErrOwnerMember = fmt.Errorf("%w: cannot remove the workspace owner", ErrConflict) ErrOwnerRole = fmt.Errorf("%w: the workspace owner must remain an admin", ErrConflict) )
ErrOwnerMember / ErrOwnerRole are the in-transaction backstops for the workspace owner binding (w5/m101), the same shape ErrLastAdmin has: the members service refuses these cases up front with a coded error, and the store rechecks under the membership advisory lock so a second adapter or a race cannot slip past the service gate.
Only the member verbs are guarded. RemoveAccountMember (ADR086 offboarding) deliberately removes an owner — it clears the binding in the same transaction, so the workspace never keeps a dangling owner.
var ( ErrInviteAlreadyAccepted = fmt.Errorf("invite already accepted: %w", ErrConflict) ErrInviteExpired = fmt.Errorf("invite expired: %w", ErrConflict) ErrInvitePlanLimit = fmt.Errorf("workspace plan cannot seat invite: %w", ErrConflict) )
Stable direct-invite redemption causes. Each wraps ErrConflict so existing REST/MCP status semantics remain unchanged while callers can classify the refusal without parsing human prose.
var ( ErrNotFound = errors.New("not found") ErrConflict = errors.New("already exists") ErrInvalid = errors.New("invalid") // ErrAccountDeletionPending is returned by membership writers after they // serialize with a newly committed account-deletion intent. ErrAccountDeletionPending = errors.New("account deletion pending") // ErrWorkspaceGone reports that a write lost a race with the workspace's own // deletion: the row it would have referenced is already gone. It is narrower // than ErrNotFound on purpose — callers acknowledge it as a no-op instead of // retrying, so only the writes that can genuinely lose that race may return // it. Today that is the Stripe lifecycle path, whose events are emitted by // the deletion itself (see verifyBillingProviderMapping). ErrWorkspaceGone = errors.New("workspace no longer exists") )
Error taxonomy shared by the store and the API: the store classifies Postgres failures into these, the API maps them to status codes (ErrInvalid→400, ErrNotFound→404, ErrConflict→409).
var ( ErrWebPushSubjectLimit = fmt.Errorf("member has the maximum number of active web-push browsers: %w", ErrConflict) ErrWebPushWorkspaceLimit = fmt.Errorf("workspace has the maximum number of active web-push browsers: %w", ErrConflict) )
var AllTriggers = []string{TriggerCreate, TriggerAPI, TriggerDeployHook, TriggerRollback, TriggerNewCommit, TriggerConfigChange}
AllTriggers is every Trigger* value — the set REST conformance proves maps onto Render's enum (w8/058). A guard test keeps it in step with the consts.
var ErrBlueprintResourceConflict = fmt.Errorf("blueprint resource claim conflict: %w", ErrConflict)
ErrBlueprintResourceConflict wraps ErrConflict when another Blueprint already owns the (tenant, kind, name) claim. Callers map it to BLUEPRINT_RESOURCE_CONFLICT.
var ErrBlueprintSyncBusy = fmt.Errorf("blueprint execution busy: %w", ErrConflict)
ErrBlueprintSyncBusy reports that a Blueprint lifecycle verb lost (or never held) execution authority: another apply owns the active claim, or the caller's generation was fenced by a newer admission or a disconnect. It wraps core.ErrConflict through MapError so every adapter answers 409; the apps layer re-codes it as BLUEPRINT_SYNC_BUSY. Callers retry after the recorded run settles, or start an explicit new sync.
var ErrFeedCursorUnloaded = errors.New("feed cursor read before Load")
ErrFeedCursorUnloaded is returned when a pass is asked to tail from a cursor whose durable watermark was never read. Failing here is the point: the zero cursor is the beginning of time, so the alternative is a silent replay of every event in the feed.
var ErrGitWebhookReplayCapacity = errors.New("git webhook replay capacity reached")
ErrGitWebhookReplayCapacity is returned before insertion when a tenant's exact durable replay-row budget is exhausted.
var ErrGitWebhookReplayEpochRetired = errors.New("git webhook replay signing epoch retired")
ErrGitWebhookReplayEpochRetired fails closed when a replica tries to accept a signing key whose claims were already safely purged after its lease expired.
var ErrLastAdmin = fmt.Errorf("%w: cannot remove or demote the last admin of a workspace", ErrInvalid)
ErrLastAdmin is the typed refusal when a demotion or removal would leave a workspace with zero administrators (codex round-16 #3).
var ErrWebhookAttemptPending = fmt.Errorf("webhook attempt is already pending: %w", ErrConflict)
ErrWebhookAttemptPending refuses a second, distinct resend while one attempt for the same logical notification is already reserved. It bounds concurrent replay fan-out without weakening idempotent repeats of the same request key.
var ErrWebhookEndpointDisabled = fmt.Errorf("webhook endpoint is disabled: %w", ErrConflict)
ErrWebhookEndpointDisabled refuses a manual resend while its destination is disabled. It wraps ErrConflict so every adapter keeps the shared conflict classification while exposing a stable feature-specific code.
var ErrWebhookEndpointLimit = fmt.Errorf("workspace has the maximum number of webhook endpoints: %w", ErrConflict)
ErrWebhookEndpointLimit is the typed refusal when a workspace is already at MaxWebhookEndpointsPerWorkspace. Wraps ErrConflict so existing REST/GraphQL/MCP error mapping treats it like the other quota refusals (cf. ErrInvitePlanLimit).
var ErrWebhookEndpointNotFound = fmt.Errorf("webhook endpoint not found: %w", ErrNotFound)
ErrWebhookEndpointNotFound distinguishes a missing owner-scoped endpoint from a missing source attempt while preserving the shared not-found class.
var WorkspacePlans = []string{PlanHobby, PlanPro, PlanScale, PlanEnterprise}
WorkspacePlans is the catalog in ladder order (cheapest first), the source of truth for validation and any plan picker.
Functions ¶
func CRName ¶
CRName is the projected CR's name, "<tenant-id>-<app>". Both parts are API-validated DNS labels of ≤30 chars, so the result always fits the 63-char object-name limit. Delegates to core.CRName — the apps feature's create path (w4/m19) computes the identical name for the same row, so whichever side (bex-api's direct Create or this reconciler's own fallback-create) gets there first, the other recognizes the CR by its LabelAppID rather than re-creating it under a different name.
func CanAddMember ¶
CanAddMember reports whether a workspace on the given plan can take another member given its current count — the single-member guard keyed on plan (Hobby caps at 1, paid plans are unlimited). A pure function so w4/m12's invite verb consults the one rule without a second copy, and so it stays out of the service-layer authz sweep (it's a predicate, not a gated verb).
func CanTransitionDeploy ¶
CanTransitionDeploy is the evidence-backed transition table. It permits forward phase skips because the backend samples Kubernetes state rather than receiving every operator edge, but rejects phase regressions and every mutation out of a terminal state. live -> deactivated is the sole exception: a newer live deploy atomically makes the prior live revision historical.
func CancelRelease ¶
func CancelRelease(ctx context.Context, c client.Client, a *appv1alpha1.App, generation int64, buildNamespace string) error
CancelRelease ends the release a deploy row requested — the half of a cancel the row cannot express — for user Cancel and for a rollout a suspend interrupted alike (w5/m114), so a resumed service serves its prior live release instead of rolling one its history says was canceled.
The stamp lands first. The operator is level-triggered and only this annotation makes prepareAppReleaseDecision take its canceled branch and settle the App (settleCanceledRelease): the last served release, or PhaseCanceled when none ever served. Deleting a deterministic build Job or kpack Image without it would only see the next pass recreate it, and an image-backed release — which has no build to delete — would roll on (w6/m52, w6/m104). A newer deploy stamps a newer release generation and so supersedes the marker.
Only the stamp can fail it: once that lands the release is ended — the operator settles from it and rewinds the release generation — and a caller that failed then would strand its row open (releaseCanceledFor closes it on the next pass regardless). The build deletes are best effort: a build left running can no longer promote.
func CheckOwnership ¶
CheckOwnership verifies that every table in the public schema is owned by the role currently connected (CURRENT_USER). Returns an error listing any mis-owned tables. Call this at startup after Migrate so a hand-run migration executed as a superuser (which creates tables owned by that superuser rather than the application role) is caught before the first request.
Incident (2026-07-12): tenant_invites was owned by postgres instead of bex, causing "permission denied for table tenant_invites" on every invite-redemption call. This check would have surfaced that at startup with a clear error.
func CronRunNameFromStartedKey ¶
CronRunNameFromStartedKey recovers the run (Job) name from a cron-start feed row's Key. Starts recorded before w4/259 stored no run id, and the source key is insert-once, so the feed derives their id from the key instead. Anything not exactly "fact:cron:<appID>:<job name>:started" yields no name: a malformed key never manufactures an association.
func DeployFailureStatus ¶
DeployFailureStatus reports whether status is a terminal failure. These can be reached by a forward skip straight from queued (the backend samples Kubernetes state; a build can fail entirely between two passes), so the transition's own clock is not evidence of when their work began.
func DeployStatusStampsDispatch ¶
DeployStatusStampsDispatch reports whether TransitionDeploy may stamp started_at from its own clock on reaching status: entering an in-progress status IS the dispatch moment being observed. Every terminal status is not: a failure arrived at by a skip collapsed a 68-second failed build into a one-microsecond duration (w6/m123), and a first observation already at live reported an image rollout whose container had been up for 13 seconds as a zero-second deploy that began when it finished (w4/m156). Terminal closes stamp only observed evidence (the operator's recorded build window), or honestly nothing.
func DeployStatusStartsExecution ¶
DeployStatusStartsExecution reports whether reaching status means the deploy's work has actually begun. A queued deploy is waiting behind another build and has dispatched nothing; canceled/deactivated are exits that can be reached without ever executing. Shared with the build_started event fact so the timeline cannot disagree with the deploy row about when a build began (w6/035). Note it says nothing about WHEN the work began: a terminal failure reached by a phase skip executed at some earlier, unobserved time — see DeployStatusStampsDispatch for the stamping half (w6/m123).
func DeployStatuses ¶
func DeployStatuses() []string
DeployStatuses returns a copy of the complete stored status vocabulary. Callers may use it to validate surface/filter parity without mutating the store's transition model.
func GitWebhookSigningEpoch ¶
GitWebhookSigningEpoch derives a non-secret stable identifier for the exact HMAC key a replica accepts. The epoch changes on key rotation, which is the only point at which claims made under the old key can safely be discarded.
func ImageRepository ¶
ImageRepository returns the registry host + repository of an image reference with Docker Hub's short forms normalized (`nginx` and `docker.io/library/nginx` are one repository) and the tag/digest dropped. Render's imageUrl contract compares exactly this: a per-deploy image must keep the service's configured host, repository and image name.
func IsOpenDeployStatus ¶
IsOpenDeployStatus reports whether status may still advance or be canceled.
func IsTerminalDeployStatus ¶
IsTerminalDeployStatus reports whether status is a known terminal state.
func ManagedAppID ¶
ManagedAppID returns the control-plane app id a projected App CR's labels carry, or "" when the labels don't prove bex-controlplane ownership (a hand-applied CR with no Postgres source row to key a fact/row on). One helper so every feature that reads the app id off a CR does it the same way.
func MapError ¶
MapError translates this taxonomy into core's, so a feature service can return a store error straight out of a verb and have every surface map it to the right status. The store error stays in the message (%v, not %w) — the core sentinel is what callers match on.
Features whose mapping differs deliberately (workspaces folds Conflict into ErrBadRequest; registrycreds and webhooks return bare sentinels) keep their own — this is the common case, not a mandate.
func Migrate ¶
Migrate applies all pending schema migrations to the database at uri (postgres:// or postgresql://). The schema ships embedded in the binary so a deploy always carries the schema it expects; a no-op when already current.
func NormalizePlan ¶
NormalizePlan validates a caller-supplied workspace plan, defaulting empty to Hobby. Unknown plans are ErrInvalid, listing the valid ones — the same shape as the internal API's tier validation.
func NormalizeResourceKind ¶
NormalizeResourceKind preserves the pre-resource-kind behavior for callers that omit the field. Persisted rows always carry an explicit kind.
func OwnerWorkspaces ¶
OwnerWorkspaces adapts a by-id routing read that finds one workspace to core.ResourceOwner's shape: a missing row is no owner, not an error.
func RedactRepoURL ¶
RedactRepoURL strips embedded userinfo from a repo URL before it is shown to viewers — the read-side companion to ValidRepo's create/update-time refusal (round-6 #13): rows stored before the rule (or written by a future code path that forgets it) must not hand a same-workspace viewer another member's token. A password-less ssh:// username is preserved (not a credential); anything unparseable is returned as-is (it cannot be cloned from either).
func RenderDeployStatus ¶
RenderDeployStatus maps a TERMINAL bex deploy status onto Render's deployStatus enum (succeeded|failed|canceled) — one mapping next to the vocabulary it interprets, shared by the events feed's deploy_ended details and the webhook payload's data.status so the same deploy can never be reported differently by the two (the drift w2/m10's `canceled` addition showed is possible).
func ResourceDisplayNameKey ¶
ResourceDisplayNameKey is the map key the usage presenter already uses to pair a kind with an id; different kinds may legally share an id.
func RoleAllowedOnPlan ¶
RoleAllowedOnPlan reports whether role is assignable on plan — the single predicate members.guardPlanRole (invite/change-role) and workspaces.ChangePlan's downgrade guard both consult, mirroring CanAddMember's shape below.
func SandboxNamespace ¶
SandboxNamespace is the sandbox-regime namespace name for a workspace id.
func TailFeed ¶
TailFeed advances cur through the feed, projecting and committing a page at a time until a page comes back short or empty. The cursor is advanced only after its page's Commit returns, so a failed write is re-read rather than skipped.
func ValidGitRef ¶
ValidGitRef reports whether v is an acceptable git branch/tag/ref for a build-from-git App (no shell metacharacters, no leading dash). Single-source for both create paths (w6/m6 t003).
func ValidGlob ¶
ValidGlob reports whether v is a safe build-filter glob pattern (Render's Build Filters, docs/ADR018): a non-empty, ≤512-byte, repository-root-relative pattern with no control characters, no backslash, no traversal ("..") component, and no leading "/", that doublestar can compile (Render's dialect — *, **, ?, [class]). Exported so bex-api's SetBuildFilter and create enforce one rule for every buildFilter pattern; a malformed pattern is rejected at the API boundary so the webhook matcher never sees one it can't compile.
func ValidRepo ¶
ValidRepo reports whether v is an acceptable git repo URL for a build-from-git App (https/ssh/git@, no whitespace or control chars, ≤2048 bytes). Exported so bex-api and the internal create API enforce one rule (w6/m6 t003).
URL userinfo is refused (codex-security round-6 #13): the repo string is stored on the App and echoed verbatim to every workspace viewer, so https://user:token@host/… (or a PAT in the username position) would turn a non-secret resource identifier into a credential leak. Credentials belong in the GitHub App integration / clone Secret, never the URL. The one exception is a password-less ssh:// username (ssh://git@host/…): the SSH user selects the account server-side and is not a bearer secret; the SCP form (git@host:…) has no password syntax at all.
A colon-less git@ value is refused (codex-security 2026-08 F5): git treats a URL with no scheme AND no colon as a LOCAL path, which selects the local transport — the one transport where a fetch-injected --upload-pack=<cmd> option executes <cmd> locally in the credential-holding clone container. The colon is what makes "git@host:path" an SCP remote; without it the value was never a remote URL at all.
func ValidRootDir ¶
ValidRootDir reports whether v is a safe build root directory: a relative path with no traversal ("..") or absolute components and no control characters, ≤512 bytes. Empty (the default — build the repo root) is valid. Exported so bex-api's SetRootDir and create enforce one rule (w6/m6 t003).
func ValidateImage ¶
ValidateImage checks an OCI image reference's grammar and registry policy. Short Docker Hub names and tag+digest references are accepted without rewriting the stored value. The kubelet pulls from the node network, outside pod egress policy: private/reserved IPs and untrusted registry hosts must stay blocked. Checking DNS here would not protect the kubelet's later resolution from rebinding.
func ValidatePushNotification ¶
func ValidatePushNotification(notification PushNotification) error
ValidatePushNotification is the producer-side admission seam. Feed projections must be able to drop one malformed logical item without making the shared watermark retry that poison event forever.
func WorkspaceNamespace ¶
WorkspaceNamespace is the hosting namespace name for a workspace id. The workspace id (tea-<xid>) is already a DNS-safe label ≤63 chars (ADR020), so it is used verbatim as the namespace name — the k8s object IS the tenant. core.AppInOwnWorkspaceNamespace assumes this identity mapping (core cannot import store); a non-identity mapping here must change that predicate too.
Types ¶
type API ¶
type API struct {
Store Store
// Kick, when non-nil, nudges the reconciler after each successful write
// so intent reaches the cluster immediately instead of on the next resync.
Kick func()
// Health reports readiness (the DB ping) for /healthz.
Health func(context.Context) error
// Token gates /v1/ behind a constant-time bearer check. Empty leaves the
// API open, so cmd/api refuses to start the :8091 listener with an empty
// token unless BEX_CP_INSECURE=1 (w1/m53 requireCPAuth) — this field is
// only ever empty in that explicit local-dev override.
Token string
// Grant, when set, writes the new tenant's OpenFGA workspace membership so
// its owner can authorize resources (replacing the model's workspace:default
// placeholder). Nil => the tenant row is still created, without a membership.
Grant MembershipGranter
Billing BillingAdmin
BillingOperations BillingOperations
// SandboxTenants resolves an OpenSandbox per-workspace key to its workspace
// id for the GET /v1/sandbox-tenants tenant-lookup endpoint (w3/m32 t006).
// Nil => the endpoint returns 503 (sandbox multi-tenancy not configured).
SandboxTenants SandboxTenantResolver
}
API is the minimal product surface over the source of truth: create a tenant, create an app, add a domain. Validation (business logic) lives here — not in the operator, not in Postgres procedures. Writes land as rows; the Reconciler projects them into App CRs.
type AbandonedBlueprintSync ¶
type AbandonedBlueprintSync struct {
RunID string
BlueprintID string
Generation int64
StartedAt time.Time
Owned bool // blueprint still names this run as its active claim
BlueprintGone bool // no live blueprint row to project onto (defensive)
}
AbandonedBlueprintSync is one stale running run the recovery sweep returns.
type AccountDeletion ¶
type AccountDeletionBlockedError ¶
type AccountDeletionBlockedError struct {
Workspaces []AccountWorkspaceDisposition
}
func (*AccountDeletionBlockedError) Error ¶
func (e *AccountDeletionBlockedError) Error() string
type ActivePushSubscription ¶
type ActivePushSubscription struct {
TenantID string
Subject string
Role string
DeviceID string
Provider string
Platform string
Token string `json:"-"`
P256dh string `json:"-"`
Auth string `json:"-"`
PushPolicy json.RawMessage `json:"-"`
CreatedAt time.Time
}
ActivePushSubscription is the internal-only delivery projection. Token is never selected by caller-facing device reads and can never enter JSON.
type AgentDispatch ¶
type AgentDispatch struct {
SessionDeleted bool
PreviousSandboxID string
SessionID string
Turn int
WorkspaceID string
Legacy bool
}
AgentDispatch survives the process that accepted a turn, including after abandonment. Tombstones are revisited to catch a late remote create response.
type AgentSession ¶
type AgentSession struct {
ID string
WorkspaceID string
Repo string
Branch string
AgentConfig json.RawMessage
SandboxID string
Phase string
Status string
HeadSHA string
PRURL string
PRNumber int
Evidence json.RawMessage
Turns int
DeliveryMode string
FailureReason string
// FailureReasonCode is a stable code for the failure's cause when a client
// acts on it, such as SANDBOX_CAPACITY_LIMIT; empty otherwise (w5/m132). Only
// AbandonAgentDispatch writes one; every other failure write clears it.
FailureReasonCode string
CreatedAt time.Time
UpdatedAt time.Time
CanceledAt *time.Time
// Hibernation (ADR059 D2/D3, w2/m68). Pinned removes the retention delete
// edge; SnapshotRef is the object-storage key (empty ⇒ no durable snapshot);
// SnapshotBytes is the storage-metering + quota dimension; HibernatedAt/
// RetainUntil drive the retention sweep (NULL while Active).
Pinned bool
SnapshotRef string
SnapshotBytes int64
SnapshotSHA string
HibernatedAt *time.Time
RetainUntil *time.Time
// ArchivedAt is the ADR065 D1 archive flag: LIST-state, orthogonal to phase.
// NULL ⇒ the session is in the working set; set ⇒ it is archived (hidden from
// the default list, mutation verbs refused) but still viewable.
ArchivedAt *time.Time
// InitialPrompt is create-only input used to atomically insert turn 1 with
// the session row. It is not an agent_sessions column and is never scanned.
InitialPrompt string
}
AgentSession is the durable control-plane record for one cloud coding-agent task (ADR047 D3). AgentConfig is kept as JSON because the driver-specific knobs evolve independently of the lifecycle contract; callers validate its public shape before persistence.
type AgentSessionFailure ¶
AgentSessionFailure is why a dispatch failed: a sentence for the reader and, when a client acts on the cause, a stable code (w5/m132).
type AgentSessionListQuery ¶
type AgentSessionListQuery struct {
Archived ArchivedFilter
Phases []string
Repo string
CreatedBefore time.Time
CreatedAfter time.Time
Cursor string
Limit int
}
AgentSessionListQuery is the ADR065 D3 filter + keyset-page contract for the workspace session list. Cursor is the id of the prior page's last item (Render's cursor idiom); an unknown or foreign cursor yields an empty page. Limit must be normalized (>0) by the caller — the store never substitutes a default, so an unbounded read is impossible by construction.
type AgentSessionTranscriptPart ¶
type AgentSessionTranscriptPart struct {
Seq int64
PartIndex int64
Turn int
Part json.RawMessage
CreatedAt time.Time
}
AgentSessionTranscriptPart is one durable UI-message-stream part (ADR047 D9, w3/m43, amended by w5/m71). Seq is the store-allocated monotonic replay cursor; PartIndex is the driver's turn-local emission ordinal. Part is the verbatim `data:` payload forwarded to a Vercel AI SDK client.
type AgentSessionTurn ¶
type AgentSessionTurn struct {
SessionID string
Turn int
Prompt string
DeliveryMode string
TranscriptComplete bool
TranscriptTruncated bool
TruncationReason string
CreatedAt time.Time
StartedAt *time.Time
CompletedAt *time.Time
}
AgentSessionTurn is the durable user intent for one accepted agent turn. Assistant output stays in AgentSessionTranscriptPart so the gateway can replay its exact wire bytes; this row supplies the role and completeness facts the byte stream itself cannot encode.
type App ¶
type App struct {
ID string `json:"id"`
TenantID string `json:"tenantId"`
Name string `json:"name"`
// Slug is the globally-unique public subdomain (Render's "slug", distinct
// from Name which is only workspace-unique): the bare Name when free
// platform-wide, or "<name>-<4-char suffix>" when CreateApp had to mint one
// to avoid a cross-tenant collision (w4/m19). The operator reads this —
// never Name — to derive the platform host.
Slug string `json:"slug"`
// Type is the App's service kind (types.AppSpec.Type's vocabulary:
// web_service | private_service | background_worker | cron_job |
// static_site). The projector derives spec.expose from it, so a row that
// does not carry it cannot be projected correctly. Immutable for the app's
// whole life, so it is recorded once at create. Empty is a pre-w6/m46 row
// and reads as web_service, exactly as the CRD contract defines the empty
// type; the projector backfills the real value from the live CR on resync.
Type string `json:"type,omitempty"`
Repo string `json:"repo,omitempty"`
Image string `json:"image,omitempty"`
// RegistryCredentialID preserves Render's tri-state image credential
// binding: nil is legacy host auto-resolution, pointer-to-empty explicitly
// selects no credential, and a non-empty value pins one workspace credential.
RegistryCredentialID *string `json:"registryCredentialId,omitempty"`
Branch string `json:"branch"`
Port int32 `json:"port"`
ContainerPolicy string `json:"containerPolicy,omitempty"`
PortMode string `json:"portMode,omitempty"`
Replicas int32 `json:"replicas"`
Tier string `json:"tier"`
IdleTTLSeconds int32 `json:"idleTTLSeconds"`
Suspended bool `json:"suspended"`
ProjectID string `json:"projectId,omitempty"`
EnvironmentID string `json:"environmentId,omitempty"`
CreatedAt time.Time `json:"createdAt"`
// DisplayName is the read projection of the CR's mutable spec.displayName
// (SetAppDisplayName). Empty means never renamed; readers fall back to
// Name. Read-only here: CreateApp does not write it.
DisplayName string `json:"displayName,omitempty"`
// CreationPending prevents the projector from dispatching a Blueprint App
// before its initial environment-group composition has committed.
CreationPending bool `json:"-"`
// InitialDisk is CreateApp input only. Save the disk and its first metering
// period in the service transaction, before a projector can observe the row.
InitialDisk *appv1alpha1.DiskSpec `json:"-"`
// FirstDeployCommit is CreateApp INPUT only (w9/001): the resolved commit
// stamped onto the first deploy row (trigger "create") CreateApp opens in
// the same transaction. Not an apps column — never persisted on, or read
// back from, the app row itself. Zero value ⇒ the first deploy carries no
// commit metadata (image-backed app, or no GitHub connection to resolve
// the branch through).
FirstDeployCommit CommitInfo `json:"-"`
// FirstDeployID is CreateApp OUTPUT only (w3/m14): the id minted for the
// first deploy row. Callers use it to navigate straight to the deploy page.
// Not stored on the app row — only populated by CreateApp, never read back.
FirstDeployID string `json:"-"`
}
App is a row of `apps` — the source-of-truth service definition the reconciler projects into an App CR. Observed state (phase, url) is NOT stored here: it lives on the App CR's status, which bex-api reads at query time.
type AppPlacement ¶
AppPlacement is authoritative membership; runtime state remains on the App CR.
type ArchivedFilter ¶
type ArchivedFilter int
Archived-membership selector for ListAgentSessions (ADR065 D3). The zero value excludes archived rows — the default working set.
const ( ArchivedExclude ArchivedFilter = iota // archived_at IS NULL (default) ArchivedOnly // archived_at IS NOT NULL ArchivedInclude // no predicate — the full history )
type AuditFilter ¶
type AuditFilter struct {
Since time.Time
Until time.Time
Cursor string
Limit int
OldestFirst bool
}
AuditFilter narrows ListAuditEvents: Since/Until bound At inclusively (Render's startTime/endTime); Cursor resumes strictly after a previously returned row's id (keyset paging, stable under concurrent inserts — unlike an OFFSET); Limit caps the page (<1 or >MaxAuditPageSize clamps to DefaultAuditPageSize). OldestFirst flips the order (Render's direction=forward, w4/013) — note a cursor's meaning follows the direction it is paged under: the same row id resumes strictly older rows backward and strictly newer rows forward.
type AuditRow ¶
type AuditRow struct {
ID string
WorkspaceID string
Caller string
CallerMethod string
Verb string
Resource string
// Target is the resource the verb acted ON ("service:my-api",
// core.ServiceTarget) — empty for a workspace-wide verb. Resource is what the
// verb was authorized AGAINST (the workspace); Target is what it changed.
Target string
// TargetName is the resource's display name when a typed datastore effect
// needs it for Render's thin webhook payload.
TargetName string
Outcome string
At time.Time
// MaintenanceModeTo is Render's MaintenanceModeEnabledEvent metadata.to.
// It is nil for every other audit verb.
MaintenanceModeTo *bool
// RoleFrom/RoleTo are the team-membership verbs' typed role detail
// (w1/m33, migration 0040): ChangeRole records old→new, Invite/AcceptInvite
// record RoleTo alone. Nil for every other verb and for pre-0040 rows.
// RevokedKeyCount is how many API keys a membership removal revoked
// (w2/m163). Nil for every other verb; 0 means the member had none.
RevokedKeyCount *int32
RoleFrom *string
RoleTo *string
// Relation is the RelCan… the decision was made against. Empty on typed
// system events and pre-0088 rows.
Relation string
// OAuthClientID / OAuthAudience / OAuthScopes are the verified grant
// facts for a human OAuth caller. Empty on session, machine, system, and
// pre-0088 rows.
OAuthClientID string
OAuthAudience string
OAuthScopes []string
}
AuditRow is one persisted audit_events row.
type AutoDeployFilter ¶
type AutoDeployFilter int16
AutoDeployFilter constrains the auto_deploy_enabled column of apps.SetAutoDeploy rows on the audit arm. It is used when the caller filters by an auto-deploy event type so that the discrimination (enabled vs disabled vs legacy changed) runs in SQL before the LIMIT rather than in Go after it. A Go-side drop after LIMIT would return short (or empty) pages, which a cursor client reads as end-of-feed.
It is a bit set, not an enum (w1/m165): Render's type filter takes several types at once (`type=autodeploy_enabled,autodeploy_disabled`), so the allowed sub-types are OR-ed together. It constrains ONLY SetAutoDeploy rows — every other verb in the same multi-type request passes through untouched.
const ( // AutoDeployFilterNone imposes no constraint — all auto_deploy_enabled values pass. AutoDeployFilterNone AutoDeployFilter = 0 // AutoDeployFilterEnabled selects rows where auto_deploy_enabled = true. AutoDeployFilterEnabled AutoDeployFilter = 1 // AutoDeployFilterDisabled selects rows where auto_deploy_enabled = false. AutoDeployFilterDisabled AutoDeployFilter = 2 // AutoDeployFilterChanged selects rows where auto_deploy_enabled IS NULL (legacy rows). AutoDeployFilterChanged AutoDeployFilter = 4 )
type BillingAdmin ¶
type BillingEnforcement ¶
type BillingExportIssue ¶
type BillingExportIssue struct {
TransactionID string `json:"transactionId"`
WorkspaceID string `json:"workspaceId"`
ResourceKind string `json:"resourceKind"`
ServiceID string `json:"serviceId"`
Kind string `json:"kind"`
Tier string `json:"tier,omitempty"`
WindowStart time.Time `json:"windowStart"`
EventName string `json:"eventName"`
IssueKind string `json:"issueKind"`
ErrorCode string `json:"errorCode"`
ErrorMessage string `json:"errorMessage"`
FirstSeenAt time.Time `json:"firstSeenAt"`
LastSeenAt time.Time `json:"lastSeenAt"`
ResolvedAt *time.Time `json:"resolvedAt,omitempty"`
Resolution string `json:"resolution,omitempty"`
Actor string `json:"actor,omitempty"`
Reason string `json:"reason,omitempty"`
}
type BillingExportReport ¶
type BillingExportReport struct {
WorkspaceID string `json:"workspaceId"`
CustomerID string `json:"customerId,omitempty"`
SubscriptionID string `json:"subscriptionId,omitempty"`
Livemode *bool `json:"livemode,omitempty"`
Rows []BillingExportRow `json:"rows"`
}
type BillingExportRow ¶
type BillingExportRow struct {
WorkspaceID string `json:"workspaceId"`
ResourceKind string `json:"resourceKind"`
ServiceID string `json:"serviceId"`
Kind string `json:"kind"`
Tier string `json:"tier,omitempty"`
WindowStart time.Time `json:"windowStart"`
Quantity int64 `json:"quantity"`
State string `json:"state"`
AttemptedAt *time.Time `json:"attemptedAt,omitempty"`
EmittedAt *time.Time `json:"emittedAt,omitempty"`
TransactionID string `json:"transactionId,omitempty"`
EventName string `json:"eventName,omitempty"`
ErrorCode string `json:"errorCode,omitempty"`
}
type BillingExportStats ¶
type BillingExportStats struct {
PendingRows int64
OldestPendingAge time.Duration
RejectedRows int64
AmbiguousRows int64
WithheldWorkspaces int64
}
BillingExportStats is the low-cardinality snapshot exported to Prometheus.
type BillingLifecycle ¶
type BillingLifecycle struct {
WorkspaceID string `json:"workspaceId"`
Status string `json:"status"`
Reason string `json:"reason,omitempty"`
GraceDeadline *time.Time `json:"-"`
SourceEventID string `json:"-"`
SourceEventAt *time.Time `json:"-"`
SourceEventOutcome string `json:"-"`
InvoiceID string `json:"-"`
SubscriptionID string `json:"-"`
TransitionVersion int64 `json:"-"`
RetryAt *time.Time `json:"-"`
ClaimedUntil *time.Time `json:"-"`
AttemptCount int `json:"-"`
LastError string `json:"-"`
EnforcedAt *time.Time `json:"-"`
RecoveredAt *time.Time `json:"-"`
RecoveryTarget string `json:"-"`
CreatedAt time.Time `json:"-"`
UpdatedAt time.Time `json:"updatedAt"`
}
BillingLifecycle is the provider-neutral durable dunning state.
type BillingNotification ¶
type BillingOperations ¶
type BillingOperations interface {
BillingExportReport(context.Context, string, time.Time, time.Time) (BillingExportReport, error)
ListBillingExportIssues(context.Context, bool, int) ([]BillingExportIssue, error)
ResolveBillingExportIssue(context.Context, string, string, string, string, time.Time) (BillingExportIssue, error)
}
type BillingOverrideRequest ¶
type BillingProviderMapping ¶
type BillingProviderMapping struct {
WorkspaceID string
CustomerID string
SubscriptionID string
Livemode bool
UpdatedAt time.Time
}
BillingProviderMapping is the durable relationship that lets a signed Stripe event be resolved without a network call on the webhook request path. Provider ids never enter tenant-editable state or public API results.
type Blueprint ¶
type Blueprint struct {
ID string `json:"id"`
TenantID string `json:"tenantId"`
Name string `json:"name"`
Repo string `json:"repo"`
Branch string `json:"branch"`
Path string `json:"path"`
AutoSync bool `json:"autoSync"`
Manifest string `json:"manifest"`
Status string `json:"status"`
LastSyncAt *time.Time `json:"lastSyncAt"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
// ExecutionGeneration is the lifecycle fencing counter (w8/m37, migration
// 0111): bumped by every admission, disconnect, and explicit
// re-creation. Only the Admit/Complete paths populate it; the remaining
// readers leave it zero rather than pay for columns they never use.
ExecutionGeneration int64 `json:"-"`
// ActiveRunID names the one admitted-but-uncompleted run, "" when idle.
// Internal to lifecycle coordination; never rendered on API views.
ActiveRunID string `json:"-"`
}
Blueprint is a row of `blueprints` — a workspace-scoped render.yaml stack source. Created automatically when deploy is called with a repo+manifest; sync re-applies the stored manifest (optionally replacing it first). w2/m62: path/AutoSync/LastSyncAt added for Git-connected instance semantics.
type BlueprintAutoSyncIntent ¶
type BlueprintAutoSyncIntent struct {
ID string `json:"id"`
TenantID string `json:"tenantId"`
BlueprintID string `json:"blueprintId"`
DeliveryDigest string `json:"deliveryDigest"`
CommitSHA string `json:"commitSha"`
Path string `json:"path"`
State string `json:"state"`
ErrorMessage *string `json:"errorMessage,omitempty"`
CreatedAt time.Time `json:"createdAt"`
ClaimedAt *time.Time `json:"claimedAt,omitempty"`
CompletedAt *time.Time `json:"completedAt,omitempty"`
}
BlueprintAutoSyncIntent is a durable accepted automatic Blueprint sync (w8/m38): persisted before the git-webhook acknowledges the delivery so a process restart cannot lose work the HMAC already authorized.
type BlueprintClaimKind ¶
type BlueprintClaimKind string
BlueprintClaimKind is the kind of resource a claim names. The constants are exactly the set blueprint_resource_claims_kind_chk admits (w5/115).
const ( BlueprintClaimService BlueprintClaimKind = "service" BlueprintClaimDatabase BlueprintClaimKind = "database" BlueprintClaimKeyValue BlueprintClaimKind = "key_value" )
func BlueprintClaimKinds ¶
func BlueprintClaimKinds() []BlueprintClaimKind
BlueprintClaimKinds is every claim kind. Tests range over it, so a new kind cannot ship without its CHECK migration or its manifest kind.
type BlueprintResourceClaim ¶
type BlueprintResourceClaim struct {
TenantID string
Kind BlueprintClaimKind
Name string
BlueprintID string
ClaimedAt time.Time
}
BlueprintResourceClaim is the durable owner of one workspace resource name (w8/m40).
func (BlueprintResourceClaim) Key ¶
func (c BlueprintResourceClaim) Key() BlueprintResourceKey
Key names the resource the claim is on.
type BlueprintResourceKey ¶
type BlueprintResourceKey struct {
Kind BlueprintClaimKind
Name string
}
BlueprintResourceKey names one claimable resource in a workspace.
type BlueprintSync ¶
type BlueprintSync struct {
ID string `json:"id"`
BlueprintID string `json:"blueprintId"`
CommitID string `json:"commitId"`
State string `json:"state"`
StartedAt time.Time `json:"startedAt"`
CompletedAt *time.Time `json:"completedAt"`
CreatedAt time.Time `json:"createdAt"`
ErrorMessage *string `json:"errorMessage"`
// Note annotates what the run did when its state cannot say it — today,
// that a confirmed takeover replaced a named blueprint on the same
// (tenant, repo, branch) row (w4/m125, migration 0131). Empty means
// nothing to report, the same convention deploys.stall_reason uses.
Note string `json:"note,omitempty"`
// ExecutionGeneration records which admission a run belongs to (w8/m37,
// migration 0111) so recovery and completion never settle a run against a
// newer generation. Internal; never rendered on API views.
ExecutionGeneration int64 `json:"-"`
}
BlueprintSync is a row of `blueprint_syncs` — one recorded sync run. State machine: created → running → success | error.
type CLITelemetryEvent ¶
type CLITelemetryEvent struct {
ID string
Subject string
WorkspaceID string
ReportedWorkspaceID string
Command string
DurationMs int64
ExitCode int
CompletionKind string
CLIVersion string
OS string
Arch string
OutputFormat string
InstallationID string
BexVersion string
LaunchedFullScreenTUI *bool
IsStdinTTY bool
IsStdoutTTY bool
IsStderrTTY bool
IsTermDumb bool
AgentSignals string
CISignals string
StartedAt *time.Time
ReceivedAt time.Time
}
CLITelemetryEvent is one ingested `POST /v1/cli-telemetry-events` delivery (w5/m92). Subject/WorkspaceID are the reporter's auth attribution, resolved server-side and never taken from the client's claim; ReportedWorkspaceID keeps the CLI's own current_workspace_id for join/debug. The remaining fields mirror Render's CliTelemetryEventPOSTInput JSON shape one-for-one so the imported CLI's sender needs no translation. BexVersion is the exception: it is bex's own launcher release, carried by a header rather than the body precisely so that shape stays diffable against upstream (w5/m94), and is empty for an unmodified upstream CLI or any pre-m94 build.
type CloneSecreter ¶
type CloneSecreter interface {
EnsureCloneSecret(ctx context.Context, namespace, appName, workspaceID, repo string) (string, error)
}
CloneSecreter is called by the Reconciler when projecting a new App CR for a repo-backed row: it mints a GitHub installation token, writes the <app>-clone Secret, and returns the Secret name for spec.cloneSecret — so private-repo builds from the first projected deploy authenticate without a separate API call. apps.Service satisfies this; cmd/api injects it when both the GitHub App and the control-plane store are wired. nil => public-clone only (no authentication, unchanged behaviour).
type CommitInfo ¶
type CommitInfo struct {
Hash string `json:"hash,omitempty"`
Message string `json:"message,omitempty"`
AuthorAt *time.Time `json:"authorAt,omitempty"`
}
CommitInfo is the git commit a build-from-git deploy runs — the resolved SHA, message, and author timestamp (w9/001 + w2/m42). Zero value = unknown: image-backed app, no GitHub connection to resolve through, resolution failed, or no prior deploy ever recorded a commit. Callers that pass zero mean "none" — CreateDeploy does not inherit. The rollout tracker is what carries a prior release's commit forward onto config_change re-rolls (hash + message + authorAt); image-backed apps with no prior commit stay empty. Commit metadata is best-effort provenance, never load-bearing — a deploy with an empty CommitInfo is still a fully valid deploy.
type CreateAppRequest ¶
type CreateAppRequest struct {
TenantID string `json:"tenantId"`
Name string `json:"name"`
Repo string `json:"repo"`
Image string `json:"image"`
Branch string `json:"branch"`
Port int32 `json:"port"`
Replicas int32 `json:"replicas"`
Tier string `json:"tier"`
IdleTTLSeconds int32 `json:"idleTTLSeconds"`
}
CreateAppRequest is the POST /v1/apps body. One of repo/image is required; zero values fall back to the platform defaults (branch main, port 3000, one replica, the tenant-independent "free" tier).
type CreateDomainRequest ¶
type CreateDomainRequest struct {
AppID string `json:"appId"`
Host string `json:"host"`
Primary bool `json:"primary"`
}
CreateDomainRequest is the POST /v1/domains body. Primary makes the host the app's canonical URL (the CR's spec.host).
type CreateTenantRequest ¶
type CreateTenantRequest struct {
Name string `json:"name"`
Plan string `json:"plan"`
// Admin, when set, is the owner identity (Kratos identity id or Hydra
// client id) granted admin on the new workspace in OpenFGA. Optional —
// omit to create the tenant row without a membership.
Admin string `json:"admin,omitempty"`
}
CreateTenantRequest is the POST /v1/tenants body.
type DatastoreEventFact ¶
type DatastoreEventFact struct {
SourceKey string
WorkspaceID string
DatastoreID string
Kind string
Type DatastoreEventFactType
At time.Time
ReasonCode string
BackupName string
BackupError string
VersionFrom string
VersionTo string
// Scheduled distinguishes a nightly backup from an on-demand one. nil
// means the fact has no schedule dimension at all (every availability
// fact), which is why it is a pointer rather than a plain bool.
Scheduled *bool
}
DatastoreEventFact is one closed, non-secret managed-datastore event record. SourceKey is a stable producer identity used for idempotency; it is never exposed directly. The backup/version/schedule detail fields are empty on every availability fact — they belong to the backup, restore, and major- upgrade kinds that share this table.
type DatastoreEventFactType ¶
type DatastoreEventFactType string
DatastoreEventFactType is the closed vocabulary of managed-datastore event facts — the Database/KeyValue sibling of ServiceEventFactType. The names come from eventvocab so the projector here, the retrievable event index, and the subscription picker cannot drift; migration 0107's fact_type CHECK is the persisted half of the same list.
const ( DatastoreFactPostgresAvailable DatastoreEventFactType = eventvocab.TypePostgresAvailable DatastoreFactKeyValueUnhealthy DatastoreEventFactType = eventvocab.TypeKeyValueUnhealthy DatastoreFactKeyValueAvailable DatastoreEventFactType = eventvocab.TypeKeyValueAvailable DatastoreFactPostgresBackupCompleted DatastoreEventFactType = eventvocab.TypePostgresBackupCompleted DatastoreFactPostgresBackupFailed DatastoreEventFactType = eventvocab.TypePostgresBackupFailed DatastoreFactPostgresRestoreSucceeded DatastoreEventFactType = eventvocab.TypePostgresRestoreSucceeded DatastoreFactPostgresRestoreFailed DatastoreEventFactType = eventvocab.TypePostgresRestoreFailed DatastoreFactPostgresUpgradeStarted DatastoreEventFactType = eventvocab.TypePostgresUpgradeStarted DatastoreFactPostgresUpgradeSucceeded DatastoreEventFactType = eventvocab.TypePostgresUpgradeSucceeded DatastoreFactPostgresUpgradeFailed DatastoreEventFactType = eventvocab.TypePostgresUpgradeFailed )
type Deploy ¶
type Deploy struct {
ID string `json:"id"`
AppID string `json:"appId"`
Trigger string `json:"trigger"`
Image string `json:"image,omitempty"`
ResolvedImage string `json:"-"`
RollbackOf string `json:"rollbackOf,omitempty"`
Generation int64 `json:"-"`
Commit string `json:"commit,omitempty"`
CommitMessage string `json:"commitMessage,omitempty"`
CommitAuthorAt *time.Time `json:"commitAuthorAt,omitempty"`
Status string `json:"status"`
// OverlapPending distinguishes the one latest-pending overlap slot from an
// active release that may itself report Render status queued while waiting
// for build capacity. It is store-internal and never exposed on a surface.
OverlapPending bool `json:"-"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
StartedAt *time.Time `json:"startedAt,omitempty"`
FinishedAt *time.Time `json:"finishedAt,omitempty"`
// PreDeployStatus is the pre-deploy command's outcome for this deploy
// (w1/m33): ” (no step) | 'running' | 'succeeded' | 'failed' | 'canceled'. The reconciler
// projects it from the App CR's status.preDeploy so a migration failure is
// distinguishable from a health-check failure (both close as update_failed).
PreDeployStatus string `json:"preDeployStatus,omitempty"`
// FailureReason is the human-actionable cause stamped when the reconciler
// closes this deploy failed (w9/011) — the App CR's Ready-condition message
// (crash loop with the $PORT hint, image-pull failure, build error) or a
// synthesized health-gate-timeout line. Empty on non-failed deploys. A bex
// extension beyond Render's deploy shape, like RollbackOf and
// PreDeployStatus.
FailureReason string `json:"failureReason,omitempty"`
// CancelReason is the neutral cause of a canceled deploy when the cancel
// was not user-initiated (w4/089): "Superseded by dep-…" when a newer
// release replaced this row (w5/m114: on every path that cancels it), or
// the suspend that ended its rollout (w4/m171). Empty for deploys.Cancel
// (w6/m52) and for every non-canceled status. Distinct from FailureReason so the
// dashboard can render it without text-destructive treatment.
CancelReason string `json:"cancelReason,omitempty"`
// StallReason is why an OPEN deploy is not progressing (w4/m112) — the
// operator's own Ready-condition diagnosis, projected onto the row each
// reconcile pass and cleared the moment the row goes terminal (from then
// on FailureReason/CancelReason own the story). Empty means "nothing to
// report": either the rollout is progressing normally or no diagnosis has
// been reached yet. Unlike FailureReason this is a live observation, not a
// verdict — it must never be read as "this deploy failed".
StallReason string `json:"stallReason,omitempty"`
// TriggeredBy is core.Identity.Subject for the caller that opened this row
// (w4/072) — same form as audit_events.caller. Empty for unattributed
// triggers (git push, deploy hook). Not a Render deploy field — kept off
// the REST wire; the events feed projects it as Details.TriggeredByUser.
TriggeredBy string `json:"-"`
}
Deploy is a row of `deploys` — one rollout attempt of an app, Render's deploy history (list_deploys/get_deploy). Trigger is "create" (the app's first deploy, opened by CreateApp), "api" (an explicit POST .../deploys), "deploy_hook" (the service's secret URL), "new_commit" (a git-push redeploy via the HMAC webhook), or "rollback" (w2/m10: a deploy created by Rollback, RollbackOf naming the source deploy it restores). Commit/CommitMessage (w9/001) are the resolved commit a build-from-git deploy ran, captured once at open time via the workspace's GitHub App connection — "" when unresolvable (omitted by the views, not faked). ResolvedImage is the image this deploy actually put into the cluster (backfilled by CloseDeploy once the deploy reaches live — "" until then, and forever for one that never does): the only field Rollback (w2/m10) trusts as a restore target, since Image alone is "" for a build-from-git deploy until the build resolves it. Generation is the App CR's metadata.generation this deploy runs under, captured once at open time — Cancel (w2/m10) derives its build-Job identity from this stored value rather than the App's current generation, which a later, unrelated spec write could have already moved past.
type DeployFilter ¶
type DeployFilter struct {
Statuses []string
CreatedBefore time.Time
CreatedAfter time.Time
UpdatedBefore time.Time
UpdatedAfter time.Time
FinishedBefore time.Time
FinishedAfter time.Time
Cursor string
Limit int
}
DeployFilter narrows ListDeploys using Render's status and exclusive created/updated/finished time bounds. Cursor resumes strictly after a previously returned deploy's id (newest-first keyset paging, stable under concurrent inserts); an unknown cursor yields an empty page. Limit is clamped here by clampPageLimit: <=0 (absent) and >core.MaxPageLimit both become core.MaxPageLimit — no query runs uncapped.
type DeployNotification ¶
type DeployNotification struct {
TenantID string
AppName string
Status string
// DeployID and CommitMessage enrich the notification email (w7/m44): the
// deploy id builds the "View Logs" deep link, the commit message names what
// was deploying. Both empty-friendly — an image-backed deploy has no commit.
DeployID string
CommitMessage string
// CommitSHA and RepoURL build the deploy email's "View commit" link (the
// repo's web commit URL). Both empty-friendly — an image-backed deploy has
// no repo, and a repo build without a resolved SHA has no target.
CommitSHA string
RepoURL string
// FailureReason is the operator's actionable diagnosis for a failed deploy
// (w7/m79) — the same string the deploy row and every API surface carry.
// The failure email is the first thing most people read when a deploy
// breaks, and it named the commit and linked the logs without ever saying
// what went wrong. Empty for a succeeded deploy and for a failure the
// operator could not diagnose.
FailureReason string
// NotifyOnFail is the legacy failure-only override. NotificationsToSend is
// the authoritative Render policy when non-empty.
NotifyOnFail string
NotificationsToSend string
}
DeployNotification is what DeployNotifier fans out — the closed deploy's identity and outcome, plus the App's per-service notification override (NotifyOnFail: w4/m21, docs/render-artifacts/notify-on-fail.md — "default"/"notify"/"ignore", or "" for an App created before the field existed, equivalent to "default"). A struct, not positional args, matching this codebase's convention for a cross-package "fan out an event" boundary (core.AuditEvent, webhooks' payload/DueWebhookDelivery) — so the next Render field lands as one more struct field, not a sixth positional arg.
type DeployNotifier ¶
type DeployNotifier interface {
NotifyDeploy(ctx context.Context, n DeployNotification)
}
DeployNotifier is called by the Reconciler when recordDeploy closes a deploy as succeeded or failed (w3/m9) — it fans the outcome out to the workspace's members by email, per each member's notification preferences. *notifications.Service satisfies it structurally (this package cannot import notifications: notifications imports store for NotificationsStore, so the dependency must run the other way, same shape as CloneSecreter). nil => no notifications (store-off / feature-off mode, byte-identical to before this milestone). Best-effort: implementations must not return an error — a flaky relay must never block reconciliation.
type DesiredApp ¶
type DesiredApp struct {
App
// Disk is the service's live disk, nil when it has none. The projector
// turns it into spec.disk; clearing it detaches the volume.
Disk *Disk
TenantName string
PrimaryHost string
Hosts []string
HostRedirects map[string]string
// EnvironmentIPAllowList is the member's environment inbound-IP layer
// (w4/m28), projected from the environments row at read time (the
// EnvironmentLayerCIDRs shape member CRs carry) so a projector-created CR
// starts consistent with the environments fan-out. Update-path syncs stay
// fan-out-owned: this field is deliberately NOT in applyOwnedSpec.
EnvironmentIPAllowList []string
}
type DevicePushSubscription ¶
type DevicePushSubscription struct {
TenantID string `json:"tenantId"`
Subject string `json:"subject"`
DeviceID string `json:"deviceId"`
SessionID string `json:"-"`
Provider string `json:"provider"`
Platform string `json:"platform"`
Token string `json:"-"`
TokenDigest string `json:"-"`
PreferenceID string `json:"preferenceRef,omitempty"`
RevokedAt *time.Time `json:"-"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
LastRegisteredAt time.Time `json:"lastRegisteredAt"`
}
DevicePushSubscription is one opaque provider destination. Token is an internal delivery capability: it is deliberately excluded from JSON and never projected by the caller-facing notifications service.
type Disk ¶
type Disk struct {
ID string `json:"id"`
TenantID string `json:"tenantId"`
AppID string `json:"appId"`
Name string `json:"name"`
MountPath string `json:"mountPath"`
SizeGB int32 `json:"sizeGB"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
DeletedAt *time.Time `json:"deletedAt,omitempty"`
}
DesiredApp is an apps row joined with everything projection needs: the owning tenant's name (part of the CR name) and the app's domains, primary first. Disk is a persistent volume attached to one service (docs/ADR082-persistent-disks.md). Size is PROVISIONED capacity: it is what the tenant is billed for whether the service is running, suspended, or scaled to zero, exactly as Render bills its disks and as Hetzner bills the volume underneath.
type DiskUsageRow ¶
DiskUsageRow is one service's provisioned disk GB-seconds within a metering window, attributed to the owning service (ADR082 D9 — the disk is a property of its service, so the meter keys on srv-, not dsk-).
type Domain ¶
type Domain struct {
ID string `json:"id"`
AppID string `json:"appId"`
Host string `json:"host"`
Primary bool `json:"primary"`
RedirectForName string `json:"redirectForName,omitempty"`
ClaimState string `json:"claimState"`
Challenge string `json:"challenge,omitempty"`
ChallengeVersion int64 `json:"challengeVersion"`
VerificationAttempts int64 `json:"verificationAttempts"`
LastVerificationAt *time.Time `json:"lastVerificationAt,omitempty"`
VerifiedAt *time.Time `json:"verifiedAt,omitempty"`
CreatedAt time.Time `json:"createdAt"`
}
Domain is a row of `domains` — a BYOD custom domain attached to an app. The primary domain becomes the App CR's spec.host, the rest spec.hosts. ClaimState is durable ownership admission; only verified rows are projected into App CRs. TLS/cert status remains observed from the cluster.
type DomainDeclaration ¶
DomainDeclaration is one desired host plus projection metadata. Replacing a declaration set preserves an unchanged row's claim state and challenge; newly declared hosts enter pending.
type DuePushDelivery ¶
type DuePushDelivery struct {
PushNotification
DeviceID string
SessionID string
Provider string
Platform string
// MemberRole is the recipient's CURRENT workspace role, joined at claim
// time ("" = membership gone) — the send-time input to the destination
// eligibility re-check (ADR087, w6/m137), read in the same query so the
// check cannot use stale authorization input (the
// ListActivePushSubscriptions pattern). Populated by
// ClaimDuePushDeliveries only; the receipts claim has no eligibility
// decision to make.
MemberRole string
Token string `json:"-"`
P256dh string `json:"-"`
Auth string `json:"-"`
TokenDigest string `json:"-"`
ClaimedUntil time.Time
AttemptCount int
AcceptedAt *time.Time
ReceiptDueAt *time.Time
ProviderTicketID string
}
DuePushDelivery is a leased per-device row joined to its current active token. The token remains internal and is excluded from JSON by construction.
type DueWebhookAttempt ¶
type DueWebhookAttempt struct {
WebhookAttempt
URL string
Secret string
TenantID string
EndpointName string
CreatedBy string
AutomaticAttemptCount int
}
DueWebhookAttempt is one pending attempt joined with its immutable parent request and endpoint delivery capability. AutomaticAttemptCount is the completed automatic-send count used by the worker's fixed retry budget; manual attempts never increment it.
type EnsureWatermark ¶
EnsureWatermark seeds a tailer's durable watermark at `at` if none exists yet and returns the current one — EnsureWebhookWatermark / EnsurePushWatermark.
type EnvGroup ¶
type EnvGroup struct {
ID string
TenantID string
Name string
EnvironmentID string // storage id ("env-…"); "" for a workspace-scoped group
ServiceLinks []string
EnvKeys []string
FileNames []string
CreatedAt time.Time
UpdatedAt time.Time
MetaVersion int64
Content EnvGroupContent
}
EnvGroup is a row of env_groups (w8/m114): every non-secret fact about one environment group. Its env and files maps stay in OpenBao.
type EnvGroupCommit ¶
EnvGroupCommit is what a content-changing save leaves on the row.
type EnvGroupContent ¶
type EnvGroupContent struct {
State string
Generation uint64
Version int64
Op *EnvGroupOp
}
EnvGroupContent is the coordination state of the group's content: which generation is committed, whether a save holds it, and that save's operation.
type EnvGroupOp ¶
type EnvGroupOp struct {
ID string
Kind string
Phase string
LeaseUntil time.Time
EnvChanged bool
FilesChanged bool
}
EnvGroupOp is the in-flight (or stranded) content operation recorded with the claim, so another API instance can recover it after its lease.
type EnvGroupRelease ¶
type EnvGroupRelease struct {
RepairRequired bool
RetainOperation bool // idle operation is durable evidence of pending value-map cleanup
Commit *EnvGroupCommit
}
EnvGroupRelease ends a claim. By default it releases the group idle and clears the operation, with the committed content unchanged. RepairRequired keeps the operation as the evidence recovery needs instead. RetainOperation leaves it on an idle row until recovery value-map cleanup succeeds. Commit records that the save changed content: the next generation, with these names.
type Environment ¶
type Environment struct {
ID string `json:"id"`
ProjectID string `json:"projectId"`
TenantID string `json:"tenantId"`
Name string `json:"name"`
CreatedAt time.Time `json:"createdAt"`
ProtectedStatus string `json:"protectedStatus"`
NetworkIsolationEnabled bool `json:"networkIsolationEnabled"`
IPAllowList []core.IPAllowListEntry `json:"ipAllowList"`
}
Environment is a row of `environments` — a named subset of a Project's services (e.g. staging/production). Services opt-in by having environment_id set in `apps`, the same shape Project's project_id uses.
ProtectedStatus/NetworkIsolationEnabled/IPAllowList (0023, w6/m19) are Render's protected-environment ACLs: ProtectedStatus is "protected" or "unprotected" (never empty — the column CHECK + DEFAULT enforce it); NetworkIsolationEnabled, when true, is what makes environments/service.go stamp core.LabelEnvironment onto member App CRs for the operator's environment-scoped NetworkPolicy; IPAllowList is fanned out onto member Database/KeyValue CRs' own Spec.IPAllowList (environments/acl.go). Since w4/m24 (0034) made ip_allow_list a jsonb array; w1/m56 (0053) normalized its remaining rows to required {cidrBlock, description} entries before the wire/store decoder became strict.
type EventFactWriter ¶
type EventFactWriter interface {
InsertServiceEventFact(ctx context.Context, fact ServiceEventFact) (bool, error)
}
EventFactWriter is the narrow producer seam used by apps and webhook code.
type FeedCursor ¶
type FeedCursor struct {
// contains filtered or unexported fields
}
FeedCursor is one tailer's cached position in the composed feed: the durable watermark, read once per process and advanced in memory by each committed page. The cache is a pure optimization under multiple replicas — a stale cursor re-reads rows the consumer's own idempotency key then dedupes, so it converges rather than duplicating.
The zero value is UNLOADED and is not usable until Load runs.
func (*FeedCursor) Load ¶
func (c *FeedCursor) Load(ctx context.Context, ensure EnsureWatermark, at time.Time) error
Load reads the durable watermark once per process, seeding it at `at` on first start so a newly enabled feature does not replay the feed's whole history. It is idempotent: once loaded, TailFeed's own commits keep the cache current, so later calls are a no-op and cost no round trip.
func (*FeedCursor) Loaded ¶
func (c *FeedCursor) Loaded() bool
Loaded reports whether the durable watermark has been read yet. A consumer that projects rows OUTSIDE the feed (the agent-session push scan) uses it to stay behind the feed dispatch that anchors the watermark.
type FeedPass ¶
type FeedPass[T any] struct { // Until is the read window's exclusive end, already held back by // FeedCommitLag. Until time.Time // Floor optionally starts the READ later than the durable watermark, when the // consumer can prove nothing before it is deliverable anyway (webhooks: the // oldest eligibility interval's start). A read-side skip only — nothing is // persisted, and the park check below still measures the durable cursor, so a // floor can never park the watermark forward over unread rows. Floor time.Time // Verbs is the audit arm's verb allow-list. Both audit arms filter with // `e.verb = ANY($4)`, so an empty Verbs matches NO verb and drops the audit // arm from the union entirely — it is "exclude audit rows", not "every verb". // That is what the push worker wants (it projects no audit source at all); // a consumer that does want audit events must name them. Verbs []string // Tenants scopes the read to the workspaces this consumer has recipients in. // Required: every arm filters with `= ANY($5)`, so an empty Tenants returns // zero rows — which this pager cannot tell from a quiet feed, and would answer // by PARKING the durable watermark past events it never read. A consumer with // no tenants must not run a pass at all. Tenants []string // Limit is one page's size. A full page means more may be waiting, so the // pass reads again immediately instead of costing a whole tick. Limit int // Park is how far the durable cursor may lag the read window before an // otherwise-quiet pass persists it forward; 0 => DefaultFeedPark. Park time.Duration // List reads one page after (afterAt, afterKey) — ListWebhookEvents. List func(ctx context.Context, afterAt time.Time, afterKey string, until time.Time, verbs, tenants []string, limit int) ([]WebhookEventRow, error) // Project turns one page into the consumer's batch. It runs per page, not per // row, so each consumer keeps its own fan-out loop (and its own clock read) // intact. Returning an empty batch is normal — the cursor still advances past // rows this consumer does not deliver. Project func(ctx context.Context, rows []WebhookEventRow) ([]T, error) // Commit inserts the batch AND advances the durable watermark to (at, key) in // ONE transaction — EnqueueWebhookDeliveries / EnqueuePushNotifications. Commit func(ctx context.Context, batch []T, at time.Time, key string) error }
FeedPass is one pass over the feed: the window to read, the per-consumer knobs, and the three seams (read, project, commit) that make a tailer what it is. T is the consumer's own batch item — a webhook delivery, a push notification — which only its own Project and Commit ever see.
type GitConnection ¶
type GitConnection struct {
WorkspaceID string `json:"workspaceId"`
InstallationID int64 `json:"installationId"`
AccountLogin string `json:"accountLogin"`
CreatedAt time.Time `json:"createdAt"`
}
GitConnection is a row of `git_connections`: a GitHub App installation a workspace has connected (docs/ADR026-github-integration.md, ADR078). Since w2/m162 the key is composite (workspace_id, installation_id) — the model is N:N. A workspace holds many connections (one per GitHub account/org it has installed the App on), and an installation may serve many workspaces, each binding carrying its own complete three-proof sequence (ADR078 §2). A re-connect of the same (workspace, installation) pair upserts; anything else adds a row.
type GitConnectionLimitError ¶
GitConnectionLimitError reports an atomic refusal of the per-workspace quota (BEX_MAX_GIT_CONNECTIONS_PER_WORKSPACE). It is typed so the GitHub service can preserve its public GIT_CONNECTION_LIMIT dialect without making this storage package depend on API errors.
func (*GitConnectionLimitError) Error ¶
func (e *GitConnectionLimitError) Error() string
type GitHubClaimCandidate ¶
type GitHubClaimCandidate struct {
InstallationID int64 `json:"installationId"`
AccountLogin string `json:"accountLogin"`
}
GitHubClaimCandidate is one proved member of a claim selection: an installation the authorizing user was shown to administer.
type GitHubClaimSelection ¶
type GitHubClaimSelection struct {
ID string
WorkspaceID string
Subject string
Candidates []GitHubClaimCandidate
ExpiresAt time.Time
}
GitHubClaimSelection is an ambiguous claim's deferred choice (ADR078 §3a): the candidate set the callback ALREADY proved, held for the few minutes the human needs to pick one. Subject- and workspace-bound, single-use, and closed — the selection can only ever resolve to a member of Candidates.
type GitHubConnectTransaction ¶
type GitHubConnectTransaction struct {
Nonce string
TenantID string
Subject string
// InstallationID is the claim flow's optional start-time selector (ADR078
// §3a): 0 = unspecified. When set it NARROWS the server-proved candidate set
// at the callback; it can never add to it, because an installation the OAuth
// user does not administer is not a candidate regardless of what was named
// here. Unused by the install flow, which gets its id from GitHub.
InstallationID int64
ExpiresAt time.Time
}
GitHubConnectTransaction is one in-flight connect attempt: who started it, for which workspace, and until when (w1/m67 F3). It exists because the GitHub install redirect returns to an anonymous callback, so without a server-side record the flow could only ever prove that SOMEONE authorized SOME workspace — never that the human completing the installation is the one who asked.
type GitInstallationWorkspaceLimitError ¶
GitInstallationWorkspaceLimitError is GitConnectionLimitError's mirror: an atomic refusal of the per-installation quota (BEX_MAX_WORKSPACES_PER_GIT_INSTALLATION, ADR078 §2). It bounds how many workspaces one installation may serve, and therefore how wide a single push delivery can fan out (ADR078 §4a).
func (*GitInstallationWorkspaceLimitError) Error ¶
func (e *GitInstallationWorkspaceLimitError) Error() string
type GitWebhookReplayClaim ¶
GitWebhookReplayClaim is one tenant- and signing-epoch-bound body digest.
type GitWebhookReplayEpoch ¶
GitWebhookReplayEpoch identifies one key class and exact signing secret that an API replica currently accepts.
func GitWebhookReplayEpochs ¶
func GitWebhookReplayEpochs(manualSecret, githubSecret string) []GitWebhookReplayEpoch
GitWebhookReplayEpochs derives the active epochs from configured secrets.
type GroupingPair ¶
GroupingPair names one blueprint-declared project/environment grouping.
type GroupingStore ¶
type GroupingStore interface {
ListProjects(ctx context.Context, tenantID string) ([]Project, error)
CreateProject(ctx context.Context, tenantID, name string) (Project, error)
ListEnvironments(ctx context.Context, projectID string) ([]Environment, error)
CreateEnvironment(ctx context.Context, projectID, tenantID, name string) (Environment, error)
SetEnvironmentACL(ctx context.Context, id, protectedStatus string, networkIsolationEnabled bool, ipAllowList []core.IPAllowListEntry) error
// CountWorkspaceGroupings returns the workspace's durable project and
// environment totals — the quota input (w8/m20 t002). Inside a grouping
// transaction the counts are consistent with the writes.
CountWorkspaceGroupings(ctx context.Context, tenantID string) (projects, environments int, err error)
}
GroupingStore is the project/environment surface the Blueprint apply loop writes through — satisfied by *PGStore (pool-backed, non-transactional) and by the RunGroupingTx callback's tx-scoped facade (w8/m20 t001: a mid-loop failure then rolls back every grouping row from that sync).
type HourlyRow ¶
type HourlyRow struct {
WorkspaceID string
ServiceID string
Kind string
Tier string // non-empty only for instance_seconds
ResourceKind string // ResourceKindService / ResourceKindPostgres / ResourceKindKeyValue
WindowStart time.Time // truncated to the hour (UTC)
Quantity int64
// SourceHealth is durable evidence for this exact window. nil means the
// caller has no modern coverage evidence (not healthy by default).
SourceHealth []UsageSourceObservation
}
HourlyRow is one window of usage for one resource + meter kind.
type Invite ¶
type Invite struct {
ID string `json:"id"`
TenantID string `json:"tenantId"`
Email string `json:"email"`
Role string `json:"role"`
// Token is the plaintext capability IN FLIGHT only: set by CreateInvite and
// RefreshInvite (the two mints) so the caller can email the link, never
// persisted — the row stores sha256(token) (w1/041), so reads return "".
Token string `json:"token"`
InvitedBy string `json:"invitedBy,omitempty"`
CreatedAt time.Time `json:"createdAt"`
ExpiresAt time.Time `json:"expiresAt"`
AcceptedAt *time.Time `json:"acceptedAt,omitempty"`
}
Invite is a row of `tenant_invites` — a pending workspace membership addressed to an email that has no OpenFGA subject yet. Redeemed on the recipient's first authenticated login (AcceptInvitesForEmail) into a tenant_members row + the matching workspace:<id> tuple.
type Job ¶
type Job struct {
ID string `json:"id"`
ServiceName string `json:"serviceName"`
TenantID string `json:"tenantId"`
StartCommand string `json:"startCommand"`
PlanID string `json:"planId"`
Status string `json:"status"`
CreatedAt time.Time `json:"createdAt"`
StartedAt *time.Time `json:"startedAt,omitempty"`
FinishedAt *time.Time `json:"finishedAt,omitempty"`
}
Job is a row of `jobs` — a one-off command run in a service's container (Render's /services/{id}/jobs). ServiceName is the bex App name (the workspace-scoped identifier the REST surface uses); TenantID scopes visibility. Status progresses pending → running → succeeded|failed|canceled.
type JobListFilter ¶
type JobListFilter struct {
Statuses []string
CreatedBefore time.Time
CreatedAfter time.Time
StartedBefore time.Time
StartedAfter time.Time
FinishedBefore time.Time
FinishedAfter time.Time
Cursor string
Limit int
}
JobListFilter narrows ListJobs. Limit 0 means no cap (returns all).
type MembershipGranter ¶
type MembershipGranter interface {
// GrantWorkspaceAdmin makes subject an admin of workspace:<tenantID> — the
// owner of a freshly minted tenant.
GrantWorkspaceAdmin(ctx context.Context, tenantID, subject string) error
// GrantWorkspaceMember makes subject a developer of workspace:<tenantID> —
// the role a minted API key gets (least privilege over every resource verb).
GrantWorkspaceMember(ctx context.Context, tenantID, subject string) error
// GrantWorkspaceRole grants subject an arbitrary role relation on
// workspace:<tenantID> — how a redeemed invite (internal/api/tenancy.go)
// seats the accepted member at its invited role (viewer/contributor/…/admin).
GrantWorkspaceRole(ctx context.Context, tenantID, subject, relation string) error
// RevokeWorkspaceMember removes subject's membership tuple for relation
// (e.g. "developer" for a revoked API key) — on revoke.
RevokeWorkspaceMember(ctx context.Context, tenantID, subject, relation string) error
}
MembershipGranter writes/removes a subject's workspace membership in OpenFGA (the authz write side). Implemented by the authz checker and injected here so the store + api packages keep no dependency on the authz client. Structural.
type NamespaceReconciler ¶
type NamespaceReconciler struct {
Client client.Client
Store Store
Resync time.Duration
// Identity names this control-plane instance (BEX_CP_IDENTITY). It is
// stamped on every namespace this reconciler provisions and is the scope
// pruneOrphans deletes within. Empty is read as DefaultControlPlaneIdentity.
Identity string
// contains filtered or unexported fields
}
NamespaceReconciler ensures every workspace has its per-tenant namespace(s) with base isolation objects, and prunes namespaces for deleted workspaces.
func NewNamespaceReconciler ¶
func NewNamespaceReconciler(cl client.Client, store Store) *NamespaceReconciler
NewNamespaceReconciler builds a reconciler with the default resync period. Identity is left zero, which identity() reads as the default.
func (*NamespaceReconciler) EnsureWorkspace ¶
func (r *NamespaceReconciler) EnsureWorkspace(ctx context.Context, workspaceID string) error
EnsureWorkspace synchronously converges one workspace's hosting and sandbox namespaces — the on-demand twin of the level-triggered ReconcileOnce. The create path calls it when a freshly minted workspace's tea-* namespace does not exist yet: nothing kicks this reconciler on workspace mint, so without it a first create within one resync period of onboarding fails with a namespace NotFound (w2/026). Failures here are NOT collected per-object the way ReconcileOnce collects per-workspace — the caller is a request waiting on this one workspace, so the first error returns immediately.
func (*NamespaceReconciler) Kick ¶
func (r *NamespaceReconciler) Kick()
Kick schedules an immediate reconcile (non-blocking, coalescing) — called after a workspace create/delete so provisioning is not a full resync away.
func (*NamespaceReconciler) ReconcileOnce ¶
func (r *NamespaceReconciler) ReconcileOnce(ctx context.Context) error
ReconcileOnce ensures a namespace (and its base isolation objects) for every workspace, then deletes managed namespaces whose workspace no longer exists. Per-workspace failures are collected, not fatal — one bad workspace can't block the rest, mirroring the App Reconciler's error discipline.
func (*NamespaceReconciler) Run ¶
func (r *NamespaceReconciler) Run(ctx context.Context)
Run reconciles until ctx is done.
type NotificationSettings ¶
type NotificationSettings struct {
ID string `json:"id"`
TenantID string `json:"tenantId"`
Subject string `json:"subject"`
DeployStarted bool `json:"deployStarted"`
DeploySucceeded bool `json:"deploySucceeded"`
DeployFailed bool `json:"deployFailed"`
PushPolicy json.RawMessage `json:"-"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
NotificationSettings is a row of `notification_settings` — one member's override of their per-workspace deploy-email preferences (w3/m9). A member with no row is not "opted out" — internal/notifications.Service applies the failure-only default when GetNotificationSettings returns ErrNotFound.
type NotifyRecipient ¶
type NotifyRecipient struct {
Subject string
DeployStarted bool
DeploySucceeded bool
DeployFailed bool
}
NotifyRecipient is one workspace member's resolved deploy-notification preferences — an explicit notification_settings row's values, or the default (started=false, succeeded=false, failed=true) for a member who never customized them. Returned by ListNotifyRecipients, the notification service's fan-out source: it names WHO to consider emailing, not their address (email resolution is the identity provider's job, outside the store).
type ObservedDatastoreState ¶
type ObservedDatastoreState struct {
DatastoreID string
WorkspaceID string
Kind string
At time.Time
Phase string
// Suspended is spec.suspended at observation time. Only the user-driven
// Suspend verb writes it, so it separates intentional downtime from an
// outage — a suspended datastore is observed availability-empty, never
// unavailable (the hibernated-App precedent).
Suspended bool
Availability string
AvailabilityObserved bool
ReasonCode string
ReadyTransitionAt time.Time
// AvailabilitySuppressed is the App snapshot's flag: a guard blanked this
// pass's conclusion, so it leaves the checkpoint's updated_at alone.
AvailabilitySuppressed bool
// LastBackup* mirror Database.status.lastBackup for t002 edge detection.
// Empty Name means the operator has not yet projected a terminal backup.
LastBackupName string
LastBackupPhase string // completed | failed
LastBackupError string
LastBackupScheduled bool
LastBackupAt time.Time
// Recovering is true when Spec.Recovery.SourceBackupServerName is set —
// the Database was created as a restore target (ADR009).
Recovering bool
// SpecVersion / CurrentVersion drive major-upgrade edges. UpgradeFailed
// is Ready=False with reason MajorVersionUpgradeFailed.
SpecVersion string
CurrentVersion string
UpgradeFailed bool
// Checkpoint-only extras round-tripped by the memStore (PG keeps them in
// dedicated columns). Producers leave them empty; RecordObserved* fills them.
RestoreOutcome string
UpgradeKey string
}
ObservedDatastoreState is the small level-triggered snapshot the control-plane reconciler derives from typed Database/KeyValue status — ObservedServiceState with the datastore's own identity (its immutable dpg-/red- metadata.name plus the workspace its CR label carries, since a datastore has no apps row to join through). Availability is empty outside a running datastore, healthy, or unhealthy, and carries the same AvailabilityObserved / ReadyTransitionAt contract the App snapshot documents.
type ObservedServiceState ¶
type ObservedServiceState struct {
AppID string
At time.Time
ServicePhase string
// Suspended is the App's spec.suspended at observation time — the ONLY
// signal that separates a user-driven suspension from free-tier idle
// auto-hibernation, since both observe the same Hibernated phase (w6/m47).
Suspended bool
Availability string
// AvailabilityObserved distinguishes "do not advance this dimension" from
// the observed empty state used while a service is hibernated.
AvailabilityObserved bool
ReasonCode string
InstanceID string
// ReadyTransitionAt is the Ready condition's LastTransitionTime backing
// this availability conclusion (w6/m41) — the operator-side timestamp the
// reconciler's stale-conclusion guard orders an unhealthy edge against the
// last recorded healthy checkpoint, and the value recorded as that
// checkpoint's healthy_transition_at when the conclusion is healthy. Zero
// when availability was not derived from a Ready condition (e.g.
// hibernation) or the condition carried no timestamp; the guard treats
// zero as "cannot order" and fails open toward recording. A crash under an
// open deploy carries the Serving condition's stop instead when it is later
// than Ready's (the rollout start), and both the edge and the stale guard
// use it (w5/083).
ReadyTransitionAt time.Time
// AvailabilitySuppressed marks a pass whose availability conclusion a
// reconciler guard blanked: the debounce's first unhealthy pass, or a
// stale re-read (suppressAvailability). Its phase still records, but it
// leaves the checkpoint's updated_at alone — the conclusion it carried
// began before this pass, so this pass is no floor for the next edge. It
// cannot be inferred from AvailabilityObserved: a pass that concluded
// nothing (a rollout in progress) is unobserved too, and its phase change
// must move the floor.
AvailabilitySuppressed bool
}
ObservedServiceState is the small level-triggered snapshot the control-plane reconciler derives from typed App status. Availability is empty outside a running service, healthy, or unhealthy.
type PGStore ¶
type PGStore struct {
Pool *pgxpool.Pool
// OpsWorkspaceID pins the platform ops workspace (BEX_OPS_WORKSPACE,
// docs/ADR088-platform-observability-ui.md §4). Two store-level guards key
// on it: invite redemption exempts it from seat/plan gating
// (planAllowsJoin — a cap must never silently block onboarding an
// operator), and account-deletion disposition classifies a sole-member ops
// workspace as blocked rather than delete (its teardown would lock every
// operator out of the observability UI). Empty (unset) => both guards inert.
OpsWorkspaceID string
// contains filtered or unexported fields
}
PGStore is the Postgres-backed Store over a pgx pool. It holds no business logic — validation happens in the API layer, classification of Postgres errors happens here.
func NewPGStore ¶
func (*PGStore) AbandonAgentDispatch ¶
func (s *PGStore) AbandonAgentDispatch(ctx context.Context, d AgentDispatch, now time.Time, failure AgentSessionFailure) (TerminalTurnFact, error)
AbandonAgentDispatch serializes against binding and atomically settles only the accepted turn that owns the intent. A canceled/deleted/newer session still leaves a cleanup tombstone, but its lifecycle is never overwritten. When the session row is terminalized (still unbound creating/redispatching), the returned TerminalTurnFact carries timing for metrics (w5/m88); a zero Turn means no session terminalization occurred (tombstone-only or already bound — the latter returns ErrNotFound).
func (*PGStore) AbandonBlueprintSync ¶
func (s *PGStore) AbandonBlueprintSync(ctx context.Context, runID string, now time.Time, reason string) (bool, error)
AbandonBlueprintSync settles one stale running run as interrupted (w8/m37 t004 + w8/m39). The run settles only from running; the Blueprint flips to error only while the abandoned generation still owns the claim (or for a legacy row stuck syncing with no claim). Clearing the claim also bumps execution_generation so a paused worker cannot restamp ownership or pass AssertBlueprintExecution after recovery frees the row — expiry alone is not proof of death, but the generation bump is the fence. Disconnected rows and newer generations are never overwritten. It returns false when another writer settled first — an idempotent no-op, not an error.
func (*PGStore) AbortRehydrate ¶
AbortRehydrate reverts a rehydrate whose background sandbox provisioning failed (ADR059 D4), CAS-moving resuming → hibernated so the snapshot (still present) can be retried by a later Resume. Guarded to a still-snapshot-bearing resuming row so it can't clobber a session that already rehydrated or was canceled.
func (*PGStore) AcceptInviteByToken ¶
AcceptInviteByToken redeems ONE invite by its emailed token for subject — the direct-accept path (w1/m33) that makes the invite link real: the recipient may have signed up under a DIFFERENT email than the one invited, which the login-time email match (AcceptInvitesForEmail) can never redeem. The token is the capability; possession of the link is the authorization. The lookup is by sha256(token) — only the hash is at rest (w1/041). Named refusals rather than a silent no-op: unknown token is ErrNotFound, an already-accepted or expired invite is ErrConflict (the caller can say WHY the link failed), and a workspace whose current plan cannot seat the invite refuses with ErrConflict exactly like the login path's silent skip — except here the caller is told, because they asked explicitly.
func (*PGStore) AcceptInvitesForEmail ¶
func (s *PGStore) AcceptInvitesForEmail(ctx context.Context, email, subject string) ([]Invite, error)
AcceptInvitesForEmail redeems every outstanding invite addressed to email into a tenant_members row for subject, marking each invite accepted — the whole set in ONE transaction so a signup either joins all its invited workspaces or none (a partial join with dangling invites would be confusing to re-drive). It returns the accepted invites so the caller can write the matching OpenFGA tuples (Postgres and OpenFGA aren't one transaction; the row is the source of truth, the tuple a best-effort follow-up the resolver re-drives). Idempotent: a second login finds no outstanding invites and returns an empty slice. An invite for someone who already belongs to the workspace is still marked accepted (so it stops lingering) but leaves their role untouched — see redeemInvite; each returned invite carries the EFFECTIVE membership role.
func (*PGStore) AcceptPushDelivery ¶
func (s *PGStore) AcceptPushDelivery(ctx context.Context, delivery DuePushDelivery, ticketID string, at, receiptDue time.Time) (bool, error)
AcceptPushDelivery records provider acceptance only (not device receipt). expectedClaim prevents a late worker from completing another worker's lease.
func (*PGStore) AccountDeletionTombstoned ¶
func (*PGStore) AddDomain ¶
AddDomain appends or updates a non-primary domain row for apps.IntentStore. Re-adding a host this app already owns updates redirect_for_name (used when an auto-paired sibling becomes explicit); another app owning it is a real cross-app conflict.
func (*PGStore) AddDomainClaim ¶
func (s *PGStore) AddDomainClaim(ctx context.Context, appID, host, redirectForName string) (Domain, bool, error)
AddDomainClaim creates one globally-unique pending claim. A same-App retry returns the exact existing row and challenge, updating only redirect intent; a claim owned by another App remains a conflict regardless of claim state.
func (*PGStore) AddMember ¶
AddMember records a PERSON's membership in a tenant (kind defaults to 'user') — the platform tenant-create path (store/api.go, an explicit Admin) and the invite-acceptance paths. API-key bindings do NOT come through here: BindClient writes its own row marked kind='machine' (w5/m103), because the two are the same table and shape but not the same thing.
func (*PGStore) AdmitBlueprintCreate ¶
func (s *PGStore) AdmitBlueprintCreate(ctx context.Context, b Blueprint, run BlueprintSync) (Blueprint, BlueprintSync, bool, error)
AdmitBlueprintCreate atomically upserts the Blueprint row for explicit creation and claims its initial sync: a conflicting active claim loses with ErrBlueprintSyncBusy, while a disconnected row is deliberately re-established under a fresh (bumped) generation so old queued execution cannot regain authority (w8/m37 t001). The run insert commits in the same transaction — admission failure means zero workload mutations.
The third return value reports that a DISCONNECTED row was revived — a new connection on an old row (w4/120). It matters because the row keeps its id and its sync history across the boundary, so the caller can say so; and because created_at is reset for exactly that case below. Reviving is the deliberate w8/m37 behavior and is not changed here: what changed is that a connection made seconds ago no longer reports an age of two days.
func (*PGStore) AdmitBlueprintSyncRun ¶
func (s *PGStore) AdmitBlueprintSyncRun(ctx context.Context, blueprintID, tenantID string, run BlueprintSync) (Blueprint, BlueprintSync, error)
AdmitBlueprintSyncRun atomically claims the active apply for an existing Blueprint and records its running run: generation bump, active_run_id claim, syncing status, and the run insert commit together. The caller must have resolved the row through the active lookup first, so zero claimed rows means a competing admission won the race → ErrBlueprintSyncBusy, never a silent second apply.
func (*PGStore) AdvanceAccountDeletion ¶
func (*PGStore) AdvanceEnvGroupOp ¶
func (s *PGStore) AdvanceEnvGroupOp(ctx context.Context, id, opID, phase string, leaseUntil time.Time) error
AdvanceEnvGroupOp records a later phase (and a renewed lease) for opID. It does not move the content version, so the claim's release still matches.
func (*PGStore) AgentSessionSSHActivity ¶
func (s *PGStore) AgentSessionSSHActivity(ctx context.Context, resourceID string, freshSince time.Time) (hasFreshOpen bool, lastEnded *time.Time, err error)
AgentSessionSSHActivity summarizes a resource's editor-SSH activity for the agent-session idle reaper (ADR059 D2 / w2/m67): whether a still-open session (ended_at IS NULL) started at or after `freshSince` exists — a live editor that pins the sandbox and must never be reaped — and the most recent close time (ended_at), which feeds the idle clock `now − max(last turn end, last SSH disconnect)`. `freshSince` (now − the SSH session cap) discards a leaked row a crashed gateway replica never closed, so an editor pin stays bounded. It subsumes ADR054 D6's earlier open-only check.
func (*PGStore) AgentSessionTranscript ¶
func (s *PGStore) AgentSessionTranscript(ctx context.Context, sessionID string, afterSeq int64, maxBytes int64, limit int) ([]AgentSessionTranscriptPart, error)
AgentSessionTranscript returns a session's stored parts in emission order, strictly after afterSeq (pass -1 for the whole transcript), capped at maxBytes of cumulative payload: rows are scanned in seq order and the read stops before the part that would exceed the budget, so a replay never materializes more than maxBytes even if the stored transcript ever outgrew it. limit additionally bounds the ROW count in SQL (ADR065 D2's poll-shaped page; <=0 ⇒ unbounded, the gateway replay's paging is byte-budgeted). It is the replay source for reattach and for terminal-session history (ADR047 D9): it reads the durable store, never the sandbox, so it works after the sandbox is gone.
func (*PGStore) AgentSessionTranscriptBytes ¶
AgentSessionTranscriptBytes returns the total stored payload bytes of a session's transcript. Write paths seed their cumulative quota counter from it so the stored transcript can never exceed MaxAgentSessionTranscriptBytes regardless of how many turns or attaches append.
func (*PGStore) AgentSessionTranscriptMaxSeq ¶
func (s *PGStore) AgentSessionTranscriptMaxSeq(ctx context.Context, sessionID string) (int64, bool, error)
AgentSessionTranscriptMaxSeq returns the highest stored seq for a session and whether any part exists. The gateway uses it to skip driver-replayed parts it has already persisted, so its live tee resumes exactly where the store ends.
func (*PGStore) AgentSessionTranscriptProgress ¶
func (s *PGStore) AgentSessionTranscriptProgress(ctx context.Context, sessionID string, turn int, indexes []int64) (int64, []int64, error)
AgentSessionTranscriptProgress reads cumulative bytes and existing requested ordinals from one snapshot. The result is bounded by indexes, not transcript size. Historical concurrent tees may leave holes, so max(part_index) alone cannot tell a harvest which parts are duplicates.
func (*PGStore) AgentSessionTranscriptTurnMaxIndex ¶
func (s *PGStore) AgentSessionTranscriptTurnMaxIndex(ctx context.Context, sessionID string, turn int) (int64, bool, error)
AgentSessionTranscriptTurnMaxIndex returns the largest stored local driver ordinal for one turn. A reconnect uses it to skip the prefix already replayed from Postgres while still accepting a fresh sandbox's ordinal zero.
func (*PGStore) AgentSessionTurns ¶
func (s *PGStore) AgentSessionTurns(ctx context.Context, sessionID string) ([]AgentSessionTurn, error)
AgentSessionTurns returns the durable user intents in chronological order. Prompt input is capped individually and by MaxAgentSessionPromptBytes across the session; callers still stream assistant parts page-wise.
func (*PGStore) AppendAgentSessionTranscript ¶
func (s *PGStore) AppendAgentSessionTranscript(ctx context.Context, sessionID string, parts []AgentSessionTranscriptPart) error
AppendAgentSessionTranscript idempotently persists teed transcript parts. Driver ordinals restart at zero for every sandbox, so identity is the turn-local (session_id, turn, part_index). seq remains a monotonic per-session cursor allocated while holding a transaction-scoped advisory lock; concurrent gateway replicas therefore cannot collide or reorder the replay cursor.
func (*PGStore) AssertBlueprintExecution ¶
func (s *PGStore) AssertBlueprintExecution(ctx context.Context, id, tenantID string, generation int64, runID string) error
AssertBlueprintExecution confirms the caller's admitted (generation, runID) still owns the Blueprint claim (w8/m39). Callers invoke it before each resource-mutation family so a retired worker cannot start further side effects. Zero rows → ErrBlueprintSyncBusy. The same WHERE shape as Stage / Complete is the sink precondition: a Get-then-act race loses here rather than writing under a freed claim.
func (*PGStore) BackfillAppType ¶
BackfillAppType heals a pre-w6/m46 row that predates the type column. Only matching an empty type is the whole safety story: it makes the write idempotent, race-free between concurrent projector passes, and incapable of changing a type the create path already recorded (spec.type is immutable, so a disagreement would be corruption, not an update).
func (*PGStore) BeginAccountDeletion ¶
func (s *PGStore) BeginAccountDeletion(ctx context.Context, subject, email string, machineSubjects []string) (AccountDeletion, error)
BeginAccountDeletion serializes against membership mutations with the same per-workspace advisory locks they use, rechecks disposition, and writes the tombstone plus immutable workspace plan in one transaction.
func (*PGStore) BeginAgentSessionTurn ¶
func (s *PGStore) BeginAgentSessionTurn(ctx context.Context, id, prompt, deliveryMode, phase, status string) (AgentSession, error)
BeginAgentSessionTurn atomically records an accepted follow-up prompt and moves the session into its asynchronous dispatch phase. The public turn count advances here, with prompt acceptance, so provisioning failure cannot erase or renumber what the user submitted.
func (*PGStore) BeginRehydrate ¶
func (s *PGStore) BeginRehydrate(ctx context.Context, id, prompt, deliveryMode string) (AgentSession, error)
BeginRehydrate claims a hibernated session for rehydration on Resume/Steer (ADR059 D4), CAS-moving it hibernated → resuming while KEEPING the snapshot fields (the fresh sandbox is provisioned in the background; the snapshot is only consumed once it is up). A concurrent retention delete or double resume finds no `hibernated` row (ErrNotFound). The row's sandbox_id stays empty until RehydrateAgentSession adopts the new one.
func (*PGStore) BillingCustomerID ¶
func (s *PGStore) BillingCustomerID(ctx context.Context, workspaceID string, livemode bool) (string, bool, error)
BillingCustomerID returns the persisted same-mode Customer for a workspace. A mapping from the other Stripe mode is invisible: its Customer cannot be retrieved with the current key.
func (*PGStore) BillingExportReport ¶
func (*PGStore) BillingExportStats ¶
func (*PGStore) BindClient ¶
BindClient records that an API key belongs to a tenant: a tenant_members row with the key's client_id as subject and role "developer" — least privilege that still covers every resource verb, matching the FGA grant apikeys mints (w1/m9). The PK is (tenant_id, subject), so a rebind to a DIFFERENT tenant is not a PK conflict — delete any existing binding for this client first, in the same transaction, so a client is bound to at most one tenant.
func (*PGStore) BindGitConnection ¶
func (s *PGStore) BindGitConnection(ctx context.Context, c GitConnection, maxConnections, maxWorkspaces int) (GitConnection, error)
BindGitConnection performs the same-pair check, both quota checks, and the write in one transaction. The transaction-scoped advisory locks work across API replicas and distinct pool connections.
SECURITY: the counts and the insert MUST share this transaction. A standalone count beforehand lets two concurrent callbacks at limit-1 both pass. Two locks are taken because N:N has two independent caps — the workspace's fan-in and the installation's fan-out — and they are always acquired workspace-first so concurrent binds cannot deadlock on opposite orders. A same-pair reconnect is detected before either count and remains exempt from both.
func (*PGStore) BlueprintResourceOwners ¶
func (s *PGStore) BlueprintResourceOwners(ctx context.Context, tenantID string) (map[BlueprintResourceKey]string, error)
BlueprintResourceOwners maps every claimed resource in the workspace to its owning blueprint id, so an ownership check over a whole manifest costs one query rather than one per declared resource (w5/114).
func (*PGStore) BlueprintWorkspace ¶
BlueprintWorkspace returns the workspace that owns a live (non-disconnected) Blueprint id, or ErrNotFound. It is a routing read only: the caller must still authorize against the returned workspace before revealing anything, exactly as a service or env-group id resolves to its own workspace (w4/m169).
func (*PGStore) BuiltDeployIDs ¶
func (s *PGStore) BuiltDeployIDs(ctx context.Context, appID string, limit int) (map[string]bool, error)
BuiltDeployIDs is the set of an App's deploys that actually ran a build — the deploys the Events tab shows a Build started entry for. The deploy-log narrator reads it so it cannot tell a build story for a deploy the Events feed has no build record of (w4/m110 t004): a rollback and a rollout that reuses the active artifact both open a deploy row with real timestamps, and timestamps alone cannot distinguish them from a build.
Scoped to one App and capped like every other feed read. An App with no build facts yields an empty (non-nil) set, never an error.
func (*PGStore) BumpOAuthRevocation ¶
BumpOAuthRevocation advances the durable invalidation marker shared by every bex-api replica. clock_timestamp() ensures two bumps in one transaction are ordered by wall time rather than the transaction start time.
func (*PGStore) CancelWorkspaceCreationAttempt ¶
func (*PGStore) CheckBillingMutationAllowed ¶
CheckBillingMutationAllowed is the feature-service gate for billable creates, upgrades, deploys, and tenant resume attempts. Grace remains usable; the gate begins only once enforcement is due/active.
func (*PGStore) ClaimAccountDeletions ¶
func (*PGStore) ClaimAgentSessionForHibernation ¶
func (s *PGStore) ClaimAgentSessionForHibernation(ctx context.Context, id string) (AgentSession, error)
ClaimAgentSessionForHibernation atomically claims a finished session for the hibernation reclaim (ADR059 D3), moving it from a terminal phase holding a sandbox into the transient `hibernating` phase so exactly one Completer replica runs the snapshot. It is CAS-guarded like the reap path: only a completed/failed row with a live sandbox_id matches, so a concurrent Cancel or a second replica finds no row (ErrNotFound) and backs off. Returns the claimed row (its sandbox_id still set — the snapshot exec needs it).
func (*PGStore) ClaimBillingNotifications ¶
func (*PGStore) ClaimBlueprintAutoSyncIntents ¶
func (s *PGStore) ClaimBlueprintAutoSyncIntents(ctx context.Context, limit int, lease time.Duration) ([]BlueprintAutoSyncIntent, error)
ClaimBlueprintAutoSyncIntents leases a bounded batch of pending (or stale-claimed) intents for the worker. lease is how long a claimed row stays exclusive before another replica may reclaim it after process loss.
func (*PGStore) ClaimBlueprintResource ¶
func (s *PGStore) ClaimBlueprintResource(ctx context.Context, tenantID string, kind BlueprintClaimKind, name, blueprintID, expectedOwner string) error
ClaimBlueprintResource inserts or keeps ownership of (tenant, kind, name). expectedOwner "" adopts only when the row is absent or already ours; non-empty authorizes transfer only while that owner still holds the claim (intervening takeover cannot use a stale confirmation).
func (*PGStore) ClaimDueBillingLifecycle ¶
func (s *PGStore) ClaimDueBillingLifecycle(ctx context.Context, now time.Time, lease time.Duration) (BillingLifecycle, bool, error)
ClaimDueBillingLifecycle leases one enforcement/recovery row. FOR UPDATE SKIP LOCKED makes the claim safe across bex-api replicas.
func (*PGStore) ClaimDuePushDeliveries ¶
func (s *PGStore) ClaimDuePushDeliveries(ctx context.Context, now, leaseUntil time.Time, limit int) ([]DuePushDelivery, error)
ClaimDuePushDeliveries leases disjoint rows across worker replicas. A lease is not an attempt; a crash makes the row visible again after leaseUntil.
func (*PGStore) ClaimDuePushReceipts ¶
func (*PGStore) ClaimDueWebhookAttempts ¶
func (s *PGStore) ClaimDueWebhookAttempts(ctx context.Context, now, leaseUntil time.Time, limit int) ([]DueWebhookAttempt, error)
ClaimDueWebhookAttempts leases disjoint pending reservations across worker replicas. The attempt identity already exists before the claim; a crashed worker leaves it pending and reclaimable after leaseUntil rather than inventing evidence for a network exchange that might never have happened.
func (*PGStore) ClaimEnvGroupContent ¶
func (s *PGStore) ClaimEnvGroupContent(ctx context.Context, id string, expectedVersion int64, op EnvGroupOp) (int64, error)
ClaimEnvGroupContent marks the group busy for op if its content version is still expectedVersion, recording the operation in the same statement. Returns the claim's content version.
func (*PGStore) ClaimGitWebhookDelivery ¶
func (s *PGStore) ClaimGitWebhookDelivery(ctx context.Context, claim GitWebhookReplayClaim) (bool, error)
ClaimGitWebhookDelivery atomically claims a signed body within its tenant and signing-key epoch. A per-scope advisory transaction lock makes the count-before-insert cap exact even when distinct deliveries arrive on several replicas concurrently. The digest remains globally unique for rolling-upgrade compatibility with pre-0104 replicas and because the signed body itself embeds the installation/repository identity.
func (*PGStore) ClaimRoleReconciliations ¶
func (s *PGStore) ClaimRoleReconciliations(ctx context.Context, limit int) ([]RoleReconciliation, error)
ClaimRoleReconciliations leases due rows before returning them. SKIP LOCKED partitions work safely across bex-api replicas; exact-role writes remain idempotent if a worker dies after applying OpenFGA but before acknowledging.
func (*PGStore) ClaimShellNonce ¶
func (s *PGStore) ClaimShellNonce(ctx context.Context, nonce string, expiresAt time.Time) (bool, error)
ClaimShellNonce atomically claims a web-shell exec-ticket nonce across ALL gateway replicas (w1/042 L7): INSERT … ON CONFLICT DO NOTHING lets exactly one claimant win; a second redemption of the same ticket — on any replica — finds the row present and is refused. Expired rows are pruned here rather than by a janitor: shells are human-driven and rare, so the piggybacked DELETE keeps the table at "tickets minted in the last ~90s" for free.
func (*PGStore) ClaimUnboundBillingCustomers ¶
func (s *PGStore) ClaimUnboundBillingCustomers(ctx context.Context, livemode bool, horizon time.Duration, limit int) ([]BillingProviderMapping, error)
ClaimUnboundBillingCustomers claims a bounded batch of same-mode mappings that never bound a payment method, are not comped, have not opened Checkout within the horizon, and were not examined in the last day. The claim stamps reclaim_checked_at, so a kept mapping waits a day before it is re-read.
func (*PGStore) ClaimWebhookFailureNotice ¶
func (s *PGStore) ClaimWebhookFailureNotice(ctx context.Context, endpointID string, now, threshold time.Time) (bool, error)
ClaimWebhookFailureNotice compare-and-sets the endpoint's notified_at marker: it succeeds (returns true, records `now`) only when no notice has been sent since `threshold` (now − suppression window), so across replicas and restarts exactly one worker emails per window (w1/m58). NULL notified_at (never notified, or cleared on re-enable) always claims.
func (*PGStore) CleanupAccountSubject ¶
CleanupAccountSubject removes active personal state and anonymizes retained workspace/audit provenance. It runs only after workspace disposition.
func (*PGStore) ClearAgentSessionSandbox ¶
ClearAgentSessionSandbox blanks a session's sandbox_id after its sandbox has been torn down (ADR054 D6), dropping it from the deferred-teardown reaper's working set and releasing the live-sandbox unique index. It never changes phase/status — the session stays terminal — and is a no-op if already blank.
func (*PGStore) ClearPreviousSandboxTeardown ¶
func (s *PGStore) ClearPreviousSandboxTeardown(ctx context.Context, sessionID string, turn int) error
ClearPreviousSandboxTeardown blanks previous_sandbox_id on a dispatch intent after the predecessor sandbox is confirmed gone (w5/m99 t004). When the intent is an abandoned cleanup-only tombstone with no other work, the row is deleted so the table does not grow without bound.
func (*PGStore) CloseDeploy ¶
func (*PGStore) CloseUsageSourceStreams ¶
func (s *PGStore) CloseUsageSourceStreams(ctx context.Context, ref UsageResourceRef, through time.Time) error
CloseUsageSourceStreams ends one resource's coverage streams at through. A deleted resource's last health write reopens its streams, and the inventory reconcile would close them at the current boundary rather than its deletion hour, leaving phantom gaps after a catch-up (w4/m173).
func (*PGStore) CompactUsage ¶
CompactUsage folds every usage_hourly row with window_start < before into usage_monthly and purges it (docs/ADR023-usage-metering.md § Retention). A single statement, so purge and aggregate are atomic and a re-run is a no-op; the additive ON CONFLICT means a straggler row compacted on a later pass adds to its month rather than overwriting it.
func (*PGStore) CompleteAgentSessionTurn ¶
func (s *PGStore) CompleteAgentSessionTurn(ctx context.Context, sessionID string, turn int, complete, truncated bool, reason string) error
CompleteAgentSessionTurn records whether the persisted assistant transcript is complete. A false complete value is intentional: the row still preserves the accepted user prompt while making loss/truncation visible to consumers.
func (*PGStore) CompleteAppCreation ¶
CompleteAppCreation releases an explicitly prepared initial configuration. Repeating completion is safe; a concurrently deleted row is never recreated.
func (*PGStore) CompleteBillingLifecycleWork ¶
func (*PGStore) CompleteBillingNotification ¶
func (*PGStore) CompleteBlueprintAutoSyncIntent ¶
CompleteBlueprintAutoSyncIntent marks a claimed intent successfully processed.
func (*PGStore) CompleteBlueprintSync ¶
func (s *PGStore) CompleteBlueprintSync(ctx context.Context, id, tenantID, runID string, generation int64, state string, completedAt time.Time, errMsg *string) (Blueprint, error)
CompleteBlueprintSync commits a run's terminal state together with the Blueprint status projection, computed from the CURRENT row (w8/m37 t005): success lands in_sync unless auto-sync is currently off (paused); failure lands error unless auto-sync is currently off (paused). The run settles only from running under the admitted generation, and the Blueprint updates only while the admitted generation still owns the claim on a connected row — a stale completion cannot overwrite a newer run or a disconnected row, and a failed persistence never reports success.
func (*PGStore) CompletePushDelivery ¶
func (s *PGStore) CompletePushDelivery(ctx context.Context, delivery DuePushDelivery, at time.Time) (bool, error)
CompletePushDelivery marks a Web Push send delivered without a receipt poll.
func (*PGStore) CompleteRoleReconciliation ¶
func (s *PGStore) CompleteRoleReconciliation(ctx context.Context, tenantID, subject, role string) error
CompleteRoleReconciliation acknowledges only the role that was applied. A concurrent newer role upsert remains queued instead of being deleted by a stale worker.
func (*PGStore) CompleteWebhookAttempt ¶
func (s *PGStore) CompleteWebhookAttempt(ctx context.Context, completion WebhookAttemptCompletion) (bool, error)
CompleteWebhookAttempt performs the pending -> terminal transition exactly once and updates the logical notification in the same transaction. A failed automatic send reserves its next retry atomically. A failed manual send restores the automatic reservation it parked, without consuming the parent's automatic attempt_count; manual success closes the notification and drops it.
func (*PGStore) ConsumeGitHubClaimSelection ¶
func (s *PGStore) ConsumeGitHubClaimSelection(ctx context.Context, id string) (GitHubClaimSelection, error)
ConsumeGitHubClaimSelection atomically claims an unexpired selection and returns it. Single-use by construction: the row is DELETEd in the same statement that reads it, so a replayed selection — on any replica — finds nothing.
func (*PGStore) ConsumeGitHubConnectTransaction ¶
func (s *PGStore) ConsumeGitHubConnectTransaction(ctx context.Context, nonce string) (GitHubConnectTransaction, error)
ConsumeGitHubConnectTransaction atomically claims an unexpired attempt and returns it. Single-use by construction: the row is DELETEd in the same statement that reads it, so a replayed callback — on any replica — finds nothing. ErrNotFound covers unknown, already-consumed, and expired alike, so a caller cannot distinguish them (and neither can an attacker probing).
func (*PGStore) CountAppsForTenant ¶
CountAppsForTenant counts a workspace's apps (all of them, including suspended — the `apps` row exists whether or not spec.suspended) for the per-plan service cap.
func (*PGStore) CountEnvGroups ¶
CountEnvGroups is the workspace's group count, for the create quota.
func (*PGStore) CountGitConnections ¶
CountGitConnections returns how many connections a workspace holds — the per-workspace quota check (BEX_MAX_GIT_CONNECTIONS_PER_WORKSPACE, ADR075 §2).
func (*PGStore) CountInvites ¶
CountInvites counts a workspace's OUTSTANDING invites (unaccepted, unexpired) — the count the seat-cap check consults, so the Invite verb doesn't SELECT full rows just to length them. Mirrors CountTenantMembers.
func (*PGStore) CountLiveAgentSessionSandboxes ¶
func (s *PGStore) CountLiveAgentSessionSandboxes(ctx context.Context, workspaceID string, phases []string) (int, error)
CountLiveAgentSessionSandboxes counts the workspace's sessions currently in a live phase (`phases`) that hold a sandbox id — the concurrent live-sandbox working set the ADR059 D6 per-workspace cap bounds. A terminal session in its idle grace still carries a sandbox_id but is not in a live phase, so it does not count against the create/steer/resume cap. Trusted read used by the dispatch-time admission check; a small best-effort overshoot is acceptable (the dispatch CAS and reconcile bound it), so it takes no lock.
func (*PGStore) CountPinnedAgentSessions ¶
CountPinnedAgentSessions counts a workspace's pinned sessions — the ADR059 D5 pin-quota dimension. Best-effort like the live cap; no lock.
func (*PGStore) CountRegistryCredentials ¶
CountRegistryCredentials returns how many stored registry credentials the workspace owns. It backs the feature's per-workspace admission quota.
func (*PGStore) CountTenantAdmins ¶
CountTenantAdmins counts a workspace's admin members — the guard the role-change/remove verbs consult so the last admin can't demote or remove itself, leaving a workspace nobody can administer.
func (*PGStore) CountTenantMembers ¶
CountTenantMembers counts a workspace's members — the guard w4/m12's invite verb consults before adding one (Hobby's single-member cap).
func (*PGStore) CountUnreadPushNotifications ¶
func (s *PGStore) CountUnreadPushNotifications(ctx context.Context, tenantID, subject string, excludeEvents []string) (int64, error)
CountUnreadPushNotifications counts the caller's unread inbox items under the same excludeEvents filter as ListOwnPushNotifications.
func (*PGStore) CountWorkspaceDomainClaims ¶
CountWorkspaceDomainClaims returns how many claims (pending and verified) one workspace holds across all its apps — the per-workspace custom-domain quota check (BEX_MAX_CUSTOM_DOMAINS_PER_WORKSPACE, codex-security round 18), mirroring CountGitConnections.
func (*PGStore) CountWorkspaceGroupings ¶
CountWorkspaceGroupings implements the pool-backed count (also used outside a transaction, e.g. by reads).
func (*PGStore) CountWorkspacesForSubjectPlan ¶
func (s *PGStore) CountWorkspacesForSubjectPlan(ctx context.Context, subject, plan string) (int, error)
CountWorkspacesForSubjectPlan counts how many workspaces of a given plan the subject owns — the per-user plan cap check (Render's five-Hobby-workspace limit). Membership, not just ownership, is the unit Render caps on.
func (*PGStore) CreateAgentSession ¶
func (s *PGStore) CreateAgentSession(ctx context.Context, in AgentSession) (AgentSession, error)
CreateAgentSession mints the only valid agent-session id kind and persists the row in creating phase before any external OpenFGA/OpenSandbox mutation.
func (*PGStore) CreateApp ¶
CreateApp inserts the app row and opens its first deploy row (trigger "create") in one transaction — every store-managed app has exactly one deploy the instant it exists, so ListDeploys is never truthfully empty for an app the reconciler is about to project (t001: "creating a service records deploy #1"). Bundling a second, related insert into the row's own creation is the same precedent CreateTenantWithMember already sets (a tenant plus its owner membership, one transaction) — an invariant the type itself guarantees is safer here than trusting every future caller to remember a second call.
The slug (the globally-unique public subdomain, w4/m19 t002) starts as the bare name; a apps_slug_idx collision (some other tenant already claimed that bare name) makes this retry with a random "-xxxx" suffix, matching Render's own onrender.com behavior. A tenant_id+name collision (the caller's own workspace already has this name) is a real conflict and is never retried — it classifies straight to ErrConflict.
func (*PGStore) CreateDeploy ¶
func (*PGStore) CreateDisk ¶
func (s *PGStore) CreateDisk(ctx context.Context, tenantID, appID, name, mountPath string, sizeGB int32) (Disk, error)
CreateDisk inserts the disk and opens its first size period in one transaction, so a disk can never exist unmetered or be metered before it exists.
func (*PGStore) CreateDomain ¶
func (*PGStore) CreateEnvironment ¶
func (s *PGStore) CreateEnvironment(ctx context.Context, projectID, tenantID, name string) (Environment, error)
CreateEnvironment inserts a new environment seeded with the allow-all inbound rule pair (w4/m28): empty means deny-all now, so a fresh environment must start explicitly open (Render's dashboard seeds 0.0.0.0/0 the same way). CreateWithACL's explicit list — including an explicit deny-all [] — overwrites the seed via SetEnvironmentACL.
func (*PGStore) CreateGitHubClaimSelection ¶
func (s *PGStore) CreateGitHubClaimSelection(ctx context.Context, sel GitHubClaimSelection) error
CreateGitHubClaimSelection records a proved candidate set. Expired rows are pruned here rather than by a janitor, matching the connect transaction: the flow is human-driven and rare, so the piggybacked DELETE keeps the table at "selections offered in the last few minutes" for free.
func (*PGStore) CreateGitHubConnectTransaction ¶
func (s *PGStore) CreateGitHubConnectTransaction(ctx context.Context, t GitHubConnectTransaction) error
CreateGitHubConnectTransaction records a connect attempt. Expired rows are pruned here rather than by a janitor: the flow is human-driven and rare, so the piggybacked DELETE keeps the table at "attempts started in the last few minutes" for free.
func (*PGStore) CreateInvite ¶
func (s *PGStore) CreateInvite(ctx context.Context, tenantID, email, role, token, invitedBy string, expiresAt time.Time) (Invite, error)
CreateInvite records a pending invite (ErrConflict when an outstanding invite already targets the same (tenant, email) — the partial unique index). The id is minted here; expiresAt is computed by the caller so the TTL is one policy in the service layer. Only sha256(token) is stored (w1/041); the returned Invite carries the plaintext so the caller can email the link.
func (*PGStore) CreateProject ¶
func (*PGStore) CreateRegistryCredential ¶
func (s *PGStore) CreateRegistryCredential(ctx context.Context, workspaceID, name, host, username, createdBy string, expiresAt *time.Time) (RegistryCredential, error)
CreateRegistryCredential mints a new credential row (id.New(id.RegistryCredential)) and inserts it. The caller writes the secret to OpenBao itself — this method only ever touches metadata. An empty name defaults to host.
func (*PGStore) CreateRollbackDeploy ¶
func (s *PGStore) CreateRollbackDeploy(ctx context.Context, appID, image, rollbackOf string, generation int64, commit CommitInfo, triggeredBy string) (Deploy, error)
CreateRollbackDeploy opens a "rollback"-triggered deploy row (w2/m10): unlike a normal trigger, the restored image is already fully known (it ran live before), so resolved_image is set immediately rather than waiting for the reconciler's write-back to backfill it on convergence. commit is the TARGET deploy's commit metadata (w9/001) — a rollback re-runs what the restored deploy built, so its provenance is copied, never re-resolved against a branch that has since moved.
func (*PGStore) CreateSSHKey ¶
CreateSSHKey persists a canonicalized public key. The fingerprint's unique constraint is the ambiguity guard: one SSH handshake must map to one subject.
func (*PGStore) CreateTenant ¶
func (*PGStore) CreateTenantWithMember ¶
func (s *PGStore) CreateTenantWithMember(ctx context.Context, identityID, plan string) (Tenant, error)
CreateTenantWithMember mints a personal tenant for an identity in one transaction. The INSERT ... ON CONFLICT (owner_identity_id) DO UPDATE is the race-safe idempotent gate: a concurrent first login that already inserted a tenant for this identity makes this a no-op that returns the winner's row (DO UPDATE exists only to surface RETURNING for the existing row). The membership upsert is idempotent for the same reason. The tenant name is its id — a unique DNS-safe placeholder.
func (*PGStore) CreateWebhookEndpoint ¶
func (s *PGStore) CreateWebhookEndpoint(ctx context.Context, tenantID, name, url, secret string, eventTypes []string, enabled bool, createdBy string) (WebhookEndpoint, error)
CreateWebhookEndpoint mints a new endpoint row (id.New(id.Webhook)) and inserts it. The secret is minted by the caller (internal/webhooks owns the format) and returned on this one response only. Name is already validated by the service and is stored verbatim.
The count check and the insert share ONE transaction, and the count takes a row-level lock over the workspace's existing endpoints, so two concurrent creates at the boundary cannot both observe "one slot left" and both take it.
func (*PGStore) CreateWorkspace ¶
func (s *PGStore) CreateWorkspace(ctx context.Context, name, plan, ownerSubject string) (Tenant, error)
CreateWorkspace inserts a tenant row and the owner's `admin` membership in one transaction — the atomic create the workspaces feature writes through, so a workspace never exists without an owner (the OpenFGA grant is a separate, caller-driven step, see workspaces.Service.Create). Name collisions and the plan CHECK surface as ErrConflict / ErrInvalid via classify.
func (*PGStore) CreateWorkspaceCreationAttempt ¶
func (s *PGStore) CreateWorkspaceCreationAttempt(ctx context.Context, subject, name, plan, billingEmail string, paymentRequired bool, expiresAt time.Time) (WorkspaceCreationAttempt, error)
CreateWorkspaceCreationAttempt reserves ids but creates no tenant. The attempt is deliberately subject-bound and short-lived.
func (*PGStore) CurrentUsageCoverage ¶
func (s *PGStore) CurrentUsageCoverage(ctx context.Context, workspaceID string, now time.Time) (UsageCoverage, error)
CurrentUsageCoverage aggregates only explicit source-health evidence. It intentionally cannot infer health from legacy usage rows or monthly totals.
func (*PGStore) DeferAgentDispatchCleanup ¶
func (*PGStore) DeferPreviousSandboxTeardown ¶
func (s *PGStore) DeferPreviousSandboxTeardown(ctx context.Context, sessionID string, turn int, next time.Time) error
DeferPreviousSandboxTeardown pushes next_check_at on a previous-sandbox teardown intent after a failed terminate, without requiring abandoned=true (active steers keep the intent live while the new turn provisions).
func (*PGStore) DeleteAgentSession ¶
DeleteAgentSession removes a session row; its transcript parts cascade with it. Two callers: the create-path compensation (a row whose OpenFGA parent tuple could not be established) and the ADR065 D4 public delete verb — the service layer owns the D4 guardrails (fresh authorization, no-live-sandbox phase gate, snapshot-blob-before-row ordering). Cancel still preserves the audit/history row.
func (*PGStore) DeleteDisk ¶
DeleteDisk soft-deletes and closes the open size period in one transaction: the instant billing stops is the same instant the volume is released.
func (*PGStore) DeleteDomain ¶
func (*PGStore) DeleteEnvGroup ¶
DeleteEnvGroup removes the row and returns it as it was, so the caller can detach its links and remove its Secrets and OpenBao maps.
func (*PGStore) DeleteEnvironment ¶
func (*PGStore) DeleteGitConnection ¶
func (s *PGStore) DeleteGitConnection(ctx context.Context, workspaceID string, installationID int64) error
DeleteGitConnection removes one connection of a workspace by installation id. Not-found (wrong workspace or unknown installation) is ErrNotFound. Scoping the delete to workspaceID keeps one workspace from disconnecting another's installation even if it learns the id.
func (*PGStore) DeleteInvite ¶
DeleteInvite revokes a pending invite (ErrNotFound when absent / another workspace's). Idempotency is the caller's concern; a missing row is a 404.
func (*PGStore) DeleteProject ¶
func (*PGStore) DeleteRegistryCredential ¶
DeleteRegistryCredential removes the row, scoped to workspaceID. Not-found (including a cross-workspace id) is ErrNotFound.
func (*PGStore) DeleteSSHKey ¶
DeleteSSHKey scopes deletion by subject so even a guessed id cannot revoke another identity's access. Missing and foreign ids deliberately look alike.
func (*PGStore) DeleteTenant ¶
DeleteTenant removes a workspace row. The FK cascades (ON DELETE CASCADE) drop its apps, their domains, and its tenant_members in the same statement; the projector then prunes the orphaned App CRs on its next pass. Returns ErrNotFound when the id doesn't exist.
func (*PGStore) DeleteUnboundBillingProviderMapping ¶
func (s *PGStore) DeleteUnboundBillingProviderMapping(ctx context.Context, workspaceID, customerID string) error
DeleteUnboundBillingProviderMapping drops a reclaimed workspace's mapping so the next Checkout starts clean. The predicate re-checks the binding: a card bound between claim and delete keeps its row.
func (*PGStore) DeleteWebhookEndpoint ¶
DeleteWebhookEndpoint removes an endpoint (its deliveries cascade).
func (*PGStore) DeployIDByGeneration ¶
func (*PGStore) DevicePushSubscriptionWorkspaces ¶
func (s *PGStore) DevicePushSubscriptionWorkspaces(ctx context.Context, subject, deviceID string) ([]string, error)
DevicePushSubscriptionWorkspaces is the by-id routing read for unregister (w5/m115): the workspaces where subject's own deviceID is registered and active. Keyed by the caller's subject, so it never reads another member's devices.
func (*PGStore) DisconnectBlueprint ¶
DisconnectBlueprint marks the blueprint disconnected (hidden from list and ordinary by-ID reads; resources remain untouched). Already-disconnected and foreign IDs report ErrNotFound (w8/m37 t001 uniform absence). While a fresh apply still owns the active claim the verb refuses with ErrBlueprintSyncBusy so the caller can retry after it settles; a stale claim is settled inline as interrupted and the disconnect proceeds, which bounds the busy window after process loss without a separate recovery tick (w8/m37 t003).
func (*PGStore) DiskUsageForWindow ¶
func (s *PGStore) DiskUsageForWindow(ctx context.Context, from, to time.Time) ([]DiskUsageRow, error)
DiskUsageForWindow returns provisioned disk GB-seconds per service for the half-open window [from, to).
It integrates the size periods rather than sampling a current size, so a disk grown mid-window contributes its old size up to the change and its new size after it, and a disk deleted mid-window contributes only up to the deletion. Because it reads committed control-plane rows, the same window recomputed later yields the same number — which is what lets the invoice be re-derived, and what makes the meter independent of the app cluster.
func (*PGStore) DomainHostsClaimed ¶
DomainHostsClaimed returns which of hosts some service already claims, verified or still pending verification — exactly the hosts the unique domains.host constraint refuses a new service (w5/m116).
func (*PGStore) EndSSHSession ¶
func (*PGStore) EnqueueBlueprintAutoSyncIntent ¶
func (s *PGStore) EnqueueBlueprintAutoSyncIntent(ctx context.Context, intent BlueprintAutoSyncIntent) (bool, error)
EnqueueBlueprintAutoSyncIntent inserts a pending intent. ON CONFLICT (delivery_digest, blueprint_id) DO NOTHING — duplicate delivery is one logical intent. Returns whether a new row was inserted.
func (*PGStore) EnqueuePushNotifications ¶
func (s *PGStore) EnqueuePushNotifications(ctx context.Context, items []PushNotificationBatchItem, at time.Time, key string) error
EnqueuePushNotifications inserts logical and per-device rows and advances the distinct push cursor in one transaction. Composite unique constraints make feed replay, restart, and concurrent replicas converge without duplicates.
func (*PGStore) EnqueueWebhookDeliveries ¶
func (s *PGStore) EnqueueWebhookDeliveries(ctx context.Context, deliveries []WebhookDelivery, at time.Time, key string, maxPerWorkspace int) (WebhookEnqueueResult, error)
EnqueueWebhookDeliveries inserts a batch of pending deliveries and advances the watermark to (at, key) in one transaction — so a crash between the two can't drop events (re-reading the same rows re-enqueues them) or skip them. Called with an empty batch to advance the watermark past events nobody subscribes to. maxPerWorkspace == 0 explicitly disables the ceiling.
Every workspace represented in the batch is locked in sorted order with a transaction-scoped advisory lock before its open count is read. Concurrent dispatch replicas therefore cannot both observe the same last slot, while unrelated workspaces remain independent. Rows rejected by the ceiling are intentionally NOT errors: the watermark advances in the same transaction so notification pressure can never roll back or replay the source mutation.
func (*PGStore) EnsureBillingEnforcement ¶
func (s *PGStore) EnsureBillingEnforcement(ctx context.Context, e BillingEnforcement) (BillingEnforcement, error)
func (*PGStore) EnsureBillingLifecycle ¶
func (s *PGStore) EnsureBillingLifecycle(ctx context.Context, workspaceID string) (BillingLifecycle, error)
EnsureBillingLifecycle materializes the default/readable state when a Customer/Subscription is first observed. Exclusion and comp flags win.
func (*PGStore) EnsurePushWatermark ¶
EnsurePushWatermark seeds push's independent feed cursor on first start.
func (*PGStore) EnsureWebhookWatermark ¶
func (s *PGStore) EnsureWebhookWatermark(ctx context.Context, at time.Time) (time.Time, string, error)
EnsureWebhookWatermark seeds the dispatcher's watermark at `at` if none exists yet and returns the current one. Seeding at first start (rather than zero) is what keeps the feature from replaying every event from before it existed.
func (*PGStore) EnvGroupNameTaken ¶
EnvGroupNameTaken reports whether a group in the workspace uses name. It lets a create fail before its side effects; the unique constraint still decides a race.
func (*PGStore) ExpireHibernatedAgentSession ¶
func (s *PGStore) ExpireHibernatedAgentSession(ctx context.Context, id, snapshotRef string) (AgentSession, error)
ExpireHibernatedAgentSession finalizes a retention-expired session after its snapshot has been deleted from object storage (ADR059 D5): it clears the snapshot fields and moves the row to the terminal `canceled` state with a reason, keeping the conversation history. It also stamps archived_at (ADR065 D5): a session whose snapshot aged out is one nobody came back for, so it leaves the working set by itself. Guarded to the exact snapshot_ref so it can't clobber a row a concurrent Resume already rehydrated.
func (*PGStore) ExpireWorkspaceCreationAttempts ¶
func (s *PGStore) ExpireWorkspaceCreationAttempts(ctx context.Context, limit int) ([]WorkspaceCreationAttempt, error)
ExpireWorkspaceCreationAttempts claims a bounded cleanup batch and returns provider correlations to the trusted cleanup loop. Finalized work is never selected and can therefore never have its Customer removed.
func (*PGStore) ExtendBillingGrace ¶
func (*PGStore) FailAccountDeletion ¶
func (*PGStore) FailAdmittedSync ¶
func (s *PGStore) FailAdmittedSync(ctx context.Context, id, tenantID, runID string, generation int64, completedAt time.Time, errMsg *string) error
FailAdmittedSync settles an admitted run in error without projecting Blueprint status (w8/m37 t005): a preflight or stage failure leaves current name/path/autoSync settings and status untouched — the sync never owned an apply — while releasing the active claim. The run settles only from running under the admitted generation, and the claim clears only while still owned, so a disconnect or newer admission that already took authority is never overwritten. Zero settled rows (already settled elsewhere) report ErrBlueprintSyncBusy.
func (*PGStore) FailBillingLifecycleWork ¶
func (*PGStore) FailBillingNotification ¶
func (*PGStore) FailBlueprintAutoSyncIntent ¶
FailBlueprintAutoSyncIntent marks a claimed intent terminal-failed with a sanitized reason retained for diagnosis (never credentials / bodies).
func (*PGStore) FailRoleReconciliation ¶
func (*PGStore) FinalizeAgentSession ¶
func (s *PGStore) FinalizeAgentSession(ctx context.Context, id, phase, headSHA, prURL string, prNumber int, evidence json.RawMessage, failureReason string) (AgentSession, TerminalTurnFact, error)
FinalizeAgentSession records a terminal turn outcome. On success it stores the pushed head SHA, the opened draft PR, and the bounded evidence extract; on failure it stores a named reason. Evidence is left untouched when nil so a failure does not erase a prior successful turn's evidence. The phase guard is the cross-replica CAS: two Completers may observe the same running row, but only one can own its terminal transition. `hibernating` is admitted solely so the snapshot reaper can unclaim its own transient phase.
The returned TerminalTurnFact carries the current turn's durable timing for metrics (w5/m88). Callers must observe only when err == nil.
func (*PGStore) FinalizeWorkspaceCreation ¶
func (s *PGStore) FinalizeWorkspaceCreation(ctx context.Context, id, subject string, boundAt time.Time) (Tenant, error)
FinalizeWorkspaceCreation serializes finalizers on the attempt and inserts the tenant, owner membership, provider mapping, payment marker, and healthy lifecycle in one transaction. Replays return the already-created tenant.
func (*PGStore) FinishWorkspaceCreationCleanup ¶
func (s *PGStore) FinishWorkspaceCreationCleanup(ctx context.Context, id string, success bool) error
FinishWorkspaceCreationCleanup acknowledges provider cleanup. A failure only leaves a retry delay, so a persistent provider error cannot monopolize each ordered batch; success is terminal.
func (*PGStore) ForceBillingRecovery ¶
func (*PGStore) GetAgentSession ¶
func (*PGStore) GetAppPlacements ¶
func (s *PGStore) GetAppPlacements(ctx context.Context, appIDs []string) (map[string]AppPlacement, error)
GetAppPlacements reads only the requested immutable IDs. Missing rows are absent from the result; callers authorize the resources before requesting them.
func (*PGStore) GetAppProtectedStatus ¶
GetAppProtectedStatus resolves a store-managed App's protectedStatus via its Environment (w6/m19, apps.Service's destructive-verb guard, apps/protection.go): "unprotected" when the App has no environment_id, or its environment's own protected_status column otherwise. "unprotected" is the same literal 0024_environment_acl.up.sql defaults protected_status to — an App outside any environment behaves exactly like one in a freshly created, unprotected environment.
func (*PGStore) GetBillingLifecycle ¶
func (*PGStore) GetBlueprint ¶
GetBlueprint fetches an active blueprint by id, scoped to tenantID (returns ErrNotFound if the id belongs to a different workspace — or names a disconnected row, which ordinary reads treat as absent, w8/m37 t001).
func (*PGStore) GetBlueprintByRepo ¶
func (s *PGStore) GetBlueprintByRepo(ctx context.Context, tenantID, repo, branch string) (Blueprint, error)
GetBlueprintByRepo fetches the active blueprint for a tenant+repo+branch, used by the push-webhook auto-sync path. Returns ErrNotFound when unregistered — or when the row is disconnected, so webhooks cannot resurrect one (w8/m37).
func (*PGStore) GetBlueprintResourceOwner ¶
func (s *PGStore) GetBlueprintResourceOwner(ctx context.Context, tenantID string, kind BlueprintClaimKind, name string) (string, error)
GetBlueprintResourceOwner returns the owning blueprint id, or "" if unclaimed.
func (*PGStore) GetDomainClaim ¶
GetDomainClaim returns one exact host claim owned by appID.
func (*PGStore) GetEnvGroup ¶
GetEnvGroup returns one group, or ErrNotFound.
func (*PGStore) GetEnvGroupByName ¶
GetEnvGroupByName returns the workspace's group named name, or ErrNotFound.
func (*PGStore) GetEnvironment ¶
func (*PGStore) GetEnvironmentProtectedStatus ¶
func (s *PGStore) GetEnvironmentProtectedStatus(ctx context.Context, environmentID string) (string, error)
GetEnvironmentProtectedStatus returns an Environment's protectedStatus. Missing, NULL, and empty values are all unprotected: protection is opt-in, and a resource may race with deletion of its Environment row.
func (*PGStore) GetGitConnection ¶
GetGitConnection returns a workspace's oldest connection, or ErrNotFound. Since ADR075 a workspace may hold several; this backs the singular GET/POST/DELETE /v1/git/connection compatibility aliases, which act on the sole (or, ambiguously, the first) connection. Prefer ListGitConnections / GetGitConnectionByOwner for the multi-connection paths.
func (*PGStore) GetGitConnectionByOwner ¶
func (s *PGStore) GetGitConnectionByOwner(ctx context.Context, workspaceID, accountLogin string) (GitConnection, error)
GetGitConnectionByOwner resolves the workspace connection whose GitHub account login matches accountLogin (case-insensitive), or ErrNotFound. Within a workspace account_login is unique by construction (GitHub allows one installation of a given App per account), so this is the exact connection to mint a token from for a repo owned by that account (ADR075 §4).
func (*PGStore) GetGitHubClaimSelection ¶
func (s *PGStore) GetGitHubClaimSelection(ctx context.Context, id string) (GitHubClaimSelection, error)
GetGitHubClaimSelection reads an unexpired selection WITHOUT consuming it — the picker's render. ErrNotFound covers unknown, already-consumed, and expired alike, so a caller cannot distinguish them (and neither can an attacker probing). Subject matching is the service's job, on the fresh caller identity.
func (*PGStore) GetInvite ¶
GetInvite reads one invite scoped to its workspace (ErrNotFound otherwise) — the revoke verb's existence check so it can't delete another workspace's row.
func (*PGStore) GetInviteByToken ¶
GetInviteByToken reads invitation context without redeeming the capability. Accepted rows remain readable so existing members can reopen their workspace.
func (*PGStore) GetNotificationSettings ¶
func (s *PGStore) GetNotificationSettings(ctx context.Context, tenantID, subject string) (NotificationSettings, error)
GetNotificationSettings returns a member's EXPLICIT preference row — ErrNotFound when they never customized it (the caller applies the default).
func (*PGStore) GetProject ¶
func (*PGStore) GetRegistryCredential ¶
func (s *PGStore) GetRegistryCredential(ctx context.Context, workspaceID, id string) (RegistryCredential, error)
GetRegistryCredential returns one credential, scoped to workspaceID so a caller can never fetch another workspace's row by guessing its id.
func (*PGStore) GetRegistryCredentialByHost ¶
func (s *PGStore) GetRegistryCredentialByHost(ctx context.Context, workspaceID, host string) (RegistryCredential, error)
GetRegistryCredentialByHost returns the workspace's credential for host, if any — the lookup the operator-wiring materialization path (w2/m14/t002) uses to decide whether an App's image needs a pull secret. Newest wins when a workspace somehow has more than one credential for the same host.
func (*PGStore) GetRegistryCredentialByID ¶
func (s *PGStore) GetRegistryCredentialByID(ctx context.Context, id string) (RegistryCredential, error)
GetRegistryCredentialByID returns one credential without pre-scoping it to a workspace. It exists for resource-binding resolvers that must distinguish an unknown id (404) from an existing credential owned by another workspace (403). Public credential reads continue to use GetRegistryCredential so this lookup cannot accidentally become an unscoped read surface.
func (*PGStore) GetRegistryCredentialsByIDs ¶
func (s *PGStore) GetRegistryCredentialsByIDs(ctx context.Context, workspaceID string, ids []string) ([]RegistryCredential, error)
GetRegistryCredentialsByIDs returns the credentials matching any id in ids that belong to workspaceID. Batch display enrichment (name resolution) only: the workspace filter keeps the lookup within the caller's tenant so a foreign id can never resolve even to its non-secret name. Unknown or foreign ids are silently omitted.
func (*PGStore) GetServiceEvent ¶
func (s *PGStore) GetServiceEvent(ctx context.Context, workspaceID, eventID string) (ServiceEventLookup, error)
GetServiceEvent returns one event only when the owner workspace and public id both match. A foreign id and an absent id therefore share ErrNotFound, and no query hashes or scans historical source rows at request time.
func (*PGStore) GetTenantMember ¶
func (s *PGStore) GetTenantMember(ctx context.Context, tenantID, subject string) (TenantMember, error)
GetTenantMember reads one membership row (ErrNotFound when the subject is not a member) — the read the role/remove verbs consult to learn the current role (the last-admin guard) before mutating.
func (*PGStore) GetWebhookEndpoint ¶
func (s *PGStore) GetWebhookEndpoint(ctx context.Context, tenantID, id string) (WebhookEndpoint, error)
GetWebhookEndpoint returns one endpoint (secret omitted), scoped to tenantID so a caller can never fetch another workspace's row by guessing its id.
func (*PGStore) GetWorkspaceCreationAttempt ¶
func (s *PGStore) GetWorkspaceCreationAttempt(ctx context.Context, id, subject string) (WorkspaceCreationAttempt, error)
GetWorkspaceCreationAttempt returns not-found for an unknown OR foreign id, keeping attempt existence and provider state undiscoverable across subjects.
func (*PGStore) GitConnectionsByInstallation ¶
func (s *PGStore) GitConnectionsByInstallation(ctx context.Context, installationID int64) ([]GitConnection, error)
GitConnectionsByInstallation returns every workspace binding for installationID, oldest first (an empty slice when none — not ErrNotFound).
It is the reverse lookup, and the ONLY direction in which an installation resolves to workspaces: every other consumer goes workspace -> connections. The push webhook reads it to confine an app-signed delivery to the proved binding set (ADR078 §4a), where an EMPTY result must mean "act on nothing" — never a widening to a global match.
func (*PGStore) HasPendingRoleReconciliation ¶
func (s *PGStore) HasPendingRoleReconciliation(ctx context.Context, tenantID, subject string) (bool, error)
HasPendingRoleReconciliation reports whether subject still has an unconverged role intent for tenantID (round-19 #3). Not filtered on next_attempt_at: a row backed off after failures is still pending convergence, and that is exactly the window the caller needs to know about.
func (*PGStore) HibernateAgentSession ¶
func (s *PGStore) HibernateAgentSession(ctx context.Context, id, snapshotRef string, snapshotBytes int64, snapshotSHA string, retainUntil time.Time) (AgentSession, error)
HibernateAgentSession records a durable snapshot and moves the session to the `hibernated` state (ADR059 D3): it stores the object-storage ref + size + digest, stamps hibernated_at, and sets the retention deadline (NULL for a pinned row). It deliberately KEEPS sandbox_id so the Completer can terminate the (still-live) pod after this durable write; the Completer clears it once the pod is gone (ClearAgentSessionSandbox). Guarded to the `hibernating` claim so a stale writer can't hibernate a resurrected session.
func (*PGStore) IdempotentCLIRefresh ¶
func (s *PGStore) IdempotentCLIRefresh( ctx context.Context, tokenHash [sha256.Size]byte, ttl time.Duration, mint func(context.Context) ([]byte, int, error), ) ([]byte, int, error)
IdempotentCLIRefresh serializes one refresh-token digest across every API replica. The transaction-scoped advisory lock is acquired before the cache lookup and held until the mint marker has been persisted, so concurrent callers never mint simultaneously. Non-2xx OAuth responses and transport failures are returned but never cached.
Split storage (codex-security 2026-08 F2): the DURABLE row records only that a successful mint happened for this digest (plus its expiry — the marker is what makes a duplicate re-mint safe to allow, because the 60s rotation grace window is still open); the RESPONSE BYTES stay in this process only. Before this, Hydra's verbatim token response — access token + rotated refresh token, i.e. live credentials carrying bex.read/write/sensitive — was persisted as plaintext bytea, readable by anyone with database or backup access. A duplicate on another replica re-mints (one extra Hydra round trip inside the grace window); a duplicate on this replica within the TTL gets the exact cached bytes, preserving the byte-identical-response contract for the CLI.
func (*PGStore) InsertBlueprintSync ¶
func (s *PGStore) InsertBlueprintSync(ctx context.Context, run BlueprintSync) (BlueprintSync, error)
InsertBlueprintSync inserts a new sync run row. Terminal fields travel with the insert so a pre-admission failure lands directly in its final error state — one statement, no stranded running row (w8/m37 t002 failSync).
func (*PGStore) InsertCLITelemetryEvent ¶
func (s *PGStore) InsertCLITelemetryEvent(ctx context.Context, ev CLITelemetryEvent) error
func (*PGStore) InsertEnvGroup ¶
InsertEnvGroup creates a group row, idle at g.Content.Generation (1 when unset), and returns it.
func (*PGStore) InsertServiceEventFact ¶
InsertServiceEventFact appends a fact exactly once. A producer retry with the same SourceKey is a successful no-op.
func (*PGStore) InsertServiceEventFacts ¶
func (s *PGStore) InsertServiceEventFacts(ctx context.Context, facts []ServiceEventFact) error
InsertServiceEventFacts appends a producer's bounded observation set in one database round trip. Each fact keeps InsertServiceEventFact's independent source-key idempotency; validation completes before anything is queued.
func (*PGStore) IsMember ¶
IsMember reports whether the subject belongs to the workspace — the membership gate for an explicitly named workspace (w6/m14). A plain existence check, so it answers false (not ErrNotFound) for a non-member and for a workspace id that does not exist at all: both are "you may not act there".
func (*PGStore) LastDatastoreHealthyTransitionAt ¶
func (s *PGStore) LastDatastoreHealthyTransitionAt(ctx context.Context, datastoreID string) (time.Time, error)
LastDatastoreHealthyTransitionAt returns the Ready=True transition time recorded with the datastore's CURRENT healthy checkpoint — the reference the reconciler's stale-conclusion guard orders an unhealthy edge against. Zero when there is no healthy checkpoint right now or its transition time is unknown; like the App path, the guard must then fail open toward recording real outages, never toward silence.
func (*PGStore) LastHealthyTransitionAt ¶
LastHealthyTransitionAt returns the Ready=True transition time recorded with the service's CURRENT healthy checkpoint — the reference the reconciler's stale-conclusion guard (w6/m41, rejectStaleUnhealthy) orders an unhealthy edge against. Zero when there is no healthy checkpoint right now or its transition time is unknown (pre-migration row, timestamp-less condition): the guard cannot order and must fail open toward recording real outages, never toward silence.
func (*PGStore) LatestDeployCommit ¶
LatestDeployCommit returns the newest non-empty commit for appID. Rows with an empty commit (image-backed creates, unresolved git) are skipped so a later empty config_change does not hide an earlier repo-backed release.
func (*PGStore) LatestUsageWindow ¶
func (s *PGStore) LatestUsageWindow(ctx context.Context, resourceKind, serviceID, kind string) (time.Time, error)
LatestUsageWindow returns the most recent window_start for one resource and meter kind, or zero time if none exist. Tracking each meter independently lets a newly-added or temporarily unavailable meter catch up without being hidden by a newer row for another meter.
func (*PGStore) LatestUsageWindowForKind ¶
func (s *PGStore) LatestUsageWindowForKind(ctx context.Context, kind string) (time.Time, bool, error)
LatestUsageWindowForKind is the newest window any resource has recorded for one meter kind — the cursor for a meter whose rows are computed for every resource at once rather than resource by resource.
func (*PGStore) ListAbandonedBlueprintSyncs ¶
func (s *PGStore) ListAbandonedBlueprintSyncs(ctx context.Context, before time.Time, limit int) ([]AbandonedBlueprintSync, error)
ListAbandonedBlueprintSyncs returns stale running runs for the recovery sweep, oldest first, bounded per tick (w8/m37 t004). A run qualifies only past BlueprintRunRecoveryBound — a demonstrably live run is never listed.
func (*PGStore) ListActiveBillingEnforcements ¶
func (*PGStore) ListActivePushSubscriptions ¶
func (s *PGStore) ListActivePushSubscriptions(ctx context.Context) ([]ActivePushSubscription, error)
ListActivePushSubscriptions is the worker-only destination read. Membership role is read in the same query so later policy integration cannot use stale caller-supplied authorization input.
func (*PGStore) ListAgentDispatchesDue ¶
func (s *PGStore) ListAgentDispatchesDue(ctx context.Context, now time.Time) ([]AgentDispatch, error)
ListAgentDispatchesDue bounds each recovery tick. next_check_at rotates clean tombstones out of the hot set without forgetting a possibly late sandbox.
func (*PGStore) ListAgentSessions ¶
func (s *PGStore) ListAgentSessions(ctx context.Context, workspaceID string, q AgentSessionListQuery) ([]AgentSession, error)
ListAgentSessions returns one filtered page of a workspace's sessions, newest first (created_at DESC, id DESC — the order the keyset cursor pages). The default query (zero filters) is the unarchived working set.
func (*PGStore) ListAgentSessionsByPhases ¶
func (s *PGStore) ListAgentSessionsByPhases(ctx context.Context, phases []string) ([]AgentSession, error)
ListAgentSessionsByPhases returns every session across all workspaces in any of the given phases, oldest first. It powers the trusted background Completer loop (ADR047 D4) that finalizes running sessions; it performs no authorization and must never be reachable from a tenant-facing verb.
func (*PGStore) ListAuditEvents ¶
func (s *PGStore) ListAuditEvents(ctx context.Context, workspaceID string, filter AuditFilter) ([]AuditRow, error)
ListAuditEvents returns workspaceID's audit trail — newest-first by default, oldest-first with filter.OldestFirst — honoring filter's time bounds/cursor/limit — the one query the REST and GraphQL read fragments (internal/audit) both delegate to, so they can't diverge.
func (*PGStore) ListAutoSyncBlueprints ¶
func (s *PGStore) ListAutoSyncBlueprints(ctx context.Context, branch, tenantScope string) ([]Blueprint, error)
ListAutoSyncBlueprints returns every auto-sync-enabled, non-disconnected Blueprint on branch. When tenantScope is non-empty, results are confined to that workspace (GitHub App installation binding). Repo URL matching is left to the caller (repoURLsMatch) because stored repo strings vary by form.
func (*PGStore) ListBillingExportIssues ¶
func (*PGStore) ListBillingOwnerSubjects ¶
func (s *PGStore) ListBillingOwnerSubjects(ctx context.Context, workspaceID string) ([]string, error)
ListBillingOwnerSubjects returns the PEOPLE to contact about a workspace's billing (w5/m103): admins, excluding machine bindings — an API key is not somebody who can act on a failed payment.
func (*PGStore) ListBillingProviderMappings ¶
func (*PGStore) ListBlueprintResourceClaims ¶
func (s *PGStore) ListBlueprintResourceClaims(ctx context.Context, tenantID, blueprintID string) ([]BlueprintResourceClaim, error)
ListBlueprintResourceClaims returns claims owned by blueprintID.
func (*PGStore) ListBlueprintSyncs ¶
func (s *PGStore) ListBlueprintSyncs(ctx context.Context, blueprintID, cursor string, limit int) ([]BlueprintSync, error)
ListBlueprintSyncs returns sync runs for a blueprint, newest first, with cursor-based paging (exclusive, by started_at DESC + id).
func (*PGStore) ListBlueprints ¶
ListBlueprints returns all non-disconnected blueprints for a tenant, newest first.
func (*PGStore) ListDeploys ¶
func (*PGStore) ListDesiredApps ¶
func (s *PGStore) ListDesiredApps(ctx context.Context) ([]DesiredApp, error)
func (*PGStore) ListDomainClaims ¶
ListDomainClaims returns pending and verified claims in stable projection order, primary first and then creation order.
func (*PGStore) ListEnvGroups ¶
ListEnvGroups returns one workspace's groups in id order.
func (*PGStore) ListEnvironmentServices ¶
func (s *PGStore) ListEnvironmentServices(ctx context.Context, environmentID, projectID string) ([]string, error)
ListEnvironmentServices returns the public ids of all services currently in the environment. Filters on project_id too (not just environment_id): a service's project_id can drift out from under it via the independent setProjectServices verb (which knows nothing about environments), so this defends against surfacing a service that no longer belongs to the environment's own project.
func (*PGStore) ListEnvironments ¶
func (*PGStore) ListEnvironmentsForProjects ¶
func (s *PGStore) ListEnvironmentsForProjects(ctx context.Context, projectIDs []string) ([]Environment, error)
ListEnvironmentsForProjects returns the Environments of every project in projectIDs in one query, each project's ordered as ListEnvironments orders it. A page of projects reads its environments together (w5/146).
func (*PGStore) ListGitConnections ¶
func (s *PGStore) ListGitConnections(ctx context.Context, workspaceID string) ([]GitConnection, error)
ListGitConnections returns all of a workspace's connections, oldest first (an empty slice when it has none — not ErrNotFound). This is the aggregate the multi-account repo picker and the connections surface read (ADR075).
func (*PGStore) ListHibernatedForRetention ¶
func (s *PGStore) ListHibernatedForRetention(ctx context.Context, now time.Time, limit int) ([]AgentSession, error)
ListHibernatedForRetention returns unpinned hibernated sessions whose retention deadline has passed (ADR059 D5), oldest deadline first — the working set the retention sweep reclaims. The sweep deletes ONLY the snapshot object; the row and its transcript are kept (ExpireHibernatedAgentSession moves the row to canceled + archived). Pinned rows (retain_until NULL) never match. Trusted background read; the partial index backs it.
func (*PGStore) ListInvites ¶
ListInvites returns a workspace's OUTSTANDING invites — not yet accepted and not expired — oldest first. Accepted/expired rows are audit history, not pending work, so the Team page's pending list excludes them.
func (*PGStore) ListNotifyRecipients ¶
func (s *PGStore) ListNotifyRecipients(ctx context.Context, tenantID string) ([]NotifyRecipient, error)
ListNotifyRecipients returns every PERSON in tenantID (w5/m103 — an API key's binding has no mailbox; before the kind filter, every deploy failure tried to notify bound client ids) with their resolved deploy-notification preferences: an explicit row's values via the LEFT JOIN, or the failure-only default via COALESCE for a member who never customized them. One query serves the notification fan-out on every deploy lifecycle event, rather than a settings lookup per member.
func (*PGStore) ListOpenDeploys ¶
func (*PGStore) ListOwnDevicePushSubscriptions ¶
func (s *PGStore) ListOwnDevicePushSubscriptions(ctx context.Context, tenantID, subject string) ([]DevicePushSubscription, error)
ListOwnDevicePushSubscriptions returns active devices for one exact member. The SQL projection intentionally omits token and digest so even an accidental direct JSON encoding of the result cannot disclose the bearer capability.
func (*PGStore) ListOwnPushNotifications ¶
func (s *PGStore) ListOwnPushNotifications(ctx context.Context, tenantID, subject string, limit int, excludeEvents []string) ([]PushNotification, error)
ListOwnPushNotifications pages the caller's durable inbox. excludeEvents removes destination-gated event types (notifications.destination_policy.go) in SQL, shared with CountUnreadPushNotifications.
func (*PGStore) ListOwnWebPushSubscriptions ¶
func (*PGStore) ListPreviousSandboxTeardownsDue ¶
func (s *PGStore) ListPreviousSandboxTeardownsDue(ctx context.Context, now time.Time) ([]AgentDispatch, error)
ListPreviousSandboxTeardownsDue returns dispatch intents that still name a previous sandbox to tear down, due for a retry. Bound turns keep the intent as an abandoned tombstone until this cleanup succeeds (w5/m99 t004).
func (*PGStore) ListProjects ¶
func (*PGStore) ListRegistryCredentials ¶
func (s *PGStore) ListRegistryCredentials(ctx context.Context, workspaceID string) ([]RegistryCredential, error)
ListRegistryCredentials returns a workspace's stored credentials, newest first.
func (*PGStore) ListRetiredUsageResources ¶
func (s *PGStore) ListRetiredUsageResources(ctx context.Context, since time.Time) ([]RetiredUsageResource, error)
ListRetiredUsageResources returns the resources retired at or after since whose workspace still exists.
func (*PGStore) ListSSHKeys ¶
ListSSHKeys returns only one identity's keys in stable creation order.
func (*PGStore) ListSandboxTenantKeys ¶
func (s *PGStore) ListSandboxTenantKeys(ctx context.Context) ([]SandboxTenantKey, error)
ListSandboxTenantKeys lets the internal poller enumerate tenant-scoped OpenSandbox views without weakening the public workspace isolation seam.
func (*PGStore) ListServiceEvents ¶
func (s *PGStore) ListServiceEvents(ctx context.Context, appID, ownerWorkspace string, f ServiceEventFilter) ([]ServiceEventRow, error)
ListServiceEvents returns one service's composed activity feed, newest first. Audit, deploy, and fact rows all use the immutable control-plane app id.
func (*PGStore) ListServicesForProjects ¶
func (s *PGStore) ListServicesForProjects(ctx context.Context, projectIDs []string) (map[string][]string, error)
ListServicesForProjects returns the public ids of the services in each of projectIDs, keyed by project id and ordered by name, in one query for the whole page (w5/155). A project with no services has no key.
func (*PGStore) ListTenantMembers ¶
ListTenantMembers returns a workspace's PEOPLE, oldest first (w5/m103): machine bindings are excluded, because every consumer of this read — the Team page, the Render owners members API, seat usage — is answering "who belongs to this workspace", and an API key is not a who. The key's own row still exists and still authorizes; it is listed on the API-keys surface instead.
func (*PGStore) ListTenantsForSubject ¶
ListTenantsForSubject returns the workspaces the subject belongs to, oldest first — the caller's workspace list (the dashboard switcher / owners list).
func (*PGStore) ListTerminalAgentSessionsForPush ¶
func (s *PGStore) ListTerminalAgentSessionsForPush(ctx context.Context, since time.Time) ([]AgentSession, error)
ListTerminalAgentSessionsForPush returns completed/failed sessions updated at or after `since`, across all workspaces, for the push worker's agent-terminal projection (w11/m6 t005). Bounded by the recency window so the scan stays cheap; dedup to one push per (session, phase) is the notification's source_event_key ON CONFLICT, not this query. Like ListAgentSessionsByPhases it performs no authorization and is a trusted background read only.
func (*PGStore) ListTerminalAgentSessionsWithSandbox ¶
func (s *PGStore) ListTerminalAgentSessionsWithSandbox(ctx context.Context, now time.Time) ([]AgentSession, error)
ListTerminalAgentSessionsWithSandbox returns terminal sessions (completed/ failed/canceled) that still carry a sandbox id and are due for a reap attempt (sandbox_reap_after IS NULL or <= now), oldest first (ADR054 D6 / w5/m99). Eligibility is per-row — there is no now-relative updated_at window, so a teardown that fails for days is still retried once its backoff elapses. Trusted background read — no authorization. `now` drives the backoff gate.
func (*PGStore) ListWebhookAttempts ¶
func (s *PGStore) ListWebhookAttempts(ctx context.Context, filter WebhookAttemptFilter) ([]WebhookAttempt, error)
ListWebhookAttempts returns one endpoint's completed network exchanges, newest first, keyset-paged on immutable (sent_at,id). Unsent reservations do not pretend to have exchange evidence; QueueWebhookResend returns its pending reservation directly and it joins history after the worker terminalizes it.
func (*PGStore) ListWebhookDispatchEndpoints ¶
func (s *PGStore) ListWebhookDispatchEndpoints(ctx context.Context, since time.Time) ([]WebhookEndpoint, error)
ListWebhookDispatchEndpoints returns the dispatcher's subscription table, refreshed each poll pass: every endpoint that is enabled or whose last interval ended after `since`, with ALL its retained intervals in Eligibility. Every interval, because the feed cursor follows recording time while eligibility is judged on source time: a fact recorded late can belong to an interval the cursor passed long ago. A disabled endpoint stays while its enabled-period events may still arrive — they are owed to it, and the send pass holds them until it is enabled again — so the caller passes the feed position less its late-recording allowance.
Only the columns dispatch needs are selected. The destination URL is loaded later, per due delivery, so a large catalog cannot pin unbounded URL text in the shared API process (codex round-15 #2).
func (*PGStore) ListWebhookEndpoints ¶
func (s *PGStore) ListWebhookEndpoints(ctx context.Context, tenantIDs []string, afterAt time.Time, afterKey string, limit int) ([]WebhookEndpoint, error)
ListWebhookEndpoints returns the requested workspaces' endpoints, newest first, keyset-paged on immutable (created_at,id); secrets are omitted.
func (*PGStore) ListWebhookEvents ¶
func (s *PGStore) ListWebhookEvents(ctx context.Context, afterAt time.Time, afterKey string, until time.Time, verbs, tenants []string, limit int) ([]WebhookEventRow, error)
ListWebhookEvents returns the composed feed strictly after the watermark (afterAt, afterKey), up to and including `until`, oldest first, restricted to `tenants` (the subscriber set). verbs is the audit-verb subset the webhook vocabulary maps (internal/webhooks owns the mapping, exactly as internal/events owns its own).
func (*PGStore) ListWorkspaceEnvironmentServices ¶
func (s *PGStore) ListWorkspaceEnvironmentServices(ctx context.Context, tenantID string) (map[string][]string, error)
ListWorkspaceEnvironmentServices batches the service-membership half of the workspace scope index. Joining environments preserves the same project-id consistency check as ListEnvironmentServices, so stale app rows are omitted.
func (*PGStore) ListWorkspaceEnvironments ¶
func (s *PGStore) ListWorkspaceEnvironments(ctx context.Context, tenantID string) ([]Environment, error)
ListWorkspaceEnvironments returns every Environment in one workspace. The workspace scope index needs this shape directly; making the store express it avoids issuing one ListEnvironments query per Project.
func (*PGStore) MaintainGitWebhookReplayEpochs ¶
func (s *PGStore) MaintainGitWebhookReplayEpochs(ctx context.Context, epochs []GitWebhookReplayEpoch, now time.Time) (int64, error)
MaintainGitWebhookReplayEpochs heartbeats this process's accepted epochs, expires dead replica leases, and purges claims only for epochs no live lease still accepts. The finite pre-migration legacy partition is never purged: it has no authenticated epoch to prove retired.
func (*PGStore) MarkBillingEnforcementRecovered ¶
func (*PGStore) MarkCheckoutStarted ¶
MarkCheckoutStarted records that a hosted Checkout session was created for the workspace. This is intent, NOT payment state. Monotonic like the bind marker — the first attempt is the one worth keeping — and silent for an absent mapping: a workspace without a Customer opens Checkout without one (Stripe creates it on submit), so only workspaces that already own a Customer, and rows minted back when Checkout pre-created it, carry the stamp.
func (*PGStore) MarkMachineMemberships ¶
MarkMachineMemberships reclassifies the tenant_members rows of known API-key client ids as kind='machine' — the one-time reconcile for bindings written before w5/m103 added the column (they defaulted to 'user', since Hydra is the only registry that knows which subjects are clients and SQL cannot consult it). bex-api runs this at startup against the live client list, the same shape as the deploy-hook digest backfill.
It is idempotent, and deliberately one-directional: it never demotes a row back to 'user'. A client id that no longer exists in Hydra is not evidence that a human appeared under the same subject — it is a revoked key whose binding UnbindClient should have removed.
Returns how many rows it changed, so a startup that fixes nothing is distinguishable from one that quietly did work.
func (*PGStore) MarkOwnPushNotificationRead ¶
func (*PGStore) MarkUsageAttempted ¶
func (s *PGStore) MarkUsageAttempted(ctx context.Context, attempts []UsageExportAttempt, at time.Time) error
MarkUsageAttempted durably records the deterministic identifier before any provider call. The first-attempt timestamp never moves: once the identifier window expires, the row is quarantined instead of being replayed blindly.
func (*PGStore) MarkUsageEmitted ¶
MarkUsageEmitted stamps emitted_at on exactly the given rows — the outbox write after a successful ingest. Keyed by the full primary key (resource_kind, service_id, kind, tier, window_start), it only touches rows still un-emitted (emitted_at IS NULL), so a re-run after a crash between ingest and stamp is a safe no-op. A single unnest-driven statement keeps the whole batch atomic.
func (*PGStore) MarkWorkspaceCreationSetupSucceeded ¶
func (*PGStore) OAuthRevokedAt ¶
func (s *PGStore) OAuthRevokedAt(ctx context.Context, subject, clientID string) (time.Time, bool, error)
OAuthRevokedAt returns the latest shared invalidation marker for one OAuth consent chain. Missing rows are the normal never-revoked state.
func (*PGStore) ObserveSandboxMeter ¶
func (s *PGStore) ObserveSandboxMeter(ctx context.Context, obs SandboxMeterObservation) error
ObserveSandboxMeter atomically advances one sandbox cursor and accrues the non-overlapping interval since its previous observation. Only an interval whose previous phase was running is charged; creating/resuming/suspended/ errored/terminated intervals are zero. Replaying or racing an older sample is a no-op, which is the restart/retry no-double-count guarantee.
func (*PGStore) OwnerIDForSubject ¶
OwnerIDForSubject returns the stable opaque "own-" id for a subject, minting and persisting one on first sight (w6/m7): OwnerIDsForSubjects for one subject. The Render owners members surface reports userId as this id instead of leaking the raw Kratos/Hydra subject.
func (*PGStore) OwnerIDsForSubjects ¶
func (s *PGStore) OwnerIDsForSubjects(ctx context.Context, subjects []string) (map[string]string, error)
OwnerIDsForSubjects returns each subject's opaque own- id, minting one on first sight. The ids a list already has are read in one query, and only a subject without one is written (w5/104): the Team page polls its members every 30s, and an upsert writes a row version even when its DO UPDATE changes nothing. A miss still goes through the upsert, so a concurrent first sight of the same subject yields exactly one row (the subject is the primary key), and RETURNING surfaces the stored id rather than the freshly minted candidate.
func (*PGStore) PaymentEligibility ¶
func (s *PGStore) PaymentEligibility(ctx context.Context, workspaceID string) (PaymentEligibility, error)
PaymentEligibility reads the marker and both billing exemptions in one local SELECT. The tenant row is authoritative, so a workspace that has not entered Checkout yet still returns an unstamped (not missing) snapshot.
func (*PGStore) PaymentMethodBound ¶
PaymentMethodBound is the narrow marker accessor. An unknown or unstamped workspace returns false rather than leaking whether a tenant exists.
func (*PGStore) PreviewAccountDeletion ¶
func (*PGStore) PromoteDomainClaim ¶
func (s *PGStore) PromoteDomainClaim(ctx context.Context, appID, id, expectedChallenge string, at time.Time) (Domain, error)
PromoteDomainClaim atomically promotes the same pending row that supplied expectedChallenge. A concurrent delete/recreate cannot promote the new row. Already-verified rows are idempotent only when the id still matches.
func (*PGStore) PruneRetiredUsageResources ¶
PruneRetiredUsageResources drops records retired before before — their hours are past the meters' catch-up window — and records whose workspace no longer exists, which has no usage left to write.
func (*PGStore) PruneWebhookEligibility ¶
PruneWebhookEligibility deletes closed eligibility intervals that ended before `before` — the delivery retention horizon. A feed read lagging past it would treat those events as ineligible (skip, never replay), the same side the dispatcher errs on everywhere else.
func (*PGStore) PurgeAuditEvents ¶
PurgeAuditEvents deletes rows older than before — the retention sweep (internal/audit, BEX_AUDIT_RETENTION_DAYS) calls this, mirroring CompactUsage's atomic, idempotent delete.
func (*PGStore) PurgeCLITelemetryEvents ¶
PurgeCLITelemetryEvents applies the platform retention window to ingested CLI telemetry: stable installation UUIDs must not become an indefinitely retained trail. It runs in the audit service's scheduled sweep beside PurgeAuditEvents/PurgeSSHSessions (w5/m92).
func (*PGStore) PurgeProductAnalytics ¶
PurgeProductAnalytics shares the audit retention window, not its row counts.
func (*PGStore) PurgeSSHSessions ¶
PurgeSSHSessions applies the platform audit-retention window to SSH session metadata as well as ordinary audit_events. Remote addresses and target ids must not become an indefinitely retained second audit trail.
func (*PGStore) PurgeStripeBillingEvents ¶
func (*PGStore) PushDeliveryStats ¶
func (s *PGStore) PushDeliveryStats(ctx context.Context) (PushQueueStats, error)
func (*PGStore) PushNotificationWorkspaces ¶
func (s *PGStore) PushNotificationWorkspaces(ctx context.Context, subject, eventID string) ([]string, error)
PushNotificationWorkspaces returns the workspaces in which subject's own inbox holds eventID, ordered by tenant id; none for an unknown id. A routing read only (w4/m172): mark-read with no ownerId acts in the item's own workspace, then still authorizes there and updates through the tenant-scoped MarkOwnPushNotificationRead. Keyed by subject, so it can never surface another member's row; the tenant_members FK means every returned workspace is one the subject belongs to. Rows are unique per (tenant, subject, event_id), not per (subject, event_id), hence a list.
func (*PGStore) QuarantineOldUsageAttempts ¶
func (s *PGStore) QuarantineOldUsageAttempts(ctx context.Context, before, at time.Time) (int64, error)
QuarantineOldUsageAttempts stops automatic replay after Stripe's rolling identifier de-duplication window. These rows require explicit reconciliation and an audited operator decision.
func (*PGStore) QueueWebhookResend ¶
func (s *PGStore) QueueWebhookResend(ctx context.Context, request WebhookResendRequest) (WebhookAttempt, error)
QueueWebhookResend reserves one immediate manual attempt for a source attempt, owner-scoped through its endpoint. The caller key is durable: a repeat returns the same pending or completed attempt forever. A scheduled, unsent automatic retry is parked on the manual row and restored only when that manual exchange fails, so Resend neither consumes nor races the normal retry budget.
func (*PGStore) ReclaimEmptyBlueprintGroupings ¶
func (s *PGStore) ReclaimEmptyBlueprintGroupings(ctx context.Context, tenantID string, pairs []GroupingPair, referencedEnvironments, referencedProjects map[string]bool) (removedEnvironments, removedProjects []string, err error)
ReclaimEmptyBlueprintGroupings deletes, in ONE transaction, the named grouping rows a disconnected Blueprint minted that nothing still references (w8/m20 t004): an environment goes when no apps row is assigned to it and its id is not in referencedEnvironments (CR-side datastore members); a candidate project goes when it has no environments left, no apps members, and is not in referencedProjects. Deployed resources are never touched — a populated grouping survives (Render disconnect semantics). Returns the removed names for post-commit auditing.
func (*PGStore) ReconcileUsageSourceStreams ¶
func (s *PGStore) ReconcileUsageSourceStreams(ctx context.Context, active []UsageResourceRef, through time.Time) error
ReconcileUsageSourceStreams closes open streams whose resource has left the collector inventory. The observed health rows are retained for the rest of the month; only the stream's expected range becomes finite.
func (*PGStore) Record ¶
Record persists one audit event — *PGStore structurally satisfies core.AuditSink (Record(ctx, core.AuditEvent) error), so the composition root wires it directly onto core.Base.Audit (cmd/api/main.go) with no adapter type needed.
func (*PGStore) RecordAgentSessionDispatch ¶
func (s *PGStore) RecordAgentSessionDispatch(ctx context.Context, id, sandboxID, phase, status, deliveryMode string, turn int) (AgentSession, error)
RecordAgentSessionDispatch binds a newly dispatched accepted turn: it advances the phase/status, adopts a fresh sandbox id when re-dispatched, records the delivery mode ("resume" or "redispatch"). Prompt acceptance already advanced the turn counter transactionally.
Binding and abandonment serialize on the session and dispatch intent. Only the accepted turn in its provisioning phase can bind; cancellation, recovery, and newer turns fence out stale workers. Successful binding removes the intent in the same transaction so recovery cannot mistake its sandbox for an orphan.
func (*PGStore) RecordBillingNotificationMailed ¶
func (s *PGStore) RecordBillingNotificationMailed(ctx context.Context, workspaceID string, version int64, subject string) error
RecordBillingNotificationMailed adds subject to the owners the notice reached. Recording a subject twice keeps one entry.
func (*PGStore) RecordDomainVerificationAttempt ¶
func (s *PGStore) RecordDomainVerificationAttempt(ctx context.Context, appID, id string, at time.Time) error
RecordDomainVerificationAttempt records a conservative failed-or-successful resolver attempt without changing ownership state.
func (*PGStore) RecordObservedDatastoreState ¶
func (s *PGStore) RecordObservedDatastoreState(ctx context.Context, obs ObservedDatastoreState) ([]DatastoreEventFact, error)
RecordObservedDatastoreState atomically advances a datastore checkpoint and appends any availability edge it crossed — the Database/KeyValue twin of RecordObservedServiceState. The first observation is a baseline; replaying the same observation is a no-op.
func (*PGStore) RecordObservedServiceState ¶
func (s *PGStore) RecordObservedServiceState(ctx context.Context, obs ObservedServiceState) ([]ServiceEventFact, error)
RecordObservedServiceState atomically advances a service checkpoint and appends any phase/availability edges it crossed. The first observation is a baseline; replaying the same observation is a no-op.
func (*PGStore) RecordProductActivity ¶
RecordProductActivity enriches the durable, deduplicated successful effect. Subject locking prevents an in-flight recorder restoring identity after deletion.
An unset surface falls back to the column's default rather than being sent as an empty string, which its CHECK would reject. This is defensive only: the sole caller reaches here through ObserveProductActivity, which clamps the value, and logs-and-swallows any error — so no value written here can fail a resource creation. It does not make arbitrary input safe; a direct caller passing "Dashboard" still violates the CHECK, and should.
func (*PGStore) RecordProductDomainTLS ¶
func (*PGStore) RecordProductHosting ¶
func (s *PGStore) RecordProductHosting(ctx context.Context, workspace, resource, kind string, live bool, at time.Time) (bool, error)
RecordProductHosting samples current state at most every five minutes unless it changes. was_live is monotonic within the UTC day; live is the latest sample.
func (*PGStore) RecordPushReceipt ¶
func (*PGStore) RecordPushSendFailure ¶
func (*PGStore) RecordResourceDisplayNames ¶
func (s *PGStore) RecordResourceDisplayNames(ctx context.Context, tenantID string, records []ResourceDisplayName) error
RecordResourceDisplayNames upserts a batch of retained names for one tenant. It is called at service create and rename, before datastore collection, and during usage reads to refresh known names. Blank kinds, ids and names are skipped, so a resource bex never knew leaves no misleading name record.
Upsert, not insert: a rename must move the retained name forward, which is what makes a live renamed resource bill under its current name.
func (*PGStore) RecordRetiredUsageResource ¶
func (s *PGStore) RecordRetiredUsageResource(ctx context.Context, r RetiredUsageResource) error
RecordRetiredUsageResource stores r, retired now. Retiring the same resource again (a delete retried after one that failed) replaces the record.
func (*PGStore) RecordStripeBillingEvent ¶
func (s *PGStore) RecordStripeBillingEvent(ctx context.Context, e StripeBillingEvent, grace time.Duration) (BillingLifecycle, bool, bool, error)
RecordStripeBillingEvent inserts the provider event and applies its normalized transition in one transaction. Duplicate ids and stale provider timestamps are retained/recognized without repeating a state change or notification.
func (*PGStore) RecordUsageExportResult ¶
func (s *PGStore) RecordUsageExportResult(ctx context.Context, accepted []UsageExportAttempt, rejected []UsageExportReject, at time.Time) error
RecordUsageExportResult atomically stamps accepted rows and moves permanent rejects into the durable issue table. Transient failures are intentionally absent: they remain pending with their immutable first-attempt timestamp.
func (*PGStore) RecordUsageSourceHealth ¶
func (s *PGStore) RecordUsageSourceHealth(ctx context.Context, records []UsageSourceRecord) error
RecordUsageSourceHealth records an explicit degraded/unavailable attempt when no usage row can be committed. A later retry overwrites the state for the same source-window and reopens a stream closed by inventory reconciliation.
func (*PGStore) RefreshInvite ¶
func (s *PGStore) RefreshInvite(ctx context.Context, tenantID, id, token string, expiresAt time.Time) (Invite, error)
RefreshInvite pushes an unaccepted invite's expiry forward and replaces its token — the resend verb's write half (w1/m33). The token rotates (w1/041): with only sha256(token) at rest the old plaintext cannot be re-emailed, so resend mints a fresh capability and the freshly emailed link supersedes the original, which stops redeeming. An expired-but-unaccepted invite is revived (resend is how an admin recovers a lapsed invite without churning the id); an accepted or unknown invite is ErrNotFound — accepted rows are audit history, not pending work. The returned Invite carries the plaintext token for the resent mail.
func (*PGStore) RehydrateAgentSession ¶
func (s *PGStore) RehydrateAgentSession(ctx context.Context, id, sandboxID, phase, status, deliveryMode string) (AgentSession, error)
RehydrateAgentSession completes a rehydration (ADR059 D4): it adopts the fresh sandbox id and advances to the live phase. A Steer's turn counter already advanced atomically in BeginRehydrate; a prompt-less Resume does not advance. clears the snapshot fields (the blob has been consumed into the new pod — a re-hibernation writes a fresh one). Guarded to the `resuming` claim BeginRehydrate set, so it can't race a retention delete or a double resume.
func (*PGStore) ReleaseBlueprintResourceClaim ¶
func (s *PGStore) ReleaseBlueprintResourceClaim(ctx context.Context, tenantID string, kind BlueprintClaimKind, name, blueprintID string) error
ReleaseBlueprintResourceClaim drops one blueprint's claim on one resource — what a sync does for a resource its manifest stopped declaring (w4/m125).
The blueprint_id predicate is the whole point: between the list and this delete another blueprint may have taken the resource over with an explicit confirmation, and a release that ignored the current owner would silently undo that takeover. A row already owned by someone else is a no-op, which is the correct outcome — this blueprint no longer manages it either way.
func (*PGStore) ReleaseBlueprintResourceClaims ¶
func (s *PGStore) ReleaseBlueprintResourceClaims(ctx context.Context, tenantID, blueprintID string) error
ReleaseBlueprintResourceClaims drops every claim held by blueprintID in the workspace (disconnect). Resources and CR labels are cleared separately.
func (*PGStore) ReleaseEnvGroupContent ¶
func (s *PGStore) ReleaseEnvGroupContent(ctx context.Context, id string, expectedVersion int64, rel EnvGroupRelease) error
ReleaseEnvGroupContent ends the claim held at expectedVersion.
func (*PGStore) ReleaseGitWebhookDelivery ¶
func (s *PGStore) ReleaseGitWebhookDelivery(ctx context.Context, claim GitWebhookReplayClaim) error
ReleaseGitWebhookDelivery frees a claim whose delivery failed before completing, allowing the git host's retry to process it.
func (*PGStore) ReleasePushDelivery ¶
ReleasePushDelivery leaves a failed provider attempt retryable without persisting provider text. expectedClaim protects a newer lease.
func (*PGStore) RemoveAccountMember ¶
RemoveAccountMember rechecks that another admin exists and removes the deleting subject atomically. It is intentionally narrower than general member removal and clears only the onboarding owner binding.
func (*PGStore) RemoveDomain ¶
RemoveDomain deletes a domain row for apps.IntentStore. If host is the canonical target of an auto-generated redirecting sibling, that sibling is deleted in the same statement: preserving it while clearing its redirect would silently turn a generated redirect into a directly served claim. Deleting the redirecting sibling itself leaves the canonical host unchanged. A sibling the tenant explicitly re-added has a NULL redirect_for_name and is therefore independent. The operation is idempotent (not-found is ignored).
func (*PGStore) RemoveMember ¶
RemoveMember deletes a membership row (ErrNotFound when absent). The FGA tuple removal is the caller's separate, best-effort step (members.Service.Remove).
SECURITY (codex round-16 #3): removing an admin re-counts under the same tenant advisory lock as UpdateMemberRole so concurrent remove/demote cannot erase the last administrator.
func (*PGStore) RenameEnvironment ¶
func (*PGStore) RenameProject ¶
func (*PGStore) RenameTenant ¶
RenameTenant updates a workspace's display name (ErrConflict on collision, ErrNotFound when the id doesn't exist).
func (*PGStore) ReplaceDomainClaims ¶
func (s *PGStore) ReplaceDomainClaims(ctx context.Context, appID string, declarations []DomainDeclaration) ([]Domain, error)
ReplaceDomainClaims atomically reconciles a declaration set while preserving unchanged rows' ownership evidence. Newly introduced hosts start pending.
func (*PGStore) ReplaceDomains ¶
ReplaceDomains atomically reconciles one App's complete domain set. Locking the App row serializes concurrent re-syncs of the same service; the global UNIQUE(domains.host) index arbitrates claims between different services. Any conflict rolls the transaction back, preserving the former set.
func (*PGStore) ResolveBillingExportIssue ¶
func (s *PGStore) ResolveBillingExportIssue(ctx context.Context, transactionID, action, actor, reason string, at time.Time) (BillingExportIssue, error)
ResolveBillingExportIssue performs one explicit, audited operator decision: acknowledge keeps the row held, retry requeues only a definite permanent reject still inside Stripe's event-time horizon, and mark_repaired stamps the row after external reconciliation proves the provider outcome.
func (*PGStore) ResourceDisplayNames ¶
func (s *PGStore) ResourceDisplayNames(ctx context.Context, tenantID string, refs []ResourceDisplayName) (map[string]string, error)
ResourceDisplayNames resolves retained names for one tenant, keyed by ResourceDisplayNameKey. Missing ids are simply absent from the map — a resource bex never recorded a name for has none to retain.
func (*PGStore) RevokeAllDevicePushSubscriptions ¶
func (s *PGStore) RevokeAllDevicePushSubscriptions(ctx context.Context, tenantID, subject string) (int64, error)
RevokeAllDevicePushSubscriptions removes every active destination for the caller in this workspace, used by explicit all-device logout/revocation.
func (*PGStore) RevokeAllWebPushSubscriptions ¶
func (*PGStore) RevokeDevicePushSubscription ¶
func (s *PGStore) RevokeDevicePushSubscription(ctx context.Context, tenantID, subject, deviceID string) (bool, error)
RevokeDevicePushSubscription is an idempotent, member-scoped logout/delete. A guessed device id belonging to another member looks exactly like no row.
func (*PGStore) RevokeExactPushSubscription ¶
func (*PGStore) RevokeWebPushSubscription ¶
func (*PGStore) RollbackAppCreation ¶
RollbackAppCreation removes only a just-provisioned App and the analytics emitted while attempting it, in one transaction. Ordinary deletion preserves recent history even when its creation event has aged out of retention.
func (*PGStore) RunGitWebhookReplayMaintenance ¶
func (s *PGStore) RunGitWebhookReplayMaintenance(ctx context.Context, epochs []GitWebhookReplayEpoch)
RunGitWebhookReplayMaintenance keeps epoch liveness authoritative across API replicas. A graceful stop removes its leases without waiting for crash expiry.
func (*PGStore) RunGroupingTx ¶
RunGroupingTx runs fn against a transaction-scoped GroupingStore: every grouping write inside fn commits together or not at all. fn returning an error (or a panic) rolls the whole set back.
func (*PGStore) SSHKeyByFingerprint ¶
SSHKeyByFingerprint is the gateway-only lookup that maps a presented public key to its owning subject. It returns public metadata only.
func (*PGStore) SandboxKeyForWorkspace ¶
SandboxKeyForWorkspace returns the workspace's OpenSandbox tenant key, minting one on first use. It is the KeyProvider behind every sandbox request, so it reads a stored key first: the upsert writes a new row version even when it changes nothing (w5/087). Race-safe: first-mints that all miss the read collapse on UNIQUE(workspace_id), and DO UPDATE (where DO NOTHING would return no row on conflict) hands each loser the stored key.
func (*PGStore) SandboxKeyLookup ¶
SandboxKeyLookup returns the workspace's existing OpenSandbox tenant key WITHOUT minting one (unlike SandboxKeyForWorkspace) — the workspace-delete purger (w1/m61) uses it to find the key that enumerates + terminates the workspace's sandboxes. found=false means no key was ever minted, which the purger reads as "no sandbox was ever created" (the first create is what mints the key), so there is nothing to tear down. It never writes, so it is safe to call during teardown without resurrecting a key the cascade is about to drop.
func (*PGStore) SandboxUsageMetadata ¶
func (s *PGStore) SandboxUsageMetadata(ctx context.Context, tenantID string, sandboxIDs []string) (map[string]SandboxUsageMetadata, error)
SandboxUsageMetadata resolves labels for both current and previous session sandboxes, which outlive their workloads, alongside durable meter observations. A repo-less session (w3/m79) is labelled by its id, "agent session <id>": unique, and findable in the session list, where the create-time `plan · image` name would not say which session ran (w1/112).
func (*PGStore) ScheduleAgentSessionSandboxReap ¶
func (s *PGStore) ScheduleAgentSessionSandboxReap(ctx context.Context, id string, now time.Time) error
ScheduleAgentSessionSandboxReap pushes the next deferred-teardown attempt for a terminal session whose terminate just failed. Attempts grow the backoff (1m, 2m, 4m, … capped at 1h) so a flapping substrate does not hammer the API, but the row never ages out of the scan (w5/m99 t002).
func (*PGStore) SelectUnemittedUsage ¶
func (s *PGStore) SelectUnemittedUsage(ctx context.Context, floor, sealBefore time.Time, limit int, requirePaymentMethod bool) ([]HourlyRow, error)
SelectUnemittedUsage returns up to limit sealed usage_hourly rows that have not yet shipped to Stripe — the billing outbox read (docs/ADR040-billing-metronome.md §4). A row qualifies when its window is final (window_start < sealBefore, i.e. past the rewrite horizon), not below the billing floor (window_start >= floor), still un-emitted, and its workspace is not billing_excluded. Oldest first, so batches ship in order and the loop makes forward progress. The JOIN filters excluded tenants at the source: an excluded workspace's rows are never even considered for export.
func (*PGStore) ServiceEventWorkspaces ¶
ServiceEventWorkspaces returns every workspace that indexes an evt-… id, ordered by workspace id; none for an unknown id. A routing read only (w4/m172): the verb picks one of them, authorizes there and loads the event through the workspace-scoped GetServiceEvent. Event ids are DERIVED from the source row's event key (ids.Derive), not minted per workspace, and the key itself embeds a globally unique source id — but a workspace:default audit row is indexed under every matching owner (migration 0083), so the same id can legitimately resolve to several workspaces. The full, deterministic list is returned so the caller can prefer one it is a member of instead of the store guessing.
func (*PGStore) ServiceNameTaken ¶
func (s *PGStore) ServiceNameTaken(ctx context.Context, tenantID, selfID, name string) (bool, error)
ServiceNameTaken reports whether another service in tenantID than selfID ("" for one not created yet) was created as, or is displayed as, name: the refusal CreateApp and SetAppDisplayName make under the workspace's name lock (refuseTakenServiceName), read ahead of them so a create's plan and a patch's preflight meet it before anything is written (w5/m116).
func (*PGStore) SetAgentSessionArchived ¶
func (s *PGStore) SetAgentSessionArchived(ctx context.Context, id string, archived bool) (AgentSession, error)
SetAgentSessionArchived flips the ADR065 D1 archive flag. Idempotent both ways: re-archiving keeps the original archived_at (the first archive time is the honest one), and unarchiving a working-set row is a no-op. It never touches phase/status — archive is list-state, not lifecycle-state.
func (*PGStore) SetAgentSessionCanceled ¶
func (s *PGStore) SetAgentSessionCanceled(ctx context.Context, id string) (AgentSession, TerminalTurnFact, error)
SetAgentSessionCanceled is the Cancel verb's terminal CAS (w5/m88): first writer to leave a non-canceled phase wins, stamps the current turn's completed_at, and returns timing for metrics. A repeated Cancel after the session is already canceled returns ErrNotFound so observers do not double-count.
func (*PGStore) SetAgentSessionFailure ¶
func (s *PGStore) SetAgentSessionFailure(ctx context.Context, id, sandboxID, reason string) (AgentSession, error)
SetAgentSessionFailure terminalizes a still-active session from a background dispatch/resume/steer failure with a named reason. Unlike SetAgentSessionLifecycle it records the reason in failure_reason (the field the dashboard's failed-session callout reads) and sets status to the terminal 'failed', so a provisioning failure surfaces identically to a driver-reported failure via FinalizeAgentSession (w5/m80 t005, closes w5/048). It preserves SetAgentSessionLifecycle's conditional sandbox_id write so an egress-transition failure still records the just-canceled sandbox for the reaper. The caller (setLifecycleIfActive) guards against clobbering a settled/canceling session.
func (*PGStore) SetAgentSessionLifecycle ¶
func (s *PGStore) SetAgentSessionLifecycle(ctx context.Context, id, sandboxID, phase, status string, canceled bool) (AgentSession, error)
SetAgentSessionLifecycle advances the durable session state. canceled=true stamps canceled_at exactly once; every transition advances updated_at.
func (*PGStore) SetAgentSessionPinned ¶
func (s *PGStore) SetAgentSessionPinned(ctx context.Context, id string, pinned bool, retainUntil *time.Time) (AgentSession, error)
SetAgentSessionPinned toggles the never-expire pin (ADR059 D5). Pinning clears retain_until (removes the delete edge); unpinning puts a hibernated row back on the clock with the supplied deadline (retainUntil is ignored — left NULL — while the row is not hibernated). It authorizes nothing; the caller gates it.
func (*PGStore) SetAppDisplayName ¶
SetAppDisplayName mirrors spec.displayName onto the row. Empty means never renamed, and readers fall back to apps.name.
It also moves the retained display name forward (w2/m96 t001) so the service bills under its current name, and keeps that name after deletion. The retained value follows the same "empty means fall back to apps.name" rule, resolved here rather than left for every reader.
func (*PGStore) SetAppEnvironment ¶
SetAppEnvironment atomically assigns an App row to an Environment and its owning Project. Empty ids clear both memberships (Blueprint ungrouped:).
func (*PGStore) SetAppIdleTTL ¶
SetAppIdleTTL updates the row's idle-TTL seconds (the apps feature's idle-timeout verb validates the bound before calling this). The projector carries it onto spec.idleTTLSeconds the same way it carries replicas.
func (*PGStore) SetAppImage ¶
SetAppImage updates the row's image (the deploys feature's Rollback verb, w2/m10). The projector carries it onto spec.image the same way it carries replicas/tier — the write-through-store discipline every intent field with a row as its single writer of truth follows.
func (*PGStore) SetAppPort ¶
SetAppPort saves both the port and whether it overrides image discovery. A projector resync must not undo a successful service-port update.
func (*PGStore) SetAppReplicas ¶
SetAppReplicas updates the row's replica count (the apps feature's scale verb validates the bound before calling this). The projector carries it onto spec.replicas the same way it carries suspended/tier.
func (*PGStore) SetAppSource ¶
func (s *PGStore) SetAppSource(ctx context.Context, id, repo, image, branch string, registryCredentialID *string) error
SetAppSource atomically updates repo/image/branch and the explicit registry credential binding so the projector never observes mismatched source/auth.
func (*PGStore) SetAppSuspended ¶
func (*PGStore) SetAppTier ¶
SetAppTier updates the row's tier (the apps feature's plan-change verb validates it against lego/types/tiers before calling this). The projector carries it onto spec.tier the same way it carries suspended.
func (*PGStore) SetBillingException ¶
func (s *PGStore) SetBillingException(ctx context.Context, workspaceID, exception string, enabled bool, actor, reason string, at time.Time) (bool, BillingLifecycle, error)
SetBillingException atomically applies/removes the structural exclusion or rated-but-free comp flag and moves any billing-owned suspension through the ordinary recovery worker. reason is a bounded operator explanation, never a payment detail or arbitrary payload.
func (*PGStore) SetBlueprintSyncNote ¶
SetBlueprintSyncNote annotates an already-admitted run. The note is prose about what the run did that its state cannot say, so it is composed by the service layer and written here (w4/120: a create that revived a disconnected row is a new connection on an old row, and the sync history below it belongs to the previous one).
Admission notes can be written while the run is in flight. Detachment notices are written only after successful completion; that ordering belongs to the service, which knows whether the apply actually succeeded.
func (*PGStore) SetDeployPreDeployStatus ¶
SetDeployPreDeployStatus records the pre-deploy step's outcome on a deploy row (w1/m33), no-op when unchanged (IS DISTINCT FROM guards the write so the reconciler's every-pass projection doesn't churn the row). Returns whether a row was updated.
func (*PGStore) SetDeployStallReason ¶
SetDeployStallReason records (or clears) why an open deploy is not progressing — w4/m112's in-flight sibling of the terminal failure_reason.
Guarded three ways, because this runs on every reconcile pass over every open deploy:
- `finished_at IS NULL` — a terminal row's story belongs to failure_reason/cancel_reason; a late pass must not reopen it.
- `stall_reason IS DISTINCT FROM $2` — an unchanged observation writes nothing, so a stall that persists for 15 minutes costs one UPDATE.
- `updated_at` is deliberately NOT touched. This is an observation about the row, not a transition of it; bumping the row's clock would churn every updated_at-keyed reader (the events feed, the deploy list's ordering) once per pass for as long as the stall lasts.
func (*PGStore) SetEnvironmentACL ¶
func (s *PGStore) SetEnvironmentACL(ctx context.Context, id, protectedStatus string, networkIsolationEnabled bool, ipAllowList []core.IPAllowListEntry) error
SetEnvironmentACL replaces the full protected-environment ACL triple — full-replace, not a merge, matching every other "Set" verb in this store (SetEnvironmentServices, postgres.SetIPAllowList): the caller always supplies all three fields, never a partial patch.
func (*PGStore) SetEnvironmentServices ¶
func (s *PGStore) SetEnvironmentServices(ctx context.Context, environmentID, projectID, tenantID string, serviceIDs []string) ([]core.ServicePlacementChange, error)
SetEnvironmentServices replaces the full list of services in an environment (within tenantID): clears any apps currently assigned to it, then assigns only the identified apps — mirroring SetProjectServices exactly. Public srv- ids are canonical; names remain accepted for backward compatibility with clients from before stable service ids shipped. It ALSO stamps project_id to projectID on the assigned apps: Render's model treats "in an environment" as "in that project," so assigning to an environment is sufficient to join its project too (a caller doesn't need two calls). Service ids/names not found in tenantID are silently skipped (the UPDATE affects 0 rows for them, the same convention SetProjectServices uses). Returns the per-service placement diff (w6/m134) the service layer records move events from — empty for a no-op replacement.
func (*PGStore) SetPaymentMethodBound ¶
func (s *PGStore) SetPaymentMethodBound(ctx context.Context, workspaceID string, at time.Time) error
SetPaymentMethodBound stamps the marker monotonically. Replayed Stripe webhooks retain the first successful bind timestamp, so the operation is idempotent and never moves the enforcement snapshot backwards or forwards.
func (*PGStore) SetProjectServices ¶
func (s *PGStore) SetProjectServices(ctx context.Context, projectID, tenantID string, serviceIDs []string) ([]core.ServicePlacementChange, error)
SetProjectServices replaces the full list of services in a project (within tenantID): clears any apps currently assigned to it, then assigns only the identified apps. Public srv- ids are canonical; names remain accepted for backward compatibility with clients from before stable service ids shipped. Also NULLs environment_id in the same transaction on departing rows (w4/m32) and on named rows whose environment belongs to another project (w4/m187) — a service must never keep an environment outside its project (and the App CR's frozen spec.environmentIPAllowList that implies): ListEnvironmentServices filters on project_id too, so such a row silently drops out of every environment fan-out while its k8s-projected rules stay stuck. Retained members keep a compatible environment. Returns the per-service placement diff (w6/m134): the service layer records move events from it and derives the environment-layer clear list (rows whose environment this transaction NULLed are exactly the changes with EnvironmentFrom set and EnvironmentTo nil, since nothing here ever sets environment_id). Service ids/names not found in tenantID are silently skipped (the UPDATE affects 0 rows for them).
func (*PGStore) SetTenantBillingExcluded ¶
func (s *PGStore) SetTenantBillingExcluded(ctx context.Context, tenantID string, excluded bool, actor string, at time.Time) (bool, error)
SetTenantBillingExcluded flips a workspace's billing-exclusion flag (docs/ADR040-billing-metronome.md §7, Mode A) and, when the value actually changes, records an audit_events row (verb billing.SetExclusion) attributing it to actor. This flag decides whether money is owed, so its only caller is the admin-only control-plane internal API — never a tenant. Returns whether the value changed (a no-op toggle writes no audit row); ErrNotFound when the workspace does not exist.
func (*PGStore) SetWebhookEndpointEnabled ¶
func (s *PGStore) SetWebhookEndpointEnabled(ctx context.Context, tenantID, id string, enabled bool, reason string) (WebhookEndpoint, error)
SetWebhookEndpointEnabled flips an endpoint's enabled flag (the caller's manual toggle — also how an auto-disabled endpoint is re-armed). Enabling clears any disabled reason; disabling records the caller's.
func (*PGStore) SetWorkspaceCreationSetup ¶
func (s *PGStore) SetWorkspaceCreationSetup(ctx context.Context, id, subject, customerID, setupIntentID string, livemode bool) (WorkspaceCreationAttempt, error)
SetWorkspaceCreationSetup records only safe provider correlations. A retry may repeat the same values; changing an already-recorded object is refused.
func (*PGStore) SetWorkspaceCreationSubscription ¶
func (*PGStore) SettleCanceledPreDeploy ¶
func (s *PGStore) SettleCanceledPreDeploy(ctx context.Context, appID string, generation int64, status string, finishedAt time.Time) (bool, error)
SettleCanceledPreDeploy settles the row and its pre_deploy_ended fact in one statement, so a fact can never be lost to a row that already reads settled.
func (*PGStore) StageBlueprintManifest ¶
func (s *PGStore) StageBlueprintManifest(ctx context.Context, id, tenantID string, generation int64, runID, manifest string) (Blueprint, error)
StageBlueprintManifest stores the admitted sync's preflighted manifest. Only the manifest (the sync-owned field) is written — current name/path/autoSync settings are retained (w8/m37 t005). Zero rows means the generation was fenced by a newer admission or a disconnect → ErrBlueprintSyncBusy.
func (*PGStore) StartSSHSession ¶
func (s *PGStore) StartSSHSession(ctx context.Context, session SSHSessionAudit) error
func (*PGStore) SubjectIsWorkspaceAdmin ¶
func (s *PGStore) SubjectIsWorkspaceAdmin(ctx context.Context, tenantID, subject string) (bool, error)
SubjectIsWorkspaceAdmin reports whether subject currently holds the admin role in tenantID — the webhook failure-notice recipient gate (round-14 #6): a notice discloses (a redacted projection of) a destination that may have been configured by a different admin after this subject was removed or demoted, so CURRENT authorization state, not created_by provenance, decides the recipient. False for a non-member, a non-admin, and a workspace that does not exist — all equally "do not mail".
func (*PGStore) SweepPushRetention ¶
func (*PGStore) SweepWebhookDeliveries ¶
func (s *PGStore) SweepWebhookDeliveries(ctx context.Context, before time.Time, keepPerEndpoint, limit int) (int64, error)
SweepWebhookDeliveries purges reclaimable delivery rows (w1/m67 F3). The table is both the durable delivery QUEUE and the product's history surface, so before m67 nothing ever reclaimed a finished row: ordinary tenant activity grew shared table, index, and backup storage without bound.
Terminal notifications (delivered_at or failed_at set, with no pending child) are purged with all attempts when either:
- older than `before`, so history has a finite lifetime; or
- beyond `keepPerEndpoint` most recent rows for their endpoint, so a burst inside the age window cannot evade the age rule alone.
keepPerEndpoint counts immutable attempts, not parents: a notification with eight retries consumes eight evidence slots. Whole-parent deletion prevents orphaning payloads or leaving a partial forensic sequence.
Notifications whose newest pending reservation is older than `before` are abandoned and purged too. This includes a manual resend parked on a previously terminal parent; recent reservations on old source events remain safe.
Deletion is bounded per call (`limit` for each of the two passes) and safe to run concurrently on two replicas — rows are claimed with FOR UPDATE SKIP LOCKED, so a second sweeper simply takes different rows. Returns the number deleted.
func (*PGStore) TenantContactSubjects ¶
func (s *PGStore) TenantContactSubjects(ctx context.Context, tenantIDs []string) (map[string]string, error)
TenantContactSubjects returns, for each of tenantIDs, the subject whose email is the workspace's contact: its owner binding when set, else its oldest user admin, ties broken by subject (w5/126). One query serves a whole list. Unknown ids and workspaces with neither are absent.
func (*PGStore) TenantForIdentity ¶
TenantForIdentity resolves the subject's default workspace — see the Store interface for the contract. ORDER BY m.created_at is what makes it a *default* rather than an arbitrary row: the bare join returned whichever membership Postgres happened to yield first, so a caller with two workspaces could resolve to a different one call to call (w6/m11 hit this live). The tenant id is the tie-break for two memberships written in the same instant (the same-transaction case: a workspace create inserts tenant + membership together), so the answer is stable even then.
func (*PGStore) TenantForOwner ¶
TenantForOwner returns the personal tenant an identity OWNS (by owner_identity_id), or ErrNotFound — the cheap read the onboarding path tries before the upsert, so a returning caller's login is a single SELECT, not a write transaction. Unlike TenantForIdentity (a membership JOIN that can now return a workspace the caller was merely invited to), this returns only the workspace the caller actually owns (w4/m12).
func (*PGStore) TenantOwnerSubject ¶
TenantOwnerSubject returns the identity a workspace's onboarding binding names (tenants.owner_identity_id), or "" when the binding is NULL — the shape every workspace minted through CreateWorkspace has, since only onboarding's CreateTenantWithMember sets it. ErrNotFound when the workspace doesn't exist.
The membership guards read it (w5/m101): the owner binding is the key EnsureTenant resolves the personal workspace by, so a removed or demoted owner is either silently re-granted admin on their next request or left with no workspace at all — both are refused instead.
func (*PGStore) TerminateMissingSandboxMeters ¶
func (s *PGStore) TerminateMissingSandboxMeters(ctx context.Context, workspaceID string, seen []string, observedAt time.Time) error
TerminateMissingSandboxMeters closes active cursors absent from one complete tenant-scoped OpenSandbox list. The caller invokes it only after a successful list, so an upstream outage never turns running sandboxes into terminated ones. The last known running interval accrues through observedAt.
func (*PGStore) TouchBillingProviderMapping ¶
func (s *PGStore) TouchBillingProviderMapping(ctx context.Context, workspaceID string, at time.Time) error
TouchBillingProviderMapping rotates a mapping to the back of the bounded reconciliation queue after every attempt. That prevents the oldest 500 workspaces (including a permanently failing one) from starving the rest.
func (*PGStore) TouchProject ¶
TouchProject advances projects.updated_at. Link verbs that only re-label Database/KeyValue CRs still count as a project modification for Render's project.updatedAt (w4/m109).
func (*PGStore) TouchRegistryCredential ¶
TouchRegistryCredential bumps updated_at with no other column change — used when only the OpenBao-held secret was rotated, so the metadata row still reflects when the credential last changed.
func (*PGStore) TransitionDeploy ¶
func (*PGStore) UnbindClient ¶
UnbindClient permanently revokes an API key and removes its tenant_members rows atomically. The marker also rejects already-issued tokens on every API replica. Keys are never reused, so an existing marker remains authoritative. A key that was never bound is still revoked without returning an error.
func (*PGStore) UpdateBlueprint ¶
func (s *PGStore) UpdateBlueprint(ctx context.Context, id, tenantID string, name *string, autoSync *bool, path *string, status *string, lastSyncAt *time.Time) (Blueprint, error)
UpdateBlueprint applies a partial update (only non-zero pointer fields are changed). Returns the updated row. Disconnected rows are untouched — ordinary updates cannot reactivate one (w8/m37 t001).
func (*PGStore) UpdateBlueprintSync ¶
func (s *PGStore) UpdateBlueprintSync(ctx context.Context, id, state string, completedAt *time.Time, errMsg *string) (BlueprintSync, error)
UpdateBlueprintSync updates a sync run's state, completion timestamp, and error reason (nil on success, the failure's message on error).
func (*PGStore) UpdateDisk ¶
func (s *PGStore) UpdateDisk(ctx context.Context, id string, name, mountPath *string, sizeGB *int32) (Disk, error)
UpdateDisk applies the mutable fields. A size change closes the current period and opens a new one at the same instant, so the meter integrates the old size right up to the grow and the new size from it — a disk grown mid-hour bills both, which is what makes the invoice reproducible.
func (*PGStore) UpdateEnvGroupMeta ¶
UpdateEnvGroupMeta writes g's name, Environment, links and updated_at if the row still carries g.MetaVersion, and returns the new row. A moved version is ErrEnvGroupChanged, a missing row ErrNotFound, a taken name ErrEnvGroupNameTaken.
func (*PGStore) UpdateJobStatus ¶
UpdateJobStatus transitions a job to the given terminal or running status, stamping the appropriate timestamp. The UPDATE is a no-op on already-terminal rows (prevents double-close races) — callers check the returned row's status.
func (*PGStore) UpdateMemberRole ¶
UpdateMemberRole changes an existing member's role (ErrNotFound when the subject is not a member of the workspace). The role CHECK is enforced by the API layer's validation, not the column, so an unknown role is a caller error mapped upstream, not a constraint violation here.
SECURITY (codex round-16 #3): demoting an admin re-counts admins inside the same transaction under a tenant advisory lock so two concurrent demotions cannot both pass a standalone count and leave zero admins.
func (*PGStore) UpdateRegistryCredential ¶
func (s *PGStore) UpdateRegistryCredential(ctx context.Context, workspaceID, id, name, username string, expiresAt *time.Time) (RegistryCredential, error)
UpdateRegistryCredential changes name/username/expiresAt in place (a rotated secret is a separate OpenBao write the caller makes itself — this method never touches secret material). Nil expiresAt clears any existing expiry.
func (*PGStore) UpdateTenantPlan ¶
UpdateTenantPlan changes a workspace's plan (ErrNotFound when the id doesn't exist). The plan is validated by the caller (workspaces.Service.ChangePlan via store.NormalizePlan) before this write; the `tenants.plan` CHECK constraint is the last-resort backstop, surfaced as ErrInvalid via classify.
func (*PGStore) UpdateWebhookEndpoint ¶
func (s *PGStore) UpdateWebhookEndpoint(ctx context.Context, tenantID, id, name, url string, eventTypes []string, enabled bool) (WebhookEndpoint, error)
UpdateWebhookEndpoint replaces an endpoint's mutable fields (name, url, event_types, enabled) in one SQL round trip. Re-enabling an endpoint clears any disabled_reason; disabling leaves it unchanged (use SetWebhookEndpointEnabled for an explicit reason). Secret is immutable after creation.
func (*PGStore) UpsertBillingProviderMapping ¶
func (s *PGStore) UpsertBillingProviderMapping(ctx context.Context, m BillingProviderMapping) error
func (*PGStore) UpsertBlueprint ¶
UpsertBlueprint creates a blueprint or refreshes its manifest when (tenant_id, repo, branch) already exists. The id field is ignored on conflict — the existing row's id is preserved. Returns the current row.
The conflict arm writes the manifest ONLY (w8/m37 t005): deploy-time auto-registration must never clobber current name/path/autoSync settings or lifecycle status with deploy-request defaults — a direct deploy racing an admitted sync (or a mid-run settings change) keeps the row's settings. It also refuses disconnected rows (re-establishment is confined to explicit creation through AdmitBlueprintCreate, which bumps the execution generation). Generation and active-run ownership are never written here — an auto-register racing a live sync must not fence its own admission.
func (*PGStore) UpsertDevicePushSubscription ¶
func (s *PGStore) UpsertDevicePushSubscription(ctx context.Context, sub DevicePushSubscription) (DevicePushSubscription, error)
UpsertDevicePushSubscription atomically registers or replaces one app installation. The workspace lock makes both cumulative quotas race-safe; the token digest lock serializes account-switch races for the same provider capability. A token moved to another member/device revokes its old row before activation.
func (*PGStore) UpsertNotificationPushPolicy ¶
func (s *PGStore) UpsertNotificationPushPolicy(ctx context.Context, tenantID, subject string, policy json.RawMessage) (NotificationSettings, error)
UpsertNotificationPushPolicy replaces only the caller's native-push policy. On first write it creates the same existing notification_settings row with the established failure-only email defaults; later writes leave every email preference byte-for-byte unchanged.
func (*PGStore) UpsertNotificationSettings ¶
func (s *PGStore) UpsertNotificationSettings(ctx context.Context, tenantID, subject string, deployStarted, deploySucceeded, deployFailed bool) (NotificationSettings, error)
UpsertNotificationSettings writes a member's preferences, creating the row on first write and updating it (bumping updated_at) thereafter — the single write path both REST/GraphQL/MCP `UpdateSettings` uses.
func (*PGStore) UpsertUsageHourly ¶
UpsertUsageHourly writes one window row, creating it or updating the quantity to the new value if the (resource_kind, service_id, kind, tier, window_start) key already exists. The ON CONFLICT … DO UPDATE makes the rollup loop idempotent.
func (*PGStore) UpsertWebPushSubscription ¶
func (s *PGStore) UpsertWebPushSubscription(ctx context.Context, sub WebPushSubscription) (WebPushSubscription, error)
func (*PGStore) UsageMonthToDate ¶
func (s *PGStore) UsageMonthToDate(ctx context.Context, workspaceID string, now time.Time) ([]UsageSummaryRow, error)
UsageMonthToDate returns month-to-date totals for all services in a workspace, grouped by (resource_kind, service_id, kind, tier). now is the caller-supplied clock value so tests can drive it without real time. "Month to date" is calendar-month-start (UTC) to now.
The query sums usage_hourly and usage_monthly together (w8/m4): a hot month has only hourly rows, a compacted month has only its monthly aggregate, and a month caught mid-transition (partially compacted at the 48 h clamp) has both — the UNION ALL sum is exact in every state, so period queries never depend on whether compaction has run yet.
func (*PGStore) WebhookEndpointWorkspace ¶
WebhookEndpointWorkspace returns the workspace that owns an endpoint id, or ErrNotFound. A routing read only: the verb still authorizes against that workspace and loads the row through the tenant-scoped reads (w4/m172).
func (*PGStore) WithAppAdvisoryLock ¶
WithAppAdvisoryLock runs fn while holding a transaction-scoped Postgres advisory lock for one app's deploy triggers (w8/m46), so two bex-api replicas cannot interleave a trigger's row image, CR patch and deploy-row open. The prefix keeps this lock domain apart from the workspace and subject locks above.
func (*PGStore) WithEnvGroupContentLock ¶
func (s *PGStore) WithEnvGroupContentLock(ctx context.Context, id string, fn func(context.Context) error) (err error)
WithEnvGroupContentLock serializes a group's external value/projection effects across API replicas. A lease permits recovery only after the previous live writer releases its session (or crashes); it never licenses overwriting a stalled writer's later effects. The callback's group queries use this same connection, including detached compensation, so a one-connection pool works. Statements commit individually: a crash must retain the busy claim and op. The callback must not use the supplied context concurrently or retain it.
func (*PGStore) WithTenantAdvisoryLock ¶
func (s *PGStore) WithTenantAdvisoryLock(ctx context.Context, tenantID string, fn func() error) error
WithTenantAdvisoryLock runs fn while holding a transaction-scoped Postgres advisory lock for tenantID. It coordinates count-then-write admission paths across bex-api replicas even when the write itself reaches an external provider before persisting its tenant binding.
func (*PGStore) WorkspaceForSandboxKey ¶
WorkspaceForSandboxKey resolves an OpenSandbox tenant key to its workspace id, returning ErrNotFound for an unknown key — which the tenant-lookup endpoint maps to the 401 the OpenSandbox HTTP tenant provider expects (invalid key).
type PaymentEligibility ¶
PaymentEligibility is the local paid-intent/export snapshot for one workspace. Excluded and comped are explicit operator exemptions; Bound is stamped only by the verified checkout.session.completed path (ADR046).
func (PaymentEligibility) AllowsPaidIntent ¶
func (p PaymentEligibility) AllowsPaidIntent() bool
type PlanLimits ¶
type PlanLimits struct {
// MaxServices caps services per workspace, counting suspended ones (Render:
// "Up to 25 services, all service types, including suspended"). 0 = unlimited.
MaxServices int
// MaxMembers caps members per workspace. Hobby is single-member (1); paid
// plans are unlimited (0).
MaxMembers int
// MaxWorkspacesPerUser caps how many workspaces of this plan one user may
// own. Render allows five free Hobby workspaces per user and unlimited paid
// ones, so only Hobby sets this (5). 0 = unlimited.
MaxWorkspacesPerUser int
// AllowedRoles is the role set assignable on the plan — Render's plan-gated
// role catalog (RESEARCH-workspaces.md finding 5, docs/render-artifacts/team-members.graphql):
// Hobby is single-member (no invites, the sole member is always admin); Pro
// adds Developer; Scale and Enterprise add Contributor, Viewer, and Billing.
// Lowercase (the stored/FGA form), matching members.Roles.
AllowedRoles []string
}
PlanLimits are the caps and capabilities a plan grants — the one place a plan's full shape is defined. A zero numeric field means "unlimited" — the paid plans lift Hobby's caps entirely (Render's Pro/Scale/Enterprise all allow unlimited members and services), so only Hobby carries non-zero caps.
func LimitsFor ¶
func LimitsFor(plan string) PlanLimits
LimitsFor returns the caps and capabilities for a plan. Unknown plans get the (unlimited, full-role) paid shape rather than Hobby's — validation rejects unknown plans upstream, so this is only a safe default, never a silent downgrade.
type ProductInventoryCollector ¶
ProductInventoryCollector runs off the reconciliation/request paths. A source is published atomically only after its entire authoritative list succeeds.
func (*ProductInventoryCollector) Collect ¶
func (c *ProductInventoryCollector) Collect(ctx context.Context) error
func (*ProductInventoryCollector) Run ¶
func (c *ProductInventoryCollector) Run(ctx context.Context)
type Project ¶
type Project struct {
ID string `json:"id"`
TenantID string `json:"tenantId"`
Name string `json:"name"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
Project is a row of `projects` — a named grouping of services within a workspace. Services opt-in to a project by having project_id set in `apps`.
type PushNotification ¶
type PushNotification struct {
TenantID string
Subject string
SourceEventKey string
EventID string
EventType string
Title string
Body string
Urgency string
ResourceKind string
ResourceID string
DeepLink string
OccurredAt time.Time
DeliverAt time.Time
CreatedAt time.Time
ReadAt *time.Time
}
PushNotification is one durable logical inbox item. It is intentionally thin and token-free; clients fetch details through the authenticated API.
type PushNotificationBatchItem ¶
type PushNotificationBatchItem struct {
Notification PushNotification
DeviceIDs []string
}
PushNotificationBatchItem fans one logical notification out to these exact active device identities in the same transaction as the push watermark.
type PushQueueStats ¶
type PushSweepResult ¶
type QuotaCaps ¶
QuotaCaps are a plan's per-workspace object-count ceilings for Services/Postgres/KeyValues — the same numbers the per-namespace ResourceQuota (quotaForPlan, namespaces.go) enforces at the API server in place of the retired BEX_MAX_SERVICES/_POSTGRES/_KEYVALUES app-code caps (ADR043 D3). One source of truth so enforcement and the "3/5 services" display surface (workspaces.Service.ResourceLimits) can't drift apart.
func QuotaCapsForPlan ¶
QuotaCapsForPlan returns plan's object-count ceilings. Matches on PlanHobby (plus "" and "free" for legacy/test rows created before plan normalization) so every real Hobby workspace — whose stored plan is always "hobby" via NormalizePlan — gets the intended Render-Hobby-anchored ceiling rather than silently falling through to the generous paid default. Services reuses LimitsFor(PlanHobby).MaxServices — the same Render-Hobby-anchor number — rather than a second hardcoded literal, so the two catalogs can't drift.
type Reconciler ¶
type Reconciler struct {
// ProductObserver samples hosting/TLS without changing reconciliation intent.
ProductObserver func(context.Context, DesiredApp, *appv1alpha1.App)
Client client.Client
Store Store
Resync time.Duration // full-resync interval
// Identity names this control-plane instance (BEX_CP_IDENTITY). It is
// stamped on every App CR this projector owns and scopes its delete-by-
// absence pass. Empty is read as DefaultControlPlaneIdentity.
Identity string
// BuildNamespace is BEX_BUILD_NAMESPACE: where a release a suspend ended
// has its build artifacts deleted (CancelRelease). Empty means the App's
// own namespace.
BuildNamespace string
// DeployGateTimeout bounds how long a deploy may stay open before
// recordDeploy closes it as failed even though the CR's phase never
// reached Failed on its own (see defaultDeployGateTimeout).
DeployGateTimeout time.Duration
BuildGateTimeout time.Duration
PreDeployGateTimeout time.Duration
// CloneSecrets, when non-nil, is called for each new projected App CR
// whose row has a non-empty Repo, to mint and write the per-app
// clone-credential Secret. Useful for rows created via the internal CP
// API (store/api.go) where the public-surface create path hasn't already
// done so. Soft failure: a minting error is logged but the CR is still
// created (public repos don't need a secret).
CloneSecrets CloneSecreter
// DeployNotifier, when non-nil, is called every time recordDeploy closes a
// deploy as succeeded or failed — see DeployNotifier. nil => no emails
// (notifications feature off / store off).
DeployNotifier DeployNotifier
// Metrics, when non-nil, counts the conclusions rejectStaleUnhealthy
// refuses (w6/m41). nil => rejections are logged but not metered.
Metrics *ReconcilerMetrics
// contains filtered or unexported fields
}
Reconciler projects the source of truth into the cluster: each apps row (+ its domains) becomes an App CR; rows deleted from Postgres get their CR deleted; the CR's observed status (phase, url) is written back to the row. It is level-triggered — a full resync every Resync plus a Kick after API writes — so etcd stays a rebuildable projection of Postgres.
func NewReconciler ¶
func NewReconciler(cl client.Client, store Store) *Reconciler
func (*Reconciler) Kick ¶
func (r *Reconciler) Kick()
Kick schedules an immediate reconcile (non-blocking, coalescing). The API calls it after every successful write so a POST is projected within milliseconds instead of a resync period.
func (*Reconciler) ReconcileOnce ¶
func (r *Reconciler) ReconcileOnce(ctx context.Context) error
ReconcileOnce drives one full pass: desired rows vs. existing managed CRs → create / update / delete, then copies each CR's status back to its row. Per-app failures are collected, not fatal — one bad row can't block the rest.
func (*Reconciler) Run ¶
func (r *Reconciler) Run(ctx context.Context)
Run reconciles until ctx is done.
type ReconcilerMetrics ¶
type ReconcilerMetrics struct {
// contains filtered or unexported fields
}
ReconcilerMetrics makes the reconciler's refused conclusions a readable signal instead of an invisible suppression (w6/m41): a guard that silently drops observations is one incident away from being blamed for a missed outage. The healthy shape is zero in steady state, climbing during a control-plane incident (informer staleness is exactly the condition that produces time-traveled conclusions).
func NewReconcilerMetrics ¶
func NewReconcilerMetrics(registerer prometheus.Registerer) *ReconcilerMetrics
func (*ReconcilerMetrics) Rejection ¶
func (m *ReconcilerMetrics) Rejection(reason, subject string)
Rejection counts one refused conclusion. Both labels come from closed vocabularies — reason from rejectReason*, subject from rejectSubject* — never anything tenant-derived, so label cardinality stays bounded regardless of fleet size. subject separates the App observation path from the managed- datastore one (w3/m82): they share the guard but not the incident.
type RegistryCredential ¶
type RegistryCredential struct {
ID string
WorkspaceID string
// Name is a human display label (Render's registryCredential.name) — the
// caller may leave it empty at creation, in which case CreateRegistryCredential
// defaults it to host.
Name string
Host string
Username string
ExpiresAt *time.Time
CreatedBy string
CreatedAt time.Time
UpdatedAt time.Time
}
RegistryCredential is a row of `registry_credentials` (w2/m14): the metadata for a workspace's stored credential to a private external image registry. The secret value is never a field here — it lives in OpenBao, resolved separately by the registrycreds feature (docs/ADR013-secrets.md, the same split the env-vars/secret-files feature uses).
type ResourceDisplayName ¶
type ResourceDisplayName struct {
Kind string // ResourceKind* — "service", "postgres", "key_value", "sandbox"
ID string
Name string
}
ResourceDisplayName is one retained (kind, id) -> name record for a tenant. See migration 0121: the record outlives the resource so a charge line can still name what it is billing for.
type RetiredUsageResource ¶
type RetiredUsageResource struct {
ResourceKind string
ResourceID string
WorkspaceID string
// Name is an App's service name (its pod identity derives from it) or a
// datastore's CR name; Namespace, Display and Public are datastores' only.
Name string
Namespace string
Display string
Public bool
Tier string
CreatedAt time.Time
RetiredAt time.Time
}
RetiredUsageResource is a deleted resource the usage meters still owe hours for, through the hour it was deleted in (w4/m173; ADR023). Apps are retired by a trigger on their row's deletion (migration 0151); datastores, which have no row, by their delete verbs through UsageRetirementRecorder.
func RetiredDatastoreFor ¶
func RetiredDatastoreFor(obj client.Object) (r RetiredUsageResource, ok bool)
RetiredDatastoreFor is what metering needs from a Database or KeyValue CR about to be deleted; ok is false for anything else, or a CR no workspace owns (which the meters never listed).
type RoleReconciliation ¶
type RoleReconciliation struct {
TenantID string
Subject string
Role string
Attempts int
CreatedAt time.Time
UpdatedAt time.Time
}
RoleReconciliation is one claimed Postgres -> OpenFGA exact-role repair. Subject is the raw identity id; callers add OpenFGA's user: prefix.
type SSHKey ¶
type SSHKey struct {
ID string `json:"id"`
Subject string `json:"-"`
Name string `json:"name"`
PublicKey string `json:"publicKey"`
Fingerprint string `json:"fingerprint"`
CreatedAt time.Time `json:"createdAt"`
}
SSHKey is one identity-owned public key. Private key material never enters the API or store; Fingerprint is the canonical OpenSSH SHA256 fingerprint.
type SSHSessionAudit ¶
type SSHSessionAudit struct {
ID string
Subject string
WorkspaceID string
ServiceID string
InstanceID string
RemoteAddress string
StartedAt time.Time
}
SSHSessionAudit contains connection metadata only. Its schema deliberately has no command, environment, or stream-content column, making terminal-data capture impossible through this persistence seam.
type SandboxMeterObservation ¶
type SandboxMeterObservation struct {
WorkspaceID string
SandboxID string
Phase string
Tier string
WeightMilli int64
ObservedAt time.Time
}
SandboxMeterObservation is one authoritative OpenSandbox phase sample. WeightMilli is the sandbox shape expressed as milli-vCPU equivalents, with memory folded in at the documented AgentCore CPU:memory price ratio.
type SandboxTenantKey ¶
SandboxTenantKey is one workspace's OpenSandbox tenant credential. It is used only by the internal metering poller and must never be logged.
type SandboxTenantResolver ¶
type SandboxTenantResolver interface {
WorkspaceForSandboxKey(ctx context.Context, apiKey string) (string, error)
}
SandboxTenantResolver maps an OpenSandbox tenant key to its workspace id. Satisfied by *PGStore (WorkspaceForSandboxKey); injected by cmd/api so the store's big Store interface (and its fakes) need not carry the method.
type SandboxUsageMetadata ¶
SandboxUsageMetadata keeps historical session labels separate from the last observed lifecycle. An empty Phase means unknown; a label never proves liveness.
type ServiceEventFact ¶
type ServiceEventFact struct {
SourceKey string
AppID string
Type ServiceEventFactType
At time.Time
DeployID string
Image string
ReasonCode string
InstanceID string
FromCount *int32
ToCount *int32
BranchFrom string
BranchTo string
CommitID string
CommitURL string
// Status is the terminal outcome of a lifecycle-step fact (build_ended,
// pre_deploy_ended, job_run_ended): one of EventStatus* or "" for the
// started/observed kinds that have no outcome. Closed set (w7/m66).
Status string
// ExitCode is a cron_job_run_ended detail (w4/m114): a failed run's
// container exit status (with ReasonCode non_zero_exit or oom_killed).
// RunID is the run's public crr- id on both cron_job_run_started (w4/259)
// and cron_job_run_ended.
ExitCode *int32
RunID string
}
ServiceEventFact is a closed, non-secret event record. SourceKey is a stable producer identity used for idempotency; it is never exposed directly.
func CanceledBuildLifecycleFacts ¶
func CanceledBuildLifecycleFacts(open Deploy) []ServiceEventFact
CanceledBuildLifecycleFacts derives the build_started/build_ended pair (if any) for a deploy the Cancel verb closes directly (w6/m128) — the one lifecycle transition that bypasses the reconciler entirely, so recordDeploy never runs for it and buildLifecycleFacts is otherwise unreachable. It reuses that same rule rather than re-deriving it: canceled while queued still emits neither fact, canceled mid-build emits build_ended(canceled), and canceled after the build finished emits build_ended(succeeded). open must be the deploy row exactly as it stood before the cancel closed it.
type ServiceEventFactType ¶
type ServiceEventFactType string
ServiceEventFactType is the closed vocabulary persisted outside deploy and audit rows. Keep this in the store package: the control-plane reconciler and signed Git webhook produce facts without importing the presentation feature.
const ( EventFactImagePullFailed ServiceEventFactType = "image_pull_failed" EventFactServiceSuspended ServiceEventFactType = "service_suspended" EventFactServiceResumed ServiceEventFactType = "service_resumed" // Free-tier idle auto-sleep (w6/m47). Deliberately NOT the suspended/resumed // pair: those stay exclusively user-driven, so a webhook or push subscriber // watching for an unexpected suspension is not woken by every routine sleep // cycle of a free service. EventFactServiceHibernated ServiceEventFactType = "service_hibernated" EventFactServiceWoken ServiceEventFactType = "service_woken" EventFactServerFailed ServiceEventFactType = "server_failed" EventFactServerAvailable ServiceEventFactType = "server_available" EventFactBranchChanged ServiceEventFactType = "branch_changed" EventFactBranchDeleted ServiceEventFactType = "branch_deleted" EventFactCommitIgnored ServiceEventFactType = "commit_ignored" EventFactAutoscalingStarted ServiceEventFactType = "autoscaling_started" EventFactAutoscalingEnded ServiceEventFactType = "autoscaling_ended" // Deploy-lifecycle facts (w7/m66): the build, pre-deploy, and one-off-job // beats Render shows as distinct timeline entries. The *_ended kinds carry a // closed Status (succeeded|failed|canceled); the operator observes them // through the same control-plane reconciler path image_pull_failed rides. EventFactBuildStarted ServiceEventFactType = "build_started" EventFactBuildEnded ServiceEventFactType = "build_ended" EventFactPreDeployStarted ServiceEventFactType = "pre_deploy_started" EventFactPreDeployEnded ServiceEventFactType = "pre_deploy_ended" EventFactJobRunEnded ServiceEventFactType = "job_run_ended" EventFactCronRunStarted ServiceEventFactType = "cron_job_run_started" EventFactCronRunEnded ServiceEventFactType = "cron_job_run_ended" )
type ServiceEventFilter ¶
type ServiceEventFilter struct {
// Since/Until bound At inclusively (Render's startTime/endTime).
Since time.Time
Until time.Time
// AfterAt/AfterKey resume strictly after a previously returned event —
// keyset paging on the feed's total order (At DESC, Key DESC), so a row
// inserted between two pages shifts nothing already returned. Zero AfterAt
// starts at the head.
AfterAt time.Time
AfterKey string
// Verbs are the audit verbs that map to an event type the caller asked for
// (internal/events owns the mapping; the store never interprets a verb).
Verbs []string
// Phases are the deploy transitions the caller asked for (EventPhaseStarted
// and/or EventPhaseEnded).
Phases []string
// FactTypes are the closed service_event_facts kinds requested by the caller.
FactTypes []string
// AutoDeploy pushes down the auto-deploy boolean discrimination into SQL when
// the Verbs set includes apps.SetAutoDeploy: the OR of the sub-types to keep,
// applied to SetAutoDeploy rows only. AutoDeployFilterNone (zero value) means
// no additional constraint on auto_deploy_enabled.
AutoDeploy AutoDeployFilter
// Limit caps the page (<1 or >core.MaxPageLimit clamps to core.DefaultPageLimit).
Limit int
}
ServiceEventFilter narrows ListServiceEvents.
Verbs and Phases are how the caller's event-TYPE filter is pushed down into SQL. They are the only two knobs the vocabulary needs, and passing them (rather than filtering the result in Go) is what keeps a page exactly `Limit` long: a Go-side filter after the LIMIT returns short — sometimes empty — pages, which a cursor client reads as the end of the feed and stops on. Empty Verbs excludes every audit row; empty Phases excludes every deploy row.
type ServiceEventLookup ¶
type ServiceEventLookup struct {
Event ServiceEventRow
ServiceID string
}
ServiceEventLookup is one globally-addressed event plus the resource identity materialized beside its source key. ServiceID is the canonical public id: apps.id (srv-…) for a service, or the typed dpg-/red- target for a datastore.
type ServiceEventRow ¶
type ServiceEventRow struct {
// Key is the row's stable identity within the feed: "<source row id>:<phase>"
// for a deploy ("dep-abc:started"), "<audit row id>:" for an audit event. It
// is the sort tiebreak and the cursor's second component — unique by
// construction, since a source row id is unique and a row projects each phase
// at most once. internal/events hashes it into the public evt-… id.
Key string
At time.Time
// Source is EventSourceDeploy, EventSourceAudit, or EventSourceFact.
Source string
// Phase is EventPhaseStarted/EventPhaseEnded for a deploy row; empty for audit.
Phase string
// Deploy rows only.
DeployID string
Trigger string // "create" (the app's first deploy) | "api"; the started phase only
Status string // the deploy's terminal status; the ended phase only
// PreDeployStatus is the deploy's pre-deploy step outcome (w1/m33): ” |
// 'running' | 'succeeded' | 'failed'; the ended phase only.
PreDeployStatus string
// FailureReason is the human-actionable cause stamped when the reconciler
// closes the deploy failed (w1/m138); the ended phase only. Empty on
// non-failed deploys — the deploys table stores it that way.
FailureReason string
// CancelReason is the neutral cause of a non-user cancel (w4/089) —
// "Superseded by dep-…", or the suspend that ended the rollout (w4/m171) —
// on deploy_ended and on lifecycle facts joined to that deploy. Empty for
// user cancels and non-canceled rows.
CancelReason string
// StallReason is why an OPEN deploy is not progressing (w4/m112) — the
// operator's live diagnosis of the current revision's pods. The STARTED
// phase only, and only while the deploy is open: the row's own column is
// cleared as it goes terminal. An observation, not a verdict.
StallReason string
// Deployed image URI; empty for non-deploy rows. (w1/m47)
Image string
// Commit ID (git revision); empty for non-deploy rows. (w1/m47)
CommitID string
// Commit message; empty for non-deploy rows. (w1/m47)
CommitMessage string
// When the deploy started executing; nil for non-deploy or not-yet-started. (w1/m47)
StartedAt *time.Time
// When the deploy finished (terminal status reached); nil for non-deploy or ongoing. (w1/m47)
FinishedAt *time.Time
// Audit rows and deploy rows (w4/072). Same subject form as audit_events.caller.
Verb string // e.g. "apps.Suspend"; empty on deploy rows
Caller string // core.Identity.Subject; empty when the source has none
// Typed per-verb detail fields from audit_events — nil for every other verb.
PlanFrom *string
PlanTo *string
InstanceCountFrom *int32
InstanceCountTo *int32
AutoscalingMinFrom *int32
AutoscalingMaxFrom *int32
AutoscalingMinTo *int32
AutoscalingMaxTo *int32
AutoDeployEnabled *bool
// service_moved placement pair (w6/m134): public prj-/env- ids, nil = no
// placement on that side. Nil for every other verb.
ProjectFrom *string
ProjectTo *string
EnvironmentFrom *string
EnvironmentTo *string
// Managed-datastore configuration values (w3/m82): what a field-level
// datastore effect set its field TO. Nil for every other verb.
HighAvailabilityEnabled *bool
ConnectionPoolEnabled *bool
DiskSizeGB *int32
MaxmemoryPolicy *string
PersistenceMode *string
// Typed service_event_facts columns. FactType is the closed discriminator;
// all remaining values are bounded scalars used only by the types that own
// them. Image and CommitID reuse the deploy columns above.
FactType string
ReasonCode string
InstanceID string
FromCount *int32
ToCount *int32
BranchFrom string
BranchTo string
CommitURL string
// FactStatus is a lifecycle-step fact's terminal outcome (build_ended,
// pre_deploy_ended, job_run_ended): succeeded|failed|canceled, or "" for the
// started/observed kinds. A distinct column from the deploy-arm Status above,
// which carries a deploy row's terminal status (w7/m66).
FactStatus string
// FactExitCode belongs to cron_job_run_ended (w4/m114): the failed run's
// container exit status. FactRunID is the run's public crr- id on cron
// starts (w4/259) and ends.
FactExitCode *int32
FactRunID string
}
ServiceEventRow is one row of the composed feed — the raw projection, before internal/events maps it onto Render's event vocabulary. Deploy rows fill DeployID/Trigger/Status and Caller (the opener's subject, w4/072); audit rows fill Verb/Caller and the typed per-verb detail fields. No column here can carry a free-form value: deploy rows hold ids, a status enum, and the same identity subject audit_events.caller stores; audit rows hold a verb name, a caller subject, and typed scalars mirroring audit_events' typed columns.
type SetBillingExcludedRequest ¶
type SetBillingExcludedRequest struct {
Excluded bool `json:"excluded"`
Actor string `json:"actor,omitempty"`
}
SetBillingExcludedRequest is the PATCH /v1/tenants/{id}/billing-excluded body. Actor attributes the audit row (defaults to "control-plane").
type Store ¶
type Store interface {
CreateTenant(ctx context.Context, name, plan string) (Tenant, error)
ListTenants(ctx context.Context) ([]Tenant, error)
// GetTenant and CountAppsForTenant back the per-plan service cap the create
// path enforces (w6/m1): a Hobby workspace is capped at 25 apps.
GetTenant(ctx context.Context, id string) (Tenant, error)
CountAppsForTenant(ctx context.Context, tenantID string) (int, error)
// SetTenantBillingExcluded flips a workspace's Stripe billing-exclusion
// flag (docs/ADR040-billing-metronome.md §7), auditing the change. Admin-only
// — the control-plane internal API is its sole caller. Returns whether the
// value changed; ErrNotFound for an unknown workspace.
SetTenantBillingExcluded(ctx context.Context, tenantID string, excluded bool, actor string, at time.Time) (bool, error)
// TenantForIdentity returns the subject's DEFAULT workspace (w6/m14): the
// tenant of its OLDEST membership in tenant_members, or ErrNotFound. One
// lookup serves both human (Kratos identity id) and machine (Hydra client
// id) callers since tenant_members.subject covers both kinds of id.
//
// The default-workspace contract: a subject may belong to several
// workspaces (w6/m1 multi-workspace, w4/m12 invites), so this is the
// implicit resolution used when a caller names no workspace — deterministic
// by membership created_at (tenant id as the tie-break), never an arbitrary
// row of the join. Oldest-first makes it the workspace the caller has had
// longest, which for a human is the personal tenant minted on first login,
// matching Render ("passing a user id returns that user's default
// workspace"). A caller that wants another of its workspaces names it
// explicitly (REST ownerId / GraphQL ownerId / MCP workspaceId →
// core.WithWorkspace), which is membership-checked at core.Base.
TenantForIdentity(ctx context.Context, subject string) (Tenant, error)
// IsMember reports whether a subject belongs to a workspace — the check
// core.Base runs before honoring an explicit workspace override, so naming
// a workspace the caller is not a member of is refused (ErrForbidden)
// rather than silently redirected to the caller's own (w6/m14).
IsMember(ctx context.Context, subject, tenantID string) (bool, error)
// CreateTenantWithMember mints a personal tenant for an identity on first
// login: a tenant row owned by the identity plus an admin membership. It is
// idempotent and race-safe — concurrent first logins for the same identity
// yield exactly one tenant (the partial unique index on owner_identity_id
// is the gate, not a check-then-insert). The tenant name is a placeholder
// (its id) pending a future rename API.
CreateTenantWithMember(ctx context.Context, identityID, plan string) (Tenant, error)
// AddMember records a subject's membership in a tenant (idempotent). The
// platform tenant-create path uses it to make the Admin identity a member —
// without the row the resolver can't map that identity to its workspace.
AddMember(ctx context.Context, subject, tenantID, role string) error
// BindClient records that an API key belongs to a tenant — a tenant_members
// row keyed by the key's client_id, the same table TenantForIdentity reads
// (idempotent — a re-bind to the same or another tenant upserts). The
// api-keys mint calls this after creating the Hydra client.
BindClient(ctx context.Context, clientID, tenantID string) error
// UnbindClient removes an API key's tenant binding (idempotent — a key that
// was never bound is not an error). The api-keys revoke calls this.
UnbindClient(ctx context.Context, clientID string) error
CreateApp(ctx context.Context, a App) (App, error)
GetApp(ctx context.Context, id string) (App, error)
ListApps(ctx context.Context) ([]App, error)
DeleteApp(ctx context.Context, id string) error
CreateDomain(ctx context.Context, appID, host string, primary bool) (Domain, error)
ReplaceDomains(ctx context.Context, appID, primary string, hosts []string) error
// DeleteDomain removes a custom domain row. Not-found is ErrNotFound.
DeleteDomain(ctx context.Context, appID, host string) error
// Persistent service disks (docs/ADR082-persistent-disks.md). The row is
// the source of truth for both the projected spec.disk and the provisioned
// GB-seconds meter, which is why every write also closes or opens a size
// period — see service_disk_sizes in migration 0096.
//
// CreateDisk returns ErrConflict when the service already has a live disk:
// a service can carry at most one, and the unique index enforces it even
// against a racing second create.
CreateDisk(ctx context.Context, tenantID, appID, name, mountPath string, sizeGB int32) (Disk, error)
GetDisk(ctx context.Context, id string) (Disk, error)
// ListDisks returns the workspace's live disks, newest first. An empty
// appID lists them all; a non-empty one narrows to a single service.
ListDisks(ctx context.Context, tenantID, appID string) ([]Disk, error)
// UpdateDisk applies a grow and/or a rename/remount. sizeGB below the
// current size is ErrInvalid — a disk never shrinks (ADR082 D2).
UpdateDisk(ctx context.Context, id string, name, mountPath *string, sizeGB *int32) (Disk, error)
// DeleteDisk soft-deletes the row, which both detaches the volume (the
// projector clears spec.disk) and stops the meter by closing the open size
// period. The row is retained so an already-billed period stays auditable.
DeleteDisk(ctx context.Context, id string) error
// DiskUsageForWindow integrates provisioned disk GB-seconds per service
// over [from, to); LatestUsageWindowForKind is that meter's cursor.
DiskUsageForWindow(ctx context.Context, from, to time.Time) ([]DiskUsageRow, error)
LatestUsageWindowForKind(ctx context.Context, kind string) (time.Time, bool, error)
ListDesiredApps(ctx context.Context) ([]DesiredApp, error)
// SetAppSuspended flips the row's suspended flag — the single write path
// for suspend/resume on store-managed Apps. bex-api's lifecycle verbs call
// this (row first, then the CR fast-path) so the projection loop never
// reverts a suspend it didn't know about.
SetAppSuspended(ctx context.Context, id string, suspended bool) error
// SetAppTier updates the row's tier — the single write path for plan
// changes on store-managed Apps, same row-first rationale as
// SetAppSuspended.
SetAppTier(ctx context.Context, id string, tier string) error
// SetAppReplicas updates the row's replica count — the single write path
// for the manual-scale verb on store-managed Apps, same row-first
// rationale as SetAppSuspended (the projector owns spec.replicas).
SetAppReplicas(ctx context.Context, id string, replicas int32) error
// SetAppIdleTTL updates the row's idle-TTL — the single write path for the
// idle-timeout verb on store-managed Apps (the projector owns
// spec.idleTTLSeconds), same row-first rationale as SetAppReplicas.
SetAppIdleTTL(ctx context.Context, id string, seconds int32) error
// SetAppDisplayName mirrors the App CR's mutable human label onto the row.
// Unlike its neighbours here the row is NOT the writer of truth: the CR's
// spec.displayName is, and this is only its read projection, kept so a
// store-side query can resolve the label without a per-row k8s read
// (w6/m101 — the workspace-wide event feed, webhook and push dispatch
// alike, is its only reader).
SetAppDisplayName(ctx context.Context, id string, displayName string) error
// SetAppSource atomically updates the projector-owned source tuple and its
// context-sensitive registry credential binding.
SetAppSource(ctx context.Context, id, repo, image, branch string, registryCredentialID *string) error
// BackfillAppType records the service type of a row created before the type
// column existed (w6/m46 t001), read off the row's own live App CR. It is a
// one-way heal, never a rename: the type is immutable, so it only ever
// writes a row whose type is still empty and reports whether it wrote. Not a
// lifecycle verb — the projector is its sole caller.
BackfillAppType(ctx context.Context, id, serviceType string) (bool, error)
// SetAppImage updates the row's image — the single write path for a
// rollback's restored image on store-managed Apps (the projector owns
// spec.image), same row-first rationale as SetAppReplicas (w2/m10).
SetAppImage(ctx context.Context, id string, image string) error
// GetEnvironmentProtectedStatus resolves an Environment's destructive-verb
// protection state for Database/KeyValue members whose membership is stored
// as a CR label rather than in a control-plane resource row.
GetEnvironmentProtectedStatus(ctx context.Context, environmentID string) (string, error)
// UpsertUsageHourly writes one window row idempotently (ON CONFLICT DO
// UPDATE) — the write path for the metering loop (w8/m1). Re-processing
// the same window is safe.
UpsertUsageHourly(ctx context.Context, row HourlyRow) error
// RecordUsageSourceHealth persists explicit source evidence for a window
// that could not produce a usage row. Missing evidence remains unknown.
RecordUsageSourceHealth(ctx context.Context, records []UsageSourceRecord) error
// ReconcileUsageSourceStreams closes streams for resources no longer in the
// collector inventory, without deleting their current-month evidence.
ReconcileUsageSourceStreams(ctx context.Context, active []UsageResourceRef, through time.Time) error
// CurrentUsageCoverage aggregates explicit current-month source evidence.
CurrentUsageCoverage(ctx context.Context, workspaceID string, now time.Time) (UsageCoverage, error)
// LatestUsageWindow returns the most-recent window_start for one resource
// and meter kind so each meter can catch up independently after a restart.
// Returns zero time when no matching rows exist yet.
LatestUsageWindow(ctx context.Context, resourceKind, serviceID, kind string) (time.Time, error)
// UsageMonthToDate returns month-to-date aggregates (grouped by service /
// kind / tier) for a workspace, bounded by the caller-supplied now so tests
// don't depend on wall time. Sums usage_hourly and usage_monthly together,
// so the result is exact whether or not the month has been compacted.
UsageMonthToDate(ctx context.Context, workspaceID string, now time.Time) ([]UsageSummaryRow, error)
// CompactUsage folds hourly rows older than before into usage_monthly and
// purges them — atomic and idempotent; the retention loop (w8/m4) calls it
// daily with the hot-window boundary.
CompactUsage(ctx context.Context, before time.Time) (UsageCompaction, error)
// CreateDeploy opens a new deploy row for appID (status DeployCreated) —
// CreateApp calls this for an app's first deploy
// (trigger "create"); the deploys feature's Trigger verb calls it for an
// explicit redeploy (trigger "api"). generation is the App CR's
// metadata.generation this deploy runs under, captured once at open time
// (w2/m10) — Cancel's build-Job identity is derived from this stored
// value, not a fresh re-fetch, so a later unrelated spec write can't make
// it compute the wrong Job name. commit is the resolved commit this deploy
// runs (w9/001), zero when unresolvable. triggeredBy is
// core.Identity.Subject for the caller that opened the row (w4/072), or
// "" for git auto-deploy / deploy-hook / system paths. The reconciler's
// write-back closes it.
CreateDeploy(ctx context.Context, appID, trigger, image string, generation int64, commit CommitInfo, triggeredBy string) (Deploy, error)
// LatestDeployCommit returns the newest non-empty commit for the app, or
// zero CommitInfo when none exists (image-backed history, or only empty
// rows). Used by rollout.Tracker to carry a prior release's provenance
// onto config_change re-rolls — CreateDeploy itself never inherits.
LatestDeployCommit(ctx context.Context, appID string) (CommitInfo, error)
// CreateRollbackDeploy opens a new deploy row for appID whose trigger is
// "rollback" and whose image/resolvedImage are the target being restored
// — Render models rollback as a fresh deploy, never a history rewrite
// (w2/m10). rollbackOf records provenance: the source deploy id being
// rolled back to; commit is the target's own commit metadata (w9/001),
// copied rather than re-resolved against a branch that has since moved.
// triggeredBy is core.Identity.Subject for the rollback caller (w4/072).
CreateRollbackDeploy(ctx context.Context, appID, image, rollbackOf string, generation int64, commit CommitInfo, triggeredBy string) (Deploy, error)
// ListDeploys returns an app's deploy history, newest first, narrowed by
// filter (w2/m31) — a zero DeployFilter returns the full history, the
// pre-m31 contract.
ListDeploys(ctx context.Context, appID string, filter DeployFilter) ([]Deploy, error)
// GetDeploy fetches one deploy scoped to appID — a deployID belonging to a
// different app is ErrNotFound, not a cross-app leak.
GetDeploy(ctx context.Context, appID, deployID string) (Deploy, error)
// ListOpenDeploys returns every non-terminal deploy across all apps in one
// query — the reconciler's write-back hook
// calls this once per ReconcileOnce pass and looks apps up in the result,
// rather than one query per app in its per-app loop.
ListOpenDeploys(ctx context.Context) ([]Deploy, error)
// TransitionDeploy applies one legal lifecycle transition, advances
// updated_at only when the status actually changes, stamps started_at on
// the first dispatch-observing phase (see DeployStatusStampsDispatch) and
// finished_at on terminal transitions, and atomically deactivates a prior
// live deploy when this one reaches live. failureReason (w9/011) is stored
// with the same transition when non-empty — pass it only alongside a
// failure status. startedAt is observed execution-start evidence (the
// operator's recorded build window, w6/m123), applied only when the row
// carries none — a terminal skip (failure, or live first observed at live,
// w4/m156) otherwise leaves started_at honestly null rather than
// fabricating one; pass nil without evidence.
// A stale/repeated/invalid transition returns false without changing data.
// cancelReason (w4/089) is stored with the same transition when non-empty —
// pass it only alongside DeployCanceled from the reconciler's supersede or
// suspend close; deploys.Cancel / CloseDeploy leave it empty.
TransitionDeploy(ctx context.Context, id, status, resolvedImage, failureReason, failureCode, cancelReason string, startedAt *time.Time) (bool, error)
// DeployIDByGeneration returns the deploy id for appID at generation, or
// "" when none exists (w4/089 supersede naming). Newest wins if somehow
// multiple rows share a generation.
DeployIDByGeneration(ctx context.Context, appID string, generation int64) (string, error)
// CloseDeploy is the terminal-transition compatibility seam used by the
// deploy service's Cancel path. It delegates to TransitionDeploy.
CloseDeploy(ctx context.Context, id, status, resolvedImage string) (bool, error)
// SetDeployPreDeployStatus records the pre-deploy step's outcome (” |
// 'running' | 'succeeded' | 'failed') on a deploy row (w1/m33), projected from
// the App CR's status.preDeploy by the reconciler. No-op when unchanged;
// returns whether a row was updated.
SetDeployPreDeployStatus(ctx context.Context, id, status string) (bool, error)
// SettleCanceledPreDeploy gives a canceled release's deploy the verdict
// ('succeeded' | 'failed') of the pre-deploy step its close settled as
// 'canceled' while it still ran, and records the step's pre_deploy_ended
// fact at finishedAt (w5/085). Returns whether a row was settled; a repeat,
// or any other row, is a no-op.
SettleCanceledPreDeploy(ctx context.Context, appID string, generation int64, status string, finishedAt time.Time) (bool, error)
// SetDeployStallReason records why an OPEN deploy is not progressing, or
// clears it with "" (w4/m112). Projected from the App CR's Ready condition
// each pass; ignored once the row is terminal. Best-effort observation —
// never a verdict, and never a reason to fail reconciliation.
SetDeployStallReason(ctx context.Context, id, reason string) error
// RecordObservedServiceState persists level-triggered App status edges through
// a typed checkpoint; repeated reconciler observations are no-ops.
RecordObservedServiceState(ctx context.Context, obs ObservedServiceState) ([]ServiceEventFact, error)
// LastHealthyTransitionAt returns the Ready=True transition time recorded
// with the service's current healthy checkpoint — the reference the
// reconciler's stale-conclusion guard (w6/m41) orders an unhealthy edge
// against. Zero when there is no healthy checkpoint or its transition time
// is unknown; callers must fail open toward recording, never silence.
LastHealthyTransitionAt(ctx context.Context, appID string) (time.Time, error)
// RecordObservedDatastoreState is RecordObservedServiceState for a managed
// Database or KeyValue (w3/m82): the same checkpoint CAS keyed on the
// datastore's own dpg-/red- id, emitting the availability edges Render
// names postgres_unavailable/postgres_available and
// key_value_unhealthy/key_value_available.
RecordObservedDatastoreState(ctx context.Context, obs ObservedDatastoreState) ([]DatastoreEventFact, error)
// LastDatastoreHealthyTransitionAt is LastHealthyTransitionAt's datastore
// twin, with the same fail-open-toward-recording contract.
LastDatastoreHealthyTransitionAt(ctx context.Context, datastoreID string) (time.Time, error)
InsertServiceEventFact(ctx context.Context, fact ServiceEventFact) (bool, error)
InsertServiceEventFacts(ctx context.Context, facts []ServiceEventFact) error
}
Store is the persistence boundary. The API writes through it, the reconciler reads desired state and projects it into App CRs. Observed state (phase/url) is not persisted — it stays on the CR. The production implementation is PGStore; tests use an in-memory fake.
type StripeBillingEvent ¶
type StripeBillingEvent struct {
EventID string
EventType string
WorkspaceID string
CustomerID string
SubscriptionID string
ObjectID string
Livemode bool
ProviderCreatedAt time.Time
ReceivedAt time.Time
Outcome string
Reason string
}
StripeBillingEvent is the normalized, non-sensitive subset retained from a signature-verified event. Outcome is failure or success; the full body, payment method, card, and customer billing details are never persisted.
type Tenant ¶
type Tenant struct {
ID string `json:"id"`
Name string `json:"name"`
Plan string `json:"plan"`
CreatedAt time.Time `json:"createdAt"`
}
Tenant is a row of `tenants` — who owns apps; plan names the tier ladder row.
type TenantMember ¶
type TenantMember struct {
TenantID string `json:"tenantId"`
Subject string `json:"subject"`
Role string `json:"role"`
CreatedAt time.Time `json:"createdAt"`
// Kind is MemberKindUser for a person and MemberKindMachine for an API
// key's binding (w5/m103). Both rows authorize; only people are members.
Kind string `json:"kind"`
}
TenantMember is a row of `tenant_members` — a subject's membership of a workspace and their role. Subject is the OpenFGA user (Kratos identity id or Hydra client id) granted the matching relation on workspace:<tenant-id>.
type TerminalTurnFact ¶
type TerminalTurnFact struct {
Turn int
AcceptedAt time.Time
StartedAt *time.Time // nil when the turn never reached running (no bind)
TerminalAt time.Time
}
TerminalTurnFact is the immutable timing for a successful terminal CAS on the session's current turn (w5/m88). Callers observe metrics only when Finalize* succeeds; ErrNotFound means another writer already owned the transition.
type UsageCompaction ¶
type UsageCompaction struct {
Months int64 // distinct calendar months compacted
HourlyRows int64 // usage_hourly rows purged
}
UsageCompaction reports what one CompactUsage pass did — the operational summary the compaction loop logs.
type UsageCoverage ¶
UsageCoverage is the store's current-month evidence aggregate.
type UsageExportAttempt ¶
UsageExportAttempt binds the durable outbox row to the deterministic Stripe identifier and meter name selected by the billing mapper.
type UsageExportReject ¶
type UsageExportReject struct {
Attempt UsageExportAttempt
Code string
Message string
}
UsageExportReject is the non-secret, bounded context retained for a permanent provider rejection.
type UsageResourceRef ¶
type UsageResourceRef struct {
ResourceKind string `json:"resourceKind"`
ServiceID string `json:"serviceId"`
}
UsageResourceRef is the current collector inventory. Reconciliation closes streams for deleted resources so they do not hold the common watermark back forever; their historical health rows remain intact.
type UsageRetirementRecorder ¶
type UsageRetirementRecorder interface {
RecordRetiredUsageResource(ctx context.Context, r RetiredUsageResource) error
}
UsageRetirementRecorder records a datastore's retirement before its CR is deleted.
type UsageSourceObservation ¶
UsageSourceObservation is one source's accounting health for an hourly usage row. ExpectedFrom is the earliest hour the resource was expected to contribute; it prevents a mid-month migration from blessing legacy gaps.
type UsageSourceRecord ¶
type UsageSourceRecord struct {
WorkspaceID string
ResourceKind string
ServiceID string
Kind string
WindowStart time.Time
UsageSourceObservation
}
UsageSourceRecord persists health when no usage row can be written (for example a transport error). It makes partial evidence explicit instead of relying on a missing row, which remains unknown/legacy.
type UsageSummaryRow ¶
type UsageSummaryRow struct {
ServiceID string
Kind string
Tier string // non-empty only for instance_seconds
ResourceKind string // ResourceKindService / ResourceKindPostgres / ResourceKindKeyValue
Total int64
}
UsageSummaryRow is one resource/meter-kind/tier aggregate as returned by UsageMonthToDate — the raw numbers m2's core verb formats for adapters.
type WebPushSubscription ¶
type WebPushSubscription struct {
TenantID string `json:"tenantId"`
Subject string `json:"subject"`
BrowserID string `json:"browserId"`
Endpoint string `json:"-"`
P256dh string `json:"-"`
Auth string `json:"-"`
EndpointDigest string `json:"-"`
PreferenceID string `json:"preferenceRef,omitempty"`
RevokedAt *time.Time `json:"-"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
LastRegisteredAt time.Time `json:"lastRegisteredAt"`
}
WebPushSubscription is one browser PushSubscription. Endpoint, keys, and digest are internal delivery capabilities and are omitted from JSON.
type WebhookAttempt ¶
type WebhookAttempt struct {
ID string
NotificationID string
EndpointID string
EventID string
EventType string
ServiceID string
AttemptNumber int
Status string
StatusCode int
TransportError string
ResponseBody string
Payload string
SentAt *time.Time
Origin string
RequestedBy string
IdempotencyKey string
ParentStatus string
NextAttemptAt *time.Time
// ResumeAutomaticAt is internal queue state: a manual reservation parks an
// unsent automatic retry here and restores it only if the manual send fails.
ResumeAutomaticAt *time.Time
CreatedAt time.Time
}
WebhookAttempt is one scheduled send and its immutable terminal evidence. NotificationID identifies the logical endpoint/event parent. Pending rows reserve an ID before a send; the worker fills SentAt and outcome exactly once. Payload is joined from the parent so evidence never copies request bytes.
type WebhookAttemptCompletion ¶
type WebhookAttemptCompletion struct {
AttemptID string
NextAttemptID string
StatusCode int
TransportError string
ResponseBody string
CompletedAt time.Time
NextAttemptAt time.Time
Delivered bool
Exhausted bool
DisableReason string
}
WebhookAttemptCompletion names terminal evidence and parent-transition controls that would otherwise be ambiguous adjacent values and booleans.
type WebhookAttemptFilter ¶
type WebhookAttemptFilter struct {
EndpointID string
SentAfter time.Time
SentBefore time.Time
Status string
AfterAt time.Time
AfterKey string
Limit int
}
WebhookAttemptFilter is one bounded immutable-attempt history query.
type WebhookDelivery ¶
type WebhookDelivery struct {
ID string
EndpointID string
EventID string
EventType string
ServiceID string
Payload string
AttemptCount int
LastStatus int
LastError string
ResponseBody string
NextAttemptAt time.Time
SentAt *time.Time
LastAttemptedAt *time.Time
DeliveredAt *time.Time
FailedAt *time.Time
CreatedAt time.Time
}
WebhookDelivery is the logical parent row for one event × one subscribed endpoint. Immutable send evidence lives in WebhookAttempt child rows.
type WebhookEligibility ¶
WebhookEligibility is one stretch an endpoint was enabled (w4/m212): [EnabledAt, DisabledAt), DisabledAt nil while it still is. A source event is eligible for the endpoint only when its time falls inside one.
type WebhookEndpoint ¶
type WebhookEndpoint struct {
ID string
TenantID string
Name string
URL string
Secret string
EventTypes []string
Enabled bool
DisabledReason string
CreatedBy string
CreatedAt time.Time
UpdatedAt time.Time
// Eligibility is populated only by ListWebhookDispatchEndpoints.
Eligibility []WebhookEligibility
// Latest* is the newest completed immutable attempt for this endpoint and
// the state of that attempt's logical notification, fetched in the same
// query as the endpoint rather than by one history query per endpoint.
// Populated by every read and every mutation response since w4/117 — it was
// list-only, so a single-endpoint read reported an endpoint with four
// successful deliveries as having never delivered anything.
LatestAttemptStatus string
LatestAttemptAt *time.Time
LatestParentStatus string
}
WebhookEndpoint is a row of `webhook_endpoints`. Secret is populated only by CreateWebhookEndpoint (the mint-once read) and by the sender's DueWebhookDeliveries join (it signs with it) — every caller-facing read (List/Get) leaves it "", structurally: those queries never select the column.
type WebhookEnqueueResult ¶
WebhookEnqueueResult is aggregate, bounded evidence from one dispatcher commit. It deliberately carries no workspace, endpoint, event, URL, or payload dimension: callers may log/meter it without turning tenant-controlled values into an observability cardinality or confidentiality problem.
type WebhookEventRow ¶
type WebhookEventRow struct {
// CursorAt is when the source row became dispatch-visible. It normally equals
// At; late-persisted observed facts use service_event_facts.recorded_at so an
// older occurrence timestamp cannot fall behind the durable watermark.
CursorAt time.Time
Key string
At time.Time
TenantID string
ServiceID string
ServiceName string
Source string // EventSourceDeploy | EventSourceAudit | EventSourceFact
Phase string // deploy rows: EventPhaseStarted | EventPhaseEnded
DeployID string
// Status is the deploy's terminal status on the ended phase, and the fact's
// own status on fact rows. Empty elsewhere.
Status string
Verb string // audit rows: e.g. "apps.Suspend"
// AutoDeployEnabled discriminates apps.SetAutoDeploy into Render's enabled
// and disabled event types. nil on every other row (and on legacy audit rows).
AutoDeployEnabled *bool
FactType string // fact rows: closed service_event_facts.fact_type
// AppID is the app's internal control-plane id, as opposed to ServiceID's
// public composite. Empty on the datastore audit arm, which has no app.
AppID string
}
WebhookEventRow is one workspace-attributed row of the dispatcher's composed feed — the same projection ServiceEventRow carries, plus the tenant and service identity the join contributes (which the per-service feed takes as parameters instead). ServiceID is the PUBLIC service id — the projected CR name "<tenantName>-<appName>" (core.CRName), what GET /v1/services/{id} accepts — so a webhook receiver can turn a payload straight into an API call; ServiceName is the human label that service is shown under everywhere else — its displayName once renamed, else the immutable name (appDisplayLabel), so a payload never reports a stale creation-time name. Datastore rows carry the audit row's own target_name.
type WebhookResendRequest ¶
type WebhookResendRequest struct {
TenantID string
EndpointID string
SourceAttemptID string
RequestedBy string
IdempotencyKey string
RequestedAt time.Time
}
WebhookResendRequest names the several identities at the store seam so owner, endpoint, source-attempt, and caller IDs cannot be accidentally transposed.
type WorkspaceCreationAttempt ¶
type WorkspaceCreationAttempt struct {
ID string
WorkspaceID string
OwnerSubject string
Name string
Plan string
BillingEmail string
PaymentRequired bool
State string
ProviderCustomerID string
ProviderSetupIntentID string
ProviderPaymentMethodID string
ProviderSubscriptionID string
ProviderLivemode *bool
ExpiresAt time.Time
CleanupClaimedUntil *time.Time
FinalizedAt *time.Time
CreatedAt time.Time
UpdatedAt time.Time
}
WorkspaceCreationAttempt is the non-sensitive, durable state of one pre-tenant create flow. Provider ids never cross the public workspace API; the service returns only the client secret needed by the owning browser.
Source Files
¶
- accounts.go
- advisory.go
- agentdispatch.go
- agentsessions.go
- api.go
- app_placements.go
- audit.go
- billing.go
- billing_lifecycle.go
- billing_operations.go
- blueprint_auto_sync.go
- blueprint_lifecycle.go
- blueprint_resource_claims.go
- blueprints.go
- cli_refresh.go
- clitelemetry.go
- datastore_event_facts.go
- datastore_lifecycle_facts.go
- datastore_observations.go
- datastore_placement.go
- deploy_lifecycle.go
- device_subscriptions.go
- envgroups.go
- envgroups_lock.go
- environments.go
- event_facts.go
- events.go
- feedtail.go
- github.go
- gitwebhook.go
- grouping_tx.go
- image.go
- jobs.go
- members.go
- migrate.go
- namespaces.go
- notifications.go
- oauth_revocations.go
- plans.go
- product_analytics.go
- product_inventory.go
- projects.go
- push_deliveries.go
- reconciler.go
- reconciler_metrics.go
- registrycredentials.go
- release_cancel.go
- resource_names.go
- role_reconciliations.go
- sandbox_meter.go
- sandboxtenants.go
- sshkeys.go
- sshsessions.go
- store.go
- usage.go
- usage_retired.go
- webhooks.go
- webpush_subscriptions.go
- workspace_creation.go
- workspaces.go