server

package
v0.1.1 Latest Latest
Warning

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

Go to latest
Published: Oct 4, 2026 License: Apache-2.0 Imports: 50 Imported by: 0

Documentation

Overview

Package server is the Halos console backend: fleet ingest, policy and experiment read APIs, and the embedded web console.

Index

Constants

View Source
const (
	StatusPending   = "pending"
	StatusApproving = "approving" // transient, in-memory only: a PR is being opened
	StatusApproved  = "approved"
	StatusDenied    = "denied"
)

Request statuses.

View Source
const DefaultDeviceTTL = 90 * 24 * time.Hour

DefaultDeviceTTL is how long a device token works after enrollment.

View Source
const DefaultPostureMaxAge = 45 * time.Minute

DefaultPostureMaxAge is how old a device's last report may be before its posture is stale: three default halod intervals (15m).

View Source
const (

	// ExpiryWarning is how close to expiry a ring pointer is flagged.
	ExpiryWarning = 48 * time.Hour
)
View Source
const MaxHistory = 50

MaxHistory bounds the per-reporter report history.

View Source
const MaxReportBytes = 64 << 10

MaxReportBytes caps a POST /fleet/report body.

Variables

View Source
var ErrManual = errors.New("no automatic policy change for this request kind")

ErrManual: approval is recorded but a human must make the change.

Functions

func AppendAuditOffline

func AppendAuditOffline(dir, actor, action, target string, details map[string]string, now time.Time) error

AppendAuditOffline appends one entry to <dir>/audit.jsonl using the server's hash chain, for tools that act on the data dir without a running server (halo controller run). Single-writer: the chain head is read from disk on open, so it must not run while halo-server is appending to the same dir (the server caches seq/head in memory and the chain would fork). Run the standalone controller only against a stopped server, or a separate data dir.

func CheckSeparateClone

func CheckSeparateClone(repoDir, servedDir string) error

CheckSeparateClone refuses a writer clone that is, contains, or sits inside the served policy directory (symlinks resolved): proposals reset and branch the clone, and the served policy must never show unmerged changes.

func LoadVerdicts

func LoadVerdicts(path string) (map[string]Verdict, error)

LoadVerdicts reads a JSON array of Verdict, keyed by experiment name. A missing path or file is not an error (no analysis yet).

func VerifyAuditChain

func VerifyAuditChain(entries []AuditEntry) error

VerifyAuditChain checks seq continuity, prev links and every entry hash. ponytail: truncating the newest entries is undetectable from the log alone; pin AuditResponse.Head externally if that matters.

Types

type AssignmentRow

type AssignmentRow struct {
	Experiment string `json:"experiment"`
	Status     string `json:"status"`
	Variant    string `json:"variant,omitempty"`
	Control    bool   `json:"control,omitempty"`
}

AssignmentRow is one experiment's outcome for the subject; Variant is empty when not enrolled.

type AuditEntry

type AuditEntry struct {
	Seq       uint64            `json:"seq"`
	Time      time.Time         `json:"time"`
	Actor     string            `json:"actor"`
	Action    string            `json:"action"`
	Target    string            `json:"target,omitempty"`
	IP        string            `json:"ip,omitempty"`
	RequestID string            `json:"requestId,omitempty"`
	Details   map[string]string `json:"details,omitempty"`
	Prev      string            `json:"prev"`
	Hash      string            `json:"hash"`
}

AuditEntry is one privileged action. Hash covers every other field, Prev is the previous entry's Hash (empty for the first), so editing, reordering or removing any entry but the newest breaks the chain from that point on.

type AuditResponse

type AuditResponse struct {
	Entries     []AuditEntry `json:"entries"`
	Next        uint64       `json:"next"` // pass as ?since= to get entries after these
	Total       int          `json:"total"`
	Verified    bool         `json:"verified"` // whole chain, not just this page
	VerifyError string       `json:"verifyError,omitempty"`
	Head        string       `json:"head"`
}

AuditResponse is GET /api/v1/audit.

type Catalog

type Catalog struct {
	Harnesses []HarnessPin  `json:"harnesses"`
	Launchers []string      `json:"launchers"`
	Items     []CatalogItem `json:"items"`
}

Catalog is GET /api/v1/catalog.

type CatalogItem

type CatalogItem struct {
	Kind string `json:"kind"`
	Item string `json:"item"`
	Desc string `json:"desc,omitempty"`
}

CatalogItem is something the user can request.

type ChannelRelease

