Documentation
¶
Overview ¶
Package server wires the HTTP surface: chi router, SPA fallback, JSON API, and the SSE hub. NewRouter accepts the embedded SPA filesystem as a parameter so this package has no compile-time dependency on the embed declaration in cmd/gemba.
For v0 of the scaffold, handlers return placeholder 501 responses. The real surface lands with gm-e2.6.
gm-o9t8.3.5 — server-side GitHub OAuth surface.
This file wires two routes:
GET /api/v1/auth/oauth/github/callback — placeholder/status endpoint. The device flow doesn't need a real redirect target, so when OAuth is configured we 200 with a tiny diagnostic body ({status:"ok", flow:"device"}); when OAuth is disabled we 503 with oauth_not_configured.
POST /api/v1/auth/oauth/github/login — the device-flow exchange. The CLI completes the device flow against api.github.com on its own and POSTs the resulting GitHub access_token here. We call GET https://api.github.com/user to validate the token + recover the GitHub identity, find-or- create a tenant in the tenant.Store, mint a 32-byte gemba bearer, hash it with argon2id, persist (tenant_id, github_id, hash) and return the plaintext bearer + tenant id to the caller. The plaintext bearer is never persisted server-side.
gm-o9t8.3.5.2/3.5.5 — auth_tokens persistence (with revoke + rotate).
The OAuth login handler mints a 32-byte gemba bearer, hashes it with argon2id, and stores the (tenant_id, github_id, device_label, hash) tuple in the auth_tokens table. The plaintext is never persisted — it's returned to the CLI exactly once and never recoverable from disk.
Two stores are provided:
- MemTokenStore — in-memory; used by tests and the no-Dolt fallback
- SQLTokenStore — Dolt/MySQL backed; cmd/gemba serve attaches one against the bd Dolt pool when --dolt-url is configured.
Verification is O(n) over the stored hashes because argon2id is per-record. n is small for v1 (one row per (tenant, device)); when it grows past the comfortable scan threshold we add a lookup index or move bearer verification to a stateless JWT.
gm-o9t8.3.5.5 adds soft-delete (revoked_at), last_used_at touch, and an opaque per-token id so the management surface (list, revoke, rotate) can address rows without leaking either the plaintext or the argon2id hash.
Package server wires the HTTP surface. See doc.go for the package-level overview.
/api/spec-kit/* exposes Spec Kit planning artifacts as a first-class refinement source and syncs approved artifacts into Beads.
Index ¶
- Constants
- Variables
- func NewPoolWiring(op core.OrchestrationPlaneAdaptor, wp core.WorkPlane, pool config.ResolvedPool, ...) *poolWiring
- func NewTokenID() (string, error)
- func NewWorkspaceLifecycle(cfg WorkspaceLifecycleConfig) *workspaceLifecycleHandler
- func OpenAPISpec() []byte
- func SetGTRunner(r gtRunner)
- type AdminSession
- type AdminSessionLister
- type AdoptableLister
- type AttachConfig
- type BeadsPinger
- type BindProjectRequest
- type BootstrapAnalysis
- type BootstrapAnalysisSeed
- type BootstrapCommitResult
- type BootstrapFinding
- type BootstrapPlanItem
- type BootstrapProgressFrame
- type BootstrapSource
- type BootstrapStore
- type ChangeRef
- type CommandRunner
- type ComponentResult
- type DoltProber
- type DoltProberFunc
- type DoltReadyChecker
- type DoltStatusReporter
- type DraftBead
- type DraftEpic
- type DraftMilestone
- type EgressAuditor
- type EgressTemplateProvider
- type EmbeddedDoltStatus
- type FakeS3Uploader
- type FilesystemBackend
- type MemTokenStore
- func (m *MemTokenStore) List(_ context.Context, tenantID string) ([]TokenInfo, error)
- func (m *MemTokenStore) Put(_ context.Context, rec TokenRecord) error
- func (m *MemTokenStore) Records() []TokenRecord
- func (m *MemTokenStore) Revoke(_ context.Context, tenantID, tokenID string) error
- func (m *MemTokenStore) Rotate(_ context.Context, tenantID, tokenID string) (string, string, string, error)
- func (m *MemTokenStore) VerifyToken(_ context.Context, plaintext string) (tenant.ID, bool, error)
- type MemWorkspaceLifecycleStore
- func (m *MemWorkspaceLifecycleStore) Create(_ context.Context, w WorkspaceRecord) error
- func (m *MemWorkspaceLifecycleStore) Get(_ context.Context, id string) (WorkspaceRecord, error)
- func (m *MemWorkspaceLifecycleStore) List(_ context.Context, tenantID string) ([]WorkspaceRecord, error)
- func (m *MemWorkspaceLifecycleStore) Update(_ context.Context, w WorkspaceRecord) error
- type NewProjectRatifier
- type NewProjectSession
- type NewProjectState
- type NewProjectStore
- type NonceCache
- type OrchestrationPC
- type OrchestrationSc
- type OrchestrationState
- type PersonaFile
- type PerspectiveContribution
- type PerspectiveContributionKind
- type PoolConfigEnvelope
- type PoolMember
- type PoolState
- type PoolStateEntry
- type PoolWiring
- type RatifierConfig
- type RatifyError
- type RatifyResponse
- type RecycleWriter
- type Router
- func (r *Router) ActiveProject() string
- func (r *Router) AttachAdminConsole(emails []string, sessionLister AdminSessionLister, ...)
- func (r *Router) AttachAuditEmit(fn func(ctx context.Context, event string, payload any) error)
- func (r *Router) AttachAuditor(a audit.Auditor)
- func (r *Router) AttachBootstrap(store BootstrapStore)
- func (r *Router) AttachDoltSupervisor(s DoltReadyChecker)
- func (r *Router) AttachEgressAuditor(a EgressAuditor)
- func (r *Router) AttachEgressStore(s egress.Store)
- func (r *Router) AttachEgressTemplates(p EgressTemplateProvider)
- func (r *Router) AttachHealthChecks(vault VaultProber, dolt DoltProber, auditDir string)
- func (r *Router) AttachMetricsHTTPClient(c *http.Client)
- func (r *Router) AttachMetricsHandler(h http.Handler)
- func (r *Router) AttachNewProject(store NewProjectStore, turner SkillTurner, ratifier NewProjectRatifier)
- func (r *Router) AttachOAuth(o *oauth.OAuth)
- func (r *Router) AttachOAuthTokenStore(s TokenStore)
- func (r *Router) AttachOrgGates(s *oauth.OrgGateStore)
- func (r *Router) AttachPersonaDispatcher(d *persona.Dispatcher, sr *corepersona.SkillRegistry, pr *corepersona.Registry)
- func (r *Router) AttachPersonasDir(dir string)
- func (r *Router) AttachPhase(store phase.Store)
- func (r *Router) AttachPoolConfig(path string, maxParallel, reservedForManual int)
- func (r *Router) AttachPools(resolved []config.ResolvedPool)
- func (r *Router) AttachProjects(cfg AttachConfig)
- func (r *Router) AttachQuotaLimiter(l *quota.Limiter)
- func (r *Router) AttachTenantStore(s tenant.Store)
- func (r *Router) AttachTenantWorkspaceCount(fn tenantWorkspaceCountFn)
- func (r *Router) AttachTierQuotaMiddleware(mw func(http.Handler) http.Handler)
- func (r *Router) AttachUsageAggregator(a *billing.Aggregator)
- func (r *Router) AttachVault(v vault.Vault)
- func (r *Router) AttachWalk(store walk.Store, sources walk.Sources)
- func (r *Router) AttachWalkSummary(s *walk_summary.Skill)
- func (r *Router) AttachWorkflowClient(c *workflow.Client)
- func (r *Router) AttachWorkspaceLifecycle(h *workspaceLifecycleHandler)
- func (r *Router) AttachWorkspaceRegistry(reg workspaces.Registry)
- func (r *Router) AttachWorkspacesRoot(root string)
- func (r *Router) Close()
- func (r *Router) EventsHub() *events.Hub
- func (r *Router) HealthBus() *registry.HealthBus
- func (r *Router) Host() *api.Host
- func (r *Router) InstanceID() string
- func (r *Router) PhaseStore() phase.Store
- func (r *Router) QuotaMiddleware(next http.Handler) http.Handler
- func (r *Router) SeedWorkspaceEgress(ctx context.Context, store egress.Store, wsid, name string) (egress.Template, error)
- func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request)
- func (r *Router) SetGitHubUserFetcherForTest(fn func(ctx context.Context, token string) (oauth.GitHubUser, error))
- func (r *Router) StartHealthBus()
- type S3Uploader
- type SQLTokenStore
- func (s *SQLTokenStore) List(ctx context.Context, tenantID string) ([]TokenInfo, error)
- func (s *SQLTokenStore) Migrate(ctx context.Context) error
- func (s *SQLTokenStore) Put(ctx context.Context, rec TokenRecord) error
- func (s *SQLTokenStore) Revoke(ctx context.Context, tenantID, tokenID string) error
- func (s *SQLTokenStore) Rotate(ctx context.Context, tenantID, tokenID string) (string, string, string, error)
- func (s *SQLTokenStore) VerifyToken(ctx context.Context, plaintext string) (tenant.ID, bool, error)
- type SkillTurner
- type TokenInfo
- type TokenRecord
- type TokenStore
- type VaultDeleter
- type VaultProber
- type VaultProberFunc
- type WorkspaceAgentSummary
- type WorkspaceBeadCounts
- type WorkspaceLifecycleConfig
- type WorkspaceLifecycleStore
- type WorkspaceRecord
- type WorkspaceRepoStatus
- type WorkspaceStatusResponse
Constants ¶
const ConfirmHeader = "X-GEMBA-Confirm"
ConfirmHeader is the request header every mutating route requires. Echo it back in the response so callers can confirm the server saw the right token (helps debug "did my retry land?" without parsing bodies).
const CreateAuthTokensTable = `` /* 494-byte string literal not displayed */
CreateAuthTokensTable is the DDL the tenant package's Migrate would run on a multi-tenant rig. Idempotent. Mirrors the tenants table migration convention.
gm-o9t8.3.5.5 changes:
- id is the primary key (a 32-char opaque token id minted server side). The composite (tenant_id, device_label) becomes a secondary UNIQUE so re-login from the same device still upserts.
- revoked_at: soft-delete sentinel; VerifyToken skips non-null rows.
- last_used_at: best-effort touch on successful verify.
const SoftDeleteRetention = 30 * 24 * time.Hour
SoftDeleteRetention is the default retention window for soft-deleted workspaces. After this window an external sweeper (out of scope for slice a) is expected to archive or hard-delete the row.
Variables ¶
var ErrBootstrapNotFound = errors.New("bootstrap: analysis not found")
ErrBootstrapNotFound is returned by BootstrapStore.Get / Commit when the analysis id is unknown.
var ErrNewProjectNotFound = errors.New("newproject: session not found")
ErrNewProjectNotFound is returned by NewProjectStore when the session id is unknown.
var ErrProbeUnconfigured = errors.New("health: prober upstream not configured")
ErrProbeUnconfigured is the sentinel a prober can return to signal "I am attached but my upstream is intentionally not wired" — the readyz handler treats this as a failure (the operator wired the prober, so they expect it to work). It exists so callers don't have to invent string error messages for the same condition.
var ErrTokenNotFound = errors.New("oauth: token not found")
ErrTokenNotFound is returned by Revoke/Rotate when the (tenant, token_id) tuple is not present in the store (or already revoked). HTTP handlers translate this to 404.
var ErrWorkspaceNotFound = errors.New("workspace not found")
ErrWorkspaceNotFound is returned by stores when the requested id is absent.
Functions ¶
func NewPoolWiring ¶
func NewPoolWiring( op core.OrchestrationPlaneAdaptor, wp core.WorkPlane, pool config.ResolvedPool, cfg config.PoolConfig, readers planner.OperationalContextReaders, agentType string, ) *poolWiring
NewPoolWiring constructs a PoolWiring bound to one (rig, persona) pool. The returned struct's methods plug into autodispatch.Daemon's interface fields. agentType is derived from the persona's TOML configuration upstream and threaded into the SessionDispatcher's prompt extension as `gemba:agent_type` so the native adaptor's pane-reuse path can find a slot.
func NewTokenID ¶
NewTokenID returns a 32-char hex id used as the auth_tokens primary key. Distinct from the plaintext bearer; safe to expose.
func NewWorkspaceLifecycle ¶
func NewWorkspaceLifecycle(cfg WorkspaceLifecycleConfig) *workspaceLifecycleHandler
NewWorkspaceLifecycle constructs the handler. Returns nil when cfg.Store is nil — the Router treats a nil handler as "endpoints return 503 adaptor_not_configured".
func OpenAPISpec ¶
func OpenAPISpec() []byte
OpenAPISpec returns the embedded OpenAPI document bytes. Exported so cmd/gen-core-types and offline tooling can pull the same source the runtime serves without spinning up the server.
func SetGTRunner ¶
func SetGTRunner(r gtRunner)
SetGTRunner overrides the gt-shellout runner used by the orchestration mutation handlers. Tests use this to inject a fake without spawning processes. Pass nil to restore the default.
Types ¶
type AdminSession ¶
type AdminSession struct {
ID string `json:"id"`
TenantID string `json:"tenant_id"`
Workspace string `json:"workspace,omitempty"`
AgentID string `json:"agent_id,omitempty"`
Status string `json:"status"`
StartedAt time.Time `json:"started_at,omitempty"`
}
AdminSession is the wire shape for the operator console's session inventory. Mirrors core.Session but kept narrow so the console UI gets a stable contract independent of the agent-plane internals.
type AdminSessionLister ¶
type AdminSessionLister func(ctx context.Context) ([]AdminSession, error)
AdminSessionLister is the contract the admin sessions endpoint depends on. Production wiring builds one over the OrchestrationPlane; tests inject a static list.
type AdoptableLister ¶
AdoptableLister enumerates adoptable beads DBs. Production impl connects to the configured Dolt server. Tests substitute a stub via AttachProjects(AttachConfig{AdoptableLister: ...}) so they don't need a real Dolt server on port 3307.
type AttachConfig ¶
type AttachConfig struct {
Pinger BeadsPinger
GitInitRunner CommandRunner
Now func() time.Time
// AdoptableLister backs GET /api/v1/projects/adoptable (gm-gmyl).
// Nil falls back to the production lister that opens a mysql
// connection against the configured Dolt server. Tests inject a
// stub here.
AdoptableLister AdoptableLister
}
AttachConfig configures the attach handler. Zero values are fine — Pinger nil falls back to defaultBeadsPinger, GitInitRunner nil falls back to exec.
type BeadsPinger ¶
BeadsPinger verifies a beads DB URL is reachable. The production implementation opens a mysql connection via the go-sql-driver, pings, and closes. Tests substitute a stub so they don't need a real Dolt server on port 3307.
type BindProjectRequest ¶
type BindProjectRequest struct {
// BeadsDBPath is the absolute path to the directory holding the
// beads DB (the parent of the `.beads/` subdir). The handler
// verifies the directory has a `.beads/` child before doing any
// work.
BeadsDBPath string `json:"beads_db_path"`
// TargetRepoPath is the absolute path to the directory that will
// hold the final project. For mode == "create" this MUST equal
// BeadsDBPath. For mode == "navigate" this MUST be a pre-existing
// git working tree.
TargetRepoPath string `json:"target_repo_path"`
// Mode is "create" or "navigate" — see the file header.
Mode string `json:"mode"`
}
BindProjectRequest is the request body for POST /api/v1/projects/bind.
type BootstrapAnalysis ¶
type BootstrapAnalysis struct {
ID string `json:"analysis_id"`
Source BootstrapSource `json:"source"`
Config map[string]any `json:"config,omitempty"`
Frames []BootstrapProgressFrame `json:"-"`
Plan []BootstrapPlanItem `json:"-"`
Findings []BootstrapFinding `json:"-"`
StartedAt time.Time `json:"started_at"`
CompletedAt time.Time `json:"completed_at,omitempty"`
Committed bool `json:"committed"`
}
BootstrapAnalysis is the persisted record for one in-flight analysis. The store mints id; AppendProgress drives the long-poll stream; Plan + Findings are populated on completion; Committed flips on a successful POST /commit (idempotent guard against a second commit).
type BootstrapAnalysisSeed ¶
type BootstrapAnalysisSeed struct {
Frames []BootstrapProgressFrame
Plan []BootstrapPlanItem
Findings []BootstrapFinding
}
BootstrapAnalysisSeed bundles the canned analysis output the handler synthesises off the source. The store doesn't care what the values mean — it just persists them so subsequent Get/Frames/ Plan calls can read them back.
type BootstrapCommitResult ¶
type BootstrapCommitResult struct {
ProjectPath string `json:"project_path"`
BoardURL string `json:"board_url"`
}
BootstrapCommitResult is the response shape /commit hands back. project_path lands in the success toast; board_url is the SPA's post-ratify destination.
type BootstrapFinding ¶
type BootstrapFinding struct {
ID string `json:"id"`
Level string `json:"level"` // pass | warn | fail
Title string `json:"title"`
Detail string `json:"detail,omitempty"`
}
BootstrapFinding is one row in the consistency report. level is pass | warn | fail; the SPA totals the levels for the top-line PASS=12 / WARN=2 / FAIL=0 summary.
type BootstrapPlanItem ¶
type BootstrapPlanItem struct {
ID string `json:"id"`
Kind string `json:"kind"` // epic | milestone | sprint | story
Title string `json:"title"`
Detail string `json:"detail,omitempty"`
Children []BootstrapPlanItem `json:"children,omitempty"`
}
BootstrapPlanItem is one outline row in the generated plan (epic / milestone / sprint / story). Children render the two-level outline the SPA expects.
type BootstrapProgressFrame ¶
type BootstrapProgressFrame struct {
Seq int `json:"seq"`
Line string `json:"line"`
At time.Time `json:"at"`
Done bool `json:"done,omitempty"`
}
BootstrapProgressFrame is one streamed line of analysis progress. JSON shape mirrors web/src/api/bootstrap.ts BootstrapProgress.
type BootstrapSource ¶
type BootstrapSource string
BootstrapSource enumerates the four source kinds the wizard supports per ui-spec §5.15. Mirrors the typed client's union exactly so the JSON token round-trips without translation.
const ( BootstrapSourceJira BootstrapSource = "jira" BootstrapSourceBeads BootstrapSource = "beads" BootstrapSourceSourceCode BootstrapSource = "source-code" BootstrapSourceFresh BootstrapSource = "fresh" )
type BootstrapStore ¶
type BootstrapStore interface {
// Start allocates a new analysis with frames seeded from
// `seed` (the synthesised progress sequence keyed off source).
// Returns the persisted record so the handler can read the
// minted id back without a second Get.
Start(ctx context.Context, source BootstrapSource, config map[string]any, seed BootstrapAnalysisSeed) (BootstrapAnalysis, error)
// Get returns the analysis by id. ErrBootstrapNotFound when
// unknown.
Get(ctx context.Context, id string) (BootstrapAnalysis, error)
// FramesSince returns frames with seq > since, in seq order.
// Used by GET /progress for the long-poll loop.
FramesSince(ctx context.Context, id string, since int) ([]BootstrapProgressFrame, error)
// Commit marks the analysis committed and returns the resulting
// (project_path, board_url) pair. Idempotent: a second commit
// returns the same response; ErrBootstrapAlreadyCommitted is
// surfaced when the caller wants to know they raced (the HTTP
// handler treats that as success).
Commit(ctx context.Context, id string) (BootstrapCommitResult, error)
}
BootstrapStore is the persistence interface. All methods are context-aware so a future SQL implementation honours deadlines. Mirrors walk.Store's narrow shape: callers hand pre-built records to mutation methods, the store concerns itself only with id + lifecycle.
func NewMemoryBootstrapStore ¶
func NewMemoryBootstrapStore() BootstrapStore
NewMemoryBootstrapStore returns a fresh in-memory store. cmd/gemba serve calls this once at boot and hands the result to AttachBootstrap.
type ChangeRef ¶
type ChangeRef = newproject.ChangeRef
ChangeRef points at the most-recent skill edit.
type CommandRunner ¶
CommandRunner is the shell-out abstraction the ratifier uses. The production implementation forwards to exec.CommandContext; tests substitute a recording stub so they can drive failures deterministically without touching the host's bd / git binaries.
type ComponentResult ¶
type ComponentResult struct {
OK bool `json:"ok"`
Error string `json:"error,omitempty"`
Skipped string `json:"skipped,omitempty"`
}
ComponentResult is the per-component health entry surfaced under the "components" key. Skipped is non-empty when a check did not run for a structural reason (e.g. KVM on macOS); a skipped check counts as ok.
type DoltProber ¶
DoltProber is the surface health.go uses to verify the bd/dolt adapter is reachable. Implementations should return nil only when a SELECT-style round-trip would succeed; any non-nil error fails the dolt component. The existing *supervisor.Supervisor already exposes Ready() — wire it via DoltProberFunc.
type DoltProberFunc ¶
DoltProberFunc adapts a plain function to DoltProber.
type DoltReadyChecker ¶
type DoltReadyChecker interface {
Ready() bool
}
DoltReadyChecker is the contract /api/readyz uses to ask whether the embedded dolt sql-server is reachable (gm-o9t8.1.2.3). Implemented by *supervisor.Supervisor; tests inject a stub.
type DoltStatusReporter ¶
type DoltStatusReporter interface {
DoltReadyChecker
State() string
Port() int
DataDir() string
RestartCount() int64
LastError() error
}
DoltStatusReporter is the richer status surface /api/v1/workspaces/{wsid}/status type-asserts onto its DoltReadyChecker handle to power the SPA's degraded-state banner (gm-o9t8.1.9). Implemented by *supervisor.Supervisor. The status handler tolerates a checker that does NOT satisfy this interface (test stubs, future no-op modes) — the embedded_dolt field stays null in that case.
type DraftBead ¶
type DraftBead = newproject.DraftBead
DraftBead is the leaf of the plan tree. See internal/skills/newproject for the field-level documentation; the JSON wire shape is canonical at that location.
type DraftMilestone ¶
type DraftMilestone = newproject.DraftMilestone
DraftMilestone is one milestone in the plan tree.
type EgressAuditor ¶
EgressAuditor receives one event per egress-rule mutation. Bound at boot via AttachEgressAuditor; nil is fine (handlers skip emission).
type EgressTemplateProvider ¶
type EgressTemplateProvider interface {
// Lookup returns the named bundle. An empty / unknown name
// resolves to the provider's "default" bundle. Implementations
// MUST never return an empty Name — callers persist the resolved
// name in audit so an unknown template's fallback is observable.
Lookup(name string) egress.Template
// SeedRules converts a bundle into per-workspace Rules ready for
// egress.Store.Put. The slice is priority-ordered (highest
// first) so the runtime enforcer walks bundle hosts before the
// hard-coded baseline allow-list.
SeedRules(wsid string, tpl egress.Template, now time.Time) []egress.Rule
}
EgressTemplateProvider is the contract workspace-create uses to resolve a named template + materialize it as a slice of egress.Rule rows keyed to the new workspace.
type EmbeddedDoltStatus ¶
type EmbeddedDoltStatus struct {
Enabled bool `json:"enabled"`
State string `json:"state"`
Port int `json:"port"`
DataDir string `json:"data_dir"`
RestartCount int64 `json:"restart_count"`
LastError string `json:"last_error,omitempty"`
}
EmbeddedDoltStatus surfaces the embedded-dolt supervisor's posture so the SPA can render a degraded-state banner when the subprocess is restarting or has given up (gm-o9t8.1.9). Null when the supervisor isn't wired (external Dolt via --dolt-url, or the noop adaptor).
State values:
- "starting" — Start() in flight; first SELECT 1 hasn't landed yet
- "ready" — last probe succeeded; subprocess healthy
- "restarting" — a recent probe failed; supervisor is in backoff / respawn between probes
- "failed" — maxConsecutiveFailures exhausted; supervisor gave up
type FakeS3Uploader ¶
type FakeS3Uploader struct {
Bucket string
Objects map[string][]byte
// contains filtered or unexported fields
}
FakeS3Uploader is an in-memory test double. Safe for concurrent use.
func NewFakeS3Uploader ¶
func NewFakeS3Uploader(bucket string) *FakeS3Uploader
NewFakeS3Uploader returns an in-memory S3Uploader stub.
type FilesystemBackend ¶
type FilesystemBackend interface {
Stat(path string) (os.FileInfo, error)
MkdirAll(path string, perm os.FileMode) error
WriteFile(path string, data []byte, perm os.FileMode) error
RemoveAll(path string) error
}
FilesystemBackend is the file-write abstraction. Production wires it to os.MkdirAll / os.WriteFile / os.RemoveAll; tests substitute a stub that maps writes onto an in-memory tree (or a tmpfs). Splitting it out lets us test step-by-step rollback without spawning subprocesses.
type MemTokenStore ¶
type MemTokenStore struct {
// contains filtered or unexported fields
}
MemTokenStore is the in-memory TokenStore — process-local, lost on restart. Tests and single-user fallback use this; production multi-tenant rigs attach SQLTokenStore.
func NewMemTokenStore ¶
func NewMemTokenStore() *MemTokenStore
NewMemTokenStore returns a zero-valued in-memory token store.
func (*MemTokenStore) Put ¶
func (m *MemTokenStore) Put(_ context.Context, rec TokenRecord) error
Put implements TokenStore. Upserts on (tenant_id, device_label) so a re-login from the same device replaces the prior hash rather than stacking dead rows. If the incoming record has no ID, one is minted.
func (*MemTokenStore) Records ¶
func (m *MemTokenStore) Records() []TokenRecord
Records returns a copy of the stored records — test-only accessor.
func (*MemTokenStore) Revoke ¶
func (m *MemTokenStore) Revoke(_ context.Context, tenantID, tokenID string) error
Revoke soft-deletes the record identified by (tenantID, tokenID). The tenant scope is enforced here so a cross-tenant request hits ErrTokenNotFound rather than silently revoking a sibling row.
func (*MemTokenStore) Rotate ¶
func (m *MemTokenStore) Rotate(_ context.Context, tenantID, tokenID string) (string, string, string, error)
Rotate mints a fresh bearer for the (tenantID, tokenID) row, hashes it, revokes the old row, and inserts a new row with a fresh id. The new row inherits device_label + github_id from the prior row. Returns (newBearer, newID, deviceLabel).
func (*MemTokenStore) VerifyToken ¶
VerifyToken implements TokenStore. Iterates the stored records and runs argon2id Verify against each hash. Returns the matching tenant id on success, or ("", false, nil) on miss. Skips rows whose RevokedAt is non-nil. Best-effort touches LastUsedAt on success.
type MemWorkspaceLifecycleStore ¶
type MemWorkspaceLifecycleStore struct {
// contains filtered or unexported fields
}
MemWorkspaceLifecycleStore is the in-memory default store. Safe for concurrent use.
func NewMemWorkspaceLifecycleStore ¶
func NewMemWorkspaceLifecycleStore() *MemWorkspaceLifecycleStore
NewMemWorkspaceLifecycleStore returns an empty in-memory store.
func (*MemWorkspaceLifecycleStore) Create ¶
func (m *MemWorkspaceLifecycleStore) Create(_ context.Context, w WorkspaceRecord) error
Create implements WorkspaceLifecycleStore.
func (*MemWorkspaceLifecycleStore) Get ¶
func (m *MemWorkspaceLifecycleStore) Get(_ context.Context, id string) (WorkspaceRecord, error)
Get implements WorkspaceLifecycleStore.
func (*MemWorkspaceLifecycleStore) List ¶
func (m *MemWorkspaceLifecycleStore) List(_ context.Context, tenantID string) ([]WorkspaceRecord, error)
List implements WorkspaceLifecycleStore.
func (*MemWorkspaceLifecycleStore) Update ¶
func (m *MemWorkspaceLifecycleStore) Update(_ context.Context, w WorkspaceRecord) error
Update implements WorkspaceLifecycleStore.
type NewProjectRatifier ¶
type NewProjectRatifier interface {
Ratify(ctx context.Context, state NewProjectState) (RatifyResponse, error)
}
NewProjectRatifier executes the atomic ratification transaction. Returns the SPA's success envelope on commit, or a structured RatifyError on any step failure.
func NewRatifier ¶
func NewRatifier(cfg RatifierConfig) NewProjectRatifier
NewRatifier builds a production NewProjectRatifier. cmd/gemba serve calls this once at boot and hands the result to AttachNewProject.
type NewProjectSession ¶
type NewProjectSession struct {
ID string
State NewProjectState
StartedAt time.Time
}
NewProjectSession is the in-memory record for one /onboard conversation. Lives only in server process memory — refresh / restart / Ctrl-C discards it (one-shot persistence is by design; see docs/design/newproject.md §One-shot persistence).
type NewProjectState ¶
type NewProjectState = newproject.NewProjectState
NewProjectState is the authoritative state for one /api/v1/newproject conversation session.
type NewProjectStore ¶
type NewProjectStore interface {
// Start allocates a new session with the empty state and returns
// the persisted record so the handler can read the minted id back
// without a second Get.
Start(ctx context.Context) (NewProjectSession, error)
// Get returns the session by id. ErrNewProjectNotFound when
// unknown.
Get(ctx context.Context, id string) (NewProjectSession, error)
// Update replaces the state of an existing session. Returns
// ErrNewProjectNotFound if the id is unknown.
Update(ctx context.Context, id string, state NewProjectState) error
// Delete drops the session from the store. Used after a
// successful ratify to free memory; idempotent — deleting an
// unknown id is not an error.
Delete(ctx context.Context, id string)
}
NewProjectStore is the persistence interface for /onboard conversation sessions. Mirrors the bootstrap / walk store pattern: callers hand pre-built records to mutation methods, the store concerns itself only with id + lifecycle.
func NewMemoryNewProjectStore ¶
func NewMemoryNewProjectStore() NewProjectStore
NewMemoryNewProjectStore returns a fresh in-memory store. cmd/gemba serve calls this once at boot and hands the result to AttachNewProject.
type NonceCache ¶
type NonceCache struct {
// contains filtered or unexported fields
}
NonceCache caches recent (nonce → response) tuples so a replayed mutating request returns the cached response unchanged.
Capacity bounds memory; TTL keeps stale entries from sitting forever once the LRU stops touching them.
func NewNonceCache ¶
func NewNonceCache(capacity int, ttl time.Duration) *NonceCache
NewNonceCache returns a cache with the given capacity (max entries) and TTL. Defaults: capacity=1024, ttl=5min — sized for normal SPA double-click protection without a real footprint cost.
func (*NonceCache) Get ¶
func (c *NonceCache) Get(nonce string) (cachedResponse, bool)
Get returns the cached response for nonce when present and not expired. Touches LRU order so a hot nonce stays warm.
func (*NonceCache) Put ¶
func (c *NonceCache) Put(nonce string, resp cachedResponse)
Put stores resp under nonce, evicting the least-recently-used entry if the cache is at capacity.
type OrchestrationPC ¶
type OrchestrationPC struct {
Scope string `json:"scope"`
Persona string `json:"persona"`
Name string `json:"name"`
State string `json:"state,omitempty"`
}
OrchestrationPC — one polecat row, gt-only. Empty on native.
type OrchestrationSc ¶
type OrchestrationSc struct {
ID string `json:"id"`
Kind string `json:"kind"` // "rig" | "local"
Personas []string `json:"personas"`
}
OrchestrationSc — one scope row. For native this is the single implicit local scope ("local"). For gt this is one rig.
type OrchestrationState ¶
type OrchestrationState struct {
AdaptorID string `json:"adaptor_id"`
Scopes []OrchestrationSc `json:"scopes"`
AgentTypes []string `json:"agent_types"`
Polecats []OrchestrationPC `json:"polecats,omitempty"`
}
OrchestrationState is the wire shape /api/orchestration/state returns. Mirrors web/src/api/orchestrationState.ts.
type PersonaFile ¶
type PersonaFile struct {
ID string `json:"id"` // filename stem
Name string `json:"name"` // [persona] name (top-level "name" key)
AgentType string `json:"agent_type"` // declared agent_type if present
Skills []string `json:"skills,omitempty"`
}
PersonaFile is one row in the response.
type PerspectiveContribution ¶
type PerspectiveContribution struct {
PersonaID string `json:"persona_id"`
PersonaName string `json:"persona_name,omitempty"`
PersonaIcon string `json:"persona_icon,omitempty"`
Kind PerspectiveContributionKind `json:"kind"`
Content string `json:"content"`
At time.Time `json:"at"`
}
PerspectiveContribution is one persona's volunteered comment on an active agenda item. Stable JSON shape — additive fields can land later without breaking the SPA.
type PerspectiveContributionKind ¶
type PerspectiveContributionKind string
PerspectiveContributionKind classifies an inline perspective. Kept as a typed string so the SPA can branch on it for styling (blocking → red, amendment → amber, note → neutral) without parsing the human-readable content.
const ( PerspectiveKindBlocking PerspectiveContributionKind = "blocking" PerspectiveKindAmendment PerspectiveContributionKind = "amendment" PerspectiveKindNote PerspectiveContributionKind = "note" )
type PoolConfigEnvelope ¶
type PoolConfigEnvelope struct {
Path string `json:"path"`
Body string `json:"body"`
Parsed poolConfigJSON `json:"parsed"`
MaxParallel int `json:"max_parallel"`
ReservedForManual int `json:"reserved_for_manual"`
}
PoolConfigEnvelope is the wire shape both GET and PUT return. Mirrors web/src/api/poolConfig.ts.
type PoolMember ¶
type PoolMember struct {
SessionID string `json:"session_id"`
PaneID string `json:"pane_id,omitempty"`
Status string `json:"status"`
LastBead string `json:"last_bead,omitempty"`
BeadsDoneThisMember int `json:"beads_done_this_member,omitempty"`
LastRecycleAt *time.Time `json:"last_recycle_at,omitempty"`
}
PoolMember is the wire shape of one pool slot's row.
type PoolState ¶
type PoolState struct {
Pools []PoolStateEntry `json:"pools"`
CapturedAt time.Time `json:"captured_at"`
}
PoolState is the response envelope for GET /api/pools.
type PoolStateEntry ¶
type PoolStateEntry struct {
Scope string `json:"scope"`
Persona string `json:"persona"`
SizeTargetDeclared int `json:"size_target_declared"`
SizeTargetEffective int `json:"size_target_effective"`
SizeActual int `json:"size_actual"`
Idle int `json:"idle"`
Working int `json:"working"`
Members []PoolMember `json:"members"`
}
PoolStateEntry is one (scope, persona) pool's exposed state.
gm-s47n.16 (spec §2): the JSON field renamed `rig` → `scope`. Existing SPA consumers reading `rig` are updated; the change ships in lockstep with the editor.
type PoolWiring ¶
type PoolWiring = poolWiring
PoolWiring is the public alias the cli package uses to construct a daemon's adaptors. Produced by NewPoolWiring.
type RatifierConfig ¶
type RatifierConfig struct {
// HomeDir overrides $HOME for default-dir resolution. Tests set
// this so the transaction lands inside a t.TempDir() instead of
// the developer's real home.
HomeDir string
// DefaultDirOverride bypasses the config-file lookup entirely.
// Tests use this to force the projects directory without faking
// out the home-dir filesystem.
DefaultDirOverride string
// Runner shells external commands (bd, git). nil → exec-based
// default.
Runner CommandRunner
// FS abstracts filesystem writes. nil → osFS.
FS FilesystemBackend
// Now returns the current time; tests pin this for deterministic
// timestamps in workspace.toml.
Now func() time.Time
// DataRoot is the gemba-server's data directory — the parent of
// the per-workspace data tree at <DataRoot>/workspaces/<id>/
// (gm-o9t8.1.2.4). Empty disables per-workspace layout creation;
// the ratify transaction still succeeds, and the omission is
// surfaced via SeedWarnings so /api/readyz can detect the
// inconsistency. Set this in serve wiring once a stable
// server-level data dir exists.
DataRoot string
}
RatifierConfig configures the production ratifier. Zero values are fine — DefaultDir empty falls back to ~/.gemba/config.toml resolution, Runner empty falls back to exec, FS empty falls back to osFS.
type RatifyError ¶
type RatifyError struct {
// Step is the 1-based transaction step that failed (1..10, or
// "8a" for the dep-resolution sub-step which we encode as 80).
Step int
// Code is a short machine-readable token (validation_failed,
// dir_exists, git_init_failed, beads_init_failed,
// milestone_create_failed, epic_create_failed, bead_create_failed,
// dep_resolve_failed, cycle_detected, project_md_failed,
// commit_failed, …).
Code string
// Message is the human-readable diagnostic surfaced in the toast.
Message string
// Cause is the underlying error, retained for logging. May be nil
// when the step itself synthesised the error.
Cause error
}
RatifyError is the structured error every step of the transaction returns on failure. The HTTP layer maps Step + Code into the JSON envelope so the SPA can render a precise diagnostic.
func (*RatifyError) Error ¶
func (e *RatifyError) Error() string
func (*RatifyError) Unwrap ¶
func (e *RatifyError) Unwrap() error
type RatifyResponse ¶
type RatifyResponse struct {
ProjectPath string `json:"project_path"`
ProjectName string `json:"project_name"`
MilestoneCount int `json:"milestone_count"`
EpicCount int `json:"epic_count"`
// SeedWarnings carries any non-fatal failures from the FTUX seed
// steps (.gemba/agents.toml, .gemba/personas/, CLAUDE.md — gm-root.24).
// These are best-effort additions: a failure here does NOT roll back
// the project — the SPA renders a "partial seed" toast so the
// operator can hand-fix and proceed. Empty (or omitted) means every
// seed step succeeded.
SeedWarnings []string `json:"seed_warnings,omitempty"`
}
RatifyResponse is the success envelope POST /ratify returns. The SPA owns post-ratify navigation (gm-root.17.7); the server returns the new project's filesystem root, its display name, and the seeded milestone / epic counts so the handoff screen can render "seeded N milestones and M epics" without re-querying the new workspace's beads database.
type RecycleWriter ¶
type RecycleWriter struct {
// contains filtered or unexported fields
}
RecycleWriter persists session.recycled events. Constructed via NewRecycleWriter; Run loops until ctx is cancelled.
func NewRecycleWriter ¶
func NewRecycleWriter(db *sql.DB) *RecycleWriter
NewRecycleWriter wraps the given *sql.DB. nil db is allowed — returns a writer whose Run is a pure no-op.
func StartRecycleWriter ¶
func StartRecycleWriter(ctx context.Context, op core.OrchestrationPlaneAdaptor, db *sql.DB) *RecycleWriter
StartRecycleWriter is the convenience constructor + launcher used by cmd/gemba serve. Subscribes the writer to the OrchestrationPlane (filtered to session.recycled events only) and runs the loop on a new goroutine. ctx cancellation terminates the goroutine.
Best-effort: a Subscribe failure is logged but doesn't propagate — the writer becomes a no-op and gemba serve continues. The retro pipeline simply won't have recycle audit data until the next restart.
func (*RecycleWriter) Insert ¶
func (w *RecycleWriter) Insert(ctx context.Context, ev core.OrchestrationEvent) error
Insert persists one session.recycled event. Exposed so tests can drive the writer without spinning up a Subscribe channel. Returns nil on success or a wrapped error on insert failure.
func (*RecycleWriter) Run ¶
func (w *RecycleWriter) Run(ctx context.Context, in <-chan core.OrchestrationEvent)
Run reads events from in until ctx is cancelled or in is closed. Each session.recycled event is persisted via Insert; non-fatal errors are logged and the loop continues.
type Router ¶
type Router struct {
// contains filtered or unexported fields
}
Router is the package-level entry point. cmd/gemba passes in the embedded SPA filesystem so this package doesn't import the embed declaration directly. host carries the bound transport-plane adaptors so handlers can reach the WorkPlane / OrchestrationPlane without re-resolving them per request.
func NewRouter ¶
NewRouter builds the chi router. spa must be a filesystem rooted at the built Vite output (with an index.html at the top level); pass nil or an empty FS during development and the handler will return a helpful hint. host may be nil during early bring-up — handlers that need a WorkPlane must check Host() before dereferencing.
func (*Router) ActiveProject ¶
ActiveProject returns the project name the operator most recently switched to, or an empty string if no switch has occurred this session. Exposed for gm-root.17.7 (start-planning handoff) so it can read the just-created project without re-scanning the disk.
func (*Router) AttachAdminConsole ¶
func (r *Router) AttachAdminConsole( emails []string, sessionLister AdminSessionLister, auditPubKey ed25519.PublicKey, auditRecords func(ctx context.Context) ([]audit.Record, error), )
AttachAdminConsole wires the admin-console seams. Any nil argument leaves the prior value in place so callers can wire pieces incrementally.
func (*Router) AttachAuditEmit ¶
AttachAuditEmit wires a best-effort audit hook the OAuth login handler invokes on success. Tests and the no-audit production default leave it nil. Mirror of the AttachMetricsHandler pattern: optional, attach late, fail open when absent.
func (*Router) AttachAuditor ¶
AttachAuditor wires a in the admin-plane audit sink. Pass nil to disable audit emission (the handlers behave normally otherwise).
func (*Router) AttachBootstrap ¶
func (r *Router) AttachBootstrap(store BootstrapStore)
AttachBootstrap binds the BootstrapStore used by the /api/bootstrap/* handlers. Calling with nil removes the binding (handlers return 503). cmd/gemba serve calls this once at boot; tests inject memory stores per case.
func (*Router) AttachDoltSupervisor ¶
func (r *Router) AttachDoltSupervisor(s DoltReadyChecker)
AttachDoltSupervisor wires a DoltReadyChecker into the router so the /api/readyz endpoint can reflect embedded-dolt health. Nil detaches.
When no checker is attached, /api/readyz reports the server itself as ready and omits the dolt check from the response — that's the external-dolt and noop modes, where readyz can't make a meaningful claim about the upstream and shouldn't pretend to.
func (*Router) AttachEgressAuditor ¶
func (r *Router) AttachEgressAuditor(a EgressAuditor)
AttachEgressAuditor binds the audit emitter. nil clears it.
func (*Router) AttachEgressStore ¶
AttachEgressStore binds the egress storage implementation to the router. Until called, all four egress endpoints return 503 adaptor_not_configured.
func (*Router) AttachEgressTemplates ¶
func (r *Router) AttachEgressTemplates(p EgressTemplateProvider)
AttachEgressTemplates wires p as the active EgressTemplateProvider. Passing nil restores the package default (BuiltinTemplates).
func (*Router) AttachHealthChecks ¶
func (r *Router) AttachHealthChecks(vault VaultProber, dolt DoltProber, auditDir string)
AttachHealthChecks wires the readiness probes onto the router. Any argument may be empty / nil and is reported as skipped or failing per the contract above. Calling AttachHealthChecks more than once replaces the prior wiring atomically.
func (*Router) AttachMetricsHTTPClient ¶
AttachMetricsHTTPClient overrides the http.Client the /api/v1/metrics/series proxy uses to call upstream Prometheus (gm-e9m0). Tests pass a client wired to httptest.Server so they don't depend on a real Prometheus instance. cmd/gemba serve does not call this — production runs use the default 15s-timeout client.
func (*Router) AttachMetricsHandler ¶
AttachMetricsHandler binds an http.Handler to GET /metrics. Calling with nil leaves the route returning 503 — useful for tests that want to skip the metrics surface entirely. cmd/gemba serve calls this with a metrics.Collector.Handler() once at boot.
func (*Router) AttachNewProject ¶
func (r *Router) AttachNewProject(store NewProjectStore, turner SkillTurner, ratifier NewProjectRatifier)
AttachNewProject binds the /onboard session store + skill turner + ratify executor (gm-root.17.6). Calling with a nil store removes the binding (handlers return 503). cmd/gemba serve calls this once at boot; tests inject fixtures per case.
turner may be nil — when so, NewStubSkillTurner takes over. ratifier may be nil too — when so, /ratify returns 503 (the conversation surface still works for SPA preview / screenshot purposes).
func (*Router) AttachOAuth ¶
AttachOAuth wires the GitHub OAuth handle (gm-o9t8.3.5). Nil disables the callback + device-login routes (they 503 with oauth_not_configured). cmd/gemba serve calls this once at boot after parsing the OAuth flags.
func (*Router) AttachOAuthTokenStore ¶
func (r *Router) AttachOAuthTokenStore(s TokenStore)
AttachOAuthTokenStore wires the gemba bearer-token store the OAuth login handler mints into. Nil leaves /api/v1/auth/oauth/github/login returning 503 even when an OAuth handle is attached — both halves are required for the device-flow to complete.
func (*Router) AttachOrgGates ¶
func (r *Router) AttachOrgGates(s *oauth.OrgGateStore)
AttachOrgGates wires the per-tenant GitHub OrgGate registry (gm-o9t8.4.4.1). Nil disables org/team gating — every successfully authenticated GitHub user is admitted (the legacy open default).
func (*Router) AttachPersonaDispatcher ¶
func (r *Router) AttachPersonaDispatcher(d *persona.Dispatcher, sr *corepersona.SkillRegistry, pr *corepersona.Registry)
AttachPersonaDispatcher binds the persona consult dispatcher, the skill registry it draws from, and the persona registry the POST endpoint resolves persona_id against (gm-twp2). cmd/gemba serve calls this once at boot after parsing personas + registering skills; tests attach a fixture-built dispatcher per case. Routes under /api/skills* and /api/consults* return 503 until at least the dispatcher + skill registry are bound; POST /api/consults additionally requires the persona registry (the read endpoints don't).
Any argument may be nil to detach (tests that re-use a Router across cases set then clear).
func (*Router) AttachPersonasDir ¶
AttachPersonasDir binds the directory the /api/personas handler scans. Empty / unset falls back to "<cwd>/.gemba/personas".
func (*Router) AttachPhase ¶
AttachPhase binds the phase.Store the /api/v1/phase handlers read from. cmd/gemba serve calls this once at boot after seeding the initial WorkspaceState; tests inject a phase.NewMemoryStore (or a pre-seeded NewSeededMemoryStore) per case. Calling with nil detaches the binding — handlers return 503 until the next AttachPhase call.
func (*Router) AttachPoolConfig ¶
AttachPoolConfig binds the editor's load/save target. path is the pool.toml filesystem path; maxParallel is the host-wide concurrent pane cap (the same value [pool] clamp arithmetic uses); reservedForManual is the [pool] reserved_for_manual default after the cascade. cmd/gemba serve calls this once at boot.
func (*Router) AttachPools ¶
func (r *Router) AttachPools(resolved []config.ResolvedPool)
AttachPools binds the resolved pool list to the router. cmd/gemba serve calls this once at startup after Resolve runs. Calling with a zero-length slice is the "no pools configured" path — the endpoint then returns an empty list.
func (*Router) AttachProjects ¶
func (r *Router) AttachProjects(cfg AttachConfig)
AttachProjects wires the projects/attach handler dependencies. Optional — zero-value Router uses production defaults (defaultBeadsPinger, execRunner, time.Now). Tests call this to inject stubs.
func (*Router) AttachQuotaLimiter ¶
AttachQuotaLimiter binds the per-tenant token-bucket limiter consumed by QuotaMiddleware. Passing nil disables quota enforcement.
func (*Router) AttachTenantStore ¶
AttachTenantStore wires a tenant.Store into the Router. cmd/gemba serve constructs the store (SQLStore against the Dolt pool, or MemStore for the no-dolt fallback) and attaches it after the router is built. A nil store turns the /api/v1/tenants/{tid} handler into a 503 — same convention as the metrics / workflow attach points.
func (*Router) AttachTenantWorkspaceCount ¶
func (r *Router) AttachTenantWorkspaceCount(fn tenantWorkspaceCountFn)
AttachTenantWorkspaceCount wires the per-tenant workspace-count probe consulted by DELETE /api/v1/tenants/{tid}. Passing nil clears the hook (defaulting back to "assume zero").
func (*Router) AttachTierQuotaMiddleware ¶
AttachTierQuotaMiddleware installs the tier-aware quota middleware (gm-o9t8.4.2.1) so router assembly layers it onto /api after WithTenant. Passing nil disables the tier middleware. The caller owns the underlying BucketStore + Counters lifetimes; the Router only borrows the wrapped handler.
func (*Router) AttachUsageAggregator ¶
func (r *Router) AttachUsageAggregator(a *billing.Aggregator)
AttachUsageAggregator wires the billing aggregator into the router. Nil disables the /usage endpoint (returns 503).
func (*Router) AttachVault ¶
AttachVault binds the vault implementation to the router. Until called, all three secrets endpoints return 503 adaptor_not_configured. cmd/gemba serve calls this once at boot. Tests inject a fixture vault; a nil argument is the documented way to detach (e.g. between table-driven cases).
func (*Router) AttachWalk ¶
AttachWalk binds the walk.Store + walk.Sources used by the /api/v1/walks/* handlers. Calling with nil store removes the binding (handlers return 503). Sources is zero-value-safe — a nil EscalationLister / HITLLister / GateFailureLister / WorkItemLister contributes zero items to BuildAgenda. cmd/gemba serve calls this once at boot; tests inject fakes per case.
func (*Router) AttachWalkSummary ¶
func (r *Router) AttachWalkSummary(s *walk_summary.Skill)
AttachWalkSummary binds the Documentarian's walk_summary skill (gm-77u). When attached, the End handler invokes Run after the store transition so a markdown artifact lands under docs/walks/<date>-<slug>.md. Pass nil to disable — the End handler still records the transition and emits the SSE event, just without producing the artifact.
func (*Router) AttachWorkflowClient ¶
AttachWorkflowClient binds the workflow.Client the /api/workflows/* surface dispatches to (gm-e12.22.2). Until called, every workflow handler returns 503 adaptor_not_configured. cmd/gemba serve calls this once at boot with a client backed by the bd CLI on PATH; tests inject a stub Runner via NewClientWithRunner.
func (*Router) AttachWorkspaceLifecycle ¶
func (r *Router) AttachWorkspaceLifecycle(h *workspaceLifecycleHandler)
AttachWorkspaceLifecycle wires h as the active lifecycle handler. Pass nil to disable the surface (endpoints will return 503).
func (*Router) AttachWorkspaceRegistry ¶
func (r *Router) AttachWorkspaceRegistry(reg workspaces.Registry)
AttachWorkspaceRegistry binds the multi-tenant workspace registry (gm-o9t8.2.4). When set, handlers that need to resolve a wsid to an on-disk project path consult the registry first and fall back to the legacy projects-config walk on ErrNotFound. Pass nil to detach (used by tests that want the legacy behaviour explicitly).
func (*Router) AttachWorkspacesRoot ¶
AttachWorkspacesRoot binds the on-disk root that holds per-workspace cloned repos (gm-o9t8.1.4.3). The diff handler resolves <root>/<wsid>/repo/. Empty root → /diff returns 503 adaptor_not_configured.
func (*Router) Close ¶
func (r *Router) Close()
Close stops the HealthBus ticker and tears down the events hub. Safe to call when StartHealthBus was never invoked.
func (*Router) EventsHub ¶
EventsHub returns the GembaEvent fan-out broker. cmd/gemba serve uses this to AttachOrchestrationStream against the registered adaptor's Subscribe() output, so adaptor events fan to /events SSE subscribers. Returns nil only when the router was constructed without one (zero-value/test paths).
func (*Router) HealthBus ¶
HealthBus returns the registry.HealthBus this router caches adaptor status against. cmd/gemba serve uses this to wire the gemba walk's BeadsDegradedLister against the same probe ticker the SPA banner reads from (gm-vch2). Returns nil only when the router was built without one — test paths that don't exercise adaptor health.
func (*Router) Host ¶
Host returns the api.Host this router was built with, or nil if the router was constructed without one. Tests and handlers use this to reach the registered WorkPlane / OrchestrationPlane.
func (*Router) InstanceID ¶
InstanceID returns the per-process boot id stamped on startup- immutable responses (capabilities, adaptors). The SPA stores the first id it sees and full-reloads if a later response shows a different id, which is how we detect a server restart with new config without a capabilities-changed channel. gm-6m60.
func (*Router) PhaseStore ¶
PhaseStore returns the phase.Store this router was last attached with, or nil. cmd/gemba serve uses this to share the store with other subsystems (e.g. gm-3on's Purview-as-gate path will read it to decide whether a Manager mutation is allowed).
func (*Router) QuotaMiddleware ¶
QuotaMiddleware returns an http.Handler middleware that consumes one token from the bearer-bound tenant's bucket on every request. When no limiter is attached the middleware is a passthrough.
On deny: emits 429 with Retry-After (delta-seconds, RFC 9110) and the standard httperr envelope code "rate_limited".
func (*Router) SeedWorkspaceEgress ¶
func (r *Router) SeedWorkspaceEgress(ctx context.Context, store egress.Store, wsid, name string) (egress.Template, error)
SeedWorkspaceEgress materializes the template named name into store. Returns the resolved template (after the empty/unknown fallback) so the caller can audit it. A nil store is a no-op — the lifecycle handler may run in tests without a Dolt pool.
func (*Router) ServeHTTP ¶
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request)
ServeHTTP dispatches through the chi mux built in NewRouter. Router satisfies http.Handler so callers can hand it directly to http.Server.
func (*Router) SetGitHubUserFetcherForTest ¶
func (r *Router) SetGitHubUserFetcherForTest(fn func(ctx context.Context, token string) (oauth.GitHubUser, error))
SetGitHubUserFetcherForTest is the test seam for the OAuth login handler. Production callers never use this. Exported (test-only hook) rather than declared in oauth_test.go so the cli_test package can wire an httptest-backed fetcher without an internal export.
func (*Router) StartHealthBus ¶
func (r *Router) StartHealthBus()
StartHealthBus preserves the old serve wiring hook. With the current zero interval it is intentionally a no-op: adaptor health is checked by explicit refresh paths, not by a periodic background probe. gm-root.7.
type S3Uploader ¶
type S3Uploader interface {
// Upload writes payload at the given key. Returns the canonical
// remote URL (e.g. s3://bucket/key) the audit record captures.
Upload(ctx context.Context, key string, payload []byte) (string, error)
}
S3Uploader is the contract the archive handler calls into to push the layered workspace bundle to long-term object storage. The production implementation wraps an AWS SDK client; tests substitute FakeS3Uploader. Out of scope for slice a: the real wiring lives in slice (c).
type SQLTokenStore ¶
type SQLTokenStore struct {
// contains filtered or unexported fields
}
SQLTokenStore is the Dolt/MySQL-backed TokenStore. Same contract as MemTokenStore; persistence survives restart.
func NewSQLTokenStore ¶
func NewSQLTokenStore(db *sql.DB) *SQLTokenStore
NewSQLTokenStore wraps db. The pool's lifecycle is the caller's.
func (*SQLTokenStore) Migrate ¶
func (s *SQLTokenStore) Migrate(ctx context.Context) error
Migrate ensures the auth_tokens table exists. Idempotent.
func (*SQLTokenStore) Put ¶
func (s *SQLTokenStore) Put(ctx context.Context, rec TokenRecord) error
Put implements TokenStore via INSERT ... ON DUPLICATE KEY UPDATE on the (tenant_id, device_label) UNIQUE key. Re-login from the same device replaces the hash; the row's id is preserved so prior rotate/revoke handles keep working. created_at is bumped so the operator can see the most recent login time; revoked_at is cleared (a fresh login un-revokes any prior soft delete on the same device).
func (*SQLTokenStore) Revoke ¶
func (s *SQLTokenStore) Revoke(ctx context.Context, tenantID, tokenID string) error
Revoke soft-deletes (tenantID, tokenID).
func (*SQLTokenStore) Rotate ¶
func (s *SQLTokenStore) Rotate(ctx context.Context, tenantID, tokenID string) (string, string, string, error)
Rotate revokes (tenantID, tokenID) and inserts a fresh row carrying the same device_label + github_id in a single transaction so partial state is impossible. Returns (newBearer, newID, deviceLabel).
func (*SQLTokenStore) VerifyToken ¶
VerifyToken scans the auth_tokens table and runs argon2id verify against each non-revoked row. For v1 this is acceptable — token count is small. On a successful verify, last_used_at is best-effort updated; a failed update does not fail the auth check.
type SkillTurner ¶
type SkillTurner interface {
Turn(ctx context.Context, in NewProjectState, message string, edits map[string]interface{}) (out NewProjectState, reply string, err error)
// Greeting returns the assistant's opening line on /start. Empty
// is a valid greeting (the SPA renders nothing).
Greeting() string
// Probe is invoked on /start before allocating a session and
// reports whether the turner can serve the request. Real-mode
// implementations return an error wrapping the operator-facing
// diagnostic (e.g. "no LLM client configured") so the handler
// can return 503 with the diagnostic in the body. Stub turners
// return nil. May be called concurrently.
Probe(ctx context.Context) error
}
SkillTurner is the contract the Onboarder persona host (gm-root.17.10) implements: take a state + an operator message + any in-place edits and return the next state + a reply for the conversation pane.
Production serve wiring installs the real Onboarder persona host. A stub implementation remains available for tests and fallback setups; it mirrors the e2e fixture's deterministic plan-tree mutation so screenshot tests stay stable.
func NewStubSkillTurner ¶
func NewStubSkillTurner() SkillTurner
NewStubSkillTurner returns the placeholder turner. Production serve wiring uses the real Onboarder via AttachNewProject(... real-turner) (gm-root.17.10).
type TokenInfo ¶
type TokenInfo struct {
ID string `json:"id"`
DeviceLabel string `json:"device_label"`
CreatedAt time.Time `json:"created_at"`
LastUsedAt *time.Time `json:"last_used_at,omitempty"`
}
TokenInfo is the metadata view of a stored token, returned by List + Rotate. Carries no plaintext or hash material; safe to return over the wire.
type TokenRecord ¶
type TokenRecord struct {
ID string // opaque 32-char hex id; the auth_tokens primary key
TenantID tenant.ID
GitHubID int64
DeviceLabel string
HashPHC string // argon2id PHC string; the plaintext is never stored
CreatedAt time.Time
LastUsedAt *time.Time
RevokedAt *time.Time
}
TokenRecord is one persisted row in the auth_tokens table.
type TokenStore ¶
type TokenStore interface {
// Put records (tenantID, githubID, deviceLabel, argon2id-hash).
// Implementations MUST upsert on (tenantID, deviceLabel) so a
// re-login from the same device replaces the prior hash rather
// than accumulating dead rows. If rec.ID is empty Put assigns
// a fresh opaque token id.
Put(ctx context.Context, rec TokenRecord) error
// VerifyToken finds the record whose hashed value matches the
// supplied plaintext token, returning (tenantID, true) on match.
// Returns ("", false, nil) when no match — distinct from infra
// errors which return ("", false, err). Implementations MUST
// skip rows where revoked_at IS NOT NULL and SHOULD best-effort
// touch last_used_at on a successful verify.
VerifyToken(ctx context.Context, plaintext string) (tenant.ID, bool, error)
// List returns metadata for non-revoked tokens belonging to
// tenantID. Never returns plaintext or hashes.
List(ctx context.Context, tenantID string) ([]TokenInfo, error)
// Revoke soft-deletes (tenantID, tokenID). Returns
// ErrTokenNotFound when the row is absent or already revoked.
Revoke(ctx context.Context, tenantID, tokenID string) error
// Rotate issues a fresh bearer + argon2id hash and revokes the
// prior row in a single transaction. Returns (plaintext, newID,
// deviceLabel). ErrTokenNotFound when (tenantID, tokenID) is
// absent or already revoked.
Rotate(ctx context.Context, tenantID, tokenID string) (string, string, string, error)
}
TokenStore is the persistence contract for OAuth-minted gemba bearer tokens. The plaintext is never stored — only the argon2id PHC hash. Records are scoped to (tenant_id, github_id) so a single GitHub identity can hold multiple tokens (one per device) without colliding.
type VaultDeleter ¶
type VaultDeleter interface {
List(ctx context.Context, wsid string) ([]string, error)
Delete(ctx context.Context, wsid, key string) error
}
VaultDeleter narrows secrets.Vault.Delete so the archive path can shred per-workspace keys without depending on the full vault surface.
type VaultProber ¶
VaultProber is the surface health.go uses to round-trip a sentinel through the secrets vault. The real *vault.boltVault satisfies this directly; tests inject fakes that simulate Put/Inject failure.
We deliberately do NOT depend on internal/vault.Vault here — that interface requires per-workspace ids and would force a workspace allocation just to probe readiness. A narrow surface keeps the probe cheap and the test seam clean.
type VaultProberFunc ¶
VaultProberFunc adapts a plain function to VaultProber.
type WorkspaceAgentSummary ¶
type WorkspaceAgentSummary struct {
Active int `json:"active"`
LastRunID string `json:"last_run_id,omitempty"`
LastRunStatus string `json:"last_run_status,omitempty"`
LastRunSummary string `json:"last_run_summary,omitempty"`
}
WorkspaceAgentSummary captures the "agents" stripe. Active counts sessions that haven't recorded a terminal event in the last agentActiveWindow. LastRunSummary is a best-effort one-line description; today it's just "agent X on bead Y" — proper transcript summarisation is a follow-up.
type WorkspaceBeadCounts ¶
type WorkspaceBeadCounts struct {
Ready int `json:"ready"`
InFlight int `json:"in_flight"`
Blocked int `json:"blocked"`
OpenTotal int `json:"open_total"`
ClosedToday int `json:"closed_today"`
}
WorkspaceBeadCounts mirrors the buckets the dashboard renders. `ready` and `in_flight` are mapped from native bd statuses; the state_category columns aren't used here because the dashboard text is keyed on bd's vocabulary.
type WorkspaceLifecycleConfig ¶
type WorkspaceLifecycleConfig struct {
Store WorkspaceLifecycleStore
Vault VaultDeleter
S3 S3Uploader
Egress egress.Store
DataDir string
Retention time.Duration
Clock func() time.Time
IDGen func() string
}
WorkspaceLifecycleConfig bundles the optional collaborators for the lifecycle handler. Store is required; the rest fall back to nil (skipping the corresponding phase) so tests can exercise individual transitions without standing up every collaborator.
type WorkspaceLifecycleStore ¶
type WorkspaceLifecycleStore interface {
Create(ctx context.Context, w WorkspaceRecord) error
Get(ctx context.Context, id string) (WorkspaceRecord, error)
Update(ctx context.Context, w WorkspaceRecord) error
List(ctx context.Context, tenantID string) ([]WorkspaceRecord, error)
}
WorkspaceLifecycleStore persists WorkspaceRecord rows. Implementations must be safe for concurrent calls. The slice-a default is the in-memory MemWorkspaceLifecycleStore; cmd/gemba serve will swap in a Dolt-backed implementation in a follow-up.
type WorkspaceRecord ¶
type WorkspaceRecord struct {
ID string `json:"id"`
TenantID string `json:"tenant_id"`
Slug string `json:"slug"`
CreatedAt time.Time `json:"created_at"`
DeletedAt *time.Time `json:"deleted_at,omitempty"`
ArchivedAt *time.Time `json:"archived_at,omitempty"`
EgressTemplate string `json:"egress_template,omitempty"`
ArchiveLocation string `json:"archive_location,omitempty"`
}
WorkspaceRecord is the lifecycle-state row managed by this handler. Egress and secrets state live in their own stores; the record here carries the lifecycle bookkeeping (creation, soft-delete, archive) plus the egress-template name we seeded the workspace with so audit consumers can reconstruct the network policy at creation time.
func (WorkspaceRecord) IsArchived ¶
func (w WorkspaceRecord) IsArchived() bool
IsArchived reports whether the record has been archived.
func (WorkspaceRecord) IsSoftDeleted ¶
func (w WorkspaceRecord) IsSoftDeleted() bool
IsSoftDeleted reports whether the record's lifecycle is currently soft-deleted (DeletedAt set, ArchivedAt unset).
type WorkspaceRepoStatus ¶
type WorkspaceRepoStatus struct {
Head string `json:"head"`
Dirty bool `json:"dirty"`
Branch string `json:"branch"`
}
WorkspaceRepoStatus carries the git posture for the workspace repo. Nil-valued when the workspace isn't backed by a clone yet (e.g. a freshly-created project that hasn't run `git init` / clone).
type WorkspaceStatusResponse ¶
type WorkspaceStatusResponse struct {
WorkspaceID string `json:"workspace_id"`
Mode string `json:"mode"`
Repo *WorkspaceRepoStatus `json:"repo"`
Beads WorkspaceBeadCounts `json:"beads"`
Agents WorkspaceAgentSummary `json:"agents"`
EmbeddedDolt *EmbeddedDoltStatus `json:"embedded_dolt"`
}
WorkspaceStatusResponse is the JSON body GET /api/v1/workspaces/{wsid}/status returns. Fields use pointer / omitempty semantics so an unresolvable subsystem renders as null rather than as a misleading zero.
Source Files
¶
- adaptors.go
- adaptors_stream.go
- admin_console.go
- agent_groups.go
- agents.go
- autodispatch_wire.go
- beads_health.go
- beads_history.go
- billing_usage.go
- bootstrap.go
- bootstrap_drafts.go
- capabilities.go
- consults.go
- consults_apply.go
- consults_post.go
- decisions.go
- diff.go
- doc.go
- egress.go
- egress_templates.go
- embedded_personas.go
- escalations.go
- escalations_test_inject.go
- events_stream.go
- fs.go
- health.go
- interactions.go
- labels.go
- metrics_query.go
- milestone_autoclose.go
- milestone_naming.go
- newproject.go
- newproject_ratify.go
- nonce.go
- oauth.go
- oauth_tokens.go
- oauth_tokenstore.go
- onboarding_setup.go
- openapi.go
- operational_context.go
- orchestration_mutations.go
- orchestration_state.go
- persona_files.go
- personas.go
- phase.go
- planner_coach.go
- pool_config.go
- pools.go
- projects.go
- projects_adoptable.go
- projects_attach.go
- projects_bind.go
- projects_clone.go
- quota_mw.go
- readyz.go
- recycles.go
- repositories.go
- router.go
- scope_status.go
- secrets.go
- sessions.go
- skills.go
- spa.go
- spec_kit.go
- specs.go
- sprints.go
- status.go
- tenants.go
- tenants_admin.go
- walks.go
- walks_perspectives.go
- whoami.go
- work_items.go
- work_items_cascade.go
- work_items_notes.go
- work_items_write.go
- workflows.go
- workitem_notify.go
- workspaces.go
- workspaces_lifecycle.go
Directories
¶
| Path | Synopsis |
|---|---|
|
Package audit is an append-only, hash-chained, signed audit log for agent actions on gemba-server.
|
Package audit is an append-only, hash-chained, signed audit log for agent actions on gemba-server. |
|
Package dispatch holds the server-side routing policy that decides whether a new bead should co-locate in an existing intra-parallel session or trigger a fresh pane spawn (gm-root.16.4).
|
Package dispatch holds the server-side routing policy that decides whether a new bead should co-locate in an existing intra-parallel session or trigger a fresh pane spawn (gm-root.16.4). |
|
Package dolthub wraps server-side Dolt remote push/pull for a workspace's project state.
|
Package dolthub wraps server-side Dolt remote push/pull for a workspace's project state. |
|
Package gitops wraps server-side git operations against the workspace checkout.
|
Package gitops wraps server-side git operations against the workspace checkout. |
|
Package httperr is the single place every gemba data handler goes to turn a Go error into the wire-stable JSON error envelope.
|
Package httperr is the single place every gemba data handler goes to turn a Go error into the wire-stable JSON error envelope. |
|
Package metrics binds the GembaEvent stream to a Prometheus /metrics endpoint so operators can scrape adaptor health, session activity, escalation pressure, and budget posture without standing up an OTEL collector.
|
Package metrics binds the GembaEvent stream to a Prometheus /metrics endpoint so operators can scrape adaptor health, session activity, escalation pressure, and budget posture without standing up an OTEL collector. |
|
gm-o9t8.4.2.1 — tier-aware quota + rate-limit middleware.
|
gm-o9t8.4.2.1 — tier-aware quota + rate-limit middleware. |
|
Package restore implements gemba-server's "reconstruct from remotes" flow: given a git remote (canonical source) and an optional Dolt remote (canonical project state), reproduce a workable workspace on a fresh machine.
|
Package restore implements gemba-server's "reconstruct from remotes" flow: given a git remote (canonical source) and an optional Dolt remote (canonical project state), reproduce a workable workspace on a fresh machine. |
|
Package secrets provides a per-workspace encrypted-at-rest secret vault for gemba-server.
|
Package secrets provides a per-workspace encrypted-at-rest secret vault for gemba-server. |
|
Package workspacelayout owns the on-disk layout for a single Gemba workspace's per-workspace data tree (gm-o9t8.1.2.4).
|
Package workspacelayout owns the on-disk layout for a single Gemba workspace's per-workspace data tree (gm-o9t8.1.2.4). |