controltower

package
v0.3.2 Latest Latest
Warning

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

Go to latest
Published: Jun 23, 2026 License: MIT Imports: 7 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"
	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

This section is empty.

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.

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.

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

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.

Jump to

Keyboard shortcuts

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