type ChannelRelease struct {
	Channel    string     `json:"channel"`
	Published  bool       `json:"published"`
	Digest     string     `json:"digest,omitempty"`
	Seq        uint64     `json:"seq,omitempty"`
	ExpiresAt  *time.Time `json:"expiresAt,omitempty"`
	Expired    bool       `json:"expired"`
	Stale      bool       `json:"stale"`
	Suspicious bool       `json:"suspicious"`
	Error      string     `json:"error,omitempty"`
	Devices    int        `json:"devices"` // ring hosts reporting this channel's digest
}

ChannelRelease is one experiment channel of a ring's current release.

type Config

type Config struct {
	PolicyDir    string
	Token        string // bearer token required by POST /api/v1/fleet/report; must be non-empty
	VerdictsPath string // optional JSON file written by `halo exp analyze`
	Store        Store
	Log          *slog.Logger
	Now          func() time.Time
	Web          fs.FS // nil = placeholder page (defaults to the embedded console)
	// TrustedProxies are the reverse proxies whose X-Forwarded-For is believed
	// for per-client rate limiting. Empty = use the TCP peer address only.
	TrustedProxies []netip.Prefix

	// Portal auth/self-service. SessionKey signs cookies (>=32 bytes); required unless DevUser is set.
	SessionKey       []byte
	OIDCClientSecret string
	Portal           Portal
	DataDir          string        // request/device/session-revocation logs ("" = in-memory)
	DeviceTTL        time.Duration // device token lifetime after enrollment (0 = DefaultDeviceTTL)
	Writer           PolicyWriter  // nil: approvals are recorded but need manual policy changes

	// Kill switch. KillStore nil = opened from DataDir. GET
	// /api/v1/gateway/killswitch is served only when both KillKey (signs the
	// list) and GatewayToken (gateways' bearer token) are set; GET
	// /api/v1/fleet/killswitch (device token) whenever KillKey is set.
	KillStore    *controller.KillStore
	KillKey      ed25519.PrivateKey
	GatewayToken string

	// PostureMaxAge: a device whose last report is older is stale (GET
	// /api/v1/gateway/posture, served whenever GatewayToken is set). 0 = DefaultPostureMaxAge.
	PostureMaxAge time.Duration

	// DevUser disables authentication and acts as this user. INSECURE: local demos only.
	DevUser   string
	DevGroups []string
	DevAdmin  bool
}

Config wires a Server.

type ConvergenceView

type ConvergenceView struct {
	Total    int            `json:"total"`
	OnDigest int            `json:"onDigest"`
	Other    int            `json:"other"`
	Digests  map[string]int `json:"digests"`
}

ConvergenceView counts a ring's devices on the ring's current digest vs others.

type Device

type Device struct {
	ID        string    `json:"id"` // uuid
	Hash      string    `json:"hash,omitempty"`
	UserID    string    `json:"userID"`
	Groups    []string  `json:"groups,omitempty"` // IdP groups at enrollment; ring is otherwise resolved live
	CreatedAt time.Time `json:"createdAt"`
	LastSeen  time.Time `json:"lastSeen"`
	// LastAuth is when the user last authenticated for this device (enrollment);
	// Groups date from then. The token stops working at ExpiresAt: re-enroll.
	LastAuth  time.Time `json:"lastAuth,omitzero"`
	ExpiresAt time.Time `json:"expiresAt,omitzero"`
	Revoked   bool      `json:"revoked"`
}

Device is the server-side binding of a device token. Only the SHA-256 of the token is ever stored; the token itself is shown once, in the enrollment response.

type DeviceDetail

type DeviceDetail struct {
	Device  Device `json:"device"`
	Last    *Host  `json:"last,omitempty"`
	History []Host `json:"history"` // newest first, at most MaxHistory
}

DeviceDetail is GET /api/v1/devices/{id}: the binding, its latest report and recent history.

type ExperimentDetail

type ExperimentDetail struct {
	ExperimentView
	Verdict *Verdict `json:"verdict,omitempty"`
}

ExperimentDetail is an experiment plus its latest verdict, if any.

type ExperimentView

type ExperimentView struct {
	Name     string                `json:"name"`
	Type     policy.ExperimentType `json:"type"`
	Axis     policy.Axis           `json:"axis"`
	Status   string                `json:"status"`
	Rings    []string              `json:"rings"`
	Variants []policy.Variant      `json:"variants"`
	Sample   float64               `json:"sampleRate,omitempty"`
	Metrics  policy.Metrics        `json:"metrics"`
	Stopping policy.Stopping       `json:"stopping"`
}

