config

package
v0.0.0-...-13f862e Latest Latest
Warning

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

Go to latest
Published: May 22, 2026 License: MIT Imports: 13 Imported by: 0

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

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

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

View Source
const DefaultIdleCeilingMinutes = 30

DefaultIdleCeilingMinutes is the default reaper threshold. Pool members idle past this long are reaped (spec §4.4).

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

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

View Source
const DefaultRecycleAfterBeads = 5

DefaultRecycleAfterBeads is the safety-belt recycle counter. 0 disables; 5 is the suggested default (spec §4.4).

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

func ExpandRoot(raw string) (string, error)

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

func HasProjectUnder(defaultDir string) (bool, error)

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

func RedactBeadsURL(raw string) string

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):

  1. cliFlag — the value of --dolt-url if the operator set it explicitly
  2. cfg.Beads.URL — the [beads].url entry from ~/.gemba/config.toml
  3. 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:

  1. UserConfig.Projects.DefaultDir if set (relative → home-relative).
  2. ~/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

func ResolveOrchestratorConfigPath(override, workdir string) string

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:

  1. cliFlag — the --prom-url CLI flag if the operator set it explicitly.
  2. envVar — the PROM_URL environment variable (callers pass os.Getenv("PROM_URL")).
  3. cfg.Metrics.PromURL — the [metrics].prom_url entry from ~/.gemba/config.toml.
  4. "" — 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:

  1. Bead extras `persona` field (passed as beadPersona)
  2. [pool.routing.<kind>] map for the bead's kind
  3. [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.

Jump to

Keyboard shortcuts

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