internal/storage
import "github.com/neochaotic/leoflow/internal/storage"
Package storage wraps the Postgres and Redis connections used by the control plane, exposing the sqlc-generated query set and health checks.
Index
- func AttachRedisObservability(ctx context.Context, r *Redis, m RedisMetrics, interval time.Duration) func()
- func NewLeaderPool(ctx context.Context, cfg config.DatabaseSection) (*pgxpool.Pool, error)
- type ExecutionStore
- func NewExecutionStore(pg *Postgres) *ExecutionStore
- func (s *ExecutionStore) BindWarmAttempt(ctx context.Context, runID, taskID string, tryNumber int, workerPod string) error
- func (s *ExecutionStore) FailTask(ctx context.Context, taskInstanceID string, tryNumber int, reason string) error
- func (s *ExecutionStore) IsTaskInstanceLive(ctx context.Context, runID, taskID string, tryNumber int) (bool, error)
- func (s *ExecutionStore) RecordHeartbeat(ctx context.Context, id auth.AgentIdentity) error
- func (s *ExecutionStore) ReportState(ctx context.Context, id auth.AgentIdentity, state domain.TaskState, exitCode int, errMsg string) error
- func (s *ExecutionStore) RequeueForRedispatch(ctx context.Context, runID, taskID string, tryNumber int) error
- func (s *ExecutionStore) Reschedule(ctx context.Context, id auth.AgentIdentity, at time.Time) error
- func (s *ExecutionStore) RescheduleTask(ctx context.Context, taskInstanceID string, tryNumber int, at time.Time) error
- func (s *ExecutionStore) ResolveTask(ctx context.Context, runID, taskID string) (dispatch.Resolved, error)
- func (s *ExecutionStore) SucceedTask(ctx context.Context, taskInstanceID string, tryNumber int) error
- func (s *ExecutionStore) TaskSpec(ctx context.Context, id auth.AgentIdentity) (agentrpc.TaskSpec, error)
- type LogReader
- func NewLogReader(pg *Postgres, sink logs.Sink, tailer logs.Tailer) *LogReader
- func (r *LogReader) ReadLogs(ctx context.Context, tenant, dagID, runID, taskID string, tryNumber int) (io.ReadCloser, error)
- func (r *LogReader) Tail(ctx context.Context, tenant, dagID, runID, taskID string, tryNumber int) (lines <-chan string, cancel func(), err error)
- type Postgres
- type Redis
- type RedisMetrics
- type Repository
- func NewRepository(pg *Postgres) *Repository
- func (r *Repository) AddFavorite(ctx context.Context, tenant, userID, dagID string) error
- func (r *Repository) AlertEndpoint(ctx context.Context, tenantID, connID string) (endpointURL string, headers map[string]string, err error)
- func (r *Repository) BootstrapAdmin(ctx context.Context, tenant, email, password string) (bool, error)
- func (r *Repository) BootstrapAdminHash(ctx context.Context, tenant, email, hash string) (bool, error)
- func (r *Repository) ClearDagHistory(ctx context.Context, tenant, dagID string) error
- func (r *Repository) ClearImportError(ctx context.Context, tenant, filename string) error
- func (r *Repository) ClearTaskInstances(ctx context.Context, tenant, dagID, runID string, taskIDs []string, onlyFailed, resetDagRun bool) (int, error)
- func (r *Repository) CreateDagRun(ctx context.Context, tenant, dagID string, run domain.DagRun) (domain.DagRun, error)
- func (r *Repository) CreateOIDCUser(ctx context.Context, tenant, email, provider, subject string, roles []string) (*auth.User, error)
- func (r *Repository) CreateUser(ctx context.Context, tenant, email, password string, roles []string) (domain.User, error)
- func (r *Repository) DagStats(ctx context.Context, tenant string) (domain.DagStats, error)
- func (r *Repository) DeleteConnection(ctx context.Context, tenant, connID string) error
- func (r *Repository) DeleteDag(ctx context.Context, tenant, dagID string) error
- func (r *Repository) DeleteDagRun(ctx context.Context, tenant, dagID, runID string) error
- func (r *Repository) DeletePool(ctx context.Context, tenant, name string) error
- func (r *Repository) DeleteVariable(ctx context.Context, tenant, key string) error
- func (r *Repository) FavoriteDagIDs(ctx context.Context, tenant, userID string) (map[string]bool, error)
- func (r *Repository) FindUserByID(ctx context.Context, id string) (*auth.User, bool, error)
- func (r *Repository) FindUserByLogin(ctx context.Context, tenant, username string) (*auth.User, string, error)
- func (r *Repository) FindUserByOIDCSubject(ctx context.Context, provider, subject string) (*auth.User, bool, error)
- func (r *Repository) GetConnection(ctx context.Context, tenant, connID string) (domain.Connection, error)
- func (r *Repository) GetCurrentSpec(ctx context.Context, tenant, dagID string) (domain.DAGSpec, error)
- func (r *Repository) GetDag(ctx context.Context, tenant, dagID string) (domain.DAG, error)
- func (r *Repository) GetDagRun(ctx context.Context, tenant, dagID, runID string) (domain.DagRun, error)
- func (r *Repository) GetPool(ctx context.Context, tenant, name string) (domain.Pool, error)
- func (r *Repository) GetVariable(ctx context.Context, tenant, key string) (domain.Variable, error)
- func (r *Repository) HistoricalMetrics(ctx context.Context, tenant string, since, until time.Time) (domain.HistoricalMetrics, error)
- func (r *Repository) LatestRunsForDags(ctx context.Context, tenant string, dagIDs []string, perDag int) (map[string][]domain.DagRun, error)
- func (r *Repository) ListAuditLogs(ctx context.Context, tenant, dagID string, limit, offset int) ([]domain.AuditLogEntry, int, error)
- func (r *Repository) ListConnections(ctx context.Context, tenant string, limit, offset int) ([]domain.Connection, int, error)
- func (r *Repository) ListDagRuns(ctx context.Context, tenant, dagID string, limit, offset int) ([]domain.DagRun, int, error)
- func (r *Repository) ListDagVersions(ctx context.Context, tenant, dagID string) ([]domain.DagVersion, error)
- func (r *Repository) ListDags(ctx context.Context, tenant string, limit, offset int) ([]domain.DAG, int, error)
- func (r *Repository) ListDagsFiltered(ctx context.Context, tenant, runState string, paused *bool, limit, offset int) ([]domain.DAG, int, error)
- func (r *Repository) ListImportErrors(ctx context.Context, tenant string) ([]domain.ImportError, error)
- func (r *Repository) ListPools(ctx context.Context, tenant string, limit, offset int) ([]domain.Pool, int, error)
- func (r *Repository) ListTaskInstanceAttempts(ctx context.Context, tenant, dagID, runID, taskID string) ([]domain.TaskInstance, error)
- func (r *Repository) ListTaskInstances(ctx context.Context, tenant, dagID, runID string, _, _ int) ([]domain.TaskInstance, int, error)
- func (r *Repository) ListUsers(ctx context.Context, tenant string, limit, offset int) ([]domain.User, int, error)
- func (r *Repository) ListVariables(ctx context.Context, tenant string, limit, offset int) ([]domain.Variable, int, error)
- func (r *Repository) PoolSlotUsage(ctx context.Context, tenant string) (map[string]domain.PoolUsage, error)
- func (r *Repository) ReconcileUserRoles(ctx context.Context, userID string, roleNames []string) error
- func (r *Repository) RecordAuthEvent(ctx context.Context, tenant, actorUserID, action, email, outcome string, extra map[string]string) error
- func (r *Repository) RecordSecretLivenessDenial(ctx context.Context, tenantID, dagID, runID, taskID string, tryNumber int, kind, mode string) error
- func (r *Repository) RecordSecretScopeWarning(ctx context.Context, tenantID, dagID, runID, taskID, kind string, declared, total int) error
- func (r *Repository) RecordTaskActionAudit(ctx context.Context, tenant, userID, action, dagID, runID, taskID string, tryNumber int) error
- func (r *Repository) RecordUserCreatedAudit(ctx context.Context, tenant, actorUserID, createdUserID, email, roles string) error
- func (r *Repository) RegisterDagVersion(ctx context.Context, tenant string, spec domain.DAGSpec, specHash string) (bool, error)
- func (r *Repository) RemoveFavorite(ctx context.Context, tenant, userID, dagID string) error
- func (r *Repository) RoleExists(ctx context.Context, tenant, role string) (bool, error)
- func (r *Repository) SecretConnectionURIs(ctx context.Context, tenantID string) (map[string]string, error)
- func (r *Repository) SecretConnectionURIsScoped(ctx context.Context, tenantID string, names []string) (map[string]string, error)
- func (r *Repository) SecretVariables(ctx context.Context, tenantID string) (map[string]string, error)
- func (r *Repository) SecretVariablesScoped(ctx context.Context, tenantID string, names []string) (map[string]string, error)
- func (r *Repository) SetCipher(c secrets.Cipher)
- func (r *Repository) SetConnection(ctx context.Context, tenant string, c domain.Connection) error
- func (r *Repository) SetDagRunState(ctx context.Context, tenant, dagID, runID, state string) error
- func (r *Repository) SetImportError(ctx context.Context, tenant string, e domain.ImportError) error
- func (r *Repository) SetPaused(ctx context.Context, tenant, dagID string, paused bool) (domain.DAG, error)
- func (r *Repository) SetPool(ctx context.Context, tenant string, p domain.Pool) error
- func (r *Repository) SetTaskInstanceState(ctx context.Context, tenant, dagID, runID, taskID, state string) error
- func (r *Repository) SetUserPassword(ctx context.Context, tenant, email, hash string) (bool, error)
- func (r *Repository) SetVariable(ctx context.Context, tenant string, v domain.Variable) error
- func (r *Repository) TaskInstancesForRuns(ctx context.Context, tenant, dagID string, runIDs []string) ([]domain.TaskInstance, error)
- func (r *Repository) TenantUUID(ctx context.Context, name string) (string, error)
- type SchedulerStore
- func NewSchedulerStore(pg *Postgres) *SchedulerStore
- func (s *SchedulerStore) ActiveRuns(ctx context.Context) ([]scheduler.RunState, error)
- func (s *SchedulerStore) ActiveWarmTargets(ctx context.Context) ([]executor.WarmTarget, error)
- func (s *SchedulerStore) ApplyTransition(ctx context.Context, runID, taskID string, to domain.TaskState) error
- func (s *SchedulerStore) ApplyTransitions(ctx context.Context, runID string, taskIDs []string, to domain.TaskState) error
- func (s *SchedulerStore) ClaimAlertAttempt(ctx context.Context, runID string, maxAttempts int, backoff time.Duration) (int, error)
- func (s *SchedulerStore) CreateScheduledRun(ctx context.Context, dagID string, logical time.Time) error
- func (s *SchedulerStore) FailDispatchExhausted(ctx context.Context, runID, taskID, reason string) error
- func (s *SchedulerStore) ListActiveStagingVolumes(ctx context.Context) ([]domain.StagingVolumeState, error)
- func (s *SchedulerStore) ListAgentLostCandidates(ctx context.Context) ([]executor.AgentLostCandidate, error)
- func (s *SchedulerStore) ListBusyWarmWorkerPods(ctx context.Context) (map[string]bool, error)
- func (s *SchedulerStore) ListReapCandidates(ctx context.Context) ([]executor.ReapCandidate, error)
- func (s *SchedulerStore) ListRunningTasks(ctx context.Context) ([]executor.PodLostCandidate, error)
- func (s *SchedulerStore) ListStaleQueuedCandidates(ctx context.Context) ([]executor.StaleQueuedCandidate, error)
- func (s *SchedulerStore) ListWarmBoundRunningTIs(ctx context.Context) ([]executor.WarmBoundTI, error)
- func (s *SchedulerStore) MarkRunAlertDelivered(ctx context.Context, runID string, attempt int) error
- func (s *SchedulerStore) MarkStagingDeleted(ctx context.Context, pvcName, reason string) error
- func (s *SchedulerStore) MarkTaskAgentLost(ctx context.Context, taskInstanceID string) (bool, error)
- func (s *SchedulerStore) MarkTaskDispatchFailed(ctx context.Context, runID, taskID, reason string) error
- func (s *SchedulerStore) MarkTaskDispatchLost(ctx context.Context, taskInstanceID string) error
- func (s *SchedulerStore) MarkTaskPodLost(ctx context.Context, taskInstanceID string) (bool, error)
- func (s *SchedulerStore) MaterializeTasks(ctx context.Context, runID string, tasks []domain.TaskSpec) error
- func (s *SchedulerStore) PoolBudgets(ctx context.Context) (map[string]int, error)
- func (s *SchedulerStore) ReapRun(ctx context.Context, runID string) error
- func (s *SchedulerStore) RecordDispatchBackpressure(ctx context.Context, runID, taskID string, nextAt time.Time) error
- func (s *SchedulerStore) RecordDispatchFailure(ctx context.Context, runID, taskID string, nextAt time.Time) error
- func (s *SchedulerStore) RecordStagingVolume(ctx context.Context, tenantID, dagID, runID, pvcName, size string) error
- func (s *SchedulerStore) RedispatchReschedule(ctx context.Context, runID, taskID string) error
- func (s *SchedulerStore) ResetForInfraReplace(ctx context.Context, runID, taskID string) (bool, error)
- func (s *SchedulerStore) ResetForRetry(ctx context.Context, runID, taskID string) (bool, error)
- func (s *SchedulerStore) ScheduledDAGs(ctx context.Context) ([]scheduler.ScheduledDAG, error)
- func (s *SchedulerStore) SetRunState(ctx context.Context, runID string, state domain.DagRunState) error
- func (s *SchedulerStore) SetTaskNote(ctx context.Context, runID, taskID, note string) error
- func (s *SchedulerStore) SetWarmExecution(exec config.ExecutionSection)
- type XComIndex
- type XComReader
- func NewXComReader(pg *Postgres, backend xcom.Backend) *XComReader
- func (r *XComReader) GetXCom(ctx context.Context, tenant, dagID, runID, taskID, key string) (xcom.Entry, error)
- func (r *XComReader) ListXComEntries(ctx context.Context, tenant, dagID, runID, taskID string) ([]domain.XComEntryMeta, error)
func AttachRedisObservability
func AttachRedisObservability(ctx context.Context, r *Redis, m RedisMetrics, interval time.Duration) func()
AttachRedisObservability registers the metrics hook and starts a goroutine that scrapes the pool stats every interval. The returned function cancels the scraper goroutine; the caller defers it (typically the datastore cleanup chain). Lite (Redis nil) is a no-op.
The scraper is a closure so the “last seen cumulative timeouts” counter (used to compute per-scrape DELTAS for the Prometheus counter, since go-redis exposes Timeouts as a cumulative value) is goroutine-local — no shared mutable state.
func NewLeaderPool
func NewLeaderPool(ctx context.Context, cfg config.DatabaseSection) (*pgxpool.Pool, error)
NewLeaderPool opens a dedicated single-connection pool for the scheduler advisory lock, so the session holding the lock is stable (ADR 0009).
type ExecutionStore
ExecutionStore resolves task execution context from Postgres. It implements both agentrpc.Store (serving the in-pod agent) and dispatch.Resolver (feeding the pod-path dispatcher) over the same dag_version spec.
type ExecutionStore struct {
// contains filtered or unexported fields
}
func NewExecutionStore
func NewExecutionStore(pg *Postgres) *ExecutionStore
NewExecutionStore builds an ExecutionStore over the given Postgres connection.
func (*ExecutionStore) BindWarmAttempt
func (s *ExecutionStore) BindWarmAttempt(ctx context.Context, runID, taskID string, tryNumber int, workerPod string) error
BindWarmAttempt records the durable warm-attempt binding (ADR 0058 N1d-a1): the warm worker pod (workerPod, its own downward-API pod name) that acked this attempt as started, stamped onto warm_worker_id so a later failover reaper can tell which running attempts a dead warm pod held. The UPDATE is guarded on state IN (‘queued’, ‘running’), so a settled attempt is never bound — an ack that races a reaper settling the row is a benign no-op (zero rows), not an error. It is written ONLY on a warm ack; a dedicated-pod attempt (and every attempt while warm pools are off) leaves warm_worker_id NULL.
N1d-a2 deferral: warm_worker_id is intentionally NOT cleared when the attempt settles. The consuming reaper filters on state, so a lingering value on a terminal TI is harmless; a settle-time clear is left to that increment.
func (*ExecutionStore) FailTask
func (s *ExecutionStore) FailTask(ctx context.Context, taskInstanceID string, tryNumber int, reason string) error
FailTask marks a task instance failed by its ID, guarded by the attempt (try_number) and the active states so it never clobbers a different attempt or a terminal row. It implements part of executor.OutcomeReporter for the pod reconciler (ADR 0052).
func (*ExecutionStore) IsTaskInstanceLive
func (s *ExecutionStore) IsTaskInstanceLive(ctx context.Context, runID, taskID string, tryNumber int) (bool, error)
IsTaskInstanceLive reports whether the attempt (runID, taskID, tryNumber) is still live — present and in an active (non-terminal) state — derived from the same predicate RecordHeartbeat writes on, but as a pure read with no side-effect (ADR 0055). It is the read-only revocation signal the secret path consults: a terminal, superseded (try_number moved on), or reaped attempt is not live, so its token stops resolving secrets even while the signature holds.
It derives ONLY from (run, task, try) + active state, exactly as the heartbeat predicate does. It must never gain a run-recency / logical_date clause: a recency term would deny a legitimate clear-and-rerun of an old run, binding credential lifetime to the run’s age rather than to the attempt.
func (*ExecutionStore) RecordHeartbeat
func (s *ExecutionStore) RecordHeartbeat(ctx context.Context, id auth.AgentIdentity) error
RecordHeartbeat stamps last_heartbeat_at on the agent’s TI so the scheduler’s heartbeat reaper (#128) can tell a live task from one whose agent has gone silent. The SQL guard skips terminal rows — a late heartbeat after a terminal report is a no-op, not a regression.
func (*ExecutionStore) ReportState
func (s *ExecutionStore) ReportState(ctx context.Context, id auth.AgentIdentity, state domain.TaskState, exitCode int, errMsg string) error
ReportState records a state transition reported by the agent, persisting the exit code and error message and stamping started/ended/duration timestamps.
func (*ExecutionStore) RequeueForRedispatch
func (s *ExecutionStore) RequeueForRedispatch(ctx context.Context, runID, taskID string, tryNumber int) error
RequeueForRedispatch re-places a reclaimed warm assignment (ADR 0058 N1d-c, H2): an attempt that a warm worker was handed but demonstrably will not run (its stream ended holding an unacked lease, or it acked started=false) sits `queued` having never run. This moves it back to `scheduled` so the planner re-admits and re-dispatches it, rather than leaving it stuck until the 3-minute dispatch-lost reaper. Guarded to state=‘queued’ and bounded to the exact attempt (runID, taskID, tryNumber): zero rows is the guard working (the TI is running or settled, or the attempt moved on), a benign no-op, not an error. It bumps neither try_number nor infra_attempts — the attempt never ran, this is a re-offer of the same attempt.
func (*ExecutionStore) Reschedule
func (s *ExecutionStore) Reschedule(ctx context.Context, id auth.AgentIdentity, at time.Time) error
Reschedule parks an active task instance in up_for_reschedule with its next-poke time, so the scheduler re-dispatches it once reschedule_at passes (#380). Used by the agent’s reschedule path; a no-op if the TI is no longer active (terminal).
func (*ExecutionStore) RescheduleTask
func (s *ExecutionStore) RescheduleTask(ctx context.Context, taskInstanceID string, tryNumber int, at time.Time) error
RescheduleTask parks a task instance in up_for_reschedule with the recovered next-poke time, guarded by the attempt and the active states, consuming no retry budget (ADR 0052). Used by the reconciler when a reschedule report was lost.
func (*ExecutionStore) ResolveTask
func (s *ExecutionStore) ResolveTask(ctx context.Context, runID, taskID string) (dispatch.Resolved, error)
ResolveTask returns the dispatcher’s execution context for a run’s task.
func (*ExecutionStore) SucceedTask
func (s *ExecutionStore) SucceedTask(ctx context.Context, taskInstanceID string, tryNumber int) error
SucceedTask marks a task instance succeeded by its ID — recovering a success whose report was lost (ADR 0052) — guarded by the attempt and the active states. A settle on an already-terminal or superseded row is a no-op.
func (*ExecutionStore) TaskSpec
func (s *ExecutionStore) TaskSpec(ctx context.Context, id auth.AgentIdentity) (agentrpc.TaskSpec, error)
TaskSpec returns the agent-facing execution spec for a task instance.
type LogReader
LogReader resolves a task attempt’s log location from API-facing identifiers and reads it from the log sink, and tails its live lines.
type LogReader struct {
// contains filtered or unexported fields
}
func NewLogReader
func NewLogReader(pg *Postgres, sink logs.Sink, tailer logs.Tailer) *LogReader
NewLogReader builds a LogReader over the given Postgres connection, sink, and live-tail tailer (tailer may be nil to disable following).
func (*LogReader) ReadLogs
func (r *LogReader) ReadLogs(ctx context.Context, tenant, dagID, runID, taskID string, tryNumber int) (io.ReadCloser, error)
ReadLogs resolves the run reference (tenant name -> id, run_id -> dag_run id), then opens the stored log for the task attempt. It returns domain.ErrNotFound when the run or its log file is absent. See issue #21 for the resolution cost.
func (*LogReader) Tail
func (r *LogReader) Tail(ctx context.Context, tenant, dagID, runID, taskID string, tryNumber int) (lines <-chan string, cancel func(), err error)
Tail subscribes to the task attempt’s live log lines, returning a line channel and a cancel function. It resolves the run reference the same way ReadLogs does so the channel matches what the agent publishes.
type Postgres
Postgres holds a pgx connection pool and the generated query set.
type Postgres struct {
Pool *pgxpool.Pool
Queries *queries.Queries
// contains filtered or unexported fields
}
func NewPostgres
func NewPostgres(ctx context.Context, cfg config.DatabaseSection) (*Postgres, error)
NewPostgres opens a connection pool and verifies connectivity, retrying transient failures during boot for up to pgStartupBudget. Pre-2026-06, the first failed ping fatal-ed the server, so a docker compose race or any Pro failover blip became a hard crash. The retry loop keeps Lite boot ergonomic and Pro startup resilient under realistic upstream-PG dynamics. A truly broken setup (wrong DSN, bad auth) still surfaces quickly because the underlying error is wrapped into the final error.
func (*Postgres) Close
func (p *Postgres) Close()
Close releases the connection pool.
func (*Postgres) Ping
func (p *Postgres) Ping(ctx context.Context) error
Ping checks database connectivity (used by /readyz).
type Redis
Redis wraps a go-redis client used for XCom and locks.
type Redis struct {
Client *redis.Client
}
func NewRedis
func NewRedis(ctx context.Context, cfg config.RedisSection) (*Redis, error)
NewRedis connects to Redis and verifies connectivity.
func (*Redis) Close
func (r *Redis) Close() error
Close releases the Redis client.
func (*Redis) Ping
func (r *Redis) Ping(ctx context.Context) error
Ping checks Redis connectivity (used by /readyz).
type RedisMetrics
RedisMetrics is the subset of observability.Metrics the redis hook + pool scraper need. Declared as a local interface so internal/storage doesn’t import internal/observability (cycle-avoidance, also lets tests inject a fake without standing up a Prometheus registry).
type RedisMetrics interface {
RecordRedisCommandFailure(reason string)
RecordRedisDialFailure(reason string)
ObserveRedisDialDuration(d time.Duration)
UpdateRedisPoolStats(active, idle, total uint32)
RecordRedisPoolTimeout()
}
type Repository
Repository implements the API resource and auth user-store interfaces over Postgres using the sqlc-generated query set.
type Repository struct {
// contains filtered or unexported fields
}
func NewRepository
func NewRepository(pg *Postgres) *Repository
NewRepository builds a Repository backed by the given Postgres connection.
func (*Repository) AddFavorite
func (r *Repository) AddFavorite(ctx context.Context, tenant, userID, dagID string) error
AddFavorite marks a DAG as a favorite for the user (idempotent).
func (*Repository) AlertEndpoint
func (r *Repository) AlertEndpoint(ctx context.Context, tenantID, connID string) (endpointURL string, headers map[string]string, err error)
AlertEndpoint resolves an alert channel connection (#424) to its endpoint for a tenant UUID: the decrypted password is the channel URL (the full webhook URL, kept encrypted at rest), and an optional `headers` object in the connection’s extra becomes request headers (e.g. an Authorization header for an endpoint whose token is not in the URL). An absent/empty URL is an error — a misconfigured alert connection must fail loud. Never expose these in UI/API.
func (*Repository) BootstrapAdmin
func (r *Repository) BootstrapAdmin(ctx context.Context, tenant, email, password string) (bool, error)
BootstrapAdmin creates a default admin user with the given password when the tenant has no users yet, assigning the seeded admin role. It returns whether a user was created (false when users already exist).
func (*Repository) BootstrapAdminHash
func (r *Repository) BootstrapAdminHash(ctx context.Context, tenant, email, hash string) (bool, error)
BootstrapAdminHash provisions the Lite admin from a precomputed bcrypt hash (so the plaintext never reaches the control plane). It RECONCILES: if the admin already exists, its password is reset to this hash. The Lite config (admin_password_hash) is the source of truth, so the password the setup printed always logs in — even against a pre-existing or stale database — without anyone having to wipe Docker volumes. The only sanctioned way to change the password, `reset-password`, also writes the config, so the two never drift. Returns true only when the admin was newly created (false when an existing one was reconciled). See cmd/leoflow-server bootstrapAdmin.
func (*Repository) ClearDagHistory
func (r *Repository) ClearDagHistory(ctx context.Context, tenant, dagID string) error
ClearDagHistory deletes a DAG’s runs (cascading task instances and XCom index rows) while keeping the DAG and its versions registered — the safe “clear” the UI trash maps to (ADR 0020). Returns ErrNotFound when the DAG is absent.
func (*Repository) ClearImportError
func (r *Repository) ClearImportError(ctx context.Context, tenant, filename string) error
ClearImportError removes any recorded error for a file (a good re-import).
func (*Repository) ClearTaskInstances
func (r *Repository) ClearTaskInstances(ctx context.Context, tenant, dagID, runID string, taskIDs []string, onlyFailed, resetDagRun bool) (int, error)
ClearTaskInstances resets tasks to none for re-run, optionally resetting the parent run to queued. When onlyFailed is true, only tasks currently in a failed-ish state (failed, upstream_failed, up_for_retry) are reset; with an empty taskIDs and onlyFailed, every failed task in the run is cleared. It returns the number of task instances actually reset.
func (*Repository) CreateDagRun
func (r *Repository) CreateDagRun(ctx context.Context, tenant, dagID string, run domain.DagRun) (domain.DagRun, error)
CreateDagRun inserts a new run for a DAG at its current version. The per-DAG max_active_runs cap (#200) is enforced here for any caller that goes through the repository — manual triggers via the API, scripted backfills, and any future programmatic trigger path — so the contract is honored in one place. A cap of zero is treated as “unlimited” to match the scheduler path (see `Scheduler.hasHeadroom`). The check races with concurrent inserts, but the small overshoot window is bounded by the number of concurrent writers and lets us avoid an advisory lock on the hot path.
func (*Repository) CreateOIDCUser
func (r *Repository) CreateOIDCUser(ctx context.Context, tenant, email, provider, subject string, roles []string) (*auth.User, error)
CreateOIDCUser just-in-time provisions an OIDC-only account (NULL password), linked by (oidc_provider, oidc_subject), and grants it the given roles. It mirrors CreateUser’s atomicity: every role is resolved BEFORE the insert so an unknown role fails cleanly as domain.ErrValidation without leaving an orphaned account, and the insert plus the grants run in one transaction so a failed grant rolls the account back. An empty role set grants none (default-deny). A concurrent double-provision surfaces as domain.ErrConflict via the unique (oidc_provider, oidc_subject) constraint. The returned user carries the granted role names so the caller can mint a token without a reload.
func (*Repository) CreateUser
func (r *Repository) CreateUser(ctx context.Context, tenant, email, password string, roles []string) (domain.User, error)
CreateUser provisions a new account in the tenant and grants it the given set of roles, returning the created user (never its password or hash). It reuses the same bcrypt hashing as the bootstrap admin path (auth.HashPassword) and the same email uniqueness guarantee, so a duplicate email surfaces as domain.ErrConflict (the API maps it to 409). Every role is resolved BEFORE the insert so an unknown role fails cleanly as domain.ErrValidation without leaving an orphaned account; an empty set grants none — the most restrictive default, leaving the user with no permissions until an admin grants a role.
The insert and the role grants run in a single transaction, so a failure in any grant rolls the user insert back. Without that atomicity a failed grant would leave an account the (tenant_id, email) UNIQUE makes impossible to recreate — every retry would 409 forever with no recovery path.
This backs `leoflow auth create-user` (ADR 0008) and is purely additive: it does not touch the bootstrap/reconcile path.
func (*Repository) DagStats
func (r *Repository) DagStats(ctx context.Context, tenant string) (domain.DagStats, error)
DagStats returns the home dashboard’s DAG counters: the total active DAG count plus how many DAGs have a latest run in the failed/running/queued state.
func (*Repository) DeleteConnection
func (r *Repository) DeleteConnection(ctx context.Context, tenant, connID string) error
DeleteConnection removes a connection, returning ErrNotFound when none matched.
func (*Repository) DeleteDag
func (r *Repository) DeleteDag(ctx context.Context, tenant, dagID string) error
DeleteDag removes a DAG and (via ON DELETE CASCADE) its versions, runs, task instances, and XCom index rows. It returns ErrNotFound when no DAG matched.
func (*Repository) DeleteDagRun
func (r *Repository) DeleteDagRun(ctx context.Context, tenant, dagID, runID string) error
DeleteDagRun removes one run (and, by cascade, its task instances and XCom). It returns domain.ErrNotFound when no run with that id exists for the DAG, so the API can answer 404 rather than a silent 204 for a bad id.
func (*Repository) DeletePool
func (r *Repository) DeletePool(ctx context.Context, tenant, name string) error
DeletePool removes a pool. It returns domain.ErrNotFound when none matched and domain.ErrConflict when the target is the implicit default pool — Airflow parity: the fallback pool the gate resolves to must never be deleted.
func (*Repository) DeleteVariable
func (r *Repository) DeleteVariable(ctx context.Context, tenant, key string) error
DeleteVariable removes a variable, returning ErrNotFound when none matched.
func (*Repository) FavoriteDagIDs
func (r *Repository) FavoriteDagIDs(ctx context.Context, tenant, userID string) (map[string]bool, error)
FavoriteDagIDs returns the set of DAG ids the user has favorited.
func (*Repository) FindUserByID
func (r *Repository) FindUserByID(ctx context.Context, id string) (*auth.User, bool, error)
FindUserByID reloads a user’s current authorization state by id: its tenant, roles, and permissions, plus whether the account is active. It is the per- request source of truth the authenticator uses on token validation. A subject that is not a valid uuid, or that matches no row, yields auth.ErrUserNotFound (the trusted in-process minting path has no backing user); any other failure is returned as-is so the caller can fail closed.
func (*Repository) FindUserByLogin
func (r *Repository) FindUserByLogin(ctx context.Context, tenant, username string) (*auth.User, string, error)
FindUserByLogin loads a user and its bcrypt hash for authentication.
func (*Repository) FindUserByOIDCSubject
func (r *Repository) FindUserByOIDCSubject(ctx context.Context, provider, subject string) (*auth.User, bool, error)
FindUserByOIDCSubject resolves an OIDC identity to a Leoflow user by its immutable (provider, subject) pair — the trusted link key for a returning SSO login. Like FindUserByID it loads the current tenant, roles, and permissions plus the active flag, so the caller reconstructs the same principal the credential path would. A pair matching no row yields auth.ErrUserNotFound (the signal to consider just-in-time provisioning); any other failure is returned as-is so the caller can fail closed.
func (*Repository) GetConnection
func (r *Repository) GetConnection(ctx context.Context, tenant, connID string) (domain.Connection, error)
GetConnection returns a connection with extra decrypted; the password is not returned (write-only). Returns ErrNotFound when absent.
func (*Repository) GetCurrentSpec
func (r *Repository) GetCurrentSpec(ctx context.Context, tenant, dagID string) (domain.DAGSpec, error)
GetCurrentSpec returns the parsed spec of the DAG’s current version, or domain.ErrNotFound if the DAG or its current version does not exist.
func (*Repository) GetDag
func (r *Repository) GetDag(ctx context.Context, tenant, dagID string) (domain.DAG, error)
GetDag returns a single DAG by its user-facing id.
func (*Repository) GetDagRun
func (r *Repository) GetDagRun(ctx context.Context, tenant, dagID, runID string) (domain.DagRun, error)
GetDagRun returns a single run by its run id.
func (*Repository) GetPool
func (r *Repository) GetPool(ctx context.Context, tenant, name string) (domain.Pool, error)
GetPool returns one pool by name, or domain.ErrNotFound.
func (*Repository) GetVariable
func (r *Repository) GetVariable(ctx context.Context, tenant, key string) (domain.Variable, error)
GetVariable returns one variable by key, or ErrNotFound.
func (*Repository) HistoricalMetrics
func (r *Repository) HistoricalMetrics(ctx context.Context, tenant string, since, until time.Time) (domain.HistoricalMetrics, error)
HistoricalMetrics returns run- and task-instance state counts for runs whose logical date falls within [since, until], keyed by Leoflow state name.
func (*Repository) LatestRunsForDags
func (r *Repository) LatestRunsForDags(ctx context.Context, tenant string, dagIDs []string, perDag int) (map[string][]domain.DagRun, error)
LatestRunsForDags returns up to perDag most-recent runs for each named DAG, keyed by dag_id, in a single windowed query (no per-DAG round trips).
func (*Repository) ListAuditLogs
func (r *Repository) ListAuditLogs(ctx context.Context, tenant, dagID string, limit, offset int) ([]domain.AuditLogEntry, int, error)
ListAuditLogs returns a page of audit-log entries for the tenant, newest first, optionally filtered to a single DAG (dagID == "" means no filter).
func (*Repository) ListConnections
func (r *Repository) ListConnections(ctx context.Context, tenant string, limit, offset int) ([]domain.Connection, int, error)
ListConnections returns a page of connections (no passwords) and the total.
func (*Repository) ListDagRuns
func (r *Repository) ListDagRuns(ctx context.Context, tenant, dagID string, limit, offset int) ([]domain.DagRun, int, error)
ListDagRuns returns a page of runs for a DAG and the total count.
func (*Repository) ListDagVersions
func (r *Repository) ListDagVersions(ctx context.Context, tenant, dagID string) ([]domain.DagVersion, error)
ListDagVersions returns the DAG’s versions, newest first, with a 1-based version_number the UI uses to query version-scoped structure.
func (*Repository) ListDags
func (r *Repository) ListDags(ctx context.Context, tenant string, limit, offset int) ([]domain.DAG, int, error)
ListDags returns a page of DAGs for the tenant and the total count.
func (*Repository) ListDagsFiltered
func (r *Repository) ListDagsFiltered(ctx context.Context, tenant, runState string, paused *bool, limit, offset int) ([]domain.DAG, int, error)
ListDagsFiltered returns a page of active DAGs for the tenant, optionally filtered by paused state and/or latest-run state, with the matching total. An empty runState or nil paused disables that filter.
func (*Repository) ListImportErrors
func (r *Repository) ListImportErrors(ctx context.Context, tenant string) ([]domain.ImportError, error)
ListImportErrors returns the tenant’s DAG parse/compile errors, newest first.
func (*Repository) ListPools
func (r *Repository) ListPools(ctx context.Context, tenant string, limit, offset int) ([]domain.Pool, int, error)
ListPools returns a page of the tenant’s named pools and the total count.
func (*Repository) ListTaskInstanceAttempts
func (r *Repository) ListTaskInstanceAttempts(ctx context.Context, tenant, dagID, runID, taskID string) ([]domain.TaskInstance, error)
ListTaskInstanceAttempts returns every attempt for (run, task), oldest first — the current task_instances row UNIONed with all archived task_instance_history rows. The UI’s /tries endpoint needs this to render one navigable tab per attempt; without history, a cleared task shows only the latest attempt and the user cannot inspect prior failures (Lima bug #241).
func (*Repository) ListTaskInstances
func (r *Repository) ListTaskInstances(ctx context.Context, tenant, dagID, runID string, _, _ int) ([]domain.TaskInstance, int, error)
ListTaskInstances returns the task instances of a run.
func (*Repository) ListUsers
func (r *Repository) ListUsers(ctx context.Context, tenant string, limit, offset int) ([]domain.User, int, error)
ListUsers returns a page of the tenant’s accounts, newest first, each with the full set of role names it holds. It never reads or returns password_hash — the list must not expose secrets. The second result is the unpaged total, so the caller can render total_entries independent of the page size.
func (*Repository) ListVariables
func (r *Repository) ListVariables(ctx context.Context, tenant string, limit, offset int) ([]domain.Variable, int, error)
ListVariables returns a page of variables for the tenant and the total count.
func (*Repository) PoolSlotUsage
func (r *Repository) PoolSlotUsage(ctx context.Context, tenant string) (map[string]domain.PoolUsage, error)
PoolSlotUsage returns per-pool occupancy for the tenant, keyed by pool name (a task instance with no pool is counted under the implicit default_pool). It feeds the Airflow PoolResponse occupancy fields.
func (*Repository) ReconcileUserRoles
func (r *Repository) ReconcileUserRoles(ctx context.Context, userID string, roleNames []string) error
ReconcileUserRoles makes the user’s granted roles exactly roleNames, atomically: it is how the identity provider stays authoritative over an OIDC user’s roles. On each OIDC login the caller passes the group-mapped role set, and this sets the DB user_roles to precisely that set, so a demotion or deprovisioning at the IdP takes effect on the next login and the per-request authz reload sees it.
Every name is resolved to a role id in the user’s OWN tenant BEFORE any write, so a name that is not a role in that tenant fails closed as domain.ErrValidation with the prior grants untouched — the login path turns that into a rejected, audited login rather than silently wiping the user’s roles. The delete and the inserts run in one transaction, making the operation idempotent (reconciling the same set yields the same rows) and an empty roleNames a full clear (default-deny).
func (*Repository) RecordAuthEvent
func (r *Repository) RecordAuthEvent(ctx context.Context, tenant, actorUserID, action, email, outcome string, extra map[string]string) error
RecordAuthEvent records an authentication event to the audit log (H5): OIDC login success/failure, tenant-pin rejection, JIT provisioning, break-glass login, and logout. It NEVER records tokens or the client secret — only the actor’s email, the resolved tenant (best-effort), the outcome, and small non-secret detail fields (e.g. the rejection reason, the attempted tenant claim). It is best-effort: the caller logs and continues on error so a flaky audit sink never turns a security decision (a 403 rejection, a successful login) into a 5xx.
The event is scoped to the resolved tenant when known; events that never resolved a tenant (a login failure, a tenant-pin rejection) fall back to the “default” tenant so the row still lands, with the attempted values in the metadata. resourceID carries the email so account-scoped auth activity is filterable alongside user.create.
func (*Repository) RecordSecretLivenessDenial
func (r *Repository) RecordSecretLivenessDenial(ctx context.Context, tenantID, dagID, runID, taskID string, tryNumber int, kind, mode string) error
RecordSecretLivenessDenial records that the secret-path liveness gate fired for a task instance whose attempt is no longer live (ADR 0055): a would-have-denied in observe mode, or a denial in enforce mode. It is scoped to the DAG resource so the event surfaces on the DAG’s Audit Log tab, with the kind (“variables” or “connections”), the run, task, attempt, and the gate mode in metadata. It records identity + kind + mode only — never secret names or values.
tenantID is the tenant UUID the agent token carries (not the tenant name): the caller is the agent RPC path, which passes AgentIdentity.TenantID. Resolving it by name silently dropped every row (#722) — including enforce-mode security denials — so mirror the secret-delivery path and parse it as a UUID.
func (*Repository) RecordSecretScopeWarning
func (r *Repository) RecordSecretScopeWarning(ctx context.Context, tenantID, dagID, runID, taskID, kind string, declared, total int) error
RecordSecretScopeWarning records that a task received the full tenant secret set while it declared only a narrower subset (ADR 0045, ADR 0055): under secret_scoping: enforce it would receive only its declared set. It is scoped to the DAG resource so the event surfaces on the DAG’s Audit Log tab, with the kind (“variables” or “connections”), the run and task, and the declared/total counts in metadata. It records counts only — never secret names or values.
tenantID is the tenant UUID the agent token carries (not the tenant name): the caller is the agent RPC path, which passes AgentIdentity.TenantID. Resolving it by name silently dropped every row (#722), so mirror the secret-delivery path and parse it as a UUID.
func (*Repository) RecordTaskActionAudit
func (r *Repository) RecordTaskActionAudit(ctx context.Context, tenant, userID, action, dagID, runID, taskID string, tryNumber int) error
RecordTaskActionAudit logs a task-level action (clear, mark state) with the acting user and the run/task/try in metadata, so the Audit Log view shows the owner and the task columns. Scoped to the DAG (resource_id = dag_id) so it appears on the DAG’s Audit Log tab.
func (*Repository) RecordUserCreatedAudit
func (r *Repository) RecordUserCreatedAudit(ctx context.Context, tenant, actorUserID, createdUserID, email, roles string) error
RecordUserCreatedAudit logs an account creation with the acting admin as the owner and the new account’s email and granted roles in metadata, scoped to the “user” resource so account-management actions are visible in the Audit Log. The roles arrive as a single comma-joined string (empty when none were granted).
func (*Repository) RegisterDagVersion
func (r *Repository) RegisterDagVersion(ctx context.Context, tenant string, spec domain.DAGSpec, specHash string) (bool, error)
RegisterDagVersion upserts the DAG and inserts a version keyed by specHash, setting it as current. It is idempotent: an existing hash yields created=false.
func (*Repository) RemoveFavorite
func (r *Repository) RemoveFavorite(ctx context.Context, tenant, userID, dagID string) error
RemoveFavorite clears a DAG’s favorite mark for the user (idempotent).
func (*Repository) RoleExists
func (r *Repository) RoleExists(ctx context.Context, tenant, role string) (bool, error)
RoleExists reports whether a role name exists for the tenant. The OIDC login path uses it to fail closed on a misconfigured default_role before minting a token for a returning user (the JIT path validates roles inside CreateOIDCUser).
func (*Repository) SecretConnectionURIs
func (r *Repository) SecretConnectionURIs(ctx context.Context, tenantID string) (map[string]string, error)
SecretConnectionURIs returns the tenant’s connections as conn_id→Airflow URI (password decrypted), for delivering to task pods (ADR 0021). The agent exports them as AIRFLOW_CONN_<CONN_ID>. tenantID is the tenant UUID carried by the agent token. Never expose these in UI/API responses.
func (*Repository) SecretConnectionURIsScoped
func (r *Repository) SecretConnectionURIsScoped(ctx context.Context, tenantID string, names []string) (map[string]string, error)
SecretConnectionURIsScoped returns only the named subset of the tenant’s connections as Airflow URIs (password decrypted), filtered in the query (ADR 0055 D1). It backs secret_scoping: enforce. An empty name set returns nothing without a query. Never expose these in UI/API responses. It shares the per-connection decrypt-and-skip-on-failure semantics of SecretConnectionURIs: one undecryptable connection is skipped with a warning, never blinding the rest of the declared set.
func (*Repository) SecretVariables
func (r *Repository) SecretVariables(ctx context.Context, tenantID string) (map[string]string, error)
SecretVariables returns the tenant’s variables as key→value, for delivering to task pods (ADR 0021). The agent exports them as AIRFLOW_VAR_<KEY>. tenantID is the tenant UUID carried by the agent token (not the tenant name).
func (*Repository) SecretVariablesScoped
func (r *Repository) SecretVariablesScoped(ctx context.Context, tenantID string, names []string) (map[string]string, error)
SecretVariablesScoped returns only the named subset of the tenant’s variables, filtered in the query (ADR 0055 D1: scope in the SQL, never post-filter the decrypted whole vault in the handler). It backs secret_scoping: enforce, where a task receives only the Variables it declared. An empty name set returns nothing without a query — enforce’s load-bearing [] case. tenantID is the tenant UUID carried by the agent token.
func (*Repository) SetCipher
func (r *Repository) SetCipher(c secrets.Cipher)
SetCipher attaches the encryption cipher used for connection secrets (ADR 0019). Without it, connection writes fail rather than storing plaintext.
func (*Repository) SetConnection
func (r *Repository) SetConnection(ctx context.Context, tenant string, c domain.Connection) error
SetConnection creates or updates a connection, encrypting password and extra at rest. It fails if no encryption cipher is configured (never stores a credential in plaintext — ADR 0019).
func (*Repository) SetDagRunState
func (r *Repository) SetDagRunState(ctx context.Context, tenant, dagID, runID, state string) error
SetDagRunState sets a DAG run’s state directly, backing the UI’s mark run success/failed actions. Terminal states stamp ended_at; re-opening to a non-terminal state clears it. started_at is preserved.
func (*Repository) SetImportError
func (r *Repository) SetImportError(ctx context.Context, tenant string, e domain.ImportError) error
SetImportError records (or replaces) the parse/compile error for a file.
func (*Repository) SetPaused
func (r *Repository) SetPaused(ctx context.Context, tenant, dagID string, paused bool) (domain.DAG, error)
SetPaused toggles the paused flag of a DAG.
func (*Repository) SetPool
func (r *Repository) SetPool(ctx context.Context, tenant string, p domain.Pool) error
SetPool creates or updates a pool (its slot cap and description). The is_default flag is not writable through this path — only the seed migration marks the implicit default pool.
func (*Repository) SetTaskInstanceState
func (r *Repository) SetTaskInstanceState(ctx context.Context, tenant, dagID, runID, taskID, state string) error
SetTaskInstanceState sets a task instance’s state directly, backing the UI’s “mark success”/“mark failed” actions. It does not run the task.
func (*Repository) SetUserPassword
func (r *Repository) SetUserPassword(ctx context.Context, tenant, email, hash string) (bool, error)
SetUserPassword sets a user’s bcrypt hash by email, returning whether a user was updated (false when no such user exists). Used by `leoflow lite reset-password`.
func (*Repository) SetVariable
func (r *Repository) SetVariable(ctx context.Context, tenant string, v domain.Variable) error
SetVariable creates or updates a variable.
func (*Repository) TaskInstancesForRuns
func (r *Repository) TaskInstancesForRuns(ctx context.Context, tenant, dagID string, runIDs []string) ([]domain.TaskInstance, error)
TaskInstancesForRuns returns the task instances of the given runs of a DAG in one query, ordered by run_id, task_id, try_number, for the grid summaries.
func (*Repository) TenantUUID
func (r *Repository) TenantUUID(ctx context.Context, name string) (string, error)
TenantUUID resolves a tenant name to its UUID string — the form the agent token carries and that the secret-delivery methods expect.
type SchedulerStore
SchedulerStore is the sqlc-backed implementation of scheduler.Store.
type SchedulerStore struct {
// contains filtered or unexported fields
}
func NewSchedulerStore
func NewSchedulerStore(pg *Postgres) *SchedulerStore
NewSchedulerStore builds a SchedulerStore over the given Postgres connection.
func (*SchedulerStore) ActiveRuns
func (s *SchedulerStore) ActiveRuns(ctx context.Context) ([]scheduler.RunState, error)
ActiveRuns loads every active dag run and projects it into the scheduler’s RunState (topology + per-task state), the read side of a scheduler tick.
func (*SchedulerStore) ActiveWarmTargets
func (s *SchedulerStore) ActiveWarmTargets(ctx context.Context) ([]executor.WarmTarget, error)
ActiveWarmTargets returns one warm target per distinct active dag_version with its effective warm-worker count (ADR 0058 N1b2b), the read side of a warm-pool reconcile tick. It reuses the same active-runs + cached-spec path as ActiveRuns: the spec is immutable per dag_version_id, so N active runs sharing a version decode it once and the effective target is derived from the DAG author’s min_idle_workers under the operator’s clamp/fallback. It implements executor.WarmTargetSource without the executor importing storage.
func (*SchedulerStore) ApplyTransition
func (s *SchedulerStore) ApplyTransition(ctx context.Context, runID, taskID string, to domain.TaskState) error
ApplyTransition moves a task instance to a new state.
func (*SchedulerStore) ApplyTransitions
func (s *SchedulerStore) ApplyTransitions(ctx context.Context, runID string, taskIDs []string, to domain.TaskState) error
ApplyTransitions moves every listed task of a run to the SAME target state in one UPDATE, the batched equivalent of calling ApplyTransition once per task. The scheduler groups a tick’s plain state-set transitions by target state and flushes each group here, collapsing R updates into one per distinct state. The per-row stamping is identical to the single-row query, so the result is byte-identical — only the statement count drops. An empty list is a no-op.
func (*SchedulerStore) ClaimAlertAttempt
func (s *SchedulerStore) ClaimAlertAttempt(ctx context.Context, runID string, maxAttempts int, backoff time.Duration) (int, error)
ClaimAlertAttempt atomically claims one on-failure send attempt for a run (#431). The UPDATE consumes an attempt and sets the next-attempt time, but only while the episode is undelivered, within budget, and past its backoff — see the query for why each predicate exists. pgx.ErrNoRows means the claim was refused on one of those grounds: not an error, just a lost claim, so report won=false.
Claiming an attempt is NOT the same as recording delivery; that is MarkRunAlertDelivered. Conflating the two is what made a failed send a permanently lost page. Returns the attempt number won (0 when the claim was refused), which the caller passes back to MarkRunAlertDelivered so a superseded send cannot stamp.
func (*SchedulerStore) CreateScheduledRun
func (s *SchedulerStore) CreateScheduledRun(ctx context.Context, dagID string, logical time.Time) error
CreateScheduledRun inserts a scheduled run for a DAG (idempotent on run_id).
func (*SchedulerStore) FailDispatchExhausted
func (s *SchedulerStore) FailDispatchExhausted(ctx context.Context, runID, taskID, reason string) error
FailDispatchExhausted fails a scheduled task as dispatch_failed once its dispatch-attempt budget is spent (ADR 0031 Amendment A).
func (*SchedulerStore) ListActiveStagingVolumes
func (s *SchedulerStore) ListActiveStagingVolumes(ctx context.Context) ([]domain.StagingVolumeState, error)
ListActiveStagingVolumes returns active staging volumes joined with their DAG run’s state (empty when the run row is gone), for the GC (ADR 0022).
func (*SchedulerStore) ListAgentLostCandidates
func (s *SchedulerStore) ListAgentLostCandidates(ctx context.Context) ([]executor.AgentLostCandidate, error)
ListAgentLostCandidates returns every `running` TI with a non-null last_heartbeat_at, for the scheduler’s TI heartbeat reaper (#128). The reaper applies the threshold per row so the SQL stays simple.
func (*SchedulerStore) ListBusyWarmWorkerPods
func (s *SchedulerStore) ListBusyWarmWorkerPods(ctx context.Context) (map[string]bool, error)
ListBusyWarmWorkerPods returns the set of warm-worker pod names currently serving a `running` attempt (ADR 0058 N1d-b): a warm worker is BUSY iff some `running` task_instance is durably bound to it (warm_worker_id = the pod’s own name). The busy-aware warm-pool reconciler reads this once per tick to classify each live warm pod as busy or idle, so scale-down/drain deletes only IDLE workers and never kills an in-flight attempt (review findings M1/M2). With warm pools off no TI is ever bound, so the set is always empty and every worker classifies as idle — byte-for-byte today. Implements executor.BusyWarmWorkerSource without the executor importing storage.
func (*SchedulerStore) ListReapCandidates
func (s *SchedulerStore) ListReapCandidates(ctx context.Context) ([]executor.ReapCandidate, error)
ListReapCandidates returns every dag_run currently in ‘running’ state with the timestamp of its most recent activity, for the scheduler’s orphan reaper. The query (sqlc.runs.ListOrphanCandidates) is the authority on how to compute the timestamp; the reaper only decides whether each one is past its threshold.
func (*SchedulerStore) ListRunningTasks
func (s *SchedulerStore) ListRunningTasks(ctx context.Context) ([]executor.PodLostCandidate, error)
ListRunningTasks returns every `running` TI with the timestamp it entered running, for the pod-lost reaper (#527). The reaper applies the grace period and the pod-liveness check per row, so the SQL stays simple.
func (*SchedulerStore) ListStaleQueuedCandidates
func (s *SchedulerStore) ListStaleQueuedCandidates(ctx context.Context) ([]executor.StaleQueuedCandidate, error)
ListStaleQueuedCandidates returns every `queued` TI with its queued_at, for the dispatch-lost reaper (#202). The reaper applies the threshold per row so the SQL stays simple.
func (*SchedulerStore) ListWarmBoundRunningTIs
func (s *SchedulerStore) ListWarmBoundRunningTIs(ctx context.Context) ([]executor.WarmBoundTI, error)
ListWarmBoundRunningTIs returns every `running` TI durably bound to a warm worker (warm_worker_id IS NOT NULL), for the warm-worker-lost reaper (ADR 0058 N1d-a2). With warm pools off no TI is ever bound, so this is always empty and the reaper is inert.
func (*SchedulerStore) MarkRunAlertDelivered
func (s *SchedulerStore) MarkRunAlertDelivered(ctx context.Context, runID string, attempt int) error
MarkRunAlertDelivered stamps a run’s on-failure alert as delivered, for the attempt the caller won. The attempt is part of the predicate so a stamp from a send that an operator clear has since superseded matches no row — see the query.
func (*SchedulerStore) MarkStagingDeleted
func (s *SchedulerStore) MarkStagingDeleted(ctx context.Context, pvcName, reason string) error
MarkStagingDeleted records that a staging volume’s PVC was deleted and why (run_succeeded | ttl_expired | orphaned).
func (*SchedulerStore) MarkTaskAgentLost
func (s *SchedulerStore) MarkTaskAgentLost(ctx context.Context, taskInstanceID string) (bool, error)
MarkTaskAgentLost transitions one TI to `failed` with the agent_lost reason. The WHERE state=‘running’ guard makes this idempotent and prevents a late terminal report being overwritten — if the row already moved, we touch zero rows and return nil.
func (*SchedulerStore) MarkTaskDispatchFailed
func (s *SchedulerStore) MarkTaskDispatchFailed(ctx context.Context, runID, taskID, reason string) error
MarkTaskDispatchFailed transitions a TI to `failed` after its asynchronous dispatch failed inside the BufferedDispatcher worker (#127). The SQL guard only targets scheduled/queued rows, so a TI that already moved to running or terminal between the worker accepting the request and the dispatch failing is left alone (defense in depth — the agent’s late progress report wins over the dispatcher’s “I failed” claim).
func (*SchedulerStore) MarkTaskDispatchLost
func (s *SchedulerStore) MarkTaskDispatchLost(ctx context.Context, taskInstanceID string) error
MarkTaskDispatchLost transitions one TI to `failed` with the dispatch_lost reason. The WHERE state=‘queued’ guard makes this idempotent: a TI that has since been dispatched (real progress landed) is left alone.
func (*SchedulerStore) MarkTaskPodLost
func (s *SchedulerStore) MarkTaskPodLost(ctx context.Context, taskInstanceID string) (bool, error)
MarkTaskPodLost transitions one TI to `failed` with the pod_lost reason. The WHERE state=‘running’ guard makes it idempotent: a TI that has since moved on (a late terminal report landed) is left alone.
func (*SchedulerStore) MaterializeTasks
func (s *SchedulerStore) MaterializeTasks(ctx context.Context, runID string, tasks []domain.TaskSpec) error
MaterializeTasks creates a none-state task instance for each task in the run, in one batched COPY rather than T INSERT round-trips. The rows are identical to the per-task loop this replaced: try_number pinned to 1, state none, and max_tries derived from the task’s retries (default 1). An empty task set is a no-op (a DAG with no tasks materializes nothing).
func (*SchedulerStore) PoolBudgets
func (s *SchedulerStore) PoolBudgets(ctx context.Context) (map[string]int, error)
PoolBudgets returns every named pool’s slot cap keyed by scheduler.PoolKey(tenantID, name) — the cross-DAG admission budget the pool gate enforces (ADR 0053 Stage 3). The scheduler calls it once per tick, and only on the Pro path; Lite never loads pool budgets.
func (*SchedulerStore) ReapRun
func (s *SchedulerStore) ReapRun(ctx context.Context, runID string) error
ReapRun fails an orphaned dag run, then any of its still-active task instances, inside a single transaction. The run UPDATE comes first and is guarded by `state = ‘running’`: if zero rows are touched, the run was no longer running (a competing finalizer beat us) and we abort with a clean rollback — the TI table is never touched. This guarantees we cannot leave a run as `success`/`failed` while flipping its TIs to `failed (orphaned)`. Idempotent: a second call on an already-failed run no-ops.
func (*SchedulerStore) RecordDispatchBackpressure
func (s *SchedulerStore) RecordDispatchBackpressure(ctx context.Context, runID, taskID string, nextAt time.Time) error
RecordDispatchBackpressure backs off a scheduled task after a retriable-forever cluster-backpressure dispatch failure (quota 403 / APF 429), setting nextAt WITHOUT incrementing dispatch_attempts so it never accumulates toward the dispatch_failed cap (ADR 0053).
func (*SchedulerStore) RecordDispatchFailure
func (s *SchedulerStore) RecordDispatchFailure(ctx context.Context, runID, taskID string, nextAt time.Time) error
RecordDispatchFailure increments a scheduled task’s dispatch-failure counter and backs off its next attempt to nextAt (ADR 0031 Amendment A).
func (*SchedulerStore) RecordStagingVolume
func (s *SchedulerStore) RecordStagingVolume(ctx context.Context, tenantID, dagID, runID, pvcName, size string) error
RecordStagingVolume records a per-run staging volume as active, keyed by PVC name (idempotent — called per task as the PVC is ensured). ADR 0022.
func (*SchedulerStore) RedispatchReschedule
func (s *SchedulerStore) RedispatchReschedule(ctx context.Context, runID, taskID string) error
RedispatchReschedule returns a task parked in up_for_reschedule to ’none’ for re-dispatch, preserving try_number (reschedule is not a retry; #380).
func (*SchedulerStore) ResetForInfraReplace
func (s *SchedulerStore) ResetForInfraReplace(ctx context.Context, runID, taskID string) (bool, error)
ResetForInfraReplace returns a task instance failed by a reaper as infra (last_failure_kind=‘infra’) to ’none’ so the scheduler re-runs it, bumping infra_attempts instead of try_number — an infrastructure fault must not consume the user’s retry budget (ADR 0051 Phase 1). It uses the failed+infra-guarded query so a late terminal report or a non-infra failure at state=‘failed’ cannot be re-placed off-budget. The bool reports whether the guarded update fired (exactly one row): a false means the TI was no longer a failed-infra candidate, so the caller must not record a re-placement it did not perform.
func (*SchedulerStore) ResetForRetry
func (s *SchedulerStore) ResetForRetry(ctx context.Context, runID, taskID string) (bool, error)
ResetForRetry returns a task instance to ’none’, clears its timestamps, and increments its try number so the scheduler re-evaluates and re-runs it. It uses the up_for_retry-guarded query so a stale retry decision cannot reset a TI that has since been re-dispatched (audit follow-up; see the query doc). The bool reports whether the guarded update actually fired (exactly one row): a false means the TI was no longer up_for_retry and nothing was reset, so the caller must not record a retry it did not perform.
func (*SchedulerStore) ScheduledDAGs
func (s *SchedulerStore) ScheduledDAGs(ctx context.Context) ([]scheduler.ScheduledDAG, error)
ScheduledDAGs returns active, unpaused, cron-scheduled DAGs with the logical date of their most recent run.
func (*SchedulerStore) SetRunState
func (s *SchedulerStore) SetRunState(ctx context.Context, runID string, state domain.DagRunState) error
SetRunState updates a run’s state.
func (*SchedulerStore) SetTaskNote
func (s *SchedulerStore) SetTaskNote(ctx context.Context, runID, taskID, note string) error
SetTaskNote attaches operational context to a task instance, shown in the UI.
func (*SchedulerStore) SetWarmExecution
func (s *SchedulerStore) SetWarmExecution(exec config.ExecutionSection)
SetWarmExecution records the operator’s warm-pool config so ActiveWarmTargets can resolve each active dag_version’s effective warm target. main.go calls it only when warm pools are enabled; left unset, warm pools read as off (every target 0).
type XComIndex
XComIndex is the Postgres-backed XCom metadata index. It implements xcom.Index, recording each pushed value so the API can find and list it.
type XComIndex struct {
// contains filtered or unexported fields
}
func NewXComIndex
func NewXComIndex(pg *Postgres) *XComIndex
NewXComIndex builds an XComIndex over the given Postgres connection.
func (*XComIndex) PurgeExpired
func (x *XComIndex) PurgeExpired(ctx context.Context) error
PurgeExpired deletes xcom_index rows past their expiry. Redis expires the values natively; this reclaims the metadata rows.
func (*XComIndex) RecordXCom
func (x *XComIndex) RecordXCom(ctx context.Context, e xcom.IndexEntry) error
RecordXCom upserts the metadata for a pushed XCom value.
type XComReader
XComReader reads XCom values for the API: it resolves the Redis key from the Postgres index by name and fetches the value from the backend.
type XComReader struct {
// contains filtered or unexported fields
}
func NewXComReader
func NewXComReader(pg *Postgres, backend xcom.Backend) *XComReader
NewXComReader builds an XComReader over the given Postgres connection and XCom backend.
func (*XComReader) GetXCom
func (r *XComReader) GetXCom(ctx context.Context, tenant, dagID, runID, taskID, key string) (xcom.Entry, error)
GetXCom returns the XCom entry for the named value, or domain.ErrNotFound when it is absent or expired (in the index or in Redis).
func (*XComReader) ListXComEntries
func (r *XComReader) ListXComEntries(ctx context.Context, tenant, dagID, runID, taskID string) ([]domain.XComEntryMeta, error)
ListXComEntries returns the metadata of every non-expired XCom pushed by a task instance (keys and timestamps, no values), for the XCom list view.
Generated by gomarkdoc