type FleetResponse

type FleetResponse struct {
	Hosts []Host      `json:"hosts"`
	Rings []RingStats `json:"rings"`
	Total int         `json:"total"`
	Drift int         `json:"driftHosts"`
}

FleetResponse is GET /api/v1/fleet.

type GitPolicyWriter

type GitPolicyWriter struct {
	RepoDir   string
	ServedDir string // the served policy dir; Propose refuses if it overlaps RepoDir
	Subdir    string // policy root inside the repo ("" = repo root)
	Base      string // base branch, default "main"
	Opener    promote.PROpener
	// Git runs git in RepoDir and returns trimmed stdout (default: os/exec); injectable for tests.
	Git func(ctx context.Context, args ...string) (string, error)
	// contains filtered or unexported fields
}

GitPolicyWriter implements PolicyWriter by editing YAML in a dedicated clone of the policy repo and handing the files to a promote.PROpener (GHOpener shells `git` + `gh`). It only opens PRs; a human merges.

RepoDir MUST be a separate clone from the served policy directory (ServedDir): each proposal hard-resets RepoDir to origin/<Base> and PROpener checks out a branch there, and the served policy must never show unmerged changes.

func (*GitPolicyWriter) Propose

func (w *GitPolicyWriter) Propose(ctx context.Context, org *policy.Org, req Request, approver string) (string, error)

func (*GitPolicyWriter) ProposeRollout

func (w *GitPolicyWriter) ProposeRollout(ctx context.Context, plan func(string) (*promote.Change, error), branch, title string, body func(string) string) (string, error)

ProposeRollout implements controller.RolloutWriter on this clone (same lock and sync as ProposeStatus).

func (*GitPolicyWriter) ProposeStatus

func (w *GitPolicyWriter) ProposeStatus(ctx context.Context, experiment, status, title, body string) (string, error)

ProposeStatus implements controller.Writer on this clone, so the console and halo-server's experiment controller share one clone, one lock and one status-PR implementation (controller.GitWriter / promote.PlanStatus).

type GuardrailVerdict

type GuardrailVerdict struct {
	Metric string `json:"metric"`
	Result struct {
		Status        string  `json:"status"` // pass | fail | inconclusive
		Regression    float64 `json:"regression"`
		Lower         float64 `json:"lower"`
		Upper         float64 `json:"upper"`
		MaxRegression float64 `json:"maxRegression"`
	} `json:"result"`
}

type HarnessPin

type HarnessPin struct {
	Name    string `json:"name"`
	Version string `json:"version"`
}

HarnessPin is one harness at the version pinned by the user's ring profile.

type HarnessStatus

type HarnessStatus struct {
	Want      string `json:"want"`
	Installed string `json:"installed"`
}

HarnessStatus mirrors cmd/halod's per-harness report.

type Host

type Host struct {
	Report
	LastSeen time.Time `json:"lastSeen"`
}

Host is the latest report for one machine plus when the server saw it.

type IssueView

type IssueView struct {
	Severity string `json:"severity"`
	Path     string `json:"path"`
	Message  string `json:"message"`
}

type Launch

type Launch struct {
	Launcher   string     `json:"launcher"`
	Filename   string     `json:"filename,omitempty"` // devcontainer
	Snippet    string     `json:"snippet,omitempty"`  // devcontainer
	URL        string     `json:"url,omitempty"`      // codespaces, coder
	Token      string     `json:"token,omitempty"`    // laptop
	ExpiresAt  *time.Time `json:"expiresAt,omitempty"`
	TTLSeconds int        `json:"ttlSeconds,omitempty"`
	Bash       string     `json:"bash,omitempty"`
	PowerShell string     `json:"powershell,omitempty"`
}

Launch is the POST /api/v1/launch/{launcher} response; fields depend on the launcher.

type LauncherSetup

type LauncherSetup struct {
	Launcher string `json:"launcher"`
	Missing  string `json:"missing"` // what the operator must change in the portal config
}

LauncherSetup is a policy-enabled launcher this server is not configured to run.

type Me

