model

package
v0.1.0-alpha.1 Latest Latest
Warning

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

Go to latest
Published: Aug 17, 2026 License: Apache-2.0 Imports: 10 Imported by: 0

Documentation

Index

Constants

View Source
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.

View Source
const (
	AuditActorUser   = "user"
	AuditActorWorker = "worker"
	AuditActorSystem = "system"
)

Audit actor kinds.

View Source
const (
	IssueStatusTodo       = "todo"
	IssueStatusInProgress = "in_progress"
	IssueStatusDone       = "done"
	IssueAssigneePerson   = "person"
	IssueAssigneeAgent    = "agent"
	IssueAssigneeWorkflow = "workflow"
)
View Source
const (
	LLMCallStatusAccepted  = "ACCEPTED"
	LLMCallStatusSucceeded = "SUCCEEDED"
	LLMCallStatusFailed    = "FAILED"
	LLMCallStatusCanceled  = "CANCELED"
)

Managed LLM call lifecycle statuses.

View Source
const (
	LLMUsageSourceReported    = "reported"
	LLMUsageSourceEstimated   = "estimated"
	LLMUsageSourceUnavailable = "unavailable"
)

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.

View Source
const (
	LLMCallSurfaceServer  = "server"
	LLMCallSurfaceCLI     = "cli"
	LLMCallSurfaceDesktop = "desktop"
	LLMCallSurfaceWorker  = "worker"
)

Surfaces a managed call can originate from.

View Source
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.

View Source
const (
	RunCreatedByTypeUser    = "user"
	RunCreatedByTypeWebhook = "webhook"
	RunCreatedByTypeSystem  = "system"
)
View Source
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"
)
View Source
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"
)
View Source
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"
)
View Source
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.

View Source
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.

View Source
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

View Source
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".

View Source
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.

View Source
var ErrEmailExists = errors.New("email already exists")

ErrEmailExists is returned by CreateUser when the email is already registered.

View Source
var ErrLLMModelNameTaken = errors.New("a model with this name already exists")

ErrLLMModelNameTaken is returned when an operator reuses a model name.

View Source
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.

View Source
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.

View Source
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.

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

ErrUserNotFound is returned when an operation names an account that is not there.

Functions

func DummyVerifyPassword

func DummyVerifyPassword(plaintext string) bool

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

func HashPassword(plaintext string) (string, error)

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

func ValidatePassword(plaintext string) error

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

func VerifyPassword(encodedHash, plaintext string) bool

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 CreateIssueInput struct {
	Title       string
	Description string
}

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 CreateWorkflowRunInput struct {
	WorkflowID     string
	IssueID        *string
	ConversationID string
	Status         string
	CreatedBy      string
	StartedAt      *int64
}

type CreateWorkflowStepRunInput

type CreateWorkflowStepRunInput struct {
	StepID        string
	StepIndex     int
	StepType      string
	TargetAgentID *string
	Prompt        string
	Status        string
}

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

type RotatedRefreshToken struct {
	UserID    string
	SessionID string
	Plaintext string
	ExpiresAt int64
}

RotatedRefreshToken is the result of exchanging one refresh token for the next. Plaintext is returned once and never recoverable afterwards.

type RunStatus

type RunStatus string

RunStatus is the canonical lifecycle status for task runs.

const (
	RunStatusPending   RunStatus = "PENDING"
	RunStatusScheduled RunStatus = "SCHEDULED"
	RunStatusRunning   RunStatus = "RUNNING"
	RunStatusSucceeded RunStatus = "SUCCEEDED"
	RunStatusFailed    RunStatus = "FAILED"
)

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 UpdateIssueInput struct {
	Title        *string
	Description  *string
	Status       *string
	AssigneeKind *string
	AssigneeID   *string
}

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 UpdateWorkflowInput struct {
	Name        *string
	Description *string
	Definition  *string
	Status      *string
}

type UpdateWorkflowRunInput

type UpdateWorkflowRunInput struct {
	Status       string
	StartedAt    *int64
	EndedAt      *int64
	ErrorMessage *string
}

type UpdateWorkflowStepRunInput

type UpdateWorkflowStepRunInput struct {
	Status        *string
	TaskID        *string
	TaskRunID     *string
	OutputSummary *string
	ErrorMessage  *string
	StartedAt     *int64
	EndedAt       *int64
}

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.

Jump to

Keyboard shortcuts

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