controltower

package
v0.3.4 Latest Latest
Warning

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

Go to latest
Published: Jun 24, 2026 License: MIT Imports: 13 Imported by: 0

Documentation

Overview

Package controltower renders the Control Tower snapshot: a safe, redacted view of the server runtime, the runner fleet, and per-pool busy/idle counts.

The snapshot is read-only and project-scoped in Phase R1. The contract is the JSON shape documented in .ai/plans/system-stats-page.md (Phase R1): the frontend mirrors every json tag 1:1, so every struct field carries an explicit snake_case tag. OperatorDiagnostics is NEVER projected into a response (model.go:188 on AutomationRun); only SafeSummary is.

Index

Constants

View Source
const (
	ScopeProject = "project"
	ScopeGlobal  = "global"
	ScopeAll     = "all"
)

Scope labels which runner population the snapshot covers. Phase R1 only implements ScopeProject; global/all arrive in R1b.

View Source
const (
	PoolIDGeneric      = "mivia-automation-runner"
	PoolIDImplReviewer = "mivia-automation-runner-impl-reviewer"
	PoolIDValReviewer  = "mivia-automation-runner-val-reviewer"
	PoolIDPlanReviewer = "mivia-automation-runner-plan-reviewer"
	PoolIDOrchestrator = "mivia-automation-runner-orchestrator"
	PoolIDCrush        = "mivia-automation-runner-crush"
)

Pool IDs mirror the six services in docker-compose.yml:106-442 (the "Runner fleet topology" table in the plan is the source of truth). These are stable identifiers the frontend and scaler both key off.

View Source
const (
	RunnerStatusBusy  = "busy"
	RunnerStatusIdle  = "idle"
	RunnerStatusStale = "stale"
	RunnerStatusOther = "other"
)

Runner row statuses. The runner is "busy" when it has at least one executing-status run; "stale" when its lease expired or heartbeat went quiet; otherwise its status reflects what it is doing. Idle runners are not enumerable until the heartbeat registry lands (Phase R2).

View Source
const (
	WarningRunsUnavailable      = "runs_unavailable"
	WarningNoProjectsRegistered = "no_projects_registered"
)

Warnings are stable, machine-readable strings; the frontend renders a localized message for each. They never carry raw error text.

Variables

This section is empty.

Functions

func NewRunnerRegistry added in v0.3.4

func NewRunnerRegistry(now func() time.Time, ttl time.Duration) *runnerRegistry

NewRunnerRegistry constructs a registry. ttl is how long a runner stays registered without a re-heartbeat; it must be > the heartbeat interval so a healthy runner never expires (default heartbeat 15s → ttl 30s = 2x). Exported so main.go can construct + wire it as the heartbeat resource sink.

Types

type ControlTowerSnapshot

type ControlTowerSnapshot struct {
	Scope     string      `json:"scope"`
	ProjectID string      `json:"project_id,omitempty"`
	Server    ServerStats `json:"server"`
	Pools     []PoolStats `json:"pools"`
	Runners   RunnerStats `json:"runners"`
	GPU       GPUStats    `json:"gpu"`
	Warnings  []string    `json:"warnings,omitempty"`
	CheckedAt time.Time   `json:"checked_at"`
}

ControlTowerSnapshot is the top-level response for GET /api/v1/projects/{id}/control-tower. The shape is the Phase R1 contract from the plan: server runtime, the six pools, runner aggregates, and GPU (present:false until R3).

type GPUDevice

type GPUDevice struct {
	Index       int     `json:"index"`
	Name        string  `json:"name,omitempty"`
	MemoryTotal uint64  `json:"memory_total_bytes"`
	MemoryUsed  uint64  `json:"memory_used_bytes"`
	Utilization float64 `json:"utilization_percent"`
}

GPUDevice is one GPU. Empty until Phase R3.

type GPUStats

type GPUStats struct {
	Present bool        `json:"present"`
	Devices []GPUDevice `json:"devices"`
}

GPUStats reports GPU presence and devices. Always {present:false, devices:[]} until Phase R3 wires the lazy NVML dlopen.

type PoolSpec

type PoolSpec struct {
	ID            string
	Kind          string
	AgentID       string
	Label         string
	MaxConcurrent int
}