type Me struct {
	Principal
	Ring        string          `json:"ring"`
	Release     string          `json:"release,omitempty"`
	Variants    []AssignmentRow `json:"variants"`
	Profile     *profileSummary `json:"profile,omitempty"`
	SelfService bool            `json:"selfService"`
	Launchers   []string        `json:"launchers"` // enabled in policy AND configured on this server
	// LauncherSetup lists enabled launchers this server cannot run yet (admins only).
	LauncherSetup []LauncherSetup `json:"launcherSetup,omitempty"`
	Requestable   []string        `json:"requestable"`
	OptInRings    []string        `json:"optInRings"` // rings the user may still ask to join
	DevInsecure   bool            `json:"devInsecure,omitempty"`
}

Me is GET /api/v1/me.

type PatchSummary

type PatchSummary struct {
	MCPServers []string `json:"mcpServers,omitempty"` // "name url"
	Hooks      []string `json:"hooks,omitempty"`      // "event matcher"
	Env        []string `json:"env,omitempty"`        // variable names
	Overrides  []string `json:"overrides,omitempty"`  // top-level keys
}

PatchSummary is one harness's fragment, as names.

type PolicyView

type PolicyView struct {
	Org         string           `json:"org"`
	Gateway     *policy.Gateway  `json:"gateway,omitempty"`
	Rings       []RingView       `json:"rings"`
	Profiles    []string         `json:"profiles"`
	Experiments []ExperimentView `json:"experiments"`
	Issues      []IssueView      `json:"issues"`
}

PolicyView is the JSON shape of GET /api/v1/policy.

type PolicyWriter

type PolicyWriter interface {
	Propose(ctx context.Context, org *policy.Org, req Request, approver string) (prURL string, err error)
}

PolicyWriter turns an approved request into a reviewable change to the policy repo. Implementations must never merge. ErrManual means no automatic patch exists for the kind.

type Portal

type Portal struct {
	// BaseURL is the externally visible URL of this server; baked into launch artifacts and enroll scripts.
	BaseURL string `json:"baseURL"`
	// Registry is the OCI repository halod pulls releases from.
	Registry string `json:"registry"`
	// RegistryPlainHTTP reads the registry over http (local/dev registries only).
	RegistryPlainHTTP bool `json:"registryPlainHTTP"`
	// PubKeyFile is the release-verification public key (PEM); served at /enroll/release.pub.
	PubKeyFile string `json:"pubKeyFile"`
	// DevcontainerFeature is the OCI ref of the halos Dev Container Feature.
	DevcontainerFeature string `json:"devcontainerFeature"`
	DevcontainerImage   string `json:"devcontainerImage"`
	// CodespacesURL / CoderURL are URL templates; {ring} and {user} are query-escaped.
	CodespacesURL string `json:"codespacesURL"`
	CoderURL      string `json:"coderURL"`
	// HalodURL is the halod download template with {os} and {arch}; HalodSHA256 pins each "<os>-<arch>" build.
	HalodURL    string            `json:"halodURL"`
	HalodSHA256 map[string]string `json:"halodSHA256"`
	// PolicyRepoDir is a dedicated git clone the PolicyWriter branches from (never the served policy dir).
	PolicyRepoDir string `json:"policyRepoDir"`
	PolicySubdir  string `json:"policySubdir"`
	PolicyBase    string `json:"policyBase"`
	// Secrets are referenced by file path so they never sit in the JSON itself.
	SessionKeyFile       string `json:"sessionKeyFile"`
	OIDCClientSecretFile string `json:"oidcClientSecretFile"`

	PubKeyPEM string `json:"-"` // loaded from PubKeyFile by the caller
}

Portal is the self-service section of the server config (JSON file, --portal-config).

func (Portal) Validate

func (p Portal) Validate(devInsecure bool) error

Validate rejects anything that could inject into the generated shell/PowerShell scripts, and a non-https baseURL (session cookies and device tokens would travel in clear) unless it is loopback or devInsecure.

type Principal

type Principal struct {
	ID     string   `json:"id"`
	Groups []string `json:"groups"`
	Admin  bool     `json:"admin"`
}

Principal is the authenticated portal user.

type ReleasesResponse

type ReleasesResponse struct {
	Rings    []RingRelease `json:"rings"`
	Stale    bool          `json:"stale"` // some ring shows cached state because the registry could not be read
	Registry string        `json:"registry"`
}

ReleasesResponse is GET /api/v1/releases.

type Report

