Documentation
¶
Overview ¶
Package config: see doc.go for the overview.
Package config holds server configuration types and the bind-policy check. This package is intentionally small and dependency-free so the bind policy can be tested in isolation and vendored without pulling in the HTTP stack.
Index ¶
- Constants
- func EncodePoolConfig(c PoolConfig) ([]byte, error)
- func ExpandRoot(raw string) (string, error)
- func HasProjectUnder(defaultDir string) (bool, error)
- func LogClampWarnings(resolved []ResolvedPool, maxParallel, reserved int)
- func RedactBeadsURL(raw string) string
- func ResolveBeadsURL(cliFlag string, cfg UserConfig) string
- func ResolveDefaultDir(cfg UserConfig) (string, error)
- func ResolveOrchestratorConfigPath(override, workdir string) string
- func ResolvePromURL(cliFlag, envVar string, cfg UserConfig) string
- type BeadsConfig
- type BeadsSource
- type GastownShaderConfig
- type LLMConfig
- type MetricsConfig
- type OrchestratorConfig
- type PoolConfig
- type PoolEntry
- type ProjectEntry
- type ProjectKind
- type ProjectsConfig
- type ResolvedPool
- type ServeConfig
- func (c ServeConfig) BeadsOnlyManifest() string
- func (c ServeConfig) BeadsSource() BeadsSource
- func (c ServeConfig) EffectiveAuthMode() string
- func (c *ServeConfig) NormalizeListen(portFlagSet bool) error
- func (c ServeConfig) OAuthEnabled() bool
- func (c ServeConfig) ProjectRoot() string
- func (c ServeConfig) ResolveBeadsDir() (string, error)
- func (c ServeConfig) TLSEnabled() bool
- func (c ServeConfig) ValidateBindPolicy() error
- func (c ServeConfig) ValidateOAuthForMultiTenant() error
- func (c ServeConfig) ValidateTLSFlags() error
- func (c ServeConfig) ValidateWorkPlaneFlags() error
- type UserConfig
Constants ¶
const DefaultAutoDispatchFloor = 0.5
DefaultAutoDispatchFloor is the scope-level default minimum Layer 5 Selection score (spec §8.1). Per-pool overrides via [pool.<scope>.<persona>] floor = N.
const DefaultBeadsURL = "mysql://root@127.0.0.1:3307/gemba"
DefaultBeadsURL is the built-in fallback Beads server URL when neither --dolt-url nor [beads].url in config.toml is set. It matches the default Dolt server address used by `bd` and Gas Town's `gt dolt start` (port 3307, root user, gemba database).
const DefaultIdleCeilingMinutes = 30
DefaultIdleCeilingMinutes is the default reaper threshold. Pool members idle past this long are reaped (spec §4.4).
const DefaultOrchestratorConfigPath = ".gemba/orchestrator.json"
DefaultOrchestratorConfigPath is where serve looks when --orchestrator-config isn't passed. Relative paths are resolved against gemba serve's working directory (typically the rig root).
const DefaultProjectsDir = "gemba/projects"
DefaultProjectsDir is the built-in fallback location for new project directories when [projects].default_dir is absent from config.toml. The directory is not required to exist at probe time — it is created on first project ratification.
const DefaultRecycleAfterBeads = 5
DefaultRecycleAfterBeads is the safety-belt recycle counter. 0 disables; 5 is the suggested default (spec §4.4).
const DefaultReservedForManual = 1
DefaultReservedForManual is the default number of pane slots held back from the pool so a manual operator drag is never starved by a saturated pool. Operators tune via [pool] reserved_for_manual = N.
Variables ¶
This section is empty.
Functions ¶
func EncodePoolConfig ¶
func EncodePoolConfig(c PoolConfig) ([]byte, error)
EncodePoolConfig serialises a PoolConfig back to TOML in the editor's canonical scope-key shape. Used by the PUT /api/pool-config handler so the on-disk file always reflects the operator's last save in a stable, human-readable form.
Key order: top-level [pool] table → [pool.routing] → [pool.<scope>.<persona>] blocks in scope-asc, persona-asc order. Deterministic so a no-op save produces an unchanged file.
func ExpandRoot ¶
ExpandRoot tilde-expands and cleans a single root path. A leading "~" is replaced with the user's home directory; relative paths are returned cleaned but otherwise untouched (callers that need absolute resolution can wrap with filepath.Abs).
func HasProjectUnder ¶
HasProjectUnder reports whether defaultDir contains at least one project directory — i.e. a direct child directory that contains a .gemba/workspace.toml file. A missing or empty defaultDir is treated as "no projects" (returns false, nil).
func LogClampWarnings ¶
func LogClampWarnings(resolved []ResolvedPool, maxParallel, reserved int)
LogClampWarnings emits a WARN slog line for every pool whose effective size was clamped down by the MaxParallel - reserved_for_manual cap.
func RedactBeadsURL ¶
RedactBeadsURL strips the password component from a mysql:// URL before it enters a log stream. If the URL has no password or cannot be parsed, the original string is returned unchanged.
func ResolveBeadsURL ¶
func ResolveBeadsURL(cliFlag string, cfg UserConfig) string
ResolveBeadsURL returns the Beads server URL to use, applying the documented resolution order (gm-root.19):
- cliFlag — the value of --dolt-url if the operator set it explicitly
- cfg.Beads.URL — the [beads].url entry from ~/.gemba/config.toml
- DefaultBeadsURL — the built-in fallback (mysql://root@127.0.0.1:3307/gemba)
The returned string is always non-empty. Callers that need to distinguish "came from CLI" from "came from config/default" should compare the return value against cliFlag themselves.
func ResolveDefaultDir ¶
func ResolveDefaultDir(cfg UserConfig) (string, error)
ResolveDefaultDir returns the absolute path of the projects default directory, applying the resolution order from the design doc:
- UserConfig.Projects.DefaultDir if set (relative → home-relative).
- ~/gemba/projects/ (built-in default).
The returned path may not exist yet; callers that need the directory to be present must create it themselves.
func ResolveOrchestratorConfigPath ¶
ResolveOrchestratorConfigPath returns the absolute path to the orchestrator config. When override is empty, falls back to the default location (./.gemba/orchestrator.json) anchored at workdir. Returns "" when the file doesn't exist — the caller treats that as "no shader configured".
func ResolvePromURL ¶
func ResolvePromURL(cliFlag, envVar string, cfg UserConfig) string
ResolvePromURL returns the Prometheus base URL the /api/v1/metrics/series proxy should query, applying the resolution order ratified under gm-sf51:
- cliFlag — the --prom-url CLI flag if the operator set it explicitly.
- envVar — the PROM_URL environment variable (callers pass os.Getenv("PROM_URL")).
- cfg.Metrics.PromURL — the [metrics].prom_url entry from ~/.gemba/config.toml.
- "" — no Prometheus configured. The handler surfaces this as a 503 KindAdaptorDegraded so the SPA can render the "awaiting Prometheus" stub.
Unlike ResolveBeadsURL there is no built-in default: an unconfigured Prometheus is a legitimate mode (the operator may not have one), not a startup error.
Types ¶
type BeadsConfig ¶
type BeadsConfig struct {
// URL is a mysql://user[:pass]@host:port/dbname connection string
// for the Dolt server hosting beads databases. When set it takes
// precedence over the built-in DefaultBeadsURL. Empty means "fall
// back to DefaultBeadsURL." The URL is consumed only when --dolt-url
// is absent from the CLI — an explicit flag always wins.
URL string `toml:"url"`
}
BeadsConfig holds the [beads] table from config.toml — the Beads server connection settings used by gemba serve when --dolt-url is not passed on the command line (gm-root.19).
type BeadsSource ¶
type BeadsSource struct {
// Kind is one of "project-dir", "dolt-url", "noop", "unconfigured".
Kind string `json:"kind"`
// Label is the short human-friendly identifier — basename of the
// project-dir path, or the database name parsed from a dolt-url.
Label string `json:"label"`
// Detail is the redacted full source: absolute project-dir path, or
// a mysql:// DSN with the user-info segment removed. Empty for
// "unconfigured" and for dolt-urls that fail to parse.
Detail string `json:"detail,omitempty"`
}
BeadsSource describes the configured WorkPlane source in a form safe to expose to operators over the API. Credentials in --dolt-url are stripped from Detail so the SPA can render the workspace identity in the topbar without leaking secrets to anyone viewing the page or its devtools.
type GastownShaderConfig ¶
type GastownShaderConfig struct {
Rig string `json:"rig"`
RigAbbr string `json:"rig_abbr"`
IDFormat string `json:"id_format,omitempty"`
TitleFormat string `json:"title_format,omitempty"`
KindPrefixes map[string]string `json:"kind_prefixes,omitempty"`
}
GastownShaderConfig mirrors gastown.Config on the wire. Lives in this package (not internal/shader/gastown) so the loader doesn't need to import the shader package — keeps config a leaf module.
type LLMConfig ¶
type LLMConfig struct {
// Provider names the chat backend. Today the only recognized
// value is "anthropic"; an empty string means "no LLM client
// configured" and consumers fail closed with a clear diagnostic.
// Open string on purpose so a future provider doesn't break the
// loader on machines that haven't upgraded.
Provider string `toml:"provider"`
// APIKey is the credential. When empty, consumers MAY fall back
// to a provider-specific environment variable (e.g.
// ANTHROPIC_API_KEY) — that fallback is the consumer's choice,
// not the loader's.
APIKey string `toml:"api_key"`
// Model selects the model identifier (e.g. claude-3-5-sonnet-...).
// Empty means "let the consumer pick a default".
Model string `toml:"model"`
// Endpoint optionally overrides the provider's default API URL.
// Empty means "use the provider's default endpoint".
Endpoint string `toml:"endpoint"`
}
LLMConfig holds the [llm] table from config.toml — the shared "default chat client" credentials referenced by docs/design/ newproject.md §"Credential resolution". Today consumed only by the Onboarder persona (gm-root.17.10); future agents that want an in-process LLM client read the same table.
type MetricsConfig ¶
type MetricsConfig struct {
// PromURL is the base URL of a Prometheus instance scraping
// gemba's /metrics endpoint, e.g. "http://prometheus:9090".
// The proxy appends /api/v1/query_range to this base. Empty
// string means "no Prometheus configured."
PromURL string `toml:"prom_url"`
}
MetricsConfig holds the [metrics] table from config.toml — the upstream Prometheus URL the /api/v1/metrics/series proxy queries (gm-e9m0; ratified under gm-sf51, "Path A" — Prometheus proxy).
When PromURL is empty the time-series endpoint returns a 503 KindAdaptorDegraded explaining that the operator must either set PROM_URL in the environment or [metrics].prom_url in this config file. The Insights panel renders an "awaiting Prometheus" stub off that response.
type OrchestratorConfig ¶
type OrchestratorConfig struct {
// Orchestrator picks the Shader implementation. Known values:
// "gastown". Unknown values cause LoadOrchestratorConfig to
// return an error so a typo doesn't silently fall back to NopShader.
Orchestrator string `json:"orchestrator"`
// Config is the implementation-specific config blob. Each shader
// owns its own Config struct in its package.
Config json.RawMessage `json:"config"`
}
OrchestratorConfig is the file's top-level shape.
func LoadOrchestratorConfig ¶
func LoadOrchestratorConfig(path string) (*OrchestratorConfig, error)
LoadOrchestratorConfig reads + parses path. Returns os.ErrNotExist when the file is missing — callers SHOULD check for that and treat it as "no shader configured" (NopShader takes over). Any other error is structural and should fail startup.
type PoolConfig ¶
type PoolConfig struct {
// DefaultSize is the scope-level fallback pool size when a
// per-pool block doesn't override. 0 means "no pool" — Phase 0
// zero-delta. Operators opt in by setting per-pool size > 0.
DefaultSize int `toml:"default_size"`
// DefaultPersona is the server-level persona fallback for the
// three-layer routing cascade (spec §3.2 / §12 q8). Used when
// a bead has no extras `persona` field AND no
// [pool.routing.<kind>] entry covers its kind.
DefaultPersona string `toml:"default_persona"`
// DefaultFloor is the scope-level auto-dispatch score floor
// (spec §8.1). 0 falls back to DefaultAutoDispatchFloor.
DefaultFloor float64 `toml:"default_floor"`
// ReservedForManual is the count of pane slots held back from
// the pool. The clamp computes
//
// effective_size = min(declared_size, MaxParallel - ReservedForManual)
//
// 0 falls back to DefaultReservedForManual (1).
ReservedForManual int `toml:"reserved_for_manual"`
// Routing maps bead kind → persona id for the middle layer of
// the persona routing cascade (spec §3.2). When neither the
// bead's extras nor this map resolve a persona, DefaultPersona
// is used. When that's also empty, the daemon refuses
// autodispatch and waits for manual drag.
Routing map[string]string `toml:"routing"`
// Pools is the per-(scope, persona) override map. Indexed by
// scope name first (e.g. "gemba" — for the native adaptor a
// singleton; for gt the rig name) then by persona id.
Pools map[string]map[string]PoolEntry `toml:"-"`
}
PoolConfig captures the [pool] table from gemba.toml — both the scope-level defaults and the per-pool overrides. Loaded by LoadPoolConfig; the cascade is applied at daemon-construction time via Resolve.
The TOML shape (all keys optional; an unconfigured PoolConfig is the Phase 0 zero-delta default — no daemons constructed):
[pool]
default_size = 0 # opt-in; 0 = no pools
default_persona = "" # server-level persona fallback
default_floor = 0.5 # auto-dispatch score floor
reserved_for_manual = 1 # pool slots held back for manual drag
[pool.routing]
# Three-layer persona routing cascade (spec §3.2):
# bead extras `persona` field (highest)
# > [pool.routing.<kind>] (this table)
# > [pool] default_persona (lowest)
epic = "pm-claude"
bug = "engineer-claude"
decision = "pm-claude"
[pool.gemba.engineer-claude]
# Per-pool overrides. Pool key = (scope, persona). Scope is implicit
# when running a single gemba server (native adaptor); for the gt
# adaptor it's the rig name. The legacy
# [pool.<rig>.<persona>] form remains accepted with a WARN.
size = 3 # target pool members (clamped by
# MaxParallel - reserved_for_manual)
floor = 0.4 # overrides default_floor for THIS pool
recycle_after_beads = 5 # 0 disables
idle_ceiling_minutes = 30 # reaper threshold
func DecodePoolConfig ¶
func DecodePoolConfig(body []byte) (PoolConfig, error)
DecodePoolConfig parses a TOML body. Exposed for testing without a real file on disk.
func LoadPoolConfig ¶
func LoadPoolConfig(path string) (PoolConfig, error)
LoadPoolConfig reads + parses path. Returns a zero-value PoolConfig (Phase 0 zero-delta) when path is empty or the file does not exist. Other I/O / parse errors propagate so the operator sees a misconfigured file as an actionable startup failure.
func (PoolConfig) Resolve ¶
func (c PoolConfig) Resolve(maxParallel int) []ResolvedPool
Resolve walks every (scope, persona) entry, applies the scope-level default cascade, and clamps Size against MaxParallel - ReservedForManual. Pools with effective size <= 0 are dropped. Returns a deterministic-order slice (scope asc, persona asc).
func (PoolConfig) ResolvePersona ¶
func (c PoolConfig) ResolvePersona(beadPersona, beadKind string) (string, bool)
ResolvePersona implements the three-layer persona routing cascade (spec §3.2). Highest precedence first:
- Bead extras `persona` field (passed as beadPersona)
- [pool.routing.<kind>] map for the bead's kind
- [pool] default_persona
Returns ("", false) when no layer resolves.
type PoolEntry ¶
type PoolEntry struct {
// Size is the declared target count of pool members. Subject
// to the MaxParallel clamp (see EffectiveSize). 0 means "fall
// back to PoolConfig.DefaultSize"; -1 means "explicitly disable
// this pool".
Size int `toml:"size"`
// AgentType names the agents.toml entry the daemon dispatches
// against (e.g. "claude", "shell-only"). Threaded through to
// SessionPrompt as `gemba:agent_type` so the native adaptor's
// pane-reuse path can find a slot. Empty defaults to "claude".
AgentType string `toml:"agent_type"`
// Floor overrides PoolConfig.DefaultFloor for this pool.
Floor float64 `toml:"floor"`
// RecycleAfterBeads bounds profile staleness.
RecycleAfterBeads int `toml:"recycle_after_beads"`
// IdleCeilingMinutes is the reaper threshold for this pool's
// members.
IdleCeilingMinutes int `toml:"idle_ceiling_minutes"`
// MinIntervalPerSession is the per-session rate limit.
MinIntervalPerSession int `toml:"min_interval_per_session_seconds"`
// MaxConcurrent is the auto-dispatch concurrency cap for this
// pool.
MaxConcurrent int `toml:"max_concurrent"`
}
PoolEntry holds the per-(scope, persona) overrides. Zero values cascade to the scope-level defaults.
type ProjectEntry ¶
type ProjectEntry struct {
// Name is the directory basename — the project's human-readable
// identifier (e.g. "my-project").
Name string
// Path is the absolute path to the project root (the directory that
// contains .gemba/workspace.toml or a .beads/ DB).
Path string
// Kind classifies the entry's setup state. See ProjectKind for the
// three values.
Kind ProjectKind
}
ProjectEntry is one discovered project — a direct child of one of the configured discovery roots that has either a beads DB or a .gemba/workspace.toml.
func ListAllProjects ¶
func ListAllProjects(defaultDir string, extraRoots []string) ([]ProjectEntry, error)
ListAllProjects scans defaultDir + every entry in extraRoots, classifies each direct-child directory, and returns the deduped union (gm-root.17.14). Order: defaultDir results first, then each extra_root in order. Dedup key is the canonicalized absolute path; ties prefer the first-seen entry.
Missing or unreadable roots are NOT fatal — they produce a slog warning and the scan continues. Only the defaultDir itself surfaces an error to the caller (via ListProjectsUnder), preserving the existing /api/v1/projects contract.
func ListProjectsUnder ¶
func ListProjectsUnder(defaultDir string) ([]ProjectEntry, error)
ListProjectsUnder enumerates projects under defaultDir. A project is a direct child directory whose ClassifyProject returns a non-empty kind — i.e. it contains a beads DB and/or a .gemba/workspace.toml. Results are returned in the order ReadDir provides them (alphabetical by name on most filesystems). A missing or empty defaultDir returns an empty slice and nil error. Any other I/O error is returned so the caller can decide how to surface it.
This is the shared scanning primitive used by both HasProjectUnder (cold-start gate, gm-root.17.4) and the /api/v1/projects list endpoint (gm-root.18 / gm-root.17.14).
type ProjectKind ¶
type ProjectKind string
ProjectKind classifies a discovered project entry by its setup completeness (gm-root.17.14). The picker UI dispatches on this to render either an "open" affordance (KindComplete) or a "finish setup" affordance (KindNeedsWorkspace, KindNeedsRepo).
const ( // KindComplete: the directory has both a beads DB (a `.beads/` // subdir, by convention) AND a `.gemba/workspace.toml`. The picker // can open this project directly. KindComplete ProjectKind = "complete" // KindNeedsWorkspace: the directory has a beads DB but no // `.gemba/workspace.toml`. The picker invites the operator to // finish setup via /v1/projects/bind. KindNeedsWorkspace ProjectKind = "needs_workspace" // KindNeedsRepo: the directory has a beads DB AND a // `.gemba/workspace.toml` but is not a git repo (no `.git/`). The // picker invites the operator to bind to an existing repo or run // `git init` via /v1/projects/bind. KindNeedsRepo ProjectKind = "needs_repo" )
func ClassifyProject ¶
func ClassifyProject(dir string) ProjectKind
ClassifyProject returns the ProjectKind for dir, or "" if dir does not look like any kind of project (no beads DB and no .gemba/workspace.toml). Callers MUST check for "" before treating the result as a project.
type ProjectsConfig ¶
type ProjectsConfig struct {
// DefaultDir is the parent directory under which new projects are
// created and under which gemba scans for existing projects at
// cold-start. A relative path is resolved against the user's home
// directory. Empty string means "use the built-in default"
// (~/gemba/projects/).
DefaultDir string `toml:"default_dir"`
// ExtraRoots are additional directories whose immediate children
// are scanned for projects (gm-root.17.14). Each entry behaves like
// DefaultDir at scan time: every direct child that contains a beads
// DB or a .gemba/workspace.toml is reported. Tilde-prefixed paths
// are expanded; relative paths are taken as-is (filepath.Clean
// applied) so callers can point at fully-qualified locations like
// "/srv/team-projects". Missing or unreadable roots produce a log
// warning at scan time but do not fail the request.
ExtraRoots []string `toml:"extra_roots"`
}
ProjectsConfig holds the [projects] table from config.toml.
type ResolvedPool ¶
type ResolvedPool struct {
// Scope is the first axis of the pool key. For native it's the
// implicit local server scope (e.g. "gemba"); for gt it's a rig
// name. Renamed from `Rig` in gm-s47n.16 (spec §2). For one
// release the Rig() method below returns the same string so
// existing callers can migrate without an immediate flag day.
Scope string
Persona string
AgentType string // "claude" default
SizeDeclared int // before clamp
SizeEffective int // after clamp (min with cap)
Floor float64 // auto-dispatch score floor
RecycleAfterBeads int
IdleCeilingMinutes int
MinIntervalSeconds int
MaxConcurrent int
// ClampActivated is true when SizeEffective < SizeDeclared
// because the MaxParallel-reserved_for_manual cap fired.
ClampActivated bool
}
ResolvedPool is one daemon's worth of effective config — the per-pool overrides cascaded against the scope-level defaults and (for Size) clamped against MaxParallel.
type ServeConfig ¶
type ServeConfig struct {
Listen string
Port int
Open bool
AuthMode string
// AuthToken, when non-empty, is the plaintext bearer compared against
// incoming requests. Set it for tests and for --auth-token overrides;
// production runs leave it empty and let the server verify against the
// hashed file at AuthTokenHashPath.
AuthToken string
// AuthTokenHashPath points at the argon2id PHC hash file written by
// `gemba auth token rotate`. Defaults to ~/.gemba/tokens/primary.
AuthTokenHashPath string
// AuthBootstrapToken is an ephemeral one-time token used by the SPA to
// exchange a launch URL fragment for the normal session cookie. It is
// generated at process start for token-auth deployments and is never
// persisted.
AuthBootstrapToken string
AuthBootstrapExpiresAt time.Time
TLSCert string
TLSKey string
TLSSelfSigned bool
// City is the path to a Gas City workspace (preferred).
// Town is the path to a Gas Town HQ (legacy; kept for back-compat).
// Exactly one may be set; an empty workspace defers to auto-detect.
City string
Town string
// ProjectDir is the project worktree root the bd WorkPlane adaptor
// targets. It is the preferred operator-facing name for the local
// project source selected by `gemba serve --project-dir`.
ProjectDir string
// BeadsDir is the legacy name for ProjectDir. It is retained so older
// scripts using `--beads-dir` and GEMBA_BEADS_DIR keep working. After
// resolution, serve normalizes BeadsDir to the resolved project root
// because the bd adaptor still consumes this field.
BeadsDir string
// DoltURL is a mysql://user[:password]@host:port/dbname connection
// string pointing at a Dolt server that already hosts a beads
// database. When set, the server skips the bd CLI path and opens
// a direct SQL connection instead (gm-0fd). Mutually exclusive with
// BeadsDir. Writes are enabled unless BeadsReadOnly is true or the
// Dolt user/server denies them.
DoltURL string
// BeadsURLSource records where the resolved DoltURL came from after
// applyBeadsURLDefault runs (gm-ygwe). Values:
//
// "cli" — the operator passed --dolt-url (or --project-dir) explicitly
// "config" — populated from ~/.gemba/config.toml's [beads].url
// "default" — populated from the built-in DefaultBeadsURL fallback
// "" — applyBeadsURLDefault has not been called yet
//
// The "default" branch is the cold-start danger zone: a stray local
// Dolt database named "gemba" would otherwise silently bind, surfacing
// stale work to a fresh operator with no projects on this machine. The
// cold-start gate (registerWorkPlane / coldStartShouldSkipBind) refuses
// to bind a WorkPlane when the source is "default" and no project
// marker exists under the configured default_dir.
BeadsURLSource string
// Noop, when true, binds the in-memory noop reference adaptors for
// both the WorkPlane and OrchestrationPlane (gm-e3.7). Intended for
// SPA development, demos, and conformance smoke-testing. Mutually
// exclusive with BeadsDir / DoltURL because the noop WorkPlane is
// itself a complete adaptor — there is no backend behind it.
// When set, --orchestration is forced to "noop".
Noop bool
// BeadsOnly, when true, runs Gemba as a Beads viewer/manager without
// binding an OrchestrationPlane or requiring a project. Mutating Beads
// actions append an informational JSONL manifest instead of dispatching
// sessions.
BeadsOnly bool
// BeadsReadOnly implies BeadsOnly and blocks every Beads mutation at
// the server and adaptor boundary. URL sources are otherwise writable
// when the configured Dolt user has write permissions.
BeadsReadOnly bool
// Restart permits serve to restart local helper processes when needed
// to enforce a selected mode, for example bd Dolt readonly posture.
Restart bool
// BeadsOnlyManifestPath optionally overrides the JSONL manifest path
// used by BeadsOnly mode. Empty defaults to .gemba/session-manifest.jsonl
// under BeadsDir when available, otherwise the current working directory.
BeadsOnlyManifestPath string
// ConfigPath is an explicit gemba.toml override. Empty means "probe
// the standard locations." File loading lands with a later bead;
// serve threads the path through today so the flag surface is stable.
ConfigPath string
// OrchestratorConfigPath points at a JSON file describing the
// active orchestrator's shader settings (gm-root.4). Empty means
// "probe ./.gemba/orchestrator.json"; missing → no shader, NopShader
// takes over. Set explicitly via --orchestrator-config.
OrchestratorConfigPath string
// Orchestration selects which OrchestrationPlaneAdaptor to bind at
// server startup (gm-native.2). The CLI flag defaults to "native"
// so /coach + /api/operational-context return data on fresh
// installs. Supported values:
// "native" — direct-to-shell adaptor (gm-native), default
// "none" or "" — no orchestration plane; SPA hides
// agent-management surfaces, /coach 503s
// Future values may include "gt" (gastown) etc.; the single-slot
// invariant (gm-native.1) means only one may be active at a time.
Orchestration string
// TerminalBackend picks the shell multiplexer backend when
// Orchestration=="native". Empty or "auto" detects via env
// (TMUX / TERM_PROGRAM). Accepted: auto | tmux | iterm | terminal.
TerminalBackend string
// AgentsRegistryPath points to the TOML file the native
// OrchestrationPlane loads its agent-type registry from
// (gm-native.6). Empty defaults to ".gemba/agents.toml" in the
// workspace. Missing file is non-fatal: the SPA's picker shows an
// empty roster until the operator drops a registry in.
AgentsRegistryPath string
// WorktreesDir overrides the default worktree parent dir for the
// native adaptor's StartSession provisioner. Empty defaults to a
// sibling "worktrees" directory next to the repo root.
WorktreesDir string
DangerouslySkipPermissions bool
// ColdStartRedirect, when true, tells the server to redirect the SPA
// root to /new because bd is present but no project was detected under
// the configured default_dir (gm-root.17.4). Set by runServe before
// NewRouter is called; not a CLI flag.
ColdStartRedirect bool
// PromURL is the base URL of an upstream Prometheus instance the
// /api/v1/metrics/series proxy queries (gm-e9m0; D4 — gm-sf51).
// Resolution order is CLI flag > PROM_URL env > [metrics].prom_url
// in ~/.gemba/config.toml > "" (unconfigured). An empty value is
// a legitimate run mode: the time-series endpoint returns a
// 503 KindAdaptorDegraded telling the SPA to render the
// "awaiting Prometheus" stub. Resolution happens once at boot,
// inside applyPromURLDefault, so handlers see the final value.
PromURL string
// CORSAllowedOrigins enables CORS on /api/* and /events when
// non-empty. CORS is OFF BY DEFAULT (gm-e4.1) — same-origin SPA
// requests don't need it, and a public CORS surface is a footgun
// (operators serve gemba on loopback by default). Wildcard "*"
// allowed for trusted dev setups; production should list explicit
// origins. Methods + headers are derived from the Gemba HTTP
// surface (GET / POST / PATCH / DELETE; X-GEMBA-Confirm).
CORSAllowedOrigins []string
// EmbeddedDolt, when true, tells `gemba serve` to spawn and
// supervise its own dolt sql-server subprocess instead of
// connecting to an externally-managed one (gm-o9t8.1.2.3). The
// runtime default lands in applyEmbeddedDoltDefault — true when
// no --dolt-url is supplied, false otherwise. Both modes can
// coexist; the bd CLI / Dolt adaptors don't care which one is
// providing the SQL endpoint.
EmbeddedDolt bool
// EmbeddedDoltSet records whether --embedded-dolt was passed
// explicitly on the command line. When false the runtime picks
// the default ("true if no --dolt-url else false").
EmbeddedDoltSet bool
// DoltDataDir is the on-disk path the embedded dolt sql-server
// uses as its working dir. Empty defaults to "<cwd>/data/dolt"
// at startup. Created with 0700 on first launch.
DoltDataDir string
// DoltEmbeddedDB is the name of the MySQL database the dolt
// adaptor will open against the embedded supervisor when
// --embedded-dolt is on and no external --dolt-url is set
// (gm-o9t8.1.7). Defaults to "gemba" at startup. Has no effect
// when --dolt-url is set (external Dolt selects its own db).
DoltEmbeddedDB string
// WorkspacesRoot is the on-disk parent directory that holds per-
// workspace <wsid>/repo/ trees (gm-o9t8.1.16). The router's
// /api/v1/workspaces/{wsid}/diff handler resolves
// <WorkspacesRoot>/<wsid>/repo/ to find the working tree it
// streams `git diff` from. Empty → /diff returns 503
// adaptor_not_configured. Defaults at startup to
// "<dirname(DoltDataDir)>/workspaces" so the layout follows the
// data-dir convention (see internal/server/workspacelayout).
WorkspacesRoot string
// VaultPath is the on-disk file backing the secrets vault
// (gm-o9t8.3.7). Empty defaults at startup to
// "<dirname(DoltDataDir)>/vault.db" — the same data-dir
// convention as WorkspacesRoot. The 32-byte KEK is read from
// the GEMBA_VAULT_KEY env var; absent → ephemeral key + WARN.
VaultPath string
// OAuthGitHubClientID is the GitHub OAuth app's client_id used by
// the device-flow login (gm-o9t8.3.5.1). Empty disables OAuth on
// the server; the legacy single-user PAT path keeps working.
//
// Resolution: --oauth-github-client-id flag wins; otherwise the
// GEMBA_OAUTH_GITHUB_CLIENT_ID env var.
OAuthGitHubClientID string
// OAuthGitHubClientSecret is the GitHub OAuth app's client_secret.
// Treated as a secret: never logged, never echoed in help output,
// and redacted in any error string that surfaces it. Required
// together with OAuthGitHubClientID for OAuthEnabled to flip true.
OAuthGitHubClientSecret string
// MultiTenantMode is the constitution.multi_tenant_mode flag. When
// true, OAuth MUST be configured (both client id and secret) or
// the server refuses to boot. Single-user / DefaultTenant mode
// leaves this false and the PAT path is sufficient.
MultiTenantMode bool
// OAuthGitHubAllowedOrgs lists GitHub organization logins permitted
// to log in via the device flow (gm-o9t8.4.4.1). Empty disables the
// org check — any successfully-authenticated GitHub user passes.
// Resolution: --oauth-github-allowed-orgs (comma-separated) > env
// GEMBA_OAUTH_GITHUB_ALLOWED_ORGS.
OAuthGitHubAllowedOrgs []string
// OAuthGitHubAllowedTeams lists "org/team" pairs permitted to log
// in (gm-o9t8.4.4.1). Empty disables the team check. Resolution:
// --oauth-github-allowed-team (repeatable). Membership in any
// listed org OR team is sufficient (union semantics).
OAuthGitHubAllowedTeams []string
// BillingMetersEnable controls whether the in-memory billing
// aggregator is wired into the router on boot (gm-o9t8.4.1.1).
// Defaults to true; offline test mode can disable to drop the
// /usage endpoint to its 503 adaptor_not_configured branch.
BillingMetersEnable bool
// PoolConfigPath points at a TOML file declaring the
// [pool.<rig>.<persona>] blocks that drive the auto-dispatch
// daemon (gm-s47n.12, spec §3.3). Empty means "no pool config" —
// the Phase 0 zero-delta default; no daemons constructed.
// Resolution order: explicit --pool-config flag > probe
// .gemba/pool.toml in cwd > "" (no pools).
//
// Pool sizing CROSS-REFERENCES MaxParallel (the orchestration
// manifest's per-host pane cap). The clamp computes
//
// effective_size = min(declared_size, MaxParallel - reserved_for_manual)
//
// reserved_for_manual defaults to 1 — at least one slot is held
// back from the pool so a human's manual drag is never starved
// by a saturated pool. When the clamp activates, gemba logs a
// WARN naming declared/cap/effective sizes and the SPA's
// /api/pools endpoint surfaces both numbers. See
// docs/deployment/pool-sizing.md for the full operator guide.
PoolConfigPath string
// WorkspacesBootstrap controls the gm-o9t8.2.4 first-boot migration
// path: when true (the default), the server scans WorkspacesRoot at
// startup and registers a workspaces row for every on-disk project
// dir keyed to the default tenant. Idempotent — only previously
// unregistered slugs are inserted. Operators who manage the
// registry externally can set this false to opt out.
WorkspacesBootstrap bool
// QuotaEnforce toggles the tier-aware quota + rate-limit
// middleware (gm-o9t8.4.2.1). Defaults to true so multi-tenant
// rigs ship with tier enforcement on; --quota-enforce=false lets
// operators disable the gate during incident response or for
// dev rigs where the limits would just be friction. The
// in-process BucketStore is wiped on shutdown — production
// deployments are expected to swap in a Redis-backed store.
QuotaEnforce bool
}
ServeConfig captures every flag `gemba serve` accepts. Held on the CLI side and passed into the server; keep it a pure data type.
func (ServeConfig) BeadsOnlyManifest ¶
func (c ServeConfig) BeadsOnlyManifest() string
BeadsOnlyManifest resolves the append-only JSONL ledger path for Beads-only mode. The path is still returned when BeadsOnly=false so status/config endpoints can show a stable default in tests; callers decide whether to use it.
func (ServeConfig) BeadsSource ¶
func (c ServeConfig) BeadsSource() BeadsSource
BeadsSource derives the displayable WorkPlane identity from the configured flags. The mutual-exclusion contract (see ValidateWorkPlaneFlags) means at most one of BeadsDir / DoltURL is set in a successfully booted server; the "unconfigured" branch is reached only by tests that build a ServeConfig directly.
func (ServeConfig) EffectiveAuthMode ¶
func (c ServeConfig) EffectiveAuthMode() string
EffectiveAuthMode returns the auth mode that will actually be applied, after defaulting to "none" and normalizing.
func (*ServeConfig) NormalizeListen ¶
func (c *ServeConfig) NormalizeListen(portFlagSet bool) error
NormalizeListen splits c.Listen if it is in host:port form (e.g. "127.0.0.1:7666" or "[::1]:7666"), routing the port half into c.Port and reducing c.Listen to the host. This lets users type the universal Unix host:port idiom for --listen instead of having to remember --port is a separate flag.
portFlagSet should be true if the caller (CLI) saw an explicit --port flag; supplying both forms is an error so precedence is never ambiguous.
func (ServeConfig) OAuthEnabled ¶
func (c ServeConfig) OAuthEnabled() bool
OAuthEnabled reports whether the GitHub OAuth device-flow login is configured. The login endpoints and callback route return 503 when this returns false. Derived (not a flag): true iff both OAuthGitHubClientID and OAuthGitHubClientSecret are non-empty.
func (ServeConfig) ProjectRoot ¶
func (c ServeConfig) ProjectRoot() string
func (ServeConfig) ResolveBeadsDir ¶
func (c ServeConfig) ResolveBeadsDir() (string, error)
ResolveBeadsDir validates the configured local project directory and returns the directory `bd` should be invoked from. The bd CLI discovers its workspace by walking up from cwd looking for `.beads/`, so the returned path is the rig root: either ProjectDir itself (when it contains `.beads/`) or its parent (when ProjectDir *is* `.beads/`). Accepting both forms matches how users talk about projects in practice — `--project-dir ~/gt/gemba` and `--project-dir ~/gt/gemba/.beads` both mean the same project.
An empty local project dir returns ("", nil); callers decide whether that's an error. Mutual exclusion with --dolt-url is handled separately by ValidateWorkPlaneFlags.
func (ServeConfig) TLSEnabled ¶
func (c ServeConfig) TLSEnabled() bool
TLSEnabled reports whether the server should bind an HTTPS listener based on the configured TLS flags.
func (ServeConfig) ValidateBindPolicy ¶
func (c ServeConfig) ValidateBindPolicy() error
ValidateBindPolicy refuses to start the server if the configuration would expose the API on a non-loopback interface without authentication.
This is the single most important security check. See gm-e5.1 for the full rationale and test matrix.
func (ServeConfig) ValidateOAuthForMultiTenant ¶
func (c ServeConfig) ValidateOAuthForMultiTenant() error
ValidateOAuthForMultiTenant enforces the multi-tenant gate: when MultiTenantMode is true the OAuth app config MUST be set or the server refuses to boot. Single-user installs (MultiTenantMode=false) pass through unconditionally — OAuth stays opt-in.
func (ServeConfig) ValidateTLSFlags ¶
func (c ServeConfig) ValidateTLSFlags() error
ValidateTLSFlags enforces the TLS selector contract: either pass --tls-cert AND --tls-key together (operator-supplied chain), or pass --tls-self-signed alone (server generates an ephemeral cert at boot), or pass nothing (plain HTTP). Mixing the two paths or supplying only one half of the cert/key pair is an error so the operator sees the problem before the listener opens.
Per gm-root locked decision #8 (AUTH): TLS is the recommended transport for any non-loopback bind, and the self-signed mode exists so an operator can stand up an HTTPS listener with one flag while they wait for a real cert.
func (ServeConfig) ValidateWorkPlaneFlags ¶
func (c ServeConfig) ValidateWorkPlaneFlags() error
ValidateWorkPlaneFlags enforces the WorkPlane selector contract: exactly one of --project-dir and --dolt-url must be set. Both select a beads backend but by different means; a server asked to honor both would have to pick one and ignore the other, and a server with neither has no WorkPlane to serve at all. In both error cases we fail before any further startup work so the operator gets a single actionable message instead of a later cryptic failure from the adaptor layer.
type UserConfig ¶
type UserConfig struct {
Projects ProjectsConfig `toml:"projects"`
LLM LLMConfig `toml:"llm"`
Beads BeadsConfig `toml:"beads"`
Metrics MetricsConfig `toml:"metrics"`
}
UserConfig mirrors the shape of ~/.gemba/config.toml. Only the fields relevant to startup-time decisions live here; the file may contain additional keys that are silently ignored.
func LoadUserConfig ¶
func LoadUserConfig(override string) (UserConfig, error)
LoadUserConfig reads ~/.gemba/config.toml (or the path pointed to by override if non-empty) and returns the parsed config. A missing file is not an error — callers receive a zero-value UserConfig and can apply their own defaults. Any other I/O or parse error is returned so the caller can decide how to surface it.