Help us shape the future of Go! Take the 2026 Go Developer Survey

api

package
v0.5.1 Latest Latest
Warning

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

Go to latest
Published: Oct 6, 2026 License: Apache-2.0 Imports: 52 Imported by: 0

Documentation

Overview

Package api implements the Airflow-compatible HTTP control plane (ADR 0007).

Index

Constants

View Source
const DefaultUIAutoRefreshIntervalSeconds = 30

DefaultUIAutoRefreshIntervalSeconds is the production-safe value returned by /ui/config when no explicit override is configured. Lite overrides this to a smaller value (typically 5s) for a snappy inner-loop dev experience; Pro keeps 30s so the SPA's polling does not hammer a shared metadata DB.

View Source
const PoolsReadOnlyDetail = "pools are read-only on this server: their slots are managed by the platform operator"

PoolsReadOnlyDetail is the stable problem detail of the 403 every pool write gets when server.pools_read_only is on. Clients may match on it to tell the lock apart from a missing permission.

Variables

View Source
var ErrNotFound = domain.ErrNotFound

ErrNotFound is returned by repositories when a resource does not exist.

Functions

func AbortProblem

func AbortProblem(c *gin.Context, status int, title, detail string)

AbortProblem writes an RFC 7807 problem response and stops the handler chain.

detail is sent to the client as-is, so it must never carry a driver, filesystem, or network error's text. Where the real cause must stay server-side, use AbortProblemCause.

func AbortProblemCause

func AbortProblemCause(c *gin.Context, status int, title, detail string, cause error)

AbortProblemCause writes an RFC 7807 problem whose detail is safe to show the caller, while handing the real error to the request log.

It exists because the two audiences need different text. A storage failure carries the driver's own message — for Postgres, "severity: message (SQLSTATE code)" with our constraint, table and column names inside — and a tenant of a multi-tenant control plane must not receive that (CWE-209). The operator, on the other hand, cannot diagnose the failure without it. Redacting the response and dropping the cause would trade one defect for another, so cause is logged verbatim under its own field.

cause may be nil, which makes this identical to AbortProblem.

func CORS

func CORS(allowed []string) gin.HandlerFunc

CORS allows the configured origins (use "*" to allow any).

func DevBypassAuth

func DevBypassAuth() gin.HandlerFunc

DevBypassAuth authenticates EVERY request as a fixed admin user, with no token required. It exists solely for `dexaflow lite` (the local, unsandboxed loop) so a developer reaches the UI without logging in. It must only be wired under the explicit dev opt-in (config auth.dev_no_auth); the server logs a prominent warning when it is active. NEVER enable this in production.

func JWTAuth

func JWTAuth(authn auth.Authenticator) gin.HandlerFunc

JWTAuth validates the bearer token on protected routes and stores the user.

func NewServer

func NewServer(deps Dependencies) *gin.Engine

NewServer builds the gin engine with the full middleware chain, health and metrics endpoints, embedded Scalar docs, and the auth token endpoint.

func NoStoreOnVolatileRoutes

func NoStoreOnVolatileRoutes() gin.HandlerFunc

NoStoreOnVolatileRoutes stamps every response served from the SPA-facing JSON surface (`/api/v2/*` and `/ui/*`) with `Cache-Control: no-store, must-revalidate` so the browser HTTP cache does not return a pre-mutation payload after a PATCH/POST/DELETE.