type Report struct {
	Time      time.Time                `json:"time"`
	Hostname  string                   `json:"hostname"`
	User      string                   `json:"user"`
	Device    string                   `json:"device,omitempty"` // set by the server for device-token reports
	Ring      string                   `json:"ring"`
	Digest    string                   `json:"digest"`
	Harnesses map[string]HarnessStatus `json:"harnesses"`
	Drift     []string                 `json:"drift"`
	LastError string                   `json:"lastError,omitempty"`
	ErrorCode string                   `json:"errorCode,omitempty"` // classifies LastError, e.g. untrusted_owner, sandbox_unavailable, no_subject_for_experiment
	// Experiment/Variant: the client-axis experiment variant the host applied.
	Experiment string `json:"experiment,omitempty"`
	Variant    string `json:"variant,omitempty"`
	Killed     bool   `json:"killed,omitempty"` // Experiment is kill-switched; the host runs the ring release
}

Report is the status payload halod POSTs (cmd/halod.Status).

type Request

type Request struct {
	ID            string     `json:"id"`
	User          string     `json:"user"`
	Kind          string     `json:"kind"` // mcp-server | model | ring-opt-in | harness
	Item          string     `json:"item"`
	Justification string     `json:"justification"`
	Ring          string     `json:"ring,omitempty"` // requester's ring at request time
	Status        string     `json:"status"`
	CreatedAt     time.Time  `json:"createdAt"`
	DecidedBy     string     `json:"decidedBy,omitempty"`
	DecidedAt     *time.Time `json:"decidedAt,omitempty"`
	PRURL         string     `json:"prURL,omitempty"`
	Note          string     `json:"note,omitempty"`
}

Request is a developer's ask for access; approval opens a policy-repo PR, never a merge.

type RingRelease

type RingRelease struct {
	Ring         string           `json:"ring"`
	Published    bool             `json:"published"` // a verified pointer exists
	Org          string           `json:"org,omitempty"`
	Digest       string           `json:"digest,omitempty"`
	Seq          uint64           `json:"seq,omitempty"`
	IssuedAt     *time.Time       `json:"issuedAt,omitempty"`
	ExpiresAt    *time.Time       `json:"expiresAt,omitempty"`
	ExpiresInSec int64            `json:"expiresInSeconds,omitempty"`
	Expired      bool             `json:"expired"`
	ExpiringSoon bool             `json:"expiringSoon"` // <48h left (and not yet expired)
	Stale        bool             `json:"stale"`
	Suspicious   bool             `json:"suspicious"` // registry served a foreign-org or regressed pointer; last good shown
	Error        string           `json:"error,omitempty"`
	Channels     []ChannelRelease `json:"channels"`    // client-axis experiment channels the ring's release routes to
	Convergence  ConvergenceView  `json:"convergence"` // hosts on the ring digest or one of its channels' digests are converged
	Drift        int              `json:"driftHosts"`
}

RingRelease is one ring's row in GET /api/v1/releases.

type RingStats

type RingStats struct {
	Ring     string                    `json:"ring"`
	Hosts    int                       `json:"hosts"`
	Percent  float64                   `json:"percent"` // of fleet
	Drift    int                       `json:"driftHosts"`
	Digests  map[string]int            `json:"digests"`
	Versions map[string]map[string]int `json:"versions"` // harness -> installed version -> hosts
}

RingStats aggregates hosts by ring x release digest x harness version.

func Aggregate

func Aggregate(hosts []Host, order []string) []RingStats

Aggregate builds ring stats; rings named in order come first, others alphabetically.

type RingView

type RingView struct {
	Name       string            `json:"name"`
	Order      int               `json:"order"`
	Profile    string            `json:"profile"`
	Release    string            `json:"release,omitempty"`
	Membership policy.Membership `json:"membership"`
	Resolved   *policy.Profile   `json:"resolved,omitempty"`
	Error      string            `json:"error,omitempty"`
}

type RolloutView

type RolloutView struct {
	Rollout    *policy.Rollout  `json:"rollout"`
	Timeline   rollout.Timeline `json:"timeline"`
	State      *rollout.State   `json:"state,omitempty"`
	StateError string           `json:"stateError,omitempty"`
}

RolloutView is one entry of GET /api/v1/rollouts: the definition, its plan, and the controller's state (last decision, gate values, hash-chained history) when this server's data dir holds it (halo-server --controller).

type Server

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

Server is the HTTP API + console.

func New

func New(cfg Config) (*Server, error)

New validates cfg and returns a Server. It does not require the policy dir to load yet.

func (*Server) Audit

func (s *Server) Audit(ctx context.Context, actor, action, target string, details map[string]string)

Audit is the best-effort form (login/logout): failures are logged, not returned. ctx carries the remote IP and request id when it derives from an HTTP request.

func (*Server) AuditStrict

