Documentation
¶
Index ¶
- Constants
- Variables
- func DummyVerifyPassword(plaintext string) bool
- func HashPassword(plaintext string) (string, error)
- func ValidatePassword(plaintext string) error
- func VerifyPassword(encodedHash, plaintext string) bool
- type Agent
- type AgentStore
- type ArtifactWithTask
- type AuditEvent
- type AuditStore
- type ClaimTaskInput
- type ClaimTaskRunInput
- type Conversation
- type ConversationMessage
- type ConversationMessageStore
- type ConversationStore
- type CreateIssueInput
- type CreateLLMModelInput
- type CreateTaskInput
- type CreateWorkflowRunInput
- type CreateWorkflowStepRunInput
- type Issue
- type IssueStore
- type LLMCall
- type LLMCallOutcome
- type LLMCallStore
- type LLMCallUsage
- type LLMModel
- type LLMModelStore
- type LoginCodeStore
- type NewRefreshToken
- type PasswordStore
- type QuotaTier
- type QuotaTierStore
- type RefreshTokenStore
- type RotatedRefreshToken
- type RunStatus
- type Task
- type TaskRun
- type TaskRunArtifact
- type TaskRunStore
- type TaskRunTerminalInfo
- type TaskStore
- type Team
- type TeamMember
- type TeamStore
- type UpdateIssueInput
- type UpdateTaskInput
- type UpdateTaskRunInput
- type UpdateWorkflowInput
- type UpdateWorkflowRunInput
- type UpdateWorkflowStepRunInput
- type UsageInWindowReader
- type User
- type UserStore
- type UserWebhookKey
- type UserWebhookKeyStore
- type WebhookKeyMeta
- type Workflow
- type WorkflowDefinition
- type WorkflowDefinitionStep
- type WorkflowRun
- type WorkflowStepRun
- type WorkflowStore
Constants ¶
const ( // AuditUserLogin records a successful login. Failures are not recorded // here — a failed login says nothing about who the actor was, and // recording attempts keyed by a supplied email would turn the audit log // into a place to write arbitrary strings. AuditUserLogin = "user.login" // AuditUserLogout records a session being revoked on purpose. It is the // counterpart to AuditUserLogin: together they bound when a session could // have been used. AuditUserLogout = "user.logout" // AuditRefreshReuse records a refresh token presented after it had already // been exchanged. It means the credential existed in two places, and the // session was revoked in response. Unlike the actions above this is not a // user's intent — it is the server reporting what it saw. AuditRefreshReuse = "auth.refresh_reuse" // AuditPasswordSet records an account's password being set or changed. The // event says that it happened, never what it became. AuditPasswordSet = "user.password_set" // AuditTeamMemberAdded and AuditTeamMemberRemoved record changes to who // can reach a team's resources. AuditTeamMemberAdded = "team.member_added" AuditTeamMemberRemoved = "team.member_removed" // AuditModelCreated, AuditModelEnabled, and AuditModelDisabled record // changes to which models a deployment will call. The catalog holds // provider credentials, so a change to it is a change to what the // deployment can spend and where prompts go. AuditModelCreated = "llm_model.created" AuditModelEnabled = "llm_model.enabled" AuditModelDisabled = "llm_model.disabled" // AuditAccessDenied records a refused team-scoped request. This is the one // action written on failure rather than success: a denial is what shows // someone probing at a boundary. AuditAccessDenied = "access.denied" )
Audit actions. These strings are persisted, so they are permanent: renaming one rewrites history for every reader that filters on it.
const ( AuditActorUser = "user" AuditActorWorker = "worker" AuditActorSystem = "system" )
Audit actor kinds.
const ( IssueStatusTodo = "todo" IssueStatusInProgress = "in_progress" IssueStatusDone = "done" IssueAssigneePerson = "person" IssueAssigneeAgent = "agent" IssueAssigneeWorkflow = "workflow" )
const ( LLMCallStatusAccepted = "ACCEPTED" LLMCallStatusSucceeded = "SUCCEEDED" LLMCallStatusFailed = "FAILED" LLMCallStatusCanceled = "CANCELED" )
Managed LLM call lifecycle statuses.
const ( LLMUsageSourceReported = "reported" LLMUsageSourceEstimated = "estimated" )
Where a call's token counts came from. Recording this keeps accounting honest when a provider reports no usage: an absent number and a zero are different facts, and only one of them may be billed.
const ( LLMCallSurfaceServer = "server" LLMCallSurfaceCLI = "cli" LLMCallSurfaceDesktop = "desktop" LLMCallSurfaceWorker = "worker" )
Surfaces a managed call can originate from.
const ( AccessTokenTTLDefault = 7 * 24 * time.Hour RefreshTokenTTLDefault = 30 * 24 * time.Hour RefreshRotationGraceDefault = 30 * time.Second )
Token lifetimes used when a deployment does not choose. The access token is a signed JWT the server never stores, so the only way to retire one early is to wait for it to expire; the refresh token is a stored row and can be revoked at any time. That asymmetry is why the long-lived half is the stored one.
const ( RunCreatedByTypeUser = "user" RunCreatedByTypeWebhook = "webhook" RunCreatedByTypeSystem = "system" )
const ( RunTriggerSourceTaskCreate = "task_create" RunTriggerSourceTaskRerun = "task_rerun" RunTriggerSourcePortalConversation = "portal_conversation" RunTriggerSourcePortalTaskCreate = "portal_task_create" RunTriggerSourcePortalTaskRerun = "portal_task_rerun" RunTriggerSourceIssueAgentRun = "issue_agent_run" RunTriggerSourceWorkflowStep = "workflow_step" RunTriggerSourceWebhook = "webhook" )
const ( // TeamRoleOwner is the initial role for the user who creates a team. TeamRoleOwner = "owner" // TeamRoleAdmin can manage shared automation assets but not membership ownership. TeamRoleAdmin = "admin" // TeamRoleMember is the basic collaboration role for invited members. TeamRoleMember = "member" // DefaultPersonalTeamName is the initial UX-facing name for a user's own space. DefaultPersonalTeamName = "My Space" )
const ( WorkflowStatusDraft = "draft" WorkflowStatusPublished = "published" WorkflowStatusArchived = "archived" WorkflowRunStatusPending = "pending" WorkflowRunStatusRunning = "running" WorkflowRunStatusSucceeded = "succeeded" WorkflowRunStatusFailed = "failed" WorkflowRunStatusCanceled = "canceled" WorkflowStepTypeAgentTask = "agent_task" WorkflowStepRunStatusPending = "pending" WorkflowStepRunStatusRunning = "running" WorkflowStepRunStatusSucceeded = "succeeded" WorkflowStepRunStatusFailed = "failed" WorkflowStepRunStatusBlocked = "blocked" )
const LoginCodeTTLDefault = time.Hour
LoginCodeTTLDefault is how long an issued code stays valid when the caller does not choose. Long enough to hand a code to someone over a chat message, short enough that a leaked one expires before it is useful.
const PasswordMaxLength = 1024
PasswordMaxLength bounds what will be hashed. Argon2 has no length limit of its own, but accepting unbounded input means accepting unbounded work.
const PasswordMinLength = 12
PasswordMinLength is the shortest password BuildMax accepts.
It is longer than the usual eight because BuildMax has no login throttling yet: an attacker who can reach the server can guess as fast as the server will hash. Length is the only defense that does not need infrastructure, so it carries more weight here than it would elsewhere. See docs/deploy/authentication.md.
Variables ¶
var ( ErrPasswordTooShort = fmt.Errorf("password must be at least %d characters", PasswordMinLength) ErrPasswordTooLong = fmt.Errorf("password must be at most %d characters", PasswordMaxLength) )
ErrPasswordTooShort and ErrPasswordTooLong report an unusable password. They are separate from a failed login: these mean "choose another", not "wrong".
var ErrDuplicateLLMCall = errors.New("llm call already exists for this client call id")
ErrDuplicateLLMCall is returned when a team reuses a client call ID. The unique index is what actually decides it, so two concurrent requests with one key cannot both open a record.
var ErrEmailExists = errors.New("email already exists")
ErrEmailExists is returned by CreateUser when the email is already registered.
var ErrLLMModelNameTaken = errors.New("a model with this name already exists")
ErrLLMModelNameTaken is returned when an operator reuses a model name.
var ErrRefreshTokenInvalid = errors.New("refresh token invalid")
ErrRefreshTokenInvalid means the token is unknown, expired, or belongs to a session that has been revoked. The three are deliberately indistinguishable to the caller.
var ErrRefreshTokenReused = errors.New("refresh token reused")
ErrRefreshTokenReused means a token that had already been rotated was presented again after the grace window. Either the client is replaying a credential it should have discarded, or someone else has a copy — and there is no way to tell which. The session is revoked before this is returned.
var ErrRunInProgress = errors.New("task has a run already in progress")
ErrRunInProgress is returned by CreateTaskRun when the task already has a run in PENDING, SCHEDULED, or RUNNING.
var ErrUserNotFound = errors.New("user not found")
ErrUserNotFound is returned when an operation names an account that is not there.
Functions ¶
func DummyVerifyPassword ¶
DummyVerifyPassword performs the same work as VerifyPassword and always fails.
Login calls it when the address has no account, so that a request for an unknown address costs the same as one for a known address with the wrong password. Without it the response time alone answers "does this person have an account here".
func HashPassword ¶
HashPassword returns a PHC-encoded argon2id hash of plaintext.
Argon2id rather than a plain SHA family: this is the one value in BuildMax that a person chose and may have reused elsewhere, so a leaked database must not turn into a list of working passwords for other services. Memory-hard hashing is what makes an offline attack on the dump expensive.
func ValidatePassword ¶
ValidatePassword reports whether plaintext may be used as a password.
Length only. A composition rule — a digit, a symbol, a capital — pushes people toward short predictable passwords that satisfy it, which is the opposite of what the length minimum is for.
func VerifyPassword ¶
VerifyPassword reports whether plaintext produced encodedHash.
A malformed or empty hash is a mismatch rather than an error. Callers use this to decide whether to authenticate, and a stored value that cannot be parsed must not become a way in.
Types ¶
type Agent ¶
type Agent struct {
ID uint `json:"-"`
AgentID string `json:"agent_id"`
UserID string `json:"user_id"`
TeamID string `json:"team_id,omitempty"`
Name string `json:"name"`
Description string `json:"description"`
Instructions string `json:"instructions"`
CreatedAt int64 `json:"created_at"`
}
Agent is a user-defined Portal agent stored in the database.
type AgentStore ¶
type AgentStore interface {
ListAgentsByUser(ctx context.Context, userID string) ([]Agent, error)
ListAgentsByTeam(ctx context.Context, teamID string) ([]Agent, error)
GetAgent(ctx context.Context, agentID string) (*Agent, error)
CreateAgent(ctx context.Context, userID, name, description, instructions string) (*Agent, error)
CreateAgentInTeam(ctx context.Context, teamID, userID, name, description, instructions string) (*Agent, error)
UpdateAgent(ctx context.Context, agentID, userID, name, description, instructions string) (*Agent, error)
UpdateAgentInTeam(ctx context.Context, agentID, teamID, name, description, instructions string) (*Agent, error)
DeleteAgent(ctx context.Context, agentID, userID string) error
DeleteAgentInTeam(ctx context.Context, agentID, teamID string) error
}
AgentStore provides persistence for Portal agents.
type ArtifactWithTask ¶
type ArtifactWithTask struct {
ArtifactID string `json:"artifact_id"`
TaskID string `json:"task_id"`
TaskRunID string `json:"task_run_id"`
ConversationID string `json:"conversation_id"`
UserID string `json:"user_id"`
CreatedAt int64 `json:"created_at"`
TaskInputSnippet string `json:"task_input_snippet"`
}
ArtifactWithTask is a DTO for listing run outputs (artifacts) with task/run context. ArtifactID holds task_run_id for API compatibility.
type AuditEvent ¶
type AuditEvent struct {
ID uint `json:"-"`
AuditEventID string `json:"audit_event_id"`
// TeamID is empty for actions that are not team-scoped, such as a login.
TeamID string `json:"team_id,omitempty"`
ActorType string `json:"actor_type"`
ActorID string `json:"actor_id"`
Action string `json:"action"`
TargetType string `json:"target_type,omitempty"`
TargetID string `json:"target_id,omitempty"`
// Detail is a short, non-sensitive note — a role name, a model alias. It
// is not a place for request bodies.
Detail string `json:"detail,omitempty"`
CreatedAt int64 `json:"created_at"`
}
AuditEvent is one recorded action.
It deliberately carries no prompts, no generated content, no tool output, and no credentials — only who did what to which object. Run diagnostics live in the durable run trace and per-call accounting in the llm_call ledger; this is the record that a meaningful action occurred, which is a different question with a different retention answer.
type AuditStore ¶
type AuditStore interface {
// RecordAuditEvent appends one event. Events are append-only; there is no
// update or delete, because a record that can be edited is not evidence.
RecordAuditEvent(ctx context.Context, in AuditEvent) error
// ListAuditEvents returns a team's events, newest first.
ListAuditEvents(ctx context.Context, teamID string, limit, offset int) ([]AuditEvent, int, error)
}
AuditStore persists audit events.
Record takes no error-returning contract the caller must handle at the call site by design: see the recorder in internal/service/audit for why a failed write is logged rather than propagated, and what that costs.
type ClaimTaskInput ¶
type ClaimTaskInput struct {
TaskID string
ExpectedStatus string
NewStatus string
StartedAt *int64
EndedAt *int64
Output *string
ErrorMessage *string
SessionID *string
}
ClaimTaskInput atomically transitions a task from ExpectedStatus to NewStatus.
type ClaimTaskRunInput ¶
type ClaimTaskRunInput struct {
TaskRunID string
ExpectedStatus RunStatus
NewStatus RunStatus
StartedAt *int64
EndedAt *int64
Output *string
ErrorMessage *string
SessionID *string
}
ClaimTaskRunInput atomically transitions a run from ExpectedStatus to NewStatus.
type Conversation ¶
type Conversation struct {
ID uint `json:"-"`
ConversationID string `json:"conversation_id"`
UserID string `json:"user_id"`
TeamID string `json:"team_id,omitempty"`
Channel string `json:"channel"`
Title string `json:"title,omitempty"`
CreatedBy string `json:"created_by"`
CreatedAt int64 `json:"created_at"`
}
Conversation is the Tier 1 conversation container.
type ConversationMessage ¶
type ConversationMessage struct {
ID uint `json:"-"`
ConversationMessageID string `json:"conversation_message_id"`
ConversationID string `json:"conversation_id"`
Role string `json:"role"`
Content string `json:"content"`
Channel *string `json:"channel,omitempty"`
ToolCallID *string `json:"tool_call_id,omitempty"`
ToolCallsJSON *string `json:"tool_calls,omitempty"`
CreatedAt int64 `json:"created_at"`
}
ConversationMessage is one message in a Tier 1 conversation.
type ConversationMessageStore ¶
type ConversationMessageStore interface {
AppendMessage(ctx context.Context, conversationID, role, content string, channel *string, toolCallID *string, toolCallsJSON *string) (*ConversationMessage, error)
ListMessages(ctx context.Context, conversationID string) ([]ConversationMessage, error)
}
ConversationMessageStore provides Tier 1 conversation message persistence. For role=assistant with tool calls, toolCallsJSON should be the JSON-encoded array of tool calls (id, name, arguments).
type ConversationStore ¶
type ConversationStore interface {
CreateConversation(ctx context.Context, userID, channel, createdBy string) (*Conversation, error)
CreateConversationInTeam(ctx context.Context, teamID, userID, channel, createdBy string) (*Conversation, error)
GetConversation(ctx context.Context, conversationID string) (*Conversation, error)
ListConversationsByUser(ctx context.Context, userID string, limit, offset int) ([]Conversation, int, error)
ListConversationsByTeam(ctx context.Context, teamID string, limit, offset int) ([]Conversation, int, error)
UpdateConversationTitle(ctx context.Context, conversationID, title string) error
}
ConversationStore provides Tier 1 conversation persistence. Conversations are user-scoped.
type CreateIssueInput ¶
type CreateLLMModelInput ¶
type CreateLLMModelInput struct {
Name string
ProviderType string
APIURL string
APIKey string
Model string
ContextWindow int
CallTimeout int
Capabilities []string
}
CreateLLMModelInput is a new catalog row, including the credential that the record itself never carries afterwards.
type CreateTaskInput ¶
type CreateTaskInput struct {
ConversationID string
TeamID string
Input string
Title string
CreatedBy string
InitialRunCreatedBy string
InitialRunCreatedByType string
InitialRunTriggerSource string
TitlePromptTokens int
TitleCompletionTokens int
AgentID *string
IssueID *string
}
CreateTaskInput is the input for CreateTask.
type CreateWorkflowRunInput ¶
type Issue ¶
type Issue struct {
ID uint `json:"-"`
IssueID string `json:"issue_id"`
UserID string `json:"user_id"`
TeamID string `json:"team_id,omitempty"`
Title string `json:"title"`
Description string `json:"description"`
Status string `json:"status"`
AssigneeKind *string `json:"assignee_kind,omitempty"`
AssigneeID *string `json:"assignee_id,omitempty"`
CreatedBy string `json:"created_by"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
}
Issue is the user-facing work-management object. It is intentionally separate from low-level task/task_run execution records.
type IssueStore ¶
type IssueStore interface {
CreateIssue(ctx context.Context, userID string, in CreateIssueInput) (*Issue, error)
CreateIssueInTeam(ctx context.Context, teamID, createdBy string, in CreateIssueInput) (*Issue, error)
ListIssuesByUser(ctx context.Context, userID string, limit, offset int) ([]Issue, int, error)
ListIssuesByTeam(ctx context.Context, teamID string, limit, offset int) ([]Issue, int, error)
GetIssue(ctx context.Context, issueID string) (*Issue, error)
UpdateIssue(ctx context.Context, issueID, userID string, in UpdateIssueInput) (*Issue, error)
UpdateIssueInTeam(ctx context.Context, issueID, teamID string, in UpdateIssueInput) (*Issue, error)
}
IssueStore provides issue persistence. Issues are user-scoped.
type LLMCall ¶
type LLMCall struct {
ID uint `json:"-"`
LLMCallID string `json:"llm_call_id"`
// ClientCallID is the caller's idempotency key, unique within a team. It is
// absent for calls the server makes on its own behalf.
ClientCallID *string `json:"client_call_id,omitempty"`
// Identity — derived from authentication, never from the request body.
TeamID string `json:"team_id"`
UserID *string `json:"user_id,omitempty"`
TaskRunID *string `json:"task_run_id,omitempty"`
// Correlation — context for investigation, not authorization input.
Surface string `json:"surface,omitempty"`
SessionID *string `json:"session_id,omitempty"`
TaskID *string `json:"task_id,omitempty"`
// Model — what the caller asked for and what it resolved to.
Alias string `json:"alias,omitempty"`
TargetID string `json:"target_id"`
ProviderType string `json:"provider_type"`
UpstreamModel string `json:"upstream_model"`
Streaming bool `json:"streaming"`
// Timing, in unix seconds like every other table.
AcceptedAt int64 `json:"accepted_at"`
UpstreamStartedAt *int64 `json:"upstream_started_at,omitempty"`
FirstDeltaAt *int64 `json:"first_delta_at,omitempty"`
CompletedAt *int64 `json:"completed_at,omitempty"`
// Outcome.
Status string `json:"status"`
// ErrorClass is the stable BuildMax error classification, never an upstream
// error body.
ErrorClass *string `json:"error_class,omitempty"`
// Attempts counts upstream attempts, so a retry does not read as two calls.
Attempts int `json:"attempts,omitempty"`
// Usage.
PromptTokens *int `json:"prompt_tokens,omitempty"`
CompletionTokens *int `json:"completion_tokens,omitempty"`
TotalTokens *int `json:"total_tokens,omitempty"`
UsageSource string `json:"usage_source,omitempty"`
}
LLMCall is one logical managed inference call.
It is an accounting and diagnostic record, not a transcript: prompts, tool arguments, tool results, and generated content are deliberately absent. Run detail belongs to durable local traces. See docs/design/llm-gateway.md.
type LLMCallOutcome ¶
type LLMCallOutcome struct {
Status string
ErrorClass *string
Attempts int
UpstreamStartedAt *int64
FirstDeltaAt *int64
CompletedAt int64
// Usage is nil when the provider reported none; the record then keeps
// LLMUsageSourceUnavailable rather than zero counts.
Usage *LLMCallUsage
}
LLMCallOutcome is the terminal state written when a call finishes.
type LLMCallStore ¶
type LLMCallStore interface {
// OpenLLMCall records an accepted call before the upstream request starts.
// It assigns the call ID and returns the stored record.
OpenLLMCall(ctx context.Context, call *LLMCall) (*LLMCall, error)
// CompleteLLMCall writes the terminal outcome of an open call.
CompleteLLMCall(ctx context.Context, llmCallID string, outcome LLMCallOutcome) error
// GetLLMCall returns one call by ID, or (nil, nil) when not found.
GetLLMCall(ctx context.Context, llmCallID string) (*LLMCall, error)
// GetLLMCallByClientID returns a team's call by the caller's idempotency
// key, or (nil, nil) when not found.
GetLLMCallByClientID(ctx context.Context, teamID, clientCallID string) (*LLMCall, error)
}
LLMCallStore persists the managed call ledger.
type LLMCallUsage ¶
type LLMCallUsage struct {
PromptTokens int `json:"prompt_tokens"`
CompletionTokens int `json:"completion_tokens"`
TotalTokens int `json:"total_tokens"`
Source string `json:"source"`
}
LLMCallUsage is the token usage reported for one call.
type LLMModel ¶
type LLMModel struct {
ID uint `json:"-"`
LLMModelID string `json:"llm_model_id"`
// Name is the operator-facing name, unique within a deployment.
Name string `json:"name"`
// ProviderType selects the client implementation.
ProviderType string `json:"provider_type"`
// APIURL is the upstream base URL.
APIURL string `json:"api_url"`
// Model is the provider's own model identifier.
Model string `json:"model"`
// ContextWindow is the usable context size; 0 uses the client default.
ContextWindow int `json:"context_window,omitempty"`
// CallTimeout bounds one upstream call in seconds; 0 uses the client default.
CallTimeout int `json:"call_timeout,omitempty"`
// Capabilities is what this model supports, e.g. "text_chat".
Capabilities []string `json:"capabilities,omitempty"`
// Enabled lets an operator retire a model without deleting it.
Enabled bool `json:"enabled"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
}
LLMModel is one operator-approved upstream the managed gateway may call.
The record deliberately has no credential field. The key lives in the same table but is read only by the component that opens a provider connection, so listing, resolving, and diagnosing models can never carry it by accident. See docs/design/llm-gateway.md.
type LLMModelStore ¶
type LLMModelStore interface {
// CreateLLMModel stores a new model and returns it without its credential.
CreateLLMModel(ctx context.Context, in CreateLLMModelInput) (*LLMModel, error)
// GetLLMModel returns one model by ID, or (nil, nil) when not found.
GetLLMModel(ctx context.Context, llmModelID string) (*LLMModel, error)
// ListLLMModels returns every model, enabled or not, oldest first.
ListLLMModels(ctx context.Context) ([]LLMModel, error)
// SetLLMModelEnabled retires or restores a model.
SetLLMModelEnabled(ctx context.Context, llmModelID string, enabled bool) error
// LLMModelCredential returns the upstream key for a model. It is the only
// way a credential leaves the store.
LLMModelCredential(ctx context.Context, llmModelID string) (string, error)
}
LLMModelStore persists the managed model catalog.
Reading a model and reading its credential are separate operations on purpose: everything that lists, resolves, or reports a model uses the first, and only the client factory uses the second.
type LoginCodeStore ¶
type LoginCodeStore interface {
// CreateLoginCode issues a single-use code for userID and returns the
// plaintext, which is never stored and cannot be recovered afterwards.
CreateLoginCode(ctx context.Context, userID string, ttl time.Duration) (plaintext string, expiresAt int64, err error)
// ConsumeLoginCode redeems a code and returns the user it belongs to.
// A code that is unknown, already used, or expired returns ("", nil) —
// the caller cannot tell which, and neither can an attacker. Redemption is
// atomic: concurrent calls with the same code produce exactly one winner.
ConsumeLoginCode(ctx context.Context, plaintext string, now int64) (userID string, err error)
}
LoginCodeStore issues and redeems single-use login codes.
This is BuildMax's answer to having no mail channel: an operator issues a code out of band (`buildmax-server user login-code`) and delivers it however they already talk to the person.
It is not the everyday credential — a password is. A code is what claims a new account and what recovers a forgotten password, which is why it is single-use and short-lived: it exists to be spent once, on the way to setting a password.
type NewRefreshToken ¶
type NewRefreshToken struct {
UserID string
// SessionID names one login chain. Every rotation keeps it, so revoking a
// session retires the whole chain rather than one link of it.
SessionID string
// Platform records which surface logged in ("portal", "cli", "desktop").
// It is a label for the operator reading the session list, not something
// the server enforces.
Platform string
TTL time.Duration
}
NewRefreshToken describes a token to issue.
type PasswordStore ¶
type PasswordStore interface {
// PasswordHash returns the stored hash for userID, or "" when the account
// has no password and can only sign in with a login code.
PasswordHash(ctx context.Context, userID string) (string, error)
// SetPassword stores an already-hashed password. Hashing belongs to the
// caller — this interface must not be a place where a plaintext password
// can be passed by mistake.
SetPassword(ctx context.Context, userID, encodedHash string, setAt int64) error
}
PasswordStore reads and writes the one credential a person chose themselves.
It is deliberately separate from UserStore. A password hash is the only value in the system whose exposure would reach beyond BuildMax — people reuse passwords — so it is fetched only by the code that verifies a login, and never rides along on a User that some handler might serialize.
type QuotaTier ¶
type QuotaTier struct {
TierName string `json:"tier_name"`
MaxRunsPerPeriod int `json:"max_runs_per_period"`
MaxTokensPerPeriod int `json:"max_tokens_per_period"`
PeriodDays int `json:"period_days"`
}
QuotaTier defines limits for a tier (e.g. free_trial, pro).
type QuotaTierStore ¶
type QuotaTierStore interface {
// GetQuotaTier returns the tier limits by tier name, or (nil, nil) when not found.
GetQuotaTier(ctx context.Context, tierName string) (*QuotaTier, error)
}
QuotaTierStore provides quota tier limits by tier name.
type RefreshTokenStore ¶
type RefreshTokenStore interface {
// CreateRefreshToken issues a token and returns the plaintext, which is
// never stored — the row holds a hash, so a database backup yields no
// usable credentials.
CreateRefreshToken(ctx context.Context, in NewRefreshToken) (plaintext string, expiresAt int64, err error)
// RotateRefreshToken exchanges plaintext for a fresh token in the same
// session, spending the presented one.
//
// Within grace of having been spent, a token may be exchanged again. That
// window is not a concession to sloppy clients: BuildMax's CLI and Desktop
// share one credentials file across independent processes, and two of them
// refreshing at the same moment is normal rather than suspicious. Both
// receive a usable token; both stay in the same session.
//
// Past the grace window a spent token means ErrRefreshTokenReused, and the
// whole session is revoked first — logging out the legitimate holder is the
// correct response when a credential may be in two hands. That error comes
// back with UserID and SessionID populated and Plaintext empty, so the
// caller can record what was revoked.
RotateRefreshToken(ctx context.Context, plaintext string, now int64, ttl, grace time.Duration) (RotatedRefreshToken, error)
// RevokeRefreshTokenSession revokes the session the token belongs to and
// reports whose it was. An unknown token is not an error: logging out
// something already gone is a success.
RevokeRefreshTokenSession(ctx context.Context, plaintext string, now int64) (userID, sessionID string, err error)
// RevokeSession revokes every live token in one session and returns how
// many it retired.
RevokeSession(ctx context.Context, sessionID string, now int64) (int64, error)
// DeleteExpiredRefreshTokens removes rows that can no longer be exchanged.
DeleteExpiredRefreshTokens(ctx context.Context, before int64) (int64, error)
}
RefreshTokenStore issues, rotates, and revokes the stored half of a login.
Rotation is what makes a stolen refresh token detectable: each exchange spends the presented token and hands back a new one, so the same token appearing twice means two holders. See RotateRefreshToken for what the store does about that, and why a short grace window has to exist.
type RotatedRefreshToken ¶
RotatedRefreshToken is the result of exchanging one refresh token for the next. Plaintext is returned once and never recoverable afterwards.
type Task ¶
type Task struct {
ID uint `json:"-"`
TaskID string `json:"task_id"`
ConversationID string `json:"conversation_id"`
TeamID string `json:"team_id,omitempty"`
IssueID *string `json:"issue_id,omitempty"`
Status string `json:"status"`
Input string `json:"input"`
Title string `json:"title,omitempty"`
TitlePromptTokens int `json:"title_prompt_tokens,omitempty"`
TitleCompletionTokens int `json:"title_completion_tokens,omitempty"`
Output *string `json:"output,omitempty"`
CreatedBy string `json:"created_by"`
CreatedAt int64 `json:"created_at"`
StartedAt *int64 `json:"started_at,omitempty"`
EndedAt *int64 `json:"ended_at,omitempty"`
ErrorMessage *string `json:"error_message,omitempty"`
SessionID *string `json:"session_id,omitempty"`
LastRunID *string `json:"last_run_id,omitempty"`
AgentID *string `json:"agent_id,omitempty"`
}
Task holds the user-visible state for a background task.
type TaskRun ¶
type TaskRun struct {
ID uint `json:"-"`
TaskRunID string `json:"task_run_id"`
TaskID string `json:"task_id"`
Input string `json:"input"`
CreatedBy string `json:"created_by,omitempty"`
CreatedByType string `json:"created_by_type,omitempty"`
TriggerSource string `json:"trigger_source,omitempty"`
Status string `json:"status"`
Output *string `json:"output,omitempty"`
ErrorMessage *string `json:"error_message,omitempty"`
StartedAt *int64 `json:"started_at,omitempty"`
EndedAt *int64 `json:"ended_at,omitempty"`
SessionID *string `json:"session_id,omitempty"`
WorkerType string `json:"worker_type,omitempty"`
K8sJobName *string `json:"k8s_job_name,omitempty"`
K8sJobCreatedAt *int64 `json:"k8s_job_created_at,omitempty"`
PromptTokens *int `json:"prompt_tokens,omitempty"`
CompletionTokens *int `json:"completion_tokens,omitempty"`
// TracePath locates this run's durable trace inside run-global storage,
// e.g. "traces/<session>/rt_….jsonl". Nil when no trace was written — the
// run failed before an agent started, or tracing was disabled.
TracePath *string `json:"trace_path,omitempty"`
CreatedAt int64 `json:"created_at"`
}
TaskRun is one execution (initial or follow-up) of a task.
type TaskRunArtifact ¶
type TaskRunArtifact struct {
ID uint `json:"-"`
TaskRunID string `json:"task_run_id"`
RelativePath string `json:"relative_path"`
}
TaskRunArtifact is one output file (artifact) for a task run.
type TaskRunStore ¶
type TaskRunStore interface {
// CreateTaskRun creates a new run (PENDING). Returns ErrRunInProgress if the task has any run in PENDING/SCHEDULED/RUNNING.
CreateTaskRun(ctx context.Context, taskID, input, createdBy, createdByType, triggerSource string) (*TaskRun, error)
// GetNextPendingTaskRun returns the oldest run with status PENDING (by created_at), or (nil, nil) if none.
GetNextPendingTaskRun(ctx context.Context) (*TaskRun, error)
GetTaskRun(ctx context.Context, taskRunID string) (*TaskRun, error)
// GetTaskRunWithTask returns the run and its task, or (nil, nil, nil) if run not found.
GetTaskRunWithTask(ctx context.Context, taskRunID string) (*TaskRun, *Task, error)
// ClaimTaskRun atomically updates a run when current status matches ExpectedStatus.
ClaimTaskRun(ctx context.Context, in ClaimTaskRunInput) (bool, error)
// UpdateRun updates a run's status and optional fields.
UpdateRun(ctx context.Context, in UpdateTaskRunInput) error
UpdateTaskRunWorkerInfo(ctx context.Context, taskRunID, workerType string, k8sJobName *string, k8sJobCreatedAt *int64) error
// OnRunComplete creates task_run_artifact rows (one per relativePath) and updates task denormalized fields. Use for SUCCEEDED runs.
OnRunComplete(ctx context.Context, taskRunID string, relativePaths []string) error
// SyncTaskFromRun updates task denormalized fields and last_run_id from the run (no output). Use for FAILED runs.
SyncTaskFromRun(ctx context.Context, taskRunID string) error
}
TaskRunStore provides task run persistence.
type TaskRunTerminalInfo ¶
type TaskRunTerminalInfo struct {
TaskRunID string
TaskID string
ConversationID string
UserID string
Status string
Output *string
ErrorMessage *string
}
TaskRunTerminalInfo describes a task run that reached a terminal state. Used by the workflow service to advance or finalize workflow step runs.
type TaskStore ¶
type TaskStore interface {
// ListTasksByConversation returns tasks in the conversation. order is "asc" (oldest first) or "desc" (latest first); default "desc".
ListTasksByConversation(ctx context.Context, conversationID string, order string) ([]Task, error)
// ListTasksByConversationPaginated returns tasks with optional executed_only filter, ordered by created_at DESC. total is total matching count.
ListTasksByConversationPaginated(ctx context.Context, conversationID string, executedOnly bool, limit, offset int) ([]Task, int, error)
ListTasksByIssue(ctx context.Context, issueID string, limit, offset int) ([]Task, int, error)
GetTask(ctx context.Context, taskID string) (*Task, error)
GetTaskBySessionID(ctx context.Context, sessionID string) (*Task, error)
// CreateTask creates a new task and its first TaskRun (input, title, PENDING). Returns the task with last_run_id set.
CreateTask(ctx context.Context, in *CreateTaskInput) (*Task, error)
UpdateTask(ctx context.Context, in UpdateTaskInput) error
ClaimTask(ctx context.Context, in ClaimTaskInput) (updated bool, err error)
}
TaskStore provides task persistence. Tasks belong to a conversation. CreateTask creates a task plus its first TaskRun (both in one transaction).
type Team ¶
type Team struct {
ID uint `json:"-"`
TeamID string `json:"team_id"`
Name string `json:"name"`
PersonalForUserID *string `json:"personal_for_user_id,omitempty"`
QuotaTier string `json:"quota_tier,omitempty"`
CreatedBy string `json:"created_by"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
}
Team is the ownership and collaboration boundary for working resources. A user's default personal team is represented by personal_for_user_id.
type TeamMember ¶
type TeamMember struct {
ID uint `json:"-"`
TeamID string `json:"team_id"`
UserID string `json:"user_id"`
Role string `json:"role"`
CreatedAt int64 `json:"created_at"`
}
TeamMember is one user's membership in a team.
type TeamStore ¶
type TeamStore interface {
// GetTeam returns the team by team_id, or (nil, nil) when not found.
GetTeam(ctx context.Context, teamID string) (*Team, error)
// GetPersonalTeamByUser returns the default personal team for the user, or (nil, nil) when not found.
GetPersonalTeamByUser(ctx context.Context, userID string) (*Team, error)
// ListTeamsByUser returns all teams the user belongs to, ordered by created_at ASC.
ListTeamsByUser(ctx context.Context, userID string) ([]Team, error)
// CreateTeam creates a new team and owner membership.
CreateTeam(ctx context.Context, name, createdBy, quotaTier string) (*Team, error)
// AddTeamMember adds or updates a team membership.
AddTeamMember(ctx context.Context, teamID, userID, role string) (*TeamMember, error)
// RemoveTeamMember removes one membership from a team.
RemoveTeamMember(ctx context.Context, teamID, userID string) error
// ListTeamMembers returns members of the team ordered by created_at ASC.
ListTeamMembers(ctx context.Context, teamID string) ([]TeamMember, error)
}
TeamStore provides team persistence and membership lookup.
type UpdateIssueInput ¶
type UpdateTaskInput ¶
type UpdateTaskInput struct {
TaskID string
Status string
StartedAt *int64
EndedAt *int64
Output *string
ErrorMessage *string
SessionID *string
}
UpdateTaskInput updates a task to the given status with optional fields.
type UpdateTaskRunInput ¶
type UpdateTaskRunInput struct {
TaskRunID string
Status RunStatus
StartedAt *int64
EndedAt *int64
Output *string
ErrorMessage *string
SessionID *string
PromptTokens *int
CompletionTokens *int
TracePath *string
}
UpdateTaskRunInput updates a run to the given status with optional fields.
type UpdateWorkflowInput ¶
type UpdateWorkflowRunInput ¶
type UsageInWindowReader ¶
type UsageInWindowReader interface {
// TeamUsageInWindow returns run count and total tokens for the team in [sinceUnix, untilUnix].
TeamUsageInWindow(ctx context.Context, teamID string, sinceUnix, untilUnix int64) (runCount, totalTokens int, err error)
}
UsageInWindowReader provides usage aggregation for a team in a time window.
type User ¶
type User struct {
ID uint `json:"-"`
UserID string `json:"user_id"`
Email string `json:"email"`
Name string `json:"name"`
QuotaTier string `json:"quota_tier,omitempty"`
LastLoginAt *int64 `json:"last_login_at,omitempty"`
LastLoginPlatform *string `json:"last_login_platform,omitempty"`
CreatedAt int64 `json:"created_at"`
// HasPassword reports whether this account can sign in with a password. The
// hash itself never travels on this struct — see PasswordStore — so that no
// handler can serialize it into a response by accident.
HasPassword bool `json:"has_password"`
}
User is the user model. JSON uses snake_case per project convention. Internal numeric ID is retained for compatibility but is not part of the public API.
type UserStore ¶
type UserStore interface {
UserByEmail(ctx context.Context, email string) (*User, error)
// GetUser returns the user by user_id, or (nil, nil) when not found.
GetUser(ctx context.Context, userID string) (*User, error)
// CreateUser creates a user with the given email. defaultQuotaTier is applied when non-empty. Returns ErrEmailExists if the email is already registered.
CreateUser(ctx context.Context, email string, defaultQuotaTier string) (*User, error)
// UpdateLoginMeta records the last login timestamp and platform for the user.
UpdateLoginMeta(ctx context.Context, userID string, loginAt int64, platform string) error
}
UserStore looks up users by email and creates new users.
type UserWebhookKey ¶
type UserWebhookKey struct {
ID uint `json:"-"`
KeyID string `json:"key_id"`
UserID string `json:"user_id"`
KeyHash string `json:"-"` // SHA256 hex of plaintext key
Name string `json:"name,omitempty"`
CreatedAt int64 `json:"created_at"`
}
UserWebhookKey is a webhook API key for a user. Plaintext key is returned only at creation; only key_hash is stored. JSON uses snake_case per project convention.
type UserWebhookKeyStore ¶
type UserWebhookKeyStore interface {
// CreateKey creates a new webhook key for the user. Returns plaintext key (e.g. whsec_...) and key_id. Caller must store plaintext securely; it is not persisted.
CreateKey(ctx context.Context, userID, name string) (plaintextKey, keyID string, err error)
// GetUserIDByKey looks up the user_id for the given plaintext key. Returns empty string if not found.
GetUserIDByKey(ctx context.Context, plaintextKey string) (userID string, err error)
// ListKeys returns key metadata for the user (no plaintext).
ListKeys(ctx context.Context, userID string) ([]WebhookKeyMeta, error)
// RevokeKey deletes the key by keyID if it belongs to the user.
RevokeKey(ctx context.Context, userID, keyID string) error
}
UserWebhookKeyStore provides per-user webhook API key persistence. Keys are stored by hash; plaintext is returned only from CreateKey.
type WebhookKeyMeta ¶
type WebhookKeyMeta struct {
KeyID string `json:"key_id"`
Name string `json:"name,omitempty"`
CreatedAt int64 `json:"created_at"`
}
WebhookKeyMeta is key metadata returned by ListKeys (no plaintext).
type Workflow ¶
type Workflow struct {
ID uint `json:"-"`
WorkflowID string `json:"workflow_id"`
TeamID string `json:"team_id"`
Name string `json:"name"`
Description string `json:"description"`
Definition string `json:"definition"`
Status string `json:"status"`
CreatedBy string `json:"created_by"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
}
Workflow is a reusable team-scoped execution plan.
type WorkflowDefinition ¶
type WorkflowDefinition struct {
Steps []WorkflowDefinitionStep `json:"steps"`
}
WorkflowDefinition is the parsed structure of a workflow definition JSON.
type WorkflowDefinitionStep ¶
type WorkflowDefinitionStep struct {
StepID string `json:"step_id"`
Type string `json:"type"`
TargetAgentID string `json:"target_agent_id"`
Prompt string `json:"prompt"`
}
WorkflowDefinitionStep describes one step in a workflow definition.
type WorkflowRun ¶
type WorkflowRun struct {
ID uint `json:"-"`
WorkflowRunID string `json:"workflow_run_id"`
WorkflowID string `json:"workflow_id"`
IssueID *string `json:"issue_id,omitempty"`
ConversationID string `json:"conversation_id"`
Status string `json:"status"`
CreatedBy string `json:"created_by"`
CreatedAt int64 `json:"created_at"`
StartedAt *int64 `json:"started_at,omitempty"`
EndedAt *int64 `json:"ended_at,omitempty"`
ErrorMessage *string `json:"error_message,omitempty"`
}
WorkflowRun is one execution attempt of a workflow.
type WorkflowStepRun ¶
type WorkflowStepRun struct {
ID uint `json:"-"`
StepRunID string `json:"workflow_step_run_id"`
WorkflowRunID string `json:"workflow_run_id"`
StepID string `json:"step_id"`
StepIndex int `json:"step_index"`
StepType string `json:"step_type"`
TargetAgentID *string `json:"target_agent_id,omitempty"`
Prompt string `json:"prompt"`
Status string `json:"status"`
TaskID *string `json:"task_id,omitempty"`
TaskRunID *string `json:"task_run_id,omitempty"`
OutputSummary *string `json:"output_summary,omitempty"`
ErrorMessage *string `json:"error_message,omitempty"`
CreatedAt int64 `json:"created_at"`
StartedAt *int64 `json:"started_at,omitempty"`
EndedAt *int64 `json:"ended_at,omitempty"`
}
WorkflowStepRun is one durable step execution record under a workflow run.
type WorkflowStore ¶
type WorkflowStore interface {
ListWorkflowsByTeam(ctx context.Context, teamID string) ([]Workflow, error)
CreateWorkflow(ctx context.Context, teamID, createdBy, name, description, definition string) (*Workflow, error)
GetWorkflow(ctx context.Context, workflowID string) (*Workflow, error)
UpdateWorkflow(ctx context.Context, workflowID, teamID string, in UpdateWorkflowInput) (*Workflow, error)
CreateWorkflowRun(ctx context.Context, in CreateWorkflowRunInput) (*WorkflowRun, error)
ListWorkflowRunsByWorkflow(ctx context.Context, workflowID string, limit, offset int) ([]WorkflowRun, int, error)
ListWorkflowRunsByIssue(ctx context.Context, issueID string, limit, offset int) ([]WorkflowRun, int, error)
GetWorkflowRun(ctx context.Context, workflowRunID string) (*WorkflowRun, error)
ListWorkflowStepRuns(ctx context.Context, workflowRunID string) ([]WorkflowStepRun, error)
CreateWorkflowStepRuns(ctx context.Context, workflowRunID string, steps []CreateWorkflowStepRunInput) ([]WorkflowStepRun, error)
UpdateWorkflowRun(ctx context.Context, workflowRunID string, in UpdateWorkflowRunInput) (*WorkflowRun, error)
UpdateWorkflowStepRun(ctx context.Context, stepRunID string, in UpdateWorkflowStepRunInput) (*WorkflowStepRun, error)
GetWorkflowStepRunByTaskID(ctx context.Context, taskID string) (*WorkflowStepRun, error)
GetWorkflowStepRunByTaskRunID(ctx context.Context, taskRunID string) (*WorkflowStepRun, error)
}
WorkflowStore provides workflow and workflow execution persistence.