Why this exists (#211, #271): mark-state PATCH succeeds in single-digit ms; TanStack Query then invalidates its in-memory cache and re-fetches. Without this header, the browser's HTTP cache layer can serve the OLD response to that re-fetch (the original GET response had no explicit caching directive, so the browser falls back to heuristic caching). The SPA renders stale state until the next "natural" refresh — the observable symptom is "marcar como falha demora uma eternidade".

Static assets (`/ide/vs/*` for the Monaco bundle) are content-hashed and SHOULD cache, so they are explicitly excluded.

We deliberately use "no-store" rather than "no-cache": no-store forbids the browser from writing the response anywhere, which is the strongest guarantee we can give a TanStack-backed SPA. "must-revalidate" is added for older intermediaries (proxies / SW) that may not honor no-store alone. This is ADR-0017-compatible: no SPA changes.

A handler that computes an ETag and authorizes every request may replace the header with "private, no-cache" when ui.etag_revalidation is on, so the browser revalidates instead of refetching (tiSummariesHandler).

func ObservabilityHandler

func ObservabilityHandler(registry *prometheus.Registry, checks map[string]HealthChecker, opts ...ObservabilityOption) http.Handler

ObservabilityHandler builds the handler served on the metrics listener: the Prometheus /metrics endpoint plus the same /healthz and /readyz the full API exposes (reusing the very same handlers, so the semantics are identical — trivial liveness, dependency-pinging readiness).

Roles that do not serve the full API — the ADR 0049 scheduler role — still need a liveness/readiness surface for the kubelet's probes. The metrics listener runs in every role, so mounting health here gives a scheduler-only pod a probe target without exposing the API, auth, or UI. The full API keeps its own /healthz and /readyz on the HTTP port, so the "all" role is unchanged; this is purely additive on the metrics port.

This handler is intentionally unauthenticated (probes carry no token) and does not run the API middleware chain; it is the same trust level as scraping /metrics, which is already public.

func Observe

func Observe(metrics Metrics, tracer trace.Tracer) gin.HandlerFunc

Observe wraps each request in an OTel span and records HTTP metrics (ADR 0010). A nil tracer falls back to the global (no-op) tracer; nil metrics are skipped, so the middleware is safe in tests.

func RequestID

func RequestID() gin.HandlerFunc

RequestID assigns a request id (honoring an inbound X-Request-Id) and echoes it.

func RequirePermission

func RequirePermission(action, resource string) gin.HandlerFunc

RequirePermission enforces an RBAC permission on a route.

func StructuredLogger

func StructuredLogger(logger *slog.Logger) gin.HandlerFunc

StructuredLogger logs one structured line per request (ADR 0010).

func UserFromContext

func UserFromContext(c *gin.Context) (*auth.User, bool)

UserFromContext returns the authenticated user stored by JWTAuth.

Types

type AuditLogPageReader added in v0.5.1

type AuditLogPageReader interface {
	ListAuditLogsAfter(ctx context.Context, tenant, dagID string, after domain.PageCursor, limit int) ([]domain.AuditLogEntry, int, error)
}

AuditLogPageReader is the keyset form of AuditLogReader.ListAuditLogs.

type AuditLogReader

type AuditLogReader interface {
	ListAuditLogs(ctx context.Context, tenant, dagID string, limit, offset int) ([]domain.AuditLogEntry, int, error)
}

AuditLogReader lists recorded actions for the Audit Log view. dagID == "" means no DAG filter.

type AuditWriter

type AuditWriter interface {
	RecordTaskActionAudit(ctx context.Context, tenant, userID, action, dagID, runID, taskID string, tryNumber int) error
}

AuditWriter records task-level actions (clear, mark state) for the Audit Log view, with the acting user and the run/task in the entry.

type AuthAuditWriter

type AuthAuditWriter interface {
	RecordAuthEvent(ctx context.Context, tenant, actorUserID, action, email, outcome string, extra map[string]string) error
}

AuthAuditWriter records authentication events to the audit sink (H5). It is best-effort: a write error never changes the auth outcome.

type ConnectionStore

type ConnectionStore interface {
	ListConnections(ctx context.Context, tenant string, limit, offset int) ([]domain.Connection, int, error)
	GetConnection(ctx context.Context, tenant, connID string) (domain.Connection, error)
	SetConnectionPatch(ctx context.Context, tenant string, p domain.ConnectionPatch) error
	DeleteConnection(ctx context.Context, tenant, connID string) error
}

ConnectionStore reads and writes Airflow-style Connections for the Admin UI. Password is encrypted at rest by the store (ADR 0019) and never returned.

type ConnectionTester

type ConnectionTester interface {
	Test(ctx context.Context, c domain.Connection) (ok bool, message string)
}

ConnectionTester checks whether a connection is well-formed. The default implementation validates STRUCTURE only and makes no network call: the Go control plane must never reach out to a user-configured host (SSRF / internal port-scan — go/request-forgery), and "reachable from the control plane" is the wrong question anyway, since a connection is used in the task's network scope, not the control plane's. Live reachability/auth is tested where the connection is actually used (the task/executor) — tracked as a follow-up.

type DagLatestRunsReader

type DagLatestRunsReader interface {
	LatestRunsForDags(ctx context.Context, tenant string, dagIDs []string, perDag int) (map[string][]domain.DagRun, error)
}

DagLatestRunsReader fetches the most-recent runs for a set of DAGs in one query, so /ui/dags can embed run history without an N+1.

type DagRepository

type DagRepository interface {
	ListDags(ctx context.Context, tenant string, limit, offset int) ([]domain.DAG, int, error)
	GetDag(ctx context.Context, tenant, dagID string) (domain.DAG, error)
	SetPaused(ctx context.Context, tenant, dagID string, paused bool) (domain.DAG, error)
	DeleteDag(ctx context.Context, tenant, dagID string) error
	ClearDagHistory(ctx context.Context, tenant, dagID string) error
	ListDagsFiltered(ctx context.Context, tenant, runState string, paused *bool, limit, offset int) ([]domain.DAG, int, error)
}

DagRepository reads, updates, and deletes registered DAGs.

type DagRunPageReader added in v0.5.1

type DagRunPageReader interface {
	ListDagRunsAfter(ctx context.Context, tenant, dagID string, states []string, after domain.PageCursor, limit int) ([]domain.DagRun, int, error)
}

DagRunPageReader is the keyset form of DagRunRepository.ListDagRuns.

type DagRunRepository

type DagRunRepository interface {
	ListDagRuns(ctx context.Context, tenant, dagID string, limit, offset int) ([]domain.DagRun, int, error)
	GetDagRun(ctx context.Context, tenant, dagID, runID string) (domain.DagRun, error)
	CreateDagRun(ctx context.Context, tenant, dagID string, run domain.DagRun) (domain.DagRun, error)
	SetDagRunState(ctx context.Context, tenant, dagID, runID, state string) error
	DeleteDagRun(ctx context.Context, tenant, dagID, runID string) error
}

DagRunRepository reads and creates DAG runs.

type DagSpecReader

type DagSpecReader interface {
	GetCurrentSpec(ctx context.Context, tenant, dagID string) (domain.DAGSpec, error)
}

DagSpecReader reads the parsed spec of a DAG's current version, the source of task topology for the grid and graph views.

type DagVersionLister

type DagVersionLister interface {
	ListDagVersions(ctx context.Context, tenant, dagID string) ([]domain.DagVersion, error)
}

DagVersionLister lists a DAG's registered versions. The Airflow UI fetches this to resolve a version_number before requesting version-scoped structure (the Graph view); without it the graph never loads. See docs/ui-compatibility.md.

type DagVersionRepository

type DagVersionRepository interface {
	// RegisterDagVersion upserts the DAG and inserts a version keyed by
	// specHash, reporting whether a new version was created (false if the hash
	// already existed — the push is idempotent).
	RegisterDagVersion(ctx context.Context, tenant string, spec domain.DAGSpec, specHash string) (bool, error)
}

DagVersionRepository registers compiled DAG versions.

type DashboardStatsReader

type DashboardStatsReader interface {
	DagStats(ctx context.Context, tenant string) (domain.DagStats, error)
	HistoricalMetrics(ctx context.Context, tenant string, since, until time.Time) (domain.HistoricalMetrics, error)
}

DashboardStatsReader backs the home dashboard widgets with real counts.

type Dependencies

type Dependencies struct {
	Logger        *slog.Logger
	Authenticator auth.Authenticator
	RateLimiter   *auth.RateLimiter
	Registry      *prometheus.Registry
	Metrics       Metrics
	Tracer        trace.Tracer
	HealthChecks  map[string]HealthChecker
	CORSOrigins   []string
	// TrustedProxies is the set of proxy IPs/CIDRs whose X-Forwarded-For header
	// gin will honor when resolving c.ClientIP(). Empty/nil trusts NO proxy, so
	// ClientIP is the direct peer and a spoofed XFF cannot forge the client IP
	// (audit H1). A Pro deployment behind an ingress sets this to the ingress
	// CIDR so per-client rate-limiting and audit see the real client.
	TrustedProxies []string
	TokenTTLSecs   int
	// TokenRenewer re-mints a still-valid user bearer with a fresh short TTL so a
	// long CLI/dev session need not re-login every TokenTTLSecs (aresta #5). Nil
	// leaves the renew route unregistered (renewal simply unavailable). In practice
	// it is the same *auth.JWTAuthenticator as Authenticator.
	TokenRenewer TokenRenewer
	// TokenMaxLifetimeSecs is the hard ceiling on a renewed session's total age
	// since first login; past it, renewal is refused and the user must
	// re-authenticate. Non-positive disables the ceiling.
	TokenMaxLifetimeSecs int
	// InstanceName is shown in the UI navbar (Airflow's instance_name). Empty
	// falls back to "Dexaflow"; `dexaflow lite` sets it to mark the DEV environment.
	InstanceName string
	// UIAutoRefreshIntervalSeconds controls the SPA's polling cadence for DAG /
	// DagRun / task-instance state refresh (Airflow's auto_refresh_interval).
	// Non-positive (the zero default) falls back to DefaultUIAutoRefreshIntervalSeconds
	// (30s, production-safe). `dexaflow lite` sets it to ~5s for a snappy inner loop.
	UIAutoRefreshIntervalSeconds int
	// UITheme is the Chakra theme /ui/config hands the UI (Airflow's `[api]
	// theme`: tokens, globalCss, icon, icon_dark_mode), already validated as a
	// JSON object at boot. Nil serves null, the stock look (#1289).
	UITheme json.RawMessage
	// UIETagRevalidation (ui.etag_revalidation) relaxes no-store to
	// "private, no-cache" with Vary: Authorization, Cookie on the routes that
	// compute an ETag, so the browser can revalidate them and get a 304. False
	// (the default) keeps no-store on every UI route.
	UIETagRevalidation bool
	// DevNoAuth replaces JWT auth with a dev-only bypass that authenticates every
	// request as an admin (no login). It is for `dexaflow lite` only and must never
	// be set in production. See DevBypassAuth.
	DevNoAuth bool
	// Edition marks the running edition ("pro", "lite", or empty). It gates
	// Pro-only surfaces: named-pool CRUD is registered as real endpoints only when
	// Edition == "pro" (ADR 0053), otherwise the Pools screen gets the graceful
	// empty-collection stub, matching how the scheduler's pool gate is Pro-gated.
	Edition string
	// PoolsReadOnly is server.pools_read_only: the pool API serves reads only and
	// every create, resize and delete answers 403 with PoolsReadOnlyDetail, for
	// every role including tenant admin. False keeps the write:pool-gated CRUD.
	PoolsReadOnly bool

	// Resource repositories. Routes for nil repositories are not registered.
	Dags           DagRepository
	DagRuns        DagRunRepository
	Tasks          TaskInstanceRepository
	Versions       DagVersionRepository
	Xcoms          XComReader
	Logs           LogReader
	Specs          DagSpecReader
	LatestRuns     DagLatestRunsReader
	TaskSummary    TaskSummaryReader
	DagVersions    DagVersionLister
	DashboardStats DashboardStatsReader
	AuditLog       AuditLogReader
	Variables      VariableStore
	Users          UserStore
	UserAudit      UserAuditWriter
	Connections    ConnectionStore
	ConnectionTest ConnectionTester
	Pools          PoolStore
	Favorites      FavoriteStore
	ImportErrors   ImportErrorStore
	Audit          AuditWriter
	ExecutorInfo   ExecutorInfo

	// Workspace backs the Lite web editor (ADR 0025). When nil the editor's
	// filesystem API is not registered (Production, or Lite without a workspace).
	Workspace WorkspaceFS

	// MonacoDir is the directory holding the pinned Monaco bundle that
	// `dexaflow setup` fetched; the editor page is served Monaco from it. Empty or
	// missing makes the page show a setup hint instead of a broken editor.
	MonacoDir string

	// ExamplesFS backs the IDE's "Download examples" button — typically the
	// `embed.FS` shipped from the leoflow root package. Nil disables the button.
	ExamplesFS fs.FS

	// SchedulerHealth reports the scheduler's heartbeat for /monitor/health.
	// When nil the component reports healthy (single-process role assumption).
	SchedulerHealth Heartbeater

	// UI serves the embedded SPA. When nil the server is API-only.
	UI UIServer

	// OIDC wiring (registered only when OIDCFlow is non-nil — provider: oidc).
	// The JWT authenticator above stays the request-path verifier in both modes.
	//
	// OIDCFlow is the discovered Authorization Code + PKCE flow; nil in JWT mode,
	// in which case the /api/v2/auth/oidc/* routes are not registered.
	OIDCFlow *oidc.Flow
	// OIDCEnabled is true when auth.provider is "oidc". It makes the credential
	// path break-glass-only: with OIDC on, an empty break-glass allowlist means
	// SSO-only (every password login rejected), NOT ungated — the secure default.
	OIDCEnabled bool
	// OIDCSettings carries the role mappings, JIT policy, default_role, and
	// break-glass allowlist the login flow and the credential gate read.
	OIDCSettings config.OIDCSection
	// ExternalSignInURL and ExternalSignOutURL are auth.external_signin_url and
	// auth.external_signout_url (#1288): the operator's own sign-in and
	// sign-out, used in place of Dexaflow's pages. Empty keeps Dexaflow's.
	ExternalSignInURL  string
	ExternalSignOutURL string
	// OIDCUsers resolves and JIT-provisions OIDC identities (the storage repo).
	OIDCUsers OIDCUserStore
	// AuthAudit records authentication events (login, tenant-pin rejection, JIT,
	// break-glass, logout) to the audit sink.
	AuthAudit AuthAuditWriter
	// TrustedIssuer, when set, enables POST /api/v2/auth/session: a token from
	// the operator's trusted issuer opens a UI session for a linked user
	// (#1284). TrustedIssuerUsers resolves those users (the storage repo).
	TrustedIssuer      TrustedIssuer
	TrustedIssuerUsers TrustedIssuerUserStore
	// TrustedIssuerOrigins are the only Origins a handoff may be posted from
	// (scheme://host[:port]), so another site cannot sign a browser in.
	TrustedIssuerOrigins []string
	// ServiceToken, when set, enables the operator service API under
	// /api/v2/service/ (#1283), authenticated by this bearer token instead of a
	// user session. ServiceTenants is its storage (the repo).
	ServiceToken   string
	ServiceTenants ServiceTenantStore
	// ServiceAllowedTenants are the tenants the service API may link issuer
	// users in: auth.trusted_issuer.allowed_tenants, where "*" allows all.
	ServiceAllowedTenants []string
	// JWTSecret is the HS256 secret the OIDC callback mints the app's _token with.
	JWTSecret string
	// SessionCookieInsecure drops the Secure attribute from the session and OIDC
	// state cookies (auth.session_cookie_insecure). It is stated negatively so the
	// zero value is the hardened one: a caller that forgets the field gets Secure.
	// See cookieSecure for why this is a setting and not derived from the request.
	SessionCookieInsecure bool
}

Dependencies bundles everything the HTTP server needs.

type ExecutorInfo

type ExecutorInfo struct {
	PodDispatchEnabled    bool
	TaskNamespace         string
	AgentControlPlaneAddr string
}

ExecutorInfo describes the control plane's execution capacity. It surfaces whether pod dispatch is available — the cluster-level answer to "why is a task stuck queued" (#46/#47). The stock Airflow UI has no widget for it, but operators (curl/monitoring) and a future custom Cluster Activity view consume it. Cluster Activity in Airflow 3.2 is otherwise the Home dashboard, already backed by /api/v2/monitor/health (#33) and /ui/dashboard/* (#39).

type FavoriteStore

type FavoriteStore interface {
	AddFavorite(ctx context.Context, tenant, userID, dagID string) error
	RemoveFavorite(ctx context.Context, tenant, userID, dagID string) error
	FavoriteDagIDs(ctx context.Context, tenant, userID string) (map[string]bool, error)
}

FavoriteStore persists per-user DAG favorites (the DAG-list star).

type HealthChecker

type HealthChecker interface {
	Ping(ctx context.Context) error
}

HealthChecker reports dependency health for readiness checks.

type Heartbeater

type Heartbeater interface {
	Heartbeat() (healthy bool, last time.Time)
}

Heartbeater reports a long-running component's liveness and last heartbeat for the monitor health endpoint. The scheduler implements it.

type ImportErrorStore

type ImportErrorStore interface {
	ListImportErrors(ctx context.Context, tenant string) ([]domain.ImportError, error)
	SetImportError(ctx context.Context, tenant string, e domain.ImportError) error
	ClearImportError(ctx context.Context, tenant, filename string) error
}

ImportErrorStore reads and writes DAG parse/compile errors that back Airflow's "Import Errors" banner on the home dashboard. The `dexaflow lite` watcher writes an entry on a failed compile and clears it on the next good compile; the public GET /api/v2/importErrors feed is what the UI polls.

type LogReader

type LogReader interface {
	ReadLogs(ctx context.Context, tenant, dagID, runID, taskID string, tryNumber int) (io.ReadCloser, error)
	Tail(ctx context.Context, tenant, dagID, runID, taskID string, tryNumber int) (<-chan string, func(), error)
}

LogReader streams a task attempt's stored logs and, for running tasks, tails new lines live.

type Metrics

type Metrics interface {
	RecordHTTPRequest(method, path string, status int, dur time.Duration)
}

Metrics records HTTP request metrics. observability.Metrics implements it.

type OIDCUserStore

type OIDCUserStore interface {
	// FindUserByOIDCSubject resolves a returning identity by its (provider,
	// subject) pair, returning the user, whether it is active, and
	// auth.ErrUserNotFound when no row matches.
	FindUserByOIDCSubject(ctx context.Context, provider, subject string) (*auth.User, bool, error)
	// CreateOIDCUser JIT-provisions an OIDC-only user with the given roles.
	CreateOIDCUser(ctx context.Context, tenant, email, provider, subject string, roles []string) (*auth.User, error)
	// RoleExists reports whether a role name exists for the tenant, used to fail
	// closed on a misconfigured default_role or role mapping.
	RoleExists(ctx context.Context, tenant, role string) (bool, error)
	// ReconcileUserRoles sets the user's DB roles to EXACTLY roleNames (the IdP is
	// authoritative). It fails closed on a name that is not a role in the tenant,
	// leaving the prior grants intact.
	ReconcileUserRoles(ctx context.Context, userID string, roleNames []string) error
}

OIDCUserStore resolves and just-in-time-provisions OIDC identities. storage implements it. The interface lives with its consumer (the callback handler).

type ObservabilityOption added in v0.5.1

type ObservabilityOption func(*observabilityOptions)

ObservabilityOption adjusts ObservabilityHandler.

func WithoutLegacyMetricNames added in v0.5.1

func WithoutLegacyMetricNames() ObservabilityOption

WithoutLegacyMetricNames serves each metric family once, under its dexaflow_ name only (observability.metrics.drop_legacy_names). It halves the scrape and the per-scrape copy of every family. It is an opt-in for installs that do not need the leoflow_ names; the default keeps them.

type PoolStore

type PoolStore interface {
	ListPools(ctx context.Context, tenant string, limit, offset int) ([]domain.Pool, int, error)
	GetPool(ctx context.Context, tenant, name string) (domain.Pool, error)
	SetPool(ctx context.Context, tenant string, p domain.Pool) error
	DeletePool(ctx context.Context, tenant, name string) error
	PoolSlotUsage(ctx context.Context, tenant string) (map[string]domain.PoolUsage, error)
}

PoolStore reads and writes named task pools for the Admin UI (ADR 0053 Stage 3). Pools are tenant-scoped; PoolSlotUsage reports per-pool occupancy for the Airflow slot fields.

type Problem

type Problem struct {
	Type     string `json:"type"`
	Title    string `json:"title"`
	Status   int    `json:"status"`
	Detail   string `json:"detail,omitempty"`
	Instance string `json:"instance,omitempty"`
}

Problem is an RFC 7807 problem-details response body.

type SchemaChecker

type SchemaChecker interface {
	SchemaReady(ctx context.Context) error
}

SchemaChecker is an optional capability a HealthChecker may also implement: asserting that the dependency's SCHEMA — not just its connection — is the one this binary requires. *storage.Postgres implements it; Redis and other schema-less dependencies do not, and keep the Ping-only contract.

It exists because a Ping is not evidence of a usable database (#1023). The invariant is enforced at boot, where the server refuses to start against a database whose schema_migrations is absent or behind — but a boot check says nothing about a database that changes UNDER a running pod, which is exactly what an ephemeral-volume recycle, a restore from an older backup, a failover to a lagging replica, or a repointed database.url does.

type ServiceTenantStore

type ServiceTenantStore interface {
	// EnsureTenant creates a tenant with the built-in roles and default pool,
	// or fills in what is missing; created reports whether it was new.
	// limits sets the tenant limits it carries and leaves the others.
	EnsureTenant(ctx context.Context, name, displayName string, defaultPoolSlots int, limits domain.TenantLimitsUpdate) (created bool, err error)
	// EnsureIssuerUser makes sure a passwordless user linked to (provider,
	// subject) exists in tenant with exactly roles.
	EnsureIssuerUser(ctx context.Context, tenant, email, provider, subject string, roles []string) (*auth.User, bool, error)
}

ServiceTenantStore is what the service API needs from storage (#1283). storage.Repository implements it.

type TaskInstanceRepository

type TaskInstanceRepository interface {
	ListTaskInstances(ctx context.Context, tenant, dagID, runID string, limit, offset int) ([]domain.TaskInstance, int, error)
	// ListTaskInstanceAttempts returns every attempt for (run, task), oldest
	// first — the current row UNIONed with the archived history. The UI's
	// /tries endpoint needs all attempts to render its navigable tabs.
	ListTaskInstanceAttempts(ctx context.Context, tenant, dagID, runID, taskID string) ([]domain.TaskInstance, error)
	ClearTaskInstances(ctx context.Context, tenant, dagID, runID string, taskIDs []string, onlyFailed bool, opts domain.ClearOptions) (int, error)
	SetTaskInstanceState(ctx context.Context, tenant, dagID, runID, taskID, state string) error
}

TaskInstanceRepository reads task instances, clears them for re-run, and sets their state directly (the UI's mark-success/failed actions).

type TaskSummaryReader

type TaskSummaryReader interface {
	TaskInstancesForRuns(ctx context.Context, tenant, dagID string, runIDs []string) ([]domain.TaskInstance, error)
}

TaskSummaryReader fetches task instances across a set of runs of a DAG, the source for the grid's per-cell state summaries.

type TokenRenewer

type TokenRenewer interface {
	RenewUserToken(ctx context.Context, token string, ttl, maxLifetime time.Duration) (renewed string, ok bool, err error)
}

TokenRenewer re-mints a still-valid user bearer with a fresh short TTL, bounded by max_lifetime since first login. *auth.JWTAuthenticator implements it via RenewUserToken; the handler depends on this narrow interface so the renew route can be tested without a real signing key. It takes a context because renewal re-proves the principal against the user store, so it must be canceled with the request.

type TrustedIssuer

type TrustedIssuer interface {
	// Provider is the key the issuer's users are linked under.
	Provider() string
	// Verify checks a token and returns the identity it carries.
	Verify(ctx context.Context, raw string) (*issuer.Identity, error)
}

TrustedIssuer verifies handoff tokens from the operator's trusted issuer. issuer.Verifier implements it.

type TrustedIssuerUserStore

type TrustedIssuerUserStore interface {
	FindUserByOIDCSubject(ctx context.Context, provider, subject string) (*auth.User, bool, error)
}

TrustedIssuerUserStore resolves a linked user. storage implements it; it is the same lookup a returning SSO login uses.

type UIServer

type UIServer interface {
	StaticHandler() http.Handler
	Index(w http.ResponseWriter, basePath string)
}

UIServer serves the embedded single-page app: static assets and an index.html shell that the SPA's client-side router falls back to. It is satisfied by internal/ui.Server. When nil, the server runs API-only and unknown paths return 404 instead of the SPA shell.

type UserAuditWriter

type UserAuditWriter interface {
	RecordUserCreatedAudit(ctx context.Context, tenant, actorUserID, createdUserID, email, roles string) error
}

UserAuditWriter records account-creation events for the Audit Log. It is a separate, narrow interface (not the task-shaped AuditWriter) so account management writes a "user" resource entry with the acting admin as owner. The granted roles are passed as a single joined string so the record captures the full set.

type UserStore

type UserStore interface {
	CreateUser(ctx context.Context, tenant, email, password string, roles []string) (domain.User, error)
	ListUsers(ctx context.Context, tenant string, limit, offset int) ([]domain.User, int, error)
}

UserStore creates control-plane accounts for the admin create-user API. The store hashes the plaintext password (reusing the bootstrap admin's bcrypt scheme) and returns the created user without any secret. A duplicate email must surface as domain.ErrConflict and an unknown role as domain.ErrValidation.

type VariableStore

type VariableStore interface {
	ListVariables(ctx context.Context, tenant string, limit, offset int) ([]domain.Variable, int, error)
	GetVariable(ctx context.Context, tenant, key string) (domain.Variable, error)
	SetVariablePatch(ctx context.Context, tenant string, p domain.VariablePatch) error
	DeleteVariable(ctx context.Context, tenant, key string) error
}

VariableStore reads and writes Airflow-style Variables for the Admin UI.

type WorkspaceFS

type WorkspaceFS interface {
	Tree() ([]workspace.Entry, error)
	Read(rel string) ([]byte, error)
	Write(rel string, data []byte) error
	Create(rel string, dir bool) error
	Move(from, to string) error
	Delete(rel string) error
}

WorkspaceFS is the workspace-confined filesystem backing the Lite web editor (ADR 0025). Every path is relative to the workspace root and confined to it.

type XComReader

type XComReader interface {
	GetXCom(ctx context.Context, tenant, dagID, runID, taskID, key string) (xcom.Entry, error)
	ListXComEntries(ctx context.Context, tenant, dagID, runID, taskID string) ([]domain.XComEntryMeta, error)
}

XComReader reads stored XCom values and lists a task instance's XCom keys for the read API.

Jump to

Keyboard shortcuts

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