func (s *Server) AuditStrict(ctx context.Context, actor, action, target string, details map[string]string) error

AuditStrict records a privileged action and returns the append error. Call it for actions that must not go unrecorded and fail the request on error.

func (*Server) Close

func (s *Server) Close() error

Close releases the files New opened under Config.DataDir. A Store or KillStore passed in via Config stays the caller's to close.

func (*Server) Handler

func (s *Server) Handler() http.Handler

Handler returns the full mux with security headers and request logging.

func (*Server) KillExperiment

func (s *Server) KillExperiment(ctx context.Context, name, actor, reason string) (bool, error)

KillExperiment trips the kill switch as actor (the in-process controller) and audit-logs it. changed is false when it was already killed. The argument order is controller.Killer's (experiment, then actor): main wires it as a controller.KillFunc, where swapped strings would still compile.

func (*Server) ReloadPolicy

func (s *Server) ReloadPolicy()

ReloadPolicy forces a policy reload on next read (wire to SIGHUP).

type Store

type Store interface {
	Put(Host) error
	All() []Host // sorted by hostname
	// History returns up to MaxHistory recent reports for key (see Host.key), newest first.
	History(key string) []Host
}

Store keeps the latest report per reporter: per device ID for device-token reports, per hostname for fleet-token reports (see Host.key).

func NewMemStore

func NewMemStore() Store

NewMemStore returns a non-persistent Store.

func OpenLogStore

func OpenLogStore(dir string) (Store, error)

OpenLogStore replays dir/reports.jsonl (last line per host wins), rewrites it compacted, and appends from then on.

type ToggleKill

type ToggleKill struct {
	By     string    `json:"by"`
	Reason string    `json:"reason,omitempty"`
	At     time.Time `json:"at"`
}

ToggleKill is the live kill state of a toggle.

type TogglePayload

type TogglePayload struct {
	Harnesses map[string]PatchSummary `json:"harnesses,omitempty"`
	Routes    map[string]string       `json:"routes,omitempty"` // alias -> "upstream/model" (or its targets)
}

TogglePayload summarises what a toggle delivers without its secrets: header and env values are never shown, override values only as key names.

type TogglePreview

type TogglePreview struct {
	Subject  toggle.Subject  `json:"subject"`
	Decision toggle.Decision `json:"decision"`
}

TogglePreview is the "who gets it?" answer of GET /api/v1/toggles/{name}?user=.

type ToggleView

type ToggleView struct {
	Name        string              `json:"name"`
	Description string              `json:"description,omitempty"`
	Owner       string              `json:"owner"`
	Expires     string              `json:"expires,omitempty"`
	Stale       bool                `json:"stale"`
	Axis        policy.Axis         `json:"axis"`
	Default     bool                `json:"default"`
	Rules       []policy.ToggleRule `json:"rules"`
	Payload     TogglePayload       `json:"payload"`
	// Kill is set while the toggle is on the signed kill list.
	Kill *ToggleKill `json:"kill,omitempty"`
}

ToggleView is the JSON shape of a toggle in GET /api/v1/toggles.

type Verdict

type Verdict struct {
	Experiment  string             `json:"experiment"`
	Verdict     string             `json:"verdict"` // promote | rollback | continue | expired
	Reason      string             `json:"reason"`
	Control     string             `json:"control"`
	Treatment   string             `json:"treatment"`
	NControl    int                `json:"nControl"`
	NTreatment  int                `json:"nTreatment"`
	PValue      float64            `json:"pValue"`
	Effect      float64            `json:"effect"`
	EffectLower *float64           `json:"effectLower,omitempty"`
	EffectUpper *float64           `json:"effectUpper,omitempty"`
	Guardrails  []GuardrailVerdict `json:"guardrails"`
	EvaluatedAt *time.Time         `json:"evaluatedAt,omitempty"`
}

Verdict is the console's view of an experiment analysis (a local view model mirroring promote.Report, plus an optional primary-effect CI for the forest plot). Keys are camelCase; encoding/json also matches promote.Report's untagged field names.

type WhoAmI

type WhoAmI struct {
	User        string          `json:"user"`
	Groups      []string        `json:"groups"`
	Ring        string          `json:"ring"`
	Profile     string          `json:"profile,omitempty"`
	Release     string          `json:"release,omitempty"`
	Assignments []AssignmentRow `json:"assignments"`
}

WhoAmI is GET /api/v1/whoami.

Jump to

Keyboard shortcuts

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