PoolSpec is the identity of one runner pool. The six pools are fixed in docker-compose.yml:106-442; the plan's "Runner fleet topology" table is the source of truth for id/kind/agent_id/max_concurrent. Label is the human-readable name shown in the UI.

func KnownPools added in v0.3.4

func KnownPools() []PoolSpec

KnownPools returns the six-pool identity table in stable order. The order matches docker-compose.yml so the frontend renders pools in the operator's mental order (generic first, crush last). Exported so the settings catalog (Phase G) can derive per-pool scale/cap settings from the same source of truth.

type PoolStats

type PoolStats struct {
	ID            string `json:"id"`
	Kind          string `json:"kind"`
	AgentID       string `json:"agent_id,omitempty"`
	Label         string `json:"label"`
	Busy          int    `json:"busy"`
	Idle          int    `json:"idle"`
	Stale         int    `json:"stale"`
	DesiredScale  *int   `json:"desired_scale"`
	ActualScale   *int   `json:"actual_scale"`
	MaxConcurrent int    `json:"max_concurrent"`
}

PoolStats is one row per runner pool. desired_scale/actual_scale are nil until Phase S wires the scaler; max_concurrent mirrors docker-compose.yml.

type ProjectLister

type ProjectLister interface {
	List() []projectregistry.Project
}

ProjectLister is the registry seam. projectregistry.Registry satisfies this structurally (Registry.List at projectregistry/model.go:86). Project scope never enumerates the registry in R1, but the Service holds one for the global/all scope arriving in R1b.

type RunListerForStats

type RunListerForStats interface {
	ListRunsForStats(ctx context.Context, filter projectautomation.RunFilter) ([]projectautomation.AutomationRun, error)
}

RunListerForStats is the read seam the snapshot uses to list automation runs WITHOUT the reconcile side effects that Service.ListRuns triggers (reconcileRunningRuns + reconcileVerifyingRuns). projectautomation.Service satisfies this structurally via its ListRunsForStats method (added in this phase); tests pass a counting fake to assert cardinality (A13).

type RunnerCurrentRun

type RunnerCurrentRun struct {
	RunID        string     `json:"run_id"`
	ProjectID    string     `json:"project_id"`
	TaskID       string     `json:"task_id,omitempty"`
	AgentID      string     `json:"agent_id,omitempty"`
	Status       string     `json:"status"`
	ChainRunID   string     `json:"chain_run_id,omitempty"`
	StartedAt    *time.Time `json:"started_at,omitempty"`
	AttemptCount int        `json:"attempt_count"`
	SafeSummary  string     `json:"safe_summary,omitempty"`
}

RunnerCurrentRun is the safe projection of an AutomationRun driving a busy runner. It deliberately omits OperatorDiagnostics (model.go:188) — only SafeSummary is exposed.

type RunnerResources

type RunnerResources struct {
	CPUPercent float64 `json:"cpu_percent"`
	MemBytes   uint64  `json:"mem_bytes"`
}

RunnerResources is the per-runner CPU/mem snapshot (Phase R2). Nil in R1.

type RunnerStats

type RunnerStats struct {
	Total  int             `json:"total"`
	Busy   int             `json:"busy"`
	Idle   int             `json:"idle"`
	Stale  int             `json:"stale"`
	ByKind map[string]int  `json:"by_kind"`
	Items  []RunnerSummary `json:"items"`
}

RunnerStats is the aggregate runner view plus the per-runner rows.

type RunnerSummary

type RunnerSummary struct {
	RunnerID        string             `json:"runner_id"`
	Kind            string             `json:"kind"`
	AgentID         string             `json:"agent_id,omitempty"`
	PoolID          string             `json:"pool_id"`
	ProjectIDs      []string           `json:"project_ids,omitempty"`
	Status          string             `json:"status"`
	LastHeartbeatAt *time.Time         `json:"last_heartbeat_at,omitempty"`
	LeaseExpiresAt  *time.Time         `json:"lease_expires_at,omitempty"`
	CurrentRuns     []RunnerCurrentRun `json:"current_runs,omitempty"`
	Resources       *RunnerResources   `json:"resources"`
}

RunnerSummary is one runner's view. Multiple concurrent runs on the same runner collapse into one row with multiple current_runs (grouped by full RunnerID, no -wN suffix collapsing — main.go:1602-1642). Resources is nil until Phase R2.

ProjectIDs carries the distinct set of projects this runner's current_runs belong to. It is populated only in global/all scope (where a single row can span multiple projects — C12) and omitted in project scope (where the top-level snapshot.project_id already carries it). The FE renders this as a per-row project badge (C13). The per-run RunnerCurrentRun.ProjectID remains the authoritative single-run attribution.

type ServerStats

type ServerStats struct {
	Goroutines        int    `json:"goroutines"`
	HeapAllocBytes    uint64 `json:"heap_alloc_bytes"`
	HeapInuseBytes    uint64 `json:"heap_inuse_bytes"`
	StackInuseBytes   uint64 `json:"stack_inuse_bytes"`
	CgroupMemBytes    uint64 `json:"cgroup_mem_bytes"`
	HostMemTotalBytes uint64 `json:"host_mem_total_bytes"`
}

ServerStats reports the mivia-server process runtime. Cgroup/host fields are zero until Phase R3 wires the cgroup/meminfo readers.

type Service

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

Service renders the Control Tower snapshot. It owns no mutable runner state in R1 (the idle registry arrives in R2); every Snapshot call is stateless aside from the runtime.MemStats read. Phase S/G add optional scaler + settings dependencies (nil-safe: scaling/settings endpoints return unsupported when absent).

func NewService

func NewService(runLister RunListerForStats, registry ProjectLister) *Service

NewService wires the run lister and registry. now is injected (default time.Now) so tests can fix the timestamp for A19 (checked_at is UTC).

func (*Service) RunnerRegistry added in v0.3.4

func (svc *Service) RunnerRegistry() *runnerRegistry

RunnerRegistry returns the runner registry (may be nil if Phase R2 not wired).

func (*Service) Scaler added in v0.3.4

func (svc *Service) Scaler() scaler.Scaler

Scaler returns the configured scaler (never nil after WithScaler).

func (*Service) Settings added in v0.3.4

func (svc *Service) Settings() *settings.Service

Settings returns the settings service (may be nil if Phase G not wired).

func (*Service) Snapshot

func (svc *Service) Snapshot(ctx context.Context, scope, projectID string) ControlTowerSnapshot

Snapshot renders the Control Tower view for a scope. Phase R1 implements ScopeProject only; global/all arrive in R1b (they fan out across the registry, one ListRunsForStats per project — A14).

The method is read-only and side-effect-free: it calls the run lister once for the project scope (A13), groups in memory, and never touches OperatorDiagnostics (A15).

func (*Service) WithRegistry added in v0.3.4

func (svc *Service) WithRegistry(r *runnerRegistry) *Service

WithRegistry attaches the Phase R2 runner registry (idle runners + per-runner resources). The registry also satisfies projectautomation.ResourceSink so the heartbeat path can populate it without importing controltower. Returns svc for chaining. Nil-safe: when not wired, resources stay nil + idle count 0.

func (*Service) WithScaler added in v0.3.4

func (svc *Service) WithScaler(s scaler.Scaler) *Service

WithScaler attaches the pool-scaling backend (Phase S). Returns svc for chaining. Nil scaler is treated as NoopScaler.

func (*Service) WithSettings added in v0.3.4

func (svc *Service) WithSettings(s *settings.Service) *Service

WithSettings attaches the settings service (Phase G) so the scale endpoint can read pool caps and persist desired-scale via the catalog.

Directories

Path Synopsis
Package httpapi wires the Control Tower read endpoint into the server's http.ServeMux.
Package httpapi wires the Control Tower read endpoint into the server's http.ServeMux.
Package scaler is the Control Tower runner-pool scaling client.
Package scaler is the Control Tower runner-pool scaling client.
Package settings is the typed Settings Catalog for Control Tower.
Package settings is the typed Settings Catalog for Control Tower.
httpapi
Package httpapi wires the Control Tower Settings Console endpoints into the server's http.ServeMux.
Package httpapi wires the Control Tower Settings Console endpoints into the server's http.ServeMux.

Jump to

Keyboard shortcuts

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