Documentation
¶
Index ¶
- Constants
- Variables
- func NewMessageID() string
- func ValidateHITLConfig(ttlSeconds int, expirationAction string) error
- type APIKey
- type APIKeyExportEntry
- type AgentIdentity
- type DeleteUserDataResult
- type Domain
- type ExpirationCandidate
- type Message
- type PendingApprovalEdit
- type SendResult
- type Store
- func (s *Store) ApproveAndSend(ctx context.Context, messageID, userID string, edits PendingApprovalEdit, ...) (*Message, error)
- func (s *Store) BootstrapUser(ctx context.Context, email string) (*User, error)
- func (s *Store) ClaimOrCreateDomain(ctx context.Context, domain, userID string) (*Domain, error)
- func (s *Store) CreateAPIKey(ctx context.Context, userID, name string) (*APIKey, error)
- func (s *Store) CreateAgent(ctx context.Context, ...) (*AgentIdentity, error)
- func (s *Store) CreateInboundMessage(ctx context.Context, ...) (*Message, error)
- func (s *Store) CreateOrGetUser(ctx context.Context, email, name, googleSub string) (*User, error)
- func (s *Store) CreateOutboundMessage(ctx context.Context, agentID string, toRecipients []string, cc []string, ...) (*Message, error)
- func (s *Store) CreatePendingOutboundMessage(ctx context.Context, agentID string, toRecipients, cc, bcc []string, ...) (*Message, error)
- func (s *Store) CreateUserSession(ctx context.Context, userID string) (string, error)
- func (s *Store) DeleteAPIKey(ctx context.Context, keyID, userID string) error
- func (s *Store) DeleteAgent(ctx context.Context, agentID, userID string) error
- func (s *Store) DeleteDomain(ctx context.Context, domain, userID string) error
- func (s *Store) DeleteExpiredMessages(ctx context.Context) (int64, error)
- func (s *Store) DeleteExpiredUserSessions(ctx context.Context) (int64, error)
- func (s *Store) DeleteUserData(ctx context.Context, userID string) (*DeleteUserDataResult, error)
- func (s *Store) DeleteUserSession(ctx context.Context, token string) error
- func (s *Store) EnsureSharedDomain(ctx context.Context, domain string) error
- func (s *Store) ExpireApproveAndSend(ctx context.Context, messageID string, ...) (*Message, error)
- func (s *Store) ExpireReject(ctx context.Context, messageID, reason string) (*Message, error)
- func (s *Store) ExportUserData(ctx context.Context, userID string) (*UserExport, error)
- func (s *Store) GetAgentByEmail(ctx context.Context, email string) (*AgentIdentity, error)
- func (s *Store) GetAgentByID(ctx context.Context, id string) (*AgentIdentity, error)
- func (s *Store) GetInboundMessage(ctx context.Context, id string) (*Message, error)
- func (s *Store) GetMessageWithContent(ctx context.Context, messageID, agentID string) (*Message, error)
- func (s *Store) GetMessagesByAgent(ctx context.Context, agentID, status string, limit int, afterTime time.Time, ...) ([]Message, error)
- func (s *Store) GetOutboundMessageForUser(ctx context.Context, messageID, userID string) (*Message, error)
- func (s *Store) GetUserByAPIKey(ctx context.Context, apiKey string) (*User, error)
- func (s *Store) GetUserByID(ctx context.Context, id string) (*User, error)
- func (s *Store) GetUserSession(ctx context.Context, token string) (*User, error)
- func (s *Store) HasAgentsOnDomain(ctx context.Context, domain, userID string) (bool, error)
- func (s *Store) ListAPIKeys(ctx context.Context, userID string) ([]APIKey, error)
- func (s *Store) ListActivityByAgent(ctx context.Context, agentID string, limit int) ([]Message, error)
- func (s *Store) ListAgentsByUser(ctx context.Context, userID string) ([]AgentIdentity, error)
- func (s *Store) ListDomainsByUser(ctx context.Context, userID string) ([]Domain, error)
- func (s *Store) ListExpiredPending(ctx context.Context, limit int) ([]ExpirationCandidate, error)
- func (s *Store) ListPendingOutboundForUser(ctx context.Context, userID string, limit int) ([]Message, error)
- func (s *Store) LookupConversationID(ctx context.Context, agentID string, messageIDs []string) (string, error)
- func (s *Store) LookupDomain(ctx context.Context, domain, userID string) (*Domain, error)
- func (s *Store) RejectPending(ctx context.Context, messageID, userID, reason string) (*Message, error)
- func (s *Store) ResolveOutboundOwner(ctx context.Context, messageID string) (userID, agentID string, err error)
- func (s *Store) UpdateAgentHITL(ctx context.Context, agentID, userID string, enabled bool, ttlSeconds int, ...) error
- func (s *Store) UpdateAgentMode(ctx context.Context, agentID, userID, agentMode, webhookURL string) error
- func (s *Store) UpdateAgentWebhook(ctx context.Context, agentID, userID, webhookURL string) error
- func (s *Store) UpdateMessageDeliveryStatus(ctx context.Context, messageID, agentID, status string) error
- func (s *Store) VerifyDomain(ctx context.Context, domain, userID string) error
- type UsageEventEntry
- type User
- type UserExport
- type UserExportUser
Constants ¶
const ( HITLMaxTTLSeconds = 604800 // 7 days HITLDefaultTTLSeconds = 604800 HITLExpirationApprove = "approve" HITLExpirationReject = "reject" HITLDefaultExpirationAct = HITLExpirationReject )
HITL constants mirror the CHECK constraints in migration 003_hitl.sql.
const ( MessageStatusSent = "sent" MessageStatusPendingApproval = "pending_approval" MessageStatusRejected = "rejected" MessageStatusExpiredApproved = "expired_approved" MessageStatusExpiredRejected = "expired_rejected" )
Message status values mirror the CHECK constraint in migration 003_hitl.sql.
const MessageTTL = 30 * 24 * time.Hour // 30 days
const SessionTTL = 7 * 24 * time.Hour
Variables ¶
var ErrDomainHasAgents = fmt.Errorf("cannot delete domain: agents still exist")
ErrDomainHasAgents is returned when a domain delete is blocked by existing agents.
var ErrDomainNotFound = fmt.Errorf("domain not found or not owned by user")
ErrDomainNotFound is returned when a domain is not found or not owned by the user.
var ErrMessageNotFound = fmt.Errorf("message not found")
ErrMessageNotFound is returned when a message is not found for the given user (either the ID doesn't exist or the message belongs to another user's agent). Handlers map this to HTTP 404.
var ErrNotPendingApproval = fmt.Errorf("message is not pending approval")
ErrNotPendingApproval is returned when an approve or reject operation targets a message that is not (or is no longer) in pending_approval status. Handlers map this to HTTP 409 Conflict.
Functions ¶
func NewMessageID ¶
func NewMessageID() string
NewMessageID returns a fresh internal message ID. Callers can use this to generate the ID up-front when they need it before storing — for example, the SMTP relay generates the ID before signing auth headers so the ID is part of the canonical string fed to HMAC.
func ValidateHITLConfig ¶
ValidateHITLConfig returns an error if the TTL or expiration action is invalid. The DB CHECK constraints are the final guard; this mirrors them for a clean, pre-query error path.
Types ¶
type APIKeyExportEntry ¶
type APIKeyExportEntry struct {
ID string `json:"id"`
Name string `json:"name"`
KeyPrefix string `json:"key_prefix"`
CreatedAt time.Time `json:"created_at"`
LastUsedAt *time.Time `json:"last_used_at,omitempty"`
RevokedAt *time.Time `json:"revoked_at,omitempty"`
} // @name APIKeyExportEntry
APIKeyExportEntry is the API-key shape included in the export. We expose only metadata; the hash itself stays internal because (a) it's not useful to the user, (b) it's a credential equivalent for offline dictionary attacks if leaked.
type AgentIdentity ¶
type AgentIdentity struct {
ID string `json:"id"`
Domain string `json:"domain"`
Email string `json:"email"`
Name string `json:"name"`
WebhookURL string `json:"webhook_url"`
AgentMode string `json:"agent_mode"`
DomainVerified bool `json:"domain_verified"`
Public bool `json:"public"`
CreatedAt time.Time `json:"created_at"`
UserID string `json:"user_id"`
HITLEnabled bool `json:"hitl_enabled"`
HITLTTLSeconds int `json:"hitl_ttl_seconds"`
HITLExpirationAction string `json:"hitl_expiration_action"`
}
func (*AgentIdentity) ActualDomain ¶
func (a *AgentIdentity) ActualDomain() string
ActualDomain returns the DNS domain for the agent.
func (*AgentIdentity) EmailAddress ¶
func (a *AgentIdentity) EmailAddress() string
EmailAddress returns the agent's email address (always the ID).
func (*AgentIdentity) IsSharedDomain ¶
func (a *AgentIdentity) IsSharedDomain(sharedDomain string) bool
IsSharedDomain returns true if the agent's domain matches the configured shared domain (the host that backs slug-based registration). When sharedDomain is empty, the deployment has slug registration disabled and no agent can be on the shared domain.
type DeleteUserDataResult ¶
type DeleteUserDataResult struct {
UsageEventsDeleted int64 `json:"usage_events_deleted"`
UsageSummariesDeleted int64 `json:"usage_summaries_deleted"`
MessagesDeleted int64 `json:"messages_deleted"`
AgentsDeleted int64 `json:"agents_deleted"`
DomainsDeleted int64 `json:"domains_deleted"`
APIKeysDeleted int64 `json:"api_keys_deleted"`
SessionsDeleted int64 `json:"sessions_deleted"`
UserDeleted bool `json:"user_deleted"`
} // @name DeleteUserDataResult
DeleteUserDataResult breaks out per-table row counts for audit logs. Operators receiving a deletion request often have to attest to what was removed; returning structured counts beats parsing a log line.
type Domain ¶
type Domain struct {
Domain string `json:"domain"`
UserID *string `json:"user_id,omitempty"`
Verified bool `json:"verified"`
VerificationToken string `json:"verification_token"`
CreatedAt time.Time `json:"created_at"`
VerifiedAt *time.Time `json:"verified_at,omitempty"`
}
Domain represents a verified or unverified domain registered by a user.
type ExpirationCandidate ¶
type ExpirationCandidate struct {
MessageID string
AgentID string
ExpirationAction string // 'approve' or 'reject'
}
ExpirationCandidate is the minimal row the expiration worker needs to decide how to finalize an expired pending message.
type Message ¶
type Message struct {
ID string `json:"id"`
AgentID string `json:"agent_id"`
Direction string `json:"direction"`
Sender string `json:"sender"`
Recipient string `json:"recipient"`
Subject string `json:"subject"`
EmailMessageID string `json:"email_message_id,omitempty"`
ProviderMessageID string `json:"provider_message_id,omitempty"`
Method string `json:"method,omitempty"`
Type string `json:"type,omitempty"`
RawMessage []byte `json:"raw_message,omitempty"`
AuthHeaders map[string]string `json:"auth_headers,omitempty"`
ConversationID string `json:"conversation_id,omitempty"`
DeliveryStatus string `json:"delivery_status,omitempty"`
CreatedAt time.Time `json:"created_at"`
ExpiresAt time.Time `json:"expires_at"`
WebhookStatus string `json:"webhook_status,omitempty"`
WebhookError string `json:"webhook_error,omitempty"`
WebhookAttempts int `json:"webhook_attempts,omitempty"`
// Outbound-only multi-recipient fields. Nil for inbound messages.
ToRecipients []string `json:"to_recipients,omitempty"`
CC []string `json:"cc,omitempty"`
BCC []string `json:"bcc,omitempty"`
// HITL approval fields. Status defaults to 'sent'; body and attachments
// are populated only while a message is in 'pending_approval', and are
// scrubbed on any terminal transition.
Status string `json:"status,omitempty"`
ApprovalExpiresAt *time.Time `json:"approval_expires_at,omitempty"`
ReviewedAt *time.Time `json:"reviewed_at,omitempty"`
RejectionReason string `json:"rejection_reason,omitempty"`
Edited bool `json:"edited,omitempty"`
BodyText string `json:"body_text,omitempty"`
BodyHTML string `json:"body_html,omitempty"`
AttachmentsJSON json.RawMessage `json:"attachments,omitempty"`
}
type PendingApprovalEdit ¶
type PendingApprovalEdit struct {
Subject *string
BodyText *string
BodyHTML *string
To []string
CC []string
BCC []string
AttachmentsJSON []byte
// AttachmentsSet must be true when the caller intends to override
// AttachmentsJSON, since nil and empty [] are both valid overrides
// (empty [] clears attachments; nil preserves).
AttachmentsSet bool
}
PendingApprovalEdit holds optional overrides a reviewer can apply when approving a pending message. Pointer-typed strings distinguish "not provided" (nil) from "explicitly empty" (pointer to ""). Slice fields distinguish "unset" (nil) from "empty list" (non-nil zero-length slice).
func (PendingApprovalEdit) Apply ¶
func (e PendingApprovalEdit) Apply(msg *Message) bool
Apply mutates msg to reflect any fields the reviewer changed. Returns true if any field was actually different from what msg already held (signals the edited flag should be set).
type SendResult ¶
type SendResult struct {
ProviderMessageID string
Method string
To []string
CC []string
BCC []string
}
SendResult carries the outcome of a sender.Send invocation back to the store for final persistence. Handlers wrap their sender.Send call in a closure that returns this type.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
func (*Store) ApproveAndSend ¶
func (s *Store) ApproveAndSend( ctx context.Context, messageID, userID string, edits PendingApprovalEdit, send func(msg *Message) (SendResult, error), ) (*Message, error)
ApproveAndSend finalizes a pending_approval message by running it through a caller-supplied send function inside a transaction that holds a row lock on the pending row. If send returns an error the transaction rolls back and the message remains pending. On success the row is updated to 'sent' with the provider-assigned Message-ID and the body/attachments columns are scrubbed.
edits, if any fields are populated, are applied to the in-memory message before send is called and the 'edited' column is set to true when any field differs from what was stored. Approval-via-magic-link callers pass the zero edits value.
Ownership is enforced by the agent -> user join. Messages owned by another user return ErrMessageNotFound. Messages whose status is not 'pending_approval' return ErrNotPendingApproval.
Concurrency / failure mode notes:
The row-level FOR UPDATE lock is held for the duration of the send callback. In practice that is bounded by outbound.SMTPRelay's per- attempt deadline (2min) plus its internal retry backoff (1s/5s/15s) — worst case ~6.5min of lock on this single row. Other rows are unaffected; deadlock is not possible because only one row is ever locked per call.
There is a narrow crash window where send() may succeed at SES but the subsequent UPDATE or Commit fails (DB connection drop, pool exhaustion). The transaction rolls back, the row stays pending, and a retry — by the same reviewer or the expiration worker — would re-send to SES. Eliminating this requires SES-side idempotency keys or a separate "send attempts" table; deferred for v1. Callers that see both a successful send callback and a subsequent error from this function should log both rather than silently retry.
func (*Store) BootstrapUser ¶
BootstrapUser finds a user by email, or creates one with a synthetic google_subject if none exists. Used by the -bootstrap-email CLI flag for self-host first-run, where there's no Google OAuth flow yet.
func (*Store) ClaimOrCreateDomain ¶
ClaimOrCreateDomain implements the atomic create/claim logic from the design doc. Creates if new, overwrites user_id if unverified, returns if verified+same user, errors if verified+different user. Callers are responsible for rejecting the configured shared domain before invoking this — the store has no concept of a reserved domain.
func (*Store) CreateAPIKey ¶
func (*Store) CreateAgent ¶
func (s *Store) CreateAgent(ctx context.Context, agentEmail, domain, name, webhookURL, agentMode, userID string) (*AgentIdentity, error)
CreateAgent inserts an agent with a domain FK. Does NOT check domain ownership — that's the API handler's responsibility (shared domain skips the check).
func (*Store) CreateInboundMessage ¶
func (s *Store) CreateInboundMessage(ctx context.Context, id, agentID, senderEmail, recipient, emailMessageID, subject, conversationID, deliveryStatus string, rawMessage []byte, authHeaders map[string]string) (*Message, error)
CreateInboundMessage stores an inbound message. If id is empty a new one is generated; otherwise the caller's pre-generated ID is used so the upstream signer can bind auth headers to the same ID that gets stored.
func (*Store) CreateOrGetUser ¶
func (*Store) CreateOutboundMessage ¶
func (s *Store) CreateOutboundMessage(ctx context.Context, agentID string, toRecipients []string, cc []string, bcc []string, subject, msgType, method, providerMessageID, conversationID string) (*Message, error)
CreateOutboundMessage stores an outbound message with multi-recipient support. The recipient param is kept for backward compat with the singular recipient column; toRecipients, cc, and bcc are the canonical outbound-only multi-recipient fields.
func (*Store) CreatePendingOutboundMessage ¶
func (s *Store) CreatePendingOutboundMessage(ctx context.Context, agentID string, toRecipients, cc, bcc []string, subject, bodyText, bodyHTML string, attachmentsJSON []byte, msgType, conversationID, replyToEmailMessageID string, ttlSeconds int) (*Message, error)
CreatePendingOutboundMessage stores a fully composed outbound email in pending_approval status, including body_text, body_html, and attachments so that approval can reconstruct the original SendRequest (or accept edits) without the caller needing to retain it. ttlSeconds sets how long the message remains pending before the expiration worker resolves it.
replyToEmailMessageID is the RFC 5322 Message-ID of the inbound being replied to (e.g. "<abc@gmail.com>"), or "" for fresh sends and test emails. It reuses the email_message_id column, which is unused for outbound rows in every other path — the column semantically carries "the Message-ID this row references" in both directions.
attachmentsJSON must be a JSON array matching the public Attachment shape ([{filename, content_type, data}, ...]) or nil. Callers that already have an []outbound.Attachment slice should json.Marshal it before passing in.
func (*Store) CreateUserSession ¶
func (*Store) DeleteAPIKey ¶
func (*Store) DeleteAgent ¶
func (*Store) DeleteDomain ¶
DeleteDomain deletes a domain only if owned by the user. The handler should check for existing agents first.
func (*Store) DeleteExpiredMessages ¶
func (*Store) DeleteExpiredUserSessions ¶
func (*Store) DeleteUserData ¶
DeleteUserData wipes everything tied to a user in a single transaction.
Schema cascades cover most of it (user_sessions, domains, agent_identities, api_keys, usage_summaries all `ON DELETE CASCADE` from users; messages cascade through agent_identities; webhook_deliveries cascade through messages). The one row that doesn't is usage_events: its FK is `ON DELETE SET NULL` so analytics survives, which we explicitly override here for full deletion.
Per-table counts are returned to the caller so an operator can attest to what was removed in audit / compliance contexts.
func (*Store) DeleteUserSession ¶
func (*Store) EnsureSharedDomain ¶
EnsureSharedDomain inserts a system row for the configured shared mail domain so slug-based agent registration can satisfy the agent_identities.domain → domains.domain foreign key. The row is owned by no user (user_id = NULL) and pre-verified — it represents infrastructure the operator runs, not user-claimed identity.
Called once at server startup. Idempotent via ON CONFLICT DO NOTHING, and a no-op when the operator has not configured a shared domain. Without this, any deployment whose shared_domain differs from the hardcoded migration seed (`agents.e2a.dev`) gets an FK violation the first time a user tries to register a slug-based agent.
func (*Store) ExpireApproveAndSend ¶
func (s *Store) ExpireApproveAndSend( ctx context.Context, messageID string, send func(msg *Message) (SendResult, error), ) (*Message, error)
ExpireApproveAndSend is the worker-side counterpart to ApproveAndSend: no user ownership check (the caller is the expiration worker, which is system-scoped), SELECT ... FOR UPDATE SKIP LOCKED so concurrent workers don't race for the same row, and the terminal status is 'expired_approved' instead of 'sent'. On send failure the transaction rolls back; the worker should then call ExpireReject to move the row to a final state so the row doesn't get picked up on every sweep.
Same concurrency / crash-window notes as ApproveAndSend apply: the row-level lock is held for the duration of the send callback (bounded by SMTPRelay timeouts), and a crash between SES acceptance and commit can leave a pending row that would re-send on the next sweep. SKIP LOCKED means multiple app instances can run the worker without contending on the same row.
func (*Store) ExpireReject ¶
ExpireReject transitions a pending_approval message to expired_rejected and scrubs body columns. No user ownership check — this is the worker path. If the row is no longer pending (racing worker, already handled) returns ErrNotPendingApproval; caller can treat as a no-op.
func (*Store) ExportUserData ¶
ExportUserData gathers everything a user owns into a single struct for the right-of-access flow. Reads run inside a REPEATABLE READ transaction so the snapshot is internally consistent even if writes arrive while the export is being assembled.
func (*Store) GetAgentByEmail ¶
GetAgentByEmail looks up an agent by email address (same as GetAgentByID since ID = email).
func (*Store) GetAgentByID ¶
GetAgentByID looks up an agent by its ID (full email) with domain verification status.
func (*Store) GetInboundMessage ¶
func (*Store) GetMessageWithContent ¶
func (s *Store) GetMessageWithContent(ctx context.Context, messageID, agentID string) (*Message, error)
GetMessageWithContent returns a full message including raw_message and auth_headers. Marks the message as 'read' if it was 'unread'.
func (*Store) GetMessagesByAgent ¶
func (s *Store) GetMessagesByAgent(ctx context.Context, agentID, status string, limit int, afterTime time.Time, afterID string) ([]Message, error)
GetMessagesByAgent returns messages for a poll-mode agent, filtered by status.
func (*Store) GetOutboundMessageForUser ¶
func (s *Store) GetOutboundMessageForUser(ctx context.Context, messageID, userID string) (*Message, error)
GetOutboundMessageForUser returns a full message row (including body, HITL fields, and attachments) if it exists and is owned by userID (via the agent the row belongs to). Inbound messages and cross-user access both return ErrMessageNotFound — the caller should not be able to distinguish "does not exist" from "belongs to someone else".
func (*Store) GetUserByAPIKey ¶
func (*Store) GetUserByID ¶
func (*Store) GetUserSession ¶
func (*Store) HasAgentsOnDomain ¶
HasAgentsOnDomain checks whether the owned domain still has agents.
func (*Store) ListAPIKeys ¶
func (*Store) ListActivityByAgent ¶
func (*Store) ListAgentsByUser ¶
ListAgentsByUser returns all agents owned by the user, joined with domain verification.
func (*Store) ListDomainsByUser ¶
ListDomainsByUser returns all domains owned by the user (excludes system rows).
func (*Store) ListExpiredPending ¶
ListExpiredPending returns pending_approval messages whose approval_expires_at is in the past, joined with their agent's hitl_expiration_action. Ordered by approval_expires_at ASC so earliest-expired are handled first.
func (*Store) ListPendingOutboundForUser ¶
func (s *Store) ListPendingOutboundForUser(ctx context.Context, userID string, limit int) ([]Message, error)
ListPendingOutboundForUser returns pending-approval messages across all of the user's agents, sorted by approval_expires_at ASC (expiring-soonest first). Body columns are not returned from this path — callers should use GetOutboundMessageForUser for detail.
func (*Store) LookupConversationID ¶
func (s *Store) LookupConversationID(ctx context.Context, agentID string, messageIDs []string) (string, error)
LookupConversationID finds a conversation_id by matching In-Reply-To / References message IDs against stored messages. Checks both email_message_id (inbound) and provider_message_id (outbound). Uses prefix matching because SES bare IDs stored in provider_message_id (e.g. <010f...>) may lack the @region.amazonses.com suffix that appears in the actual email headers sent to recipients.
func (*Store) LookupDomain ¶
LookupDomain returns a domain if it exists and is owned by the given user.
func (*Store) RejectPending ¶
func (s *Store) RejectPending(ctx context.Context, messageID, userID, reason string) (*Message, error)
RejectPending transitions a pending_approval message to rejected, records the reviewer's reason (empty string allowed), and scrubs body_text / body_html / attachments_json. Ownership checked; missing rows return ErrMessageNotFound. Non-pending rows return ErrNotPendingApproval.
func (*Store) ResolveOutboundOwner ¶
func (s *Store) ResolveOutboundOwner(ctx context.Context, messageID string) (userID, agentID string, err error)
ResolveOutboundOwner looks up the user_id and agent_id for an outbound message without requiring the caller to know the user_id up-front. It exists for token-authenticated paths (magic-link approve/reject) where the HMAC token itself is the authorization and the handler just needs enough context to dispatch into the existing user-scoped store methods.
Returns ErrMessageNotFound if the message doesn't exist or isn't outbound. The returned user_id is guaranteed to own the message's agent (via the agent_identities.user_id join).
func (*Store) UpdateAgentHITL ¶
func (s *Store) UpdateAgentHITL(ctx context.Context, agentID, userID string, enabled bool, ttlSeconds int, expirationAction string) error
UpdateAgentHITL updates all three HITL settings on an agent owned by userID. The TTL and expiration action are validated against the same rules as the DB CHECK constraints so callers get a clean error rather than a raw SQL error.
func (*Store) UpdateAgentMode ¶
func (*Store) UpdateAgentWebhook ¶
type UsageEventEntry ¶
type UsageEventEntry struct {
ID string `json:"id"`
AgentID string `json:"agent_id"`
Domain string `json:"domain"`
Direction string `json:"direction"`
EventType string `json:"event_type"`
CreatedAt time.Time `json:"created_at"`
} // @name UsageEventEntry
UsageEventEntry is one row of the usage_events table for the user.
type UserExport ¶
type UserExport struct {
GeneratedAt time.Time `json:"generated_at"`
SchemaVersion string `json:"schema_version"`
User UserExportUser `json:"user"`
Domains []Domain `json:"domains"`
Agents []AgentIdentity `json:"agents"`
APIKeys []APIKeyExportEntry `json:"api_keys"`
Messages []Message `json:"messages"`
UsageEvents []UsageEventEntry `json:"usage_events,omitempty"`
} // @name UserExport
UserExport is the structured dump returned by ExportUserData. It's designed to be a complete, machine-readable record of everything a single user owns in the system — the right-of-access counterpart to DeleteUserData below. Sensitive secrets are deliberately excluded:
- API key plaintexts are not stored anywhere (only hashes), so they can't be exported even in principle.
- google_subject is an internal OAuth identifier with no value to the user and is omitted on purpose.
- User session tokens are transient and excluded.