api

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Aug 21, 2026 License: AGPL-3.0 Imports: 77 Imported by: 0

Documentation

Overview

Package api is the single-binary server surface for Atlas: it embeds one engine.Processor behind an HTTP API and serves an embedded web UI, so a single self-contained binary can deploy BPMN models, run instances, and (as the UI grows) view them in a browser. See ADR-0011.

Respecting the single-writer invariant

The engine is a single-writer partition (invariant I3): exactly one goroutine may touch a partition's processor and state. HTTP handlers, by contrast, run concurrently. The Server bridges the two by owning a run loop goroutine that is the sole toucher of the processor; handlers submit closures to it via do and block for the result. No processor method is ever called from a handler goroutine directly.

Scope of the skeleton

This is the Milestone S skeleton (ROADMAP.md): deploy XML, create an instance, read stats, health, and a static UI shell. Deployments are durable via an on-disk sidecar store (ADR-0019) reloaded on startup, so diagrams, versions, and recovered instances survive a restart; the eventual event-sourced deployment path arrives with the Milestone 4 public API. One honest limitation remains: there is no streaming job-worker transport yet (that follows the gRPC job protocol, ADR-0007), so an instance parks at its service task — exactly the "waiting token" the live viewer shows. Such a parked job can be finished by hand over HTTP (POST /jobs/{key}/complete, the operator mirror of .../fail from the incident model), but that is a synchronous operator affordance, not the leased, at-least-once worker protocol.

Index

Constants

View Source
const (
	VisibilityPrivate = "private"
	VisibilityShared  = "shared"
)

Visibility values for a project scope. A private project is visible only to its owner (and admins); a shared project additionally grants each listed member their role. The set is intentionally small and open: an org/public value can join later without reshaping the field (ADR-0071).

View Source
const (
	ScopeRoleViewer = "viewer"
	ScopeRoleEditor = "editor"
	ScopeRoleOwner  = "owner"
)

Scope roles, lowest to highest privilege. viewer reads, editor reads+writes, owner additionally manages membership, visibility, transfer, and deletion. owner is implicit (it is the project's OwnerID), never stored in Members. The list mirrors ADR-0044's "roles are a list, only some values enforced now" discipline, so richer roles cost no reshaping.

View Source
const (
	// DefaultRetentionInterval is the default cadence of the history-retention sweep.
	// Exported so the CLI can show it as the default of --retention-interval rather
	// than restate a number that would drift from this one.
	DefaultRetentionInterval = time.Minute
	// DefaultRetentionBatch bounds how many finished instances one sweep tick evaluates,
	// so the scan never blocks the run loop (ADR-0115 / ADR-0085 no-full-scan rule).
	// Together the two bound the drain rate of a backlog: DefaultRetentionBatch per
	// DefaultRetentionInterval. Exported for the CLI, like DefaultRetentionInterval.
	DefaultRetentionBatch = 1000
)
View Source
const (
	RoleAdmin = "admin"
	RoleUser  = "user"
)

Well-known roles. Roles are a free-form list on the user, not a single "admin" bool, so richer RBAC can grow here without reshaping the record (ADR-0044). The MVP only enforces RoleAdmin (managing users requires it); every other role is stored and returned but not yet consulted.

View Source
const ExitNothingToServe = 78

ExitNothingToServe is that status, exported so `atlas worker` leaves exactly the one its supervisor parks on. 78 is sysexits.h's EX_CONFIG — "something was found in an unconfigured or misconfigured state" — which is the condition, and it is far from the statuses a panic or a signal produces.

View Source
const PrincipalTypeUser = "user"

PrincipalTypeUser is the only member reference type Phase 1 implements. The type field exists so groups ("group") slot in later without a migration (ADR-0071/0044 follow-up).

View Source
const RoleDeployAgent = "deploy-agent"

RoleDeployAgent marks the principal a deploy token resolves to: a peer Atlas publishing a bundle here (ADR-0129). It is deliberately not a user role — no account carries it, it cannot be assigned, and it grants nothing on its own. What it may reach is decided by deployAgentAllowed below.

View Source
const (
	SourceLocal = "local"
)

Identity sources. A user authenticates either against a locally stored password (SourceLocal) or, in a future enterprise build, against an external identity provider (OIDC/SAML/LDAP) that maps its subject onto User.ExternalID. Storing the source now — rather than assuming "local" everywhere — is what lets external identities coexist later without a migration (ADR-0044).

Variables

View Source
var Version = "0.3.0-dev"

Version is the Atlas product version reported to the UI and the CLI. It is UI/display metadata only and unrelated to a deployment's process version.

It is a var, not a const, so a release build can stamp the tag into it with

go build -ldflags "-X github.com/pblumer/atlas/api.Version=0.3.0"

A plain checkout build keeps the "-dev" suffix; the exact commit is always available from the embedded VCS metadata (see buildInfo).

Functions

func ApplyPendingRestore

func ApplyPendingRestore(dataDir string) (bool, error)

ApplyPendingRestore applies a staged full-snapshot restore, if one is present and complete, into dataDir — replacing each staged top-level entry and dropping the materialized state so recovery rebuilds it from the restored WAL. It MUST run before the WAL and state stores are opened (the engine holds them under the single-writer invariant), i.e. at the very start of a boot. It returns whether it applied anything.

Each staged entry is moved (rename) into place and the staging directory removed only once every entry is in place. The marker is removed last with the staging, so a crash mid-apply re-runs on the next boot: an entry already moved is simply absent from the staging and skipped, so the apply is idempotent.

func DefaultOffloadedKinds added in v0.3.0

func DefaultOffloadedKinds() []string

DefaultOffloadedKinds are the connector kinds Atlas moves onto a worker of its own accord, and supervises a worker for. It is the opt-out half of ADR-0164: somebody trying Atlas gets the out-of-process architecture without configuring anything, because the engine starts the worker itself.

A kind belongs here when a supervised worker can actually serve it, which is true two ways. Most of these need **no credential** at all: what each of them needs is something a worker has by being a separate process — a CPU for a script, network reach for a scrape, neither for a CSV parse.

Mail is the other way, and the reason the set is not simply "the unmanaged kinds". Its endpoint and password live in the connector store rather than the environment (ADR-0036/0041), so for as long as a child only inherited the environment, moving mail here would have handed every mail task to a worker with no mailbox. The engine now writes that configuration into the child's environment at spawn (see superviseEnv), which is the operator setting the worker up, done by the program — so mail is served, and it is served by the process that should be waiting on an SMTP handshake. Every managed kind here must be one superviseEnv provisions; TestEveryDefaultOffloadedKindCanBeServedByItsWorker holds that.

The credential-bearing kinds the engine cannot yet hand over stay in the engine until an operator moves their secrets themselves, with --offload-connectors.

Types

type BuildInfo

type BuildInfo struct {
	Version  string // product version (api.Version), stampable via -ldflags
	Revision string // git commit, "" if built outside a VCS checkout
	Time     string // commit time (RFC3339), "" if unknown
	Modified bool   // true if built from a dirty working tree
	Go       string // Go toolchain version
}

BuildInfo is the exported view of the running binary's version and embedded VCS build metadata, for callers outside this package — the `atlas version` command. The GET /api/v1/info endpoint reports the same values.

func Build

func Build() BuildInfo

Build returns the running binary's version together with its embedded VCS build metadata. It reuses the same cached source as the /info endpoint, so the CLI and the HTTP surface never disagree about what is running.

type LogBuffer

type LogBuffer struct {
	// contains filtered or unexported fields
}

LogBuffer is a bounded, concurrency-safe ring of the most recent process log lines. The command tees the standard logger into it (log.SetOutput(io.MultiWriter(os.Stderr, buf))) and hands it to the server via WithLogBuffer, so an operator can read recent server logs over the web — e.g. to check the script-worker startup lines — without shell access to the host.

It only retains the last max lines, so it is a diagnostic tail, not durable storage; it holds no secrets beyond whatever the process already logs.

func NewLogBuffer

func NewLogBuffer(max int) *LogBuffer

NewLogBuffer creates a LogBuffer retaining the last max lines (defaulting to 1000 when max <= 0).

func (*LogBuffer) Lines

func (b *LogBuffer) Lines() []string

Lines returns a copy of the buffered log lines, oldest first.

func (*LogBuffer) Write

func (b *LogBuffer) Write(p []byte) (int, error)

Write appends the (possibly multi-line) log output, keeping only the most recent max lines. It never errors and always reports the full length consumed, so it is safe as an io.Writer log sink behind io.MultiWriter.

type Option

type Option func(*Server)

Option configures a Server at construction. Options are applied in New before the run loop starts, so they set fields that are read-only afterwards.

func WithAuth

func WithAuth() Option

WithAuth turns on authentication enforcement: /api/v1 requires a valid session (except the login call, product info, and the OpenAPI doc), and managing users requires the admin role. Off by default, so an existing single-binary deployment is unaffected until an operator opts in (ADR-0044).

func WithCheckpointRetention added in v0.2.0

func WithCheckpointRetention(keep int) Option

WithCheckpointRetention sets how many published checkpoints are kept (ADR-0131). A checkpoint hard-links the SSTables it captured, so keeping every one would pin every file the store ever wrote; keeping a few leaves a fallback if the newest is corrupt. A non-positive value restores the default.

func WithCheckpoints added in v0.2.0

func WithCheckpoints(every time.Duration) Option

WithCheckpoints enables periodic recovery checkpoints at the given cadence (ADR-0131). Each one snapshots the applied state, so a restart replays only the log past it rather than from genesis — recovery time becomes a function of the cadence instead of the log's whole length.

It is purely additive: nothing is deleted, the WAL stays the source of truth, and a missing, failed, or corrupt checkpoint only makes the next recovery slower (invariant I2). A non-positive cadence leaves checkpointing off.

func WithCollabKeepaliveInterval

func WithCollabKeepaliveInterval(d time.Duration) Option

WithCollabKeepaliveInterval sets how often an idle collaboration SSE stream writes a keepalive comment, the mechanism that detects a half-open browser connection so its session participant is reaped (ADR-0140). A non-positive value restores the default (15s). Tests pass a short interval to exercise it.

func WithInboundBatchLimit

func WithInboundBatchLimit(n int) Option

WithInboundBatchLimit caps how many clio events one poll of a subscription reads and republishes (ADR-0075). A non-positive value restores the default. It bounds the burst a single poll hands the run loop; a large backlog then drains as bounded catch-up across ticks instead of one unbounded publish storm.

func WithInboundPollInterval

func WithInboundPollInterval(d time.Duration) Option

WithInboundPollInterval sets the clio inbound bridge's poll cadence (ADR-0075). A non-positive interval disables the bridge (useful in tests that drive it directly). The default is 2s.

func WithLogBuffer

func WithLogBuffer(b *LogBuffer) Option

WithLogBuffer wires the server's recent-log tail, exposed at GET /api/v1/logs, so an operator can read server logs from the web UI. The command builds the buffer, tees the standard logger into it, and passes it here.

func WithOffloadedConnectorKinds added in v0.3.0

func WithOffloadedConnectorKinds(kinds []string) Option

WithOffloadedConnectorKinds names the managed connector kinds this server must NOT serve itself, so their jobs park for an external worker instead (ADR-0168/0164).

This is the operative act of relocating a kind. The type-keyed pull refuses a job type an in-process handler is registered for — that refusal is what keeps one job from being worked twice — so a kind stays in the engine until its handler is turned off here. What is left is exactly what an unconfigured connector already does: the job parks on the activatable index until something takes it.

An unknown name is refused at startup rather than ignored. An operator who misspells a kind would otherwise believe they had relocated it while it kept running in the engine, which is the one outcome this flag exists to prevent.

func WithOpenSearchExportInterval added in v0.2.0

func WithOpenSearchExportInterval(d time.Duration) Option

WithOpenSearchExportInterval sets how often the exporter polls the log for newly durable records (ADR-0114). A non-positive value restores the default (5s). Tests pass a short interval to exercise the loop.

func WithOpenSearchExporter added in v0.2.0

func WithOpenSearchExporter(cfg opensearch.Config) Option

WithOpenSearchExporter enables the OpenSearch event exporter (ADR-0114): a WAL-tailing sink that mirrors the durable event log into an OpenSearch index so history stays searchable and can outlive engine-side retention. It is opt-in — a config with an empty URL leaves the exporter off. The endpoint, credentials, and index live in server config, never in a model.

func WithRetention added in v0.2.0

func WithRetention(maxAge time.Duration) Option

WithRetention enables history retention (ADR-0115): a finished instance whose terminal event is older than maxAge and whose events are already exported (its terminal position is at or below the safe position) is hard-deleted from the state store. A non-positive maxAge leaves the server with no default age — the opt-in default — and retention then applies only to definitions declaring their own atlas:historyTtl (ADR-0144).

func WithRetentionBatch added in v0.2.0

func WithRetentionBatch(n int) Option

WithRetentionBatch caps how many finished instances one retention sweep tick evaluates and purges (ADR-0115), bounding the work a single tick does on the run loop; a larger backlog then drains as bounded catch-up across ticks. A non-positive value restores the default.

func WithRetentionInterval added in v0.2.0

func WithRetentionInterval(d time.Duration) Option

WithRetentionInterval sets the retention sweep cadence (ADR-0115). A non-positive value restores the default. Tests pass a short interval to exercise the sweep.

func WithScriptWorker

func WithScriptWorker(jobType int32, exec script.Exec) Option

WithScriptWorker registers the interpreter for one script language (identified by its reserved job-type index, e.g. compiler.PwshJobTypeIndex), so a deployed script task in that language actually runs instead of parking on its job (ADR-0047). It executes arbitrary interpreter code on the server host, so register only the languages whose trust boundary is acceptable (the eventual isolation boundary is an external worker in the customer's environment). exec is the interpreter seam — pass script.New(script.Python) etc., or a fake in tests.

func WithSupervisedWorkers added in v0.3.0

func WithSupervisedWorkers(serverURL string, specs []SuperviseSpec, handles [][]string) Option

WithSupervisedWorkers asks the server to run these workers itself: one child process per entry, restarted while the server lives (ADR-0157 step 7).

It is an Option, so it comes from the process's own command line and from nowhere else. Nothing a request carries can add, change or name a supervised worker's command — the API can restart one that is already configured, and can do nothing else to it.

func WithSystemProcesses added in v0.2.0

func WithSystemProcesses() Option

WithSystemProcesses bootstrap-deploys Atlas's own embedded platform processes (user intake, access review, offboarding) into the protected system project at startup (ADR-0122). Opt-in — the installed binary (cmd/atlas) enables it, while the engine tests construct servers without it so a fresh instance still starts with no deployments.

func WithTracing added in v0.3.0

func WithTracing() Option

WithTracing wraps every /api/v1 route in an OpenTelemetry server span (ADR-0142). The command passes it when a collector endpoint is configured; without it the routes are registered bare, so tracing costs exactly nothing when nobody asked for it.

The engine is deliberately not traced — see the tracing package for why, and for the test that keeps it that way.

func WithUserProvisioning added in v0.2.0

func WithUserProvisioning() Option

WithUserProvisioning enables the in-process user-provisioning connector (create/set-password/disable Atlas logins) for the protected system project's processes (ADR-0123). Opt-in and off by default: it deliberately, and narrowly, reopens the ADR-0044/0049 boundary that no automated identity may manage users — so an instance keeps the human-in-the-loop ADR-0122 behavior until an operator turns this on. When off, a userConnector job has no worker and parks.

func WithWALCompaction added in v0.2.0

func WithWALCompaction() Option

WithWALCompaction deletes the WAL segments a recovery checkpoint and every consumer watermark make redundant (ADR-0131), bounding the log's disk instead of letting it grow with all history. It has no effect without WithCheckpoints: the cut is derived from a checkpoint, and compaction runs on the tick that takes one.

It is opt-in, unlike checkpointing, for the same reason history retention is (ADR-0115): this is the one step here that destroys data. The cut itself is conservative — the newest **fully verified** checkpoint (manifest and state files) at or below the store, floored by every consumer watermark, with a corrupt, foreign, or ahead-of-store checkpoint licensing no deletion at all — but a conservative cut is still a deletion, and an operator should choose it.

func WithoutDocs

func WithoutDocs() Option

WithoutDocs disables the OpenAPI document at /api/v1/openapi.json and the Scalar API explorer at /api/docs, which are otherwise served by default. Pass it when the interactive, mutating "Try it out" surface should not be exposed (ADR-0043).

func WithoutMetrics added in v0.2.0

func WithoutMetrics() Option

WithoutMetrics turns off the Prometheus exposition at /metrics (ADR-0142). It is served by default — a system meant to be operated should be observable without extra configuration, and the exposition carries only bounded-cardinality aggregates — so this is for an operator who does not want the surface open at all.

func WithoutVault

func WithoutVault() Option

WithoutVault disables the engine-internal encrypted secret vault, which is otherwise on by default (ADR-0070). With it disabled the secret endpoints return 503 and connector credentials resolve only from the environment (ADR-0041 A2). Pass it when Atlas must not custody a key or ciphertext at all.

type ResetPasswordOptions

type ResetPasswordOptions struct {
	// DataDir is the server's data directory (the same --data-dir the server
	// runs with). The user store lives in its "users" subdirectory.
	DataDir string
	// Username identifies the account to reset, matched case-insensitively.
	Username string
	// NewPassword is the replacement password. It must meet the same minimum
	// length the API enforces.
	NewPassword string
	// CreateAdmin, when set, creates a fresh enabled admin with Username if no
	// such user exists yet — a bootstrap escape hatch for an instance that has
	// users but no reachable admin. It never duplicates or re-roles an account
	// that already exists.
	CreateAdmin bool
	// Now is the unix-seconds timestamp stamped into the record, matching the
	// server's own UpdatedAt/CreatedAt unit. Injected so the operation is
	// deterministic and testable.
	Now int64
}

ResetPasswordOptions configures a single operator-driven password reset.

type ResetPasswordResult

type ResetPasswordResult struct {
	UserID   string
	Username string
	// Created is true when a new admin account was created (CreateAdmin), false
	// when an existing account's password was replaced.
	Created bool
}

ResetPasswordResult reports what a reset did.

func ResetPassword

func ResetPassword(opts ResetPasswordOptions) (ResetPasswordResult, error)

ResetPassword sets a new local password for a user in the store under opts.DataDir. It replaces only the password (and UpdatedAt); roles, email, and the Disabled flag are left as they are, so a reset re-grants access without silently changing what the account can do. With CreateAdmin and no matching user, it instead creates a fresh enabled admin.

It touches the same files the running server uses. Login reads the store from disk on every attempt, so a reset takes effect on the next sign-in without a restart; running it against a live server is safe (writes are atomic), though stopping the server first is never wrong.

type Server

type Server struct {

	// SuperviseSpecs, superviseHandles and superviseURL are what to run, read off
	// the server's own command line and held in the same order.
	SuperviseSpecs []SuperviseSpec
	// contains filtered or unexported fields
}

Server hosts the engine behind an HTTP surface. Construct it with New, mount Handler on an http.Server, and call Close to stop the run loop.

func New

func New(proc *engine.Processor, store *state.Store, dataDir string, opts ...Option) (*Server, error)

New builds a Server over an already-recovered processor and its store and starts the run-loop goroutine. dataDir is the base data directory; the durable deployment and draft sidecar stores live in its "deployments" and "drafts" subdirectories (ADR-0019). New reloads any deployments found there, re-registering them with the processor so recovered instances resolve their definition and the UI can render diagrams again. The caller retains ownership of proc and store (Close here stops only the loop, not the engine).

func (*Server) Close

func (s *Server) Close()

Close stops the run-loop goroutine. It does not close the processor, log, or store — the caller owns those.

func (*Server) Handler

func (s *Server) Handler() http.Handler

Handler returns the HTTP handler: JSON API under /api/v1, /healthz, the embedded web UI at the root, and — when docs are enabled (WithDocs) — the OpenAPI document at /api/v1/openapi.json and the Scalar API explorer at /api/docs.

func (*Server) InternalToken

func (s *Server) InternalToken() string

InternalToken returns the internal service token used by the in-process MCP adapter to authenticate its loopback calls (ADR-0049). It is empty unless auth is enabled. It is never served over any endpoint; only the constructing process reads it, to hand to its own MCP client.

type SuperviseSpec added in v0.3.0

type SuperviseSpec struct {
	ID    string
	Kinds []string
	// Connectors are built-in connector kinds this worker serves (--connector on the
	// child). A worker may serve these, model-authored job types, or both.
	Connectors []string
}

SuperviseSpec is one supervised worker as configured: what to call it and which job types it serves. Both come from the server's command line.

type User

type User struct {
	ID           string   `json:"id"`
	Username     string   `json:"username"`
	Email        string   `json:"email,omitempty"`
	DisplayName  string   `json:"displayName,omitempty"`
	Roles        []string `json:"roles"`
	Disabled     bool     `json:"disabled,omitempty"`
	Source       string   `json:"source"`
	ExternalID   string   `json:"externalId,omitempty"`
	PasswordHash string   `json:"passwordHash,omitempty"`
	CreatedAt    int64    `json:"createdAt"`
	UpdatedAt    int64    `json:"updatedAt"`
}

User is a person (or, later, an external identity) known to this Atlas instance. It is operator/config data, not engine state: it never flows through the WAL or the processor, so it lives in a durable sidecar store like forms and projects (ADR-0019/0028/0044) and touches none of the six engine invariants.

The field set is deliberately chosen so the enterprise trajectory (SSO, RBAC, deactivation, later multi-tenancy) needs no breaking change:

  • ID is a stable, opaque, never-reused primary key, decoupled from Username and Email so either can change (or be reassigned by an external IdP) without rewriting references to the user.
  • Roles is a list (RBAC-ready), not a boolean flag.
  • Source + ExternalID are the hook for external identity providers.
  • Disabled deactivates a user for lockout/offboarding without destroying the record (and the audit trail it anchors).
  • PasswordHash is a bcrypt hash for local users and empty for external ones.

Directories

Path Synopsis
Package collab implements the first slice of ADR-0140: live collaborative modeling sessions.
Package collab implements the first slice of ADR-0140: live collaborative modeling sessions.
Package httpapi holds the primitives every Atlas HTTP handler is written against: how a response is written, who the caller is, and where the request came from.
Package httpapi holds the primitives every Atlas HTTP handler is written against: how a response is written, who the caller is, and where the request came from.
Package layout generates BPMN diagram interchange (BPMN-DI) for models that carry none, and regenerates it for models whose layout a user has tangled.
Package layout generates BPMN diagram interchange (BPMN-DI) for models that carry none, and regenerates it for models whose layout a user has tangled.
Package processdoc serves process documentation (ADR-0143): a published BPMN process as a stored PDF plus the element prose it describes, its version history, and the revocable public link a reader without an account follows.
Package processdoc serves process documentation (ADR-0143): a published BPMN process as a stored PDF plus the element prose it describes, its version history, and the revocable public link a reader without an account follows.
Package runloop carries Atlas's single-writer boundary for design-time and API state.
Package runloop carries Atlas's single-writer boundary for design-time and API state.
Package sidecar is the durable-file discipline Atlas's design-time stores share.
Package sidecar is the durable-file discipline Atlas's design-time stores share.
Package token mints and validates Atlas's opaque share tokens.
Package token mints and validates Atlas's opaque share tokens.
Package vault is Atlas's engine-internal encrypted secret store: connector credentials sealed at rest with AES-256-GCM under a master key that never leaves the operator's control (ADR-0069), on by default with a generated key file when no operator key is supplied (ADR-0070).
Package vault is Atlas's engine-internal encrypted secret store: connector credentials sealed at rest with AES-256-GCM under a master key that never leaves the operator's control (ADR-0069), on by default with a generated key file when no operator key is supplied (ADR-0070).

Jump to

Keyboard shortcuts

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