policy

package
v0.6.1 Latest Latest
Warning

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

Go to latest
Published: Nov 3, 2026 License: Apache-2.0 Imports: 33 Imported by: 0

Documentation

Overview

Package policy defines Halos's harness-neutral policy model: the YAML documents an org keeps in its policy repo, and the in-memory form every other package (compiler, gateway, delivery, eval) consumes.

Index

Constants

View Source
const (
	// AgentSubjectPrefix starts every actor's sub in a delegated credential's
	// act claim, so an actor can never be mistaken for a human or a workload.
	AgentSubjectPrefix = "agent:"
	// DefaultAgentMaxDepth is the delegation depth when agents.maxDepth is unset:
	// an agent, and one subagent it spawns.
	DefaultAgentMaxDepth = 2
	// MaxAgentDepth is the hard ceiling: policy cannot raise maxDepth above it
	// and verifiers refuse longer chains whatever the policy says.
	MaxAgentDepth = 5
	// DefaultAgentTTL is the delegated credential lifetime when
	// agents.maxTTLSeconds is unset. The ceiling is MaxWorkloadTTL.
	DefaultAgentTTL = 15 * time.Minute
)
View Source
const (
	ScopeUser  = "user"
	ScopeGroup = "group"
	ScopeRing  = "ring"
	ScopeOrg   = "org"

	WindowDay     = "day"
	WindowWeek    = "week"
	WindowMonth   = "month"
	WindowRolling = "rolling"

	BudgetWarn    = "warn"
	BudgetEnforce = "enforce"
)

Budget scopes, windows and actions.

View Source
const (
	CloudClaude  = "claude"
	CloudCodex   = "codex"
	CloudCopilot = "copilot"
	CloudCursor  = "cursor"
	CloudGemini  = "gemini"

	CloudIdentityWorkload = "workload"

	NetworkNone      = "none"
	NetworkAllowlist = "allowlist"
	NetworkFull      = "full"

	OverLimitAlert = "alert"
	OverLimitPause = "pause"
)

Cloud agent vendors, identity modes, network modes and over-limit actions.

View Source
const (
	DLPMonitor = "monitor"
	DLPRedact  = "redact"
	DLPBlock   = "block"
	DLPOff     = "off"
)

DLP actions.

View Source
const (
	DefaultDLPThreshold    = 4
	DefaultDLPMaxScanBytes = 4 << 20
)

DLP defaults and bounds.

View Source
const (
	EgressAllow = "allow"
	EgressDeny  = "deny"
)

Egress defaults (ADR-0032).

View Source
const (
	// MCPModeAllowlist (the default) renders the registry as each harness's
	// managed allowlist: servers outside it are blocked where the harness can
	// block them, and profiles, units and toggles may only name registry servers.
	MCPModeAllowlist = "allowlist"
	// MCPModeWarn renders the registry servers but blocks nothing else: halod
	// reports unapproved servers as drift. A migration step, not an end state.
	MCPModeWarn = "warn"
)

Registry modes.

View Source
const (
	MCPAuthNone = "none"
	// MCPAuthOAuth: the server's own authorization server (MCP authorization,
	// OAuth 2.1), with a pre-registered client id or a Client ID Metadata Document.
	MCPAuthOAuth = "oauth"
	// MCPAuthEnterprise: Enterprise-Managed Authorization (the
	// io.modelcontextprotocol/enterprise-managed-authorization extension): the
	// org IdP (identity.issuer) issues an ID-JAG the client trades at the
	// server's authorization server.
	MCPAuthEnterprise = "enterprise"
	// MCPAuthHalos: halo-server is the server's authorization server and mints
	// short-lived, audience-bound access tokens for it (POST /api/v1/token/exchange
	// with resource=<server url>).
	MCPAuthHalos = "halos"
)

Authorization modes of a remote registry server (MCPAuth.Mode).

View Source
const (
	MCPPackageNPM    = "npm"
	MCPPackagePyPI   = "pypi"
	MCPPackageOCI    = "oci"
	MCPPackageBinary = "binary"
)

Package types of a stdio registry server (MCPPackage.Type), as the official MCP Registry's server.json registryType names them (binary is Halos's).

View Source
const (
	RolloutDraft     = "draft"
	RolloutActive    = "active"
	RolloutPaused    = "paused"
	RolloutAborted   = "aborted"
	RolloutCompleted = "completed"
)

Rollout statuses. Only active rollouts are driven by the controller.

View Source
const (
	SimpleProfile  = "default"
	ProviderMulti  = "multi"
	SafetyStrict   = "strict"
	SafetyStandard = "standard"
	SafetyRelaxed  = "relaxed"
)

Simple-mode names. The profile every generated ring uses is SimpleProfile.

View Source
const (
	ArtifactSkill   = "skill"   // a directory holding SKILL.md
	ArtifactPlugin  = "plugin"  // a plugin directory (.claude-plugin/plugin.json, ...)
	ArtifactHook    = "hook"    // hook scripts a hooks.items command runs
	ArtifactCommand = "command" // a slash command: one Markdown file
	ArtifactAgent   = "agent"   // a subagent definition: one Markdown file
)

Artifact kinds for SupplyChain.Artifacts (ADR-0028).

View Source
const (
	ScanOff     = "off"
	ScanWarn    = "warn"
	ScanEnforce = "enforce"
)

Scan modes for SupplyChain.Scan.

View Source
const (
	EffectOn  = "on"
	EffectOff = "off"
)

Toggle effects for ToggleRule.Effect.

View Source
const (
	GroupsOverageDeny  = "deny"
	GroupsOverageGraph = "graph"
)

Identity.GroupsOverage values.

View Source
const (
	MarketplaceGitHub      = "github"      // Repo (OWNER/REPO), optional Ref
	MarketplaceGit         = "git"         // URL, optional Ref
	MarketplaceURL         = "url"         // URL of a marketplace.json
	MarketplaceHostPattern = "hostPattern" // HostPattern, a regex over marketplace hosts
)

Marketplace source kinds for Extensions.Marketplaces.

View Source
const (
	LoginSubscription = "subscription"
	LoginAPI          = "api"
)

Login restrictions for Profile.Login.

View Source
const (
	GateOff     = "off"
	GateWarn    = "warn"
	GateEnforce = "enforce"
)

Gate modes for Ring.Posture and Ring.VersionGate.

View Source
const (
	ShadowTurnsFirst = "first"
	ShadowTurnsAll   = "all"
)

Shadow turn scopes (Experiment.Turns).

View Source
const (
	DefaultWorkloadTTL = 15 * time.Minute
	MaxWorkloadTTL     = time.Hour
)

Workload token lifetimes: halo-server never mints one longer than MaxWorkloadTTL; a rule without maxTTLSeconds gets DefaultWorkloadTTL.

View Source
const APIVersion = "halos.dev/v1"

APIVersion is the stable document version this build writes.

View Source
const APIVersionV1Alpha1 = "halos.dev/v1alpha1"

APIVersionV1Alpha1 is the pre-GA document version. It is still read, with semantics identical to APIVersion and a deprecation warning from Validate; `halo migrate` rewrites it. Removed only in halos.dev/v2 (see docs/reference/compatibility).

View Source
const EngineExternal = "external"

EngineExternal is a gateway Halos does not run.

View Source
const GitHubActionsProvider = "GitHub Actions"

GitHubActionsProvider is TenantClaims' provider name for the GitHub Actions OIDC issuer.

View Source
const IncidentFreezeName = "incident"

IncidentFreezeName names the incident freeze halo-server toggles.

View Source
const LabelGeneratedBy = "halos.dev/generated-by"

LabelGeneratedBy marks a document a tool generated for one owner, with the value GeneratedFor(rollout): `halo model switch --canary` labels the gateway.yaml override it writes, and the rollout's completion deletes it.

View Source
const MaxChannelLen = 100

MaxChannelLen bounds a delivery channel name. Channels become OCI tags ("ring-<channel>.pointer", tag limit 128) and halod state keys, so a name that would exceed this is shortened by hashing (see ChannelName).

View Source
const RootFile = "halos.yaml"

RootFile is the policy repo's root document.

View Source
const SnapshotVersion = "halos.dev/snapshot/v1"

SnapshotVersion identifies the envelope format written by Compile.

View Source
const SubjectWorkload = "workload"

SubjectWorkload is Subject.Kind for a CI / cloud workload: a principal halo-server minted from a federated OIDC token (ADR-0023), not a human.

View Source
const TestsDir = "tests"

TestsDir is the policy repo's top-level directory of PolicyTest documents (internal/policytest, `halo test`). Load does not parse it as policy.

View Source
const ToggleDateLayout = "2006-01-02"

ToggleDateLayout is the layout of Toggle.Expires.

View Source
const ToggleSaltPrefix = "halos/toggles/"

ToggleSaltPrefix namespaces percent-rollout hashing per toggle, so a toggle's cohort is independent of the ring and of every experiment (see internal/assign).

View Source
const WorkloadSubjectPrefix = "workload:"

WorkloadSubjectPrefix starts every workload subject id, so a workload can never take a human's id (ring membership, budgets, audit).

Variables

CloudVendors lists the vendors in a fixed order.

View Source
var ConsoleRoles = []string{"viewer", "operator", "approver", "auditor", "admin"}

ConsoleRoles are halo-server's roles (ADR-0015), in display order. internal/server's Roles is this list.

View Source
var DLPDetectorIDs = []string{
	"anthropic-api-key", "aws-access-key-id", "gcp-service-account", "generic-secret", "github-token",
	"gitlab-token", "jwt", "openai-api-key", "private-key", "slack-token", "slack-webhook", "stripe-key",
}

DLPDetectorIDs are the built-in secret detector ids (internal/gateway owns the detectors; tests keep the two lists equal).

View Source
var DefaultAlertAt = []float64{0.8}

DefaultAlertAt is the soft threshold when a budget sets none.

View Source
var EgressPresets = map[string][]string{

	"package-registries": {
		"registry.npmjs.org", "pypi.org", "files.pythonhosted.org", "proxy.golang.org", "sum.golang.org",
		"index.crates.io", "static.crates.io", "repo.maven.apache.org", "rubygems.org",
	},

	"vcs": {
		"github.com", "api.github.com", "codeload.github.com", "*.githubusercontent.com",
		"gitlab.com", "bitbucket.org",
	},

	"model-providers": {
		"api.anthropic.com", "api.openai.com", "generativelanguage.googleapis.com",
		"api.githubcopilot.com", "*.githubcopilot.com",
	},
}

EgressPresets are the named domain sets egress.presets expands to. Each set is the hosts the named service documents for clients; see docs/guides/egress-and-sandbox.md for the source of every entry.

View Source
var ExtraGuardrails []Guardrail

ExtraGuardrails run after the built-in guardrails. They can only add issues: the built-ins have already been judged and recorded, so an extra can never remove or downgrade one (ADR-0007). Set once at start-up.

View Source
var MetricRegistry = []MetricDef{
	{Name: "halo.task.success", Unit: "ratio", Description: "Fraction of tasks that ended successfully (eval verdict).",
		Sources: map[string]string{"claude-code": "halo eval trial verdict", "codex": "halo eval trial verdict", "gemini-cli": "halo eval trial verdict"},
		Source:  []SignalSource{SourceEval}, PerUnit: "one eval trial (pass = 1)"},
	{Name: "halo.cost.usd_per_session", Unit: "usd", Description: "Model spend per session.",
		Sources: map[string]string{"claude-code": "claude_code.cost.usage", "codex": "codex.api_request events (tokens x price)", "gemini-cli": "gemini_cli.api_response (tokens x price)"},
		Source:  []SignalSource{SourceOnlineCLI}, PerUnit: "unit's total halo.cost.usd / its distinct sessions"},
	{Name: "halo.tokens.per_session", Unit: "tokens", Description: "Input+output tokens per session.",
		Sources: map[string]string{"claude-code": "claude_code.token.usage", "codex": "codex.api_request events", "gemini-cli": "gemini_cli.token.usage"},
		Source:  []SignalSource{SourceOnlineCLI}, PerUnit: "unit's input+output halo.tokens / its distinct sessions"},
	{Name: "halo.api.error_rate", Unit: "ratio", Description: "Fraction of model API requests that failed (non-2xx or stream error).",
		Sources: map[string]string{"claude-code": "claude_code.api_error / (claude_code.api_request + claude_code.api_error) events", "codex": "codex.api_request events (http.response.status_code, error.message)", "gemini-cli": "gemini_cli.api_error / (gemini_cli.api_response + gemini_cli.api_error)", "gateway": "halo.gateway.requests{status_class!=2xx} / halo.gateway.requests"},
		Source:  []SignalSource{SourceOnlineGateway, SourceOnlineCLI}, PerUnit: "unit's failed / total model API requests"},
	{Name: "halo.edit.accept_rate", Unit: "ratio", Description: "Fraction of proposed code edits the user accepted.",
		Sources: map[string]string{"claude-code": "claude_code.code_edit_tool.decision", "codex": "codex.tool_decision events", "gemini-cli": "gemini_cli.tool_call (decision)"},
		Source:  []SignalSource{SourceOnlineCLI}, PerUnit: "unit's accepted / total edit decisions"},
	{Name: "halo.latency.p50_ms", Unit: "ms", Description: "Median latency of successful model requests.",
		Sources: map[string]string{"claude-code": "claude_code.api_request duration_ms", "codex": "codex.api_request duration_ms (2xx)", "gemini-cli": "gemini_cli.api_response duration_ms", "gateway": "halo.gateway.latency_ms{status_class=2xx} histogram"},
		Source:  []SignalSource{SourceOnlineGateway, SourceOnlineCLI}, PerUnit: "median over the unit's successful requests (gateway: interpolated from histogram buckets)"},
	{Name: "halo.latency.p95_ms", Unit: "ms", Description: "95th percentile latency of successful model requests.",
		Sources: map[string]string{"claude-code": "claude_code.api_request duration_ms", "codex": "codex.api_request duration_ms (2xx)", "gemini-cli": "gemini_cli.api_response duration_ms", "gateway": "halo.gateway.latency_ms{status_class=2xx} histogram"},
		Source:  []SignalSource{SourceOnlineGateway, SourceOnlineCLI}, PerUnit: "p95 over the unit's successful requests (gateway: interpolated from histogram buckets)"},
	{Name: "halo.session.duration_s", Unit: "s", Description: "Wall-clock session length.",
		Sources: map[string]string{"claude-code": "claude_code.active_time.total", "codex": "codex.session events", "gemini-cli": "gemini_cli.session events"},
		PerUnit: "not yet derived: no online source"},
	{Name: "halo.tool.error_rate", Unit: "ratio", Description: "Fraction of tool calls that errored.",
		Sources: map[string]string{"claude-code": "claude_code.tool_result events (success)", "codex": "codex.tool_result events (success)", "gemini-cli": "gemini_cli.tool_call (success)"},
		Source:  []SignalSource{SourceOnlineCLI}, PerUnit: "unit's failed / total tool calls"},
	{Name: "halo.eval.judge.score", Unit: "ratio", Description: "LLM-judge rubric score (0..1, higher is better) of a model response, graded by `halo eval online` on halo-shadow pairs.",
		Sources: map[string]string{"shadow": "halo eval online -> halo.eval.judge.score{halo.unit=<hashed session>}, halo.source=eval"},
		Source:  []SignalSource{SourceOnlineEval}, PerUnit: "mean judge score of one session's graded shadow pairs for that arm (unit = hashed session; a first-turn pair without a session is its own unit)", Better: "increase"},
	{Name: "halo.eval.agent.fidelity", Unit: "ratio", Description: "Share of agent steps where the arm's next action (tools called) matches the action production took on the same real prefix; the control's value is the re-sampling noise floor (ADR-0014).",
		Sources: map[string]string{"shadow": "halo eval online -> halo.eval.agent.fidelity{halo.unit=<hashed session>}, halo.source=eval (shadow experiments with turns: all)"},
		Source:  []SignalSource{SourceOnlineEval}, PerUnit: "mean step fidelity over one session's referenced steps for that arm (unit = session)", Better: "increase"},
	{Name: "halo.eval.agent.invalid_call_rate", Unit: "ratio", Description: "Share of agent steps where the arm called a tool the request did not offer or omitted a required argument (ADR-0014).",
		Sources: map[string]string{"shadow": "halo eval online -> halo.eval.agent.invalid_call_rate{halo.unit=<hashed session>}, halo.source=eval"},
		Source:  []SignalSource{SourceOnlineEval}, PerUnit: "share of one session's steps with an invalid call for that arm (unit = session)", Better: "decrease"},
}

MetricRegistry lists every metric experiments may reference; Validate rejects names not in this list.

View Source
var RolloutPresets = map[string]RolloutPreset{
	"fast": {Name: "fast", TeamBake: "4h", RingBake: "1d",
		Rings:  []Ring{presetRing("ring0-team", 0, Membership{}), presetRing("ring1-ga", 1, Membership{Default: true})},
		Canary: []RolloutStep{canary(25, "4h", 100)}},
	"standard": {Name: "standard", TeamBake: "1d", RingBake: "2d",
		Rings: []Ring{presetRing("ring0-team", 0, Membership{}), presetRing("ring1-canary", 1, Membership{Percent: 5}),
			presetRing("ring2-early", 2, Membership{Percent: 25}), presetRing("ring3-ga", 3, Membership{Default: true})},
		Canary: []RolloutStep{canary(5, "12h", 200), canary(25, "1d", 500), canary(50, "1d", 1000)}},
	"careful": {Name: "careful", TeamBake: "2d", RingBake: "3d",
		Rings: []Ring{presetRing("ring0-team", 0, Membership{}), presetRing("ring1-canary", 1, Membership{Percent: 1}),
			presetRing("ring2-early", 2, Membership{Percent: 5}), presetRing("ring3-broad", 3, Membership{Percent: 25}),
			presetRing("ring4-ga", 4, Membership{Default: true})},
		Canary: []RolloutStep{canary(1, "1d", 200), canary(5, "2d", 500), canary(25, "2d", 2000), canary(50, "3d", 4000)}},
}

RolloutPresets are fast, standard and careful.

Functions

func BypassToken added in v0.4.0

func BypassToken(s string) string

BypassToken returns the first permission-bypass token in s, or "". It is the single invariant 1 check: policy validation runs it on every string in the org, the release check on every byte of every rendered file, and vendoradmin and halo ci config on what they emit.

func CanonicalResource added in v0.4.0

func CanonicalResource(u string) (string, bool)

CanonicalResource is u in the form MCP authorization compares resources in (RFC 8707 section 2): https, lowercase scheme and host, no default port, fragment, query or credentials, and no trailing slash. ok is false for anything that is not such a URL.

func CanonicalUser added in v0.3.0

func CanonicalUser(id string) string

CanonicalUser is the one form in which Halos compares or keys a human user id: halo-server's sessions, devices, enrollments, posture, SCIM userName, access-request self-approval and whoami, and the per-user budget counters (budget.Charges) that halo-server and the gateways share. It trims surrounding space and lowercases, so an IdP that provisions "Alice@Corp.com" and logs in "alice@corp.com" is one user.

It never merges a lookalike into another letter. strings.ToLower maps the Kelvin sign U+212A to "k", U+0130 (I with dot) to "i", the Ohm sign to omega and the Angstrom sign to a-ring, so "Kelly" would key the same sessions, devices and budget as "kelly". A rune is lowercased only when uppercasing the result gives it back (a real case pair: A/a, É/é, Σ/σ); compatibility characters are kept as they are, so a lookalike id stays a different user. This is the outcome of rejecting such ids without needing a refusal at every identity entry point. Not NFKC: compatibility normalisation would merge exactly these lookalikes (and fullwidth letters) into the ASCII ids they imitate.

func ChannelName

func ChannelName(ring, exp, variant string) string

ChannelName is the delivery channel that serves variant of client-axis experiment exp to devices in ring: "<ring>.x-<exp>.<variant>". When that exceeds MaxChannelLen the experiment/variant part is replaced by the first 20 hex chars of sha256(exp NUL variant). Names are ValidName, so the result only uses [a-z0-9._-] and fits the OCI tag grammar.

The ring release names its channels in its signed manifest; halod recomputes the name and refuses a mismatch. Validate rejects policies where a channel name collides with a ring or another channel.

func CheckCustomHeaders added in v0.2.0

func CheckCustomHeaders(v string) error

CheckCustomHeaders validates a profile's newline-separated ANTHROPIC_CUSTOM_HEADERS: no CR (header injection) and no x-halo-* header, which only Halos sets.

func ClaimString added in v0.3.0

func ClaimString(claims map[string]any, name string) (string, bool)

ClaimString renders a scalar claim (string, bool, number) as a string. Objects, arrays and absent claims report false.

func Compile

func Compile(org *Org) ([]byte, error)

Compile renders org as deterministic JSON (same input => same bytes: maps are key-sorted by encoding/json, no timestamps) for the gateway plane.

func DefaultModelFor

func DefaultModelFor(provider string) string

DefaultModelFor is the model `halo init` gives the default alias on provider.

func GateMode

func GateMode(m string) string

GateMode is m with the default (warn) applied.

func GeneratedFor

func GeneratedFor(rollout string) string

GeneratedFor is the LabelGeneratedBy value of documents generated for rollout.

func HasErrors

func HasErrors(issues []Issue) bool

HasErrors reports whether any issue is an error.

func HasTokenShape

func HasTokenShape(s string) bool

HasTokenShape reports whether s contains a well-known credential shape (vendor token prefixes such as sk-ant-, ghp_, xoxb-; AWS access key ids; PEM private key blocks) outside ${VAR} references. It is the low-false- positive subset of looksSecret, safe to run over whole rendered files.

func IntersectScope added in v0.3.0

func IntersectScope(a, b []string) []string

IntersectScope intersects two model scopes where nil means unrestricted. The result is nil only when both are nil; otherwise it is non-nil (possibly empty: nothing allowed), so an empty intersection can never widen to "all".

func KindServes

func KindServes(kind, proto string) bool

KindServes reports whether an upstream of kind can serve a client speaking proto. Pass-through kinds (orchestrator, anthropic, openai, gemini) keep the client's wire as-is. bedrock additionally serves anthropic-messages clients (the request is translated and the event stream converted back); vertex serves only anthropic-messages; azure-openai only openai-responses; gemini clients are served only by kinds gemini and orchestrator.

func KnownMetric

func KnownMetric(name string) bool

KnownMetric reports whether name is in MetricRegistry.

func LooksSecret

func LooksSecret(v string) bool

LooksSecret reports whether v looks like a literal credential (see looksSecret).

func MatchDomain added in v0.4.0

func MatchDomain(pattern, host string) bool

MatchDomain reports whether host matches pattern: an exact host, or "*.example.com", which matches every subdomain of example.com but not example.com itself. Case and a trailing dot are ignored.

func ParseBake

func ParseBake(s string) (time.Duration, error)

ParseBake parses a Go duration or a whole number of days ("14d").

func ParseCardKey added in v0.3.0

func ParseCardKey(pemData string) (any, error)

ParseCardKey parses a PEM PUBLIC KEY usable for AgentCard JWS: ed25519, ECDSA P-256/P-384 or RSA >= 2048 bits.

func SecureAgentURL added in v0.3.0

func SecureAgentURL(s string) bool

SecureAgentURL reports whether s is an absolute https URL without credentials or fragment (an AgentCard or JWKS location).

func ServesNatively

func ServesNatively(tool, kind string) bool

ServesNatively reports whether upstream provider kind answers tool's wire (true for tools the gateway does not route).

func SimpleGatewayName

func SimpleGatewayName(org string) string

SimpleGatewayName is the generated Gateway document's name.

func SplitModel

func SplitModel(id, def string) (provider, model string)

SplitModel splits "<provider>/<model>" when the prefix is a known provider (Bedrock ARNs keep their slashes); otherwise the model runs on def.

func ToolModelFor

func ToolModelFor(tool, provider, defaultID string) (string, bool)

ToolModelFor reports whether tool needs its own start model because defaultID (the default alias's model, on provider) runs on a provider that cannot answer the tool's wire, and if so the vendor model to use ("<provider>/<model>"). Tools the gateway does not route (copilot-cli, cursor-agent) never need one.

func UnitForbiddenEnv added in v0.3.0

func UnitForbiddenEnv(k string) string

UnitForbiddenEnv reports why a unit may never set env key k ("" when it may): it would redirect model traffic, credentials or telemetry, or run code in the harness, its shell or the tools it calls (git, pagers, editors, compilers, language runtimes). Matching ignores case; families are prefixes.

func ValidName

func ValidName(s string) bool

ValidName reports whether s is a safe policy identifier (ring, profile, experiment, variant, model alias, upstream, MCP server).

func ValidTicket added in v0.4.0

func ValidTicket(t string) error

ValidTicket checks a ticket reference's shape: it lands in audit records and PR bodies, so it is a short token, never free text.

func ValidVersion added in v0.5.0

func ValidVersion(v string) bool

ValidVersion reports whether v is an exact semver, the only form a harness version pin may take (no ranges, no latest, no date-style versions).

func WritableRootProblem added in v0.4.0

func WritableRootProblem(r string, protected []string) string

WritableRootProblem is why r may not be a sandbox writable root next to protected ("" = fine): not absolute or ~/, a glob, the disk, a system directory or one of its ancestors, a home directory (as ~ or absolute), or overlapping a protected path. Paths compare case-insensitively (macOS and Windows file systems are), and an absolute path under a home directory (/Users/alice/.ssh) is also compared in its ~ form (~/.ssh). Shared by validation and the release check (invariant 1).

Types

type ActiveFreeze added in v0.4.0

type ActiveFreeze struct {
	Name   string `json:"name"`
	Reason string `json:"reason"`
	// Until is the end of this occurrence; zero = until a human lifts it
	// (incident freeze) or never (a window that does not parse: fail closed).
	Until    time.Time `json:"until,omitzero"`
	Incident bool      `json:"incident,omitempty"`
	By       string    `json:"by,omitempty"`
}

ActiveFreeze is one window holding a change.

type Actor added in v0.3.0

type Actor struct {
	// Agent is the agents.actors name the credential was minted for.
	Agent string `json:"agent"`
	// Harness and Session say where the agent runs (claude-code, codex, ...;
	// the harness session id). Informational: recorded for audit, never routed on.
	Harness string `json:"harness,omitempty"`
	Session string `json:"session,omitempty"`
}

Actor is one link of a delegation chain.

type AgentActor added in v0.3.0

type AgentActor struct {
	Name string `yaml:"name" json:"name"`
	// Harnesses it may run in (claude-code, codex, ...); empty = any.
	Harnesses []string `yaml:"harnesses,omitempty" json:"harnesses,omitempty"`
	// Groups: the root principal must hold one of them; empty = any root.
	Groups []string `yaml:"groups,omitempty" json:"groups,omitempty"`
	// Models narrows agents.allowedModels for this agent; empty = no narrowing.
	Models []string `yaml:"models,omitempty" json:"models,omitempty"`
}

AgentActor is one agent that may act for a principal.

type AgentLimits added in v0.3.0

type AgentLimits struct {
	// MaxDepth lowers agents.maxDepth for the profile's members; 0 = the org's.
	MaxDepth int `yaml:"maxDepth,omitempty" json:"maxDepth,omitempty"`
	// Remote names the agents.remote entries members may call; empty = every
	// entry whose rings or units admit them.
	Remote []string `yaml:"remote,omitempty" json:"remote,omitempty"`
}

AgentLimits is a profile's (or unit overlay's) bound on delegated agents. A unit may only lower MaxDepth and narrow Remote (see Loosenings).

type AgentScope added in v0.3.0

type AgentScope struct {
	Ring, Unit string
	// MaxDepth is the effective maximum chain length for this root.
	MaxDepth int
	// Remote names the agents.remote entries the root's agents may call.
	Remote []string
}

AgentScope is what the policy allows delegated agents of one root principal.

type Agents added in v0.3.0

type Agents struct {
	// MaxDepth bounds the delegation chain (default 2, at most 5).
	MaxDepth int `yaml:"maxDepth,omitempty" json:"maxDepth,omitempty"`
	// MaxTTLSeconds caps a delegated credential (default 900, at most 3600). It
	// never outlives its parent either.
	MaxTTLSeconds int `yaml:"maxTTLSeconds,omitempty" json:"maxTTLSeconds,omitempty"`
	// Experiments enrolls delegated callers in experiments with their root.
	// Default false: agent traffic gets control routing and no shadow.
	Experiments bool `yaml:"experiments,omitempty" json:"experiments,omitempty"`
	// AllowedModels are the gateway model aliases any delegated actor may call;
	// empty = every alias the root may call.
	AllowedModels []string `yaml:"allowedModels,omitempty" json:"allowedModels,omitempty"`
	// DeniedTools are tools a delegated actor's model call may not declare:
	// function names (matched ignoring case) and hosted tool identifiers such
	// as web_search, code_execution or mcp:<server> (matched as a prefix,
	// ignoring case and underscores; see DeniesTool). With any set, the
	// gateway refuses an agent call whose tools it cannot read.
	DeniedTools []string `yaml:"deniedTools,omitempty" json:"deniedTools,omitempty"`
	// Actors are the agents that may act. A name not listed is refused.
	Actors []AgentActor `yaml:"actors" json:"actors"`
	// Remote is the allowlist of A2A agents members may call.
	Remote []RemoteAgent `yaml:"remote,omitempty" json:"remote,omitempty"`
}

Agents is halos.yaml agents: which agents may act for whom, how deep and for how long, what delegated actors may not do, and the remote (A2A) agents members may call.

func (*Agents) Actor added in v0.3.0

func (a *Agents) Actor(name string) *AgentActor

Actor returns the named agent, or nil.

func (*Agents) AdmitChain added in v0.3.0

func (a *Agents) AdmitChain(rootGroups []string, d *DelegationChain) error

AdmitChain re-checks a delegation chain against the current policy for a root holding rootGroups: every actor, not only the current one, must still be in agents.actors and, when the actor lists groups, the root must hold one of them; the chain must be within agents.maxDepth. The credential says what policy allowed at issuance, not what it allows now.

func (*Agents) AllowsExperiments added in v0.3.0

func (a *Agents) AllowsExperiments() bool

AllowsExperiments reports whether delegated callers may be enrolled in experiments.

func (*Agents) ChainModelLimit added in v0.3.0

func (a *Agents) ChainModelLimit(d *DelegationChain) []string

ChainModelLimit is a delegation chain's model allowlist under the current policy: the credential's scope narrowed by agents.allowedModels and by the models of every actor along the chain. nil = unrestricted; a non-nil empty slice allows nothing.

func (*Agents) DeniesTool added in v0.3.0

func (a *Agents) DeniesTool(name string, hosted bool) bool

DeniesTool reports whether agents.deniedTools denies one tool a model call declares. A function tool (run by the client, named in the request) matches an entry ignoring case. A hosted tool (run by the provider or built in, identified by its type, its Gemini or Bedrock tool key, or mcp:<label>) matches an entry when, ignoring case and underscores, its identifier starts with the entry: web_search denies web_search_20250305 and web_search_preview, code_execution denies codeExecution, and mcp denies every mcp:<label>. Hosted identifiers carry versions and vendor spellings, so an exact match would let the next version through.

func (*Agents) Depth added in v0.3.0

func (a *Agents) Depth() int

Depth is the effective org-level maximum delegation depth.

func (*Agents) ModelLimit added in v0.3.0

func (a *Agents) ModelLimit(actor *AgentActor) []string

ModelLimit is the policy's model allowlist for actor: agents.allowedModels narrowed by the actor's own models. nil = unrestricted. A non-nil empty slice means nothing is allowed (the two lists do not intersect).

func (*Agents) RemoteAgent added in v0.3.0

func (a *Agents) RemoteAgent(name string) *RemoteAgent

RemoteAgent returns the named remote agent, or nil.

func (*Agents) TTL added in v0.3.0

func (a *Agents) TTL() time.Duration

TTL is the delegated credential lifetime cap.

type AllowFinding added in v0.4.0

type AllowFinding struct {
	Digest string `yaml:"digest" json:"digest"`
	Rule   string `yaml:"rule" json:"rule"`
	Path   string `yaml:"path,omitempty" json:"path,omitempty"`
	Reason string `yaml:"reason" json:"reason"`
}

AllowFinding suppresses scan findings by digest, so changed content is scanned afresh: Digest is either the artifact digest (any of its files) or one file's sha256 (that file only). Path ("" = any file) narrows it.

type Approval added in v0.4.0

type Approval struct {
	By string `json:"by"`
	// Groups are the approver's groups when they approved (IdP or SCIM).
	Groups []string  `json:"groups,omitempty"`
	At     time.Time `json:"at"`
	Ticket string    `json:"ticket,omitempty"`
	// Scopes are the scopes of the approver's scoped approve bindings when
	// they approved (ADR-0037). nil means a global approver: every approval
	// recorded before ADR-0037 was global. A scoped approval counts only for
	// a change one of them covers (QuorumFor).
	Scopes []RoleScope `json:"scopes,omitempty"`
	// ScopedGroups replaces Groups on a scoped approval (ScopedApproval): a
	// binary from before ADR-0037 ignores Scopes, so it must see no groups
	// and count the approval for no rule, rather than count it as global.
	ScopedGroups []string `json:"scopedGroups,omitempty"`
}

Approval is one recorded approval of a change.

func ScopedApproval added in v0.5.0

func ScopedApproval(by string, groups []string, at time.Time, ticket string, scopes []RoleScope) Approval

ScopedApproval is an approval by by, holding groups, under scoped approve bindings with scopes (nil scopes: a global approval).

func (Approval) ScopeCovers added in v0.5.0

func (a Approval) ScopeCovers(res *Resource) bool

ScopeCovers reports whether a counts for a change touching res: a global approval (Scopes nil) always does, a scoped one when one of its scopes covers res.

type ApprovalRule added in v0.4.0

type ApprovalRule struct {
	Name   string      `yaml:"name" json:"name"`
	Scope  ChangeScope `yaml:"scope,omitempty" json:"scope,omitzero"`
	Quorum int         `yaml:"quorum" json:"quorum"`
	// Groups are the IdP (or SCIM) groups an approver must hold one of.
	Groups []string `yaml:"groups" json:"groups"`
	// DistinctGroups: each counted approval must come from a different group.
	DistinctGroups bool `yaml:"distinctGroups,omitempty" json:"distinctGroups,omitempty"`
	// Ticket, when set, is a regular expression (matched whole) a change
	// ticket reference must match, e.g. ^(CHG|INC)[0-9]{7}$ or ^OPS-[0-9]+$.
	Ticket string `yaml:"ticket,omitempty" json:"ticket,omitempty"`
}

ApprovalRule requires Quorum approvals, from members of Groups, for every change its scope covers. The requester never counts.

type Artifact added in v0.4.0

type Artifact struct {
	Name string `yaml:"name" json:"name"`
	Kind string `yaml:"kind" json:"kind"`
	// Path is a file or directory in the policy repo, relative to its root.
	Path string `yaml:"path,omitempty" json:"path,omitempty"`
	// Git is a repository pinned to a full commit id.
	Git *GitSource `yaml:"git,omitempty" json:"git,omitempty"`
	// OCI is a digest-pinned reference, host/repo@sha256:<hex>; each layer is
	// one file named by its org.opencontainers.image.title annotation.
	OCI string `yaml:"oci,omitempty" json:"oci,omitempty"`
	// Digest is the artifact digest (supplychain.DigestOf). Required for git
	// and oci; for a path it is computed and, when set, checked.
	Digest    string             `yaml:"digest,omitempty" json:"digest,omitempty"`
	Signature *ArtifactSignature `yaml:"signature,omitempty" json:"signature,omitempty"`
}

Artifact is one extension artifact. Exactly one source is set.

func (Artifact) Remote added in v0.4.0

func (a Artifact) Remote() bool

Remote reports whether a's content comes from outside the policy repo.

func (Artifact) Source added in v0.4.0

func (a Artifact) Source() string

Source is a's source as one string, for manifests and BOMs.

type ArtifactContent added in v0.4.0

type ArtifactContent struct {
	Files  supplychain.Tree
	Err    string // why it could not be read
	Signer string // trusted identity whose signature verified, if any
}

ArtifactContent is an artifact's resolved content: read from the policy repo by Load, or fetched and signature-checked by `halo release build`.

type ArtifactSignature added in v0.4.0

type ArtifactSignature struct {
	Identity string `yaml:"identity" json:"identity"`
	File     string `yaml:"file" json:"file"`
}

ArtifactSignature names who signed the artifact digest and where the signature is (a file in the policy repo: base64 ed25519, or cosign's sign-blob output).

type Axis

type Axis string

Axis is where an experiment's variants differ.

const (
	AxisClient  Axis = "client"  // variants are different profiles/releases on the machine
	AxisTraffic Axis = "traffic" // variants are different model routes at the gateway
)

type Budget added in v0.3.0

type Budget struct {
	Name string `yaml:"name" json:"name"`
	// Scope picks the counter: user (one per verified subject), group (one per
	// listed group), ring (one per ring) or org (one shared counter).
	Scope string `yaml:"scope" json:"scope"`
	// Groups: for scope group, the groups that each get a counter (required);
	// for other scopes, only callers in one of them are counted. Empty = everyone.
	Groups []string `yaml:"groups,omitempty" json:"groups,omitempty"`
	// Rings limits the budget to callers routed to these rings. Empty = every ring.
	Rings []string `yaml:"rings,omitempty" json:"rings,omitempty"`
	// Window: day | week (Monday) | month, calendar windows in UTC, or rolling
	// over Period.
	Window string `yaml:"window" json:"window"`
	// Period is the rolling window length as a Go duration (e.g. 24h); rolling only.
	Period string `yaml:"period,omitempty" json:"period,omitempty"`
	// LimitUSD and LimitTokens (input + output + cache) cap the window; set at
	// least one. Either one reached exhausts the budget.
	LimitUSD    float64 `yaml:"limitUSD,omitempty" json:"limitUSD,omitempty"`
	LimitTokens int64   `yaml:"limitTokens,omitempty" json:"limitTokens,omitempty"`
	// Action when exhausted: warn (header + metric, the request passes) or
	// enforce (HTTP 429 until the window resets). Empty means warn.
	Action string `yaml:"action,omitempty" json:"action,omitempty"`
	// AlertAt are soft thresholds as fractions of the limit (default [0.8]).
	AlertAt []float64 `yaml:"alertAt,omitempty" json:"alertAt,omitempty"`
}

Budget caps model spend over a window for one scope (ADR-0017).

func (*Budget) Alerts added in v0.3.0

func (b *Budget) Alerts() []float64

Alerts are the soft thresholds with the default applied.

func (*Budget) Mode added in v0.3.0

func (b *Budget) Mode() string

Mode is the action with the default (warn) applied.

func (*Budget) RollingPeriod added in v0.3.0

func (b *Budget) RollingPeriod() time.Duration

RollingPeriod is the parsed Period (0 unless the window is rolling and valid).

type ChangeScope added in v0.4.0

type ChangeScope struct {
	Rings []string `yaml:"rings,omitempty" json:"rings,omitempty"`
	Units []string `yaml:"units,omitempty" json:"units,omitempty"`
}

ChangeScope selects what a window or rule covers: changes to any listed ring or any listed unit's members. Empty is org-wide.

type ChangeTarget added in v0.4.0

type ChangeTarget struct {
	Rings []string `json:"rings,omitempty"`
	Units []string `json:"units,omitempty"`
}

ChangeTarget is what a change touches: rings and units.

type CloudAgent added in v0.4.0

type CloudAgent struct {
	// Enabled false renders nothing that runs the agent and makes every
	// observed task a violation; it never deletes a vendor setting.
	Enabled bool `yaml:"enabled" json:"enabled"`
	// Repos the agent may work in: owner/repo or owner/* (every repo of owner).
	Repos []string `yaml:"repos,omitempty" json:"repos,omitempty"`
	// Models the agent may use (vendor model ids). The first is rendered as
	// the default where the vendor takes a model in config.
	Models []string `yaml:"models,omitempty" json:"models,omitempty"`
	// MaxConcurrent caps running tasks.
	MaxConcurrent CloudConcurrency `yaml:"maxConcurrent,omitempty" json:"maxConcurrent,omitempty"`
	// Budgets cap vendor-reported task spend (ADR-0017 budget fields).
	Budgets []Budget `yaml:"budgets,omitempty" json:"budgets,omitempty"`
	// Network is the egress the agent's sandbox gets, where the vendor has one.
	Network *CloudNetwork `yaml:"network,omitempty" json:"network,omitempty"`
	// Identity "workload" makes rendered workflows call the model through the
	// Halos gateway with a short-lived workload credential (ADR-0023) instead
	// of a long-lived vendor key. Empty uses the vendor's repository secret.
	Identity string `yaml:"identity,omitempty" json:"identity,omitempty"`
	// OnOverLimit is alert (default: report only) or pause (also pause the
	// agent where the vendor API can, reversibly; never auto-resumed).
	OnOverLimit string `yaml:"onOverLimit,omitempty" json:"onOverLimit,omitempty"`
	// Version pins the agent CLI where the vendor takes one (gemini).
	Version string `yaml:"version,omitempty" json:"version,omitempty"`
	// TimeoutMinutes caps one rendered workflow job (copilot at most 59).
	TimeoutMinutes int `yaml:"timeoutMinutes,omitempty" json:"timeoutMinutes,omitempty"`
}

CloudAgent is one vendor's cloud agent policy.

func (*CloudAgent) ModelAllowed added in v0.4.0

func (a *CloudAgent) ModelAllowed(model string) bool

ModelAllowed reports whether model may be used. No list means any model; an empty model (the vendor did not report one) is allowed.

func (*CloudAgent) Pauses added in v0.4.0

func (a *CloudAgent) Pauses() bool

Pauses reports whether the agent pauses on an over-limit.

func (*CloudAgent) RepoAllowed added in v0.4.0

func (a *CloudAgent) RepoAllowed(repo string) bool

RepoAllowed reports whether owner/repo matches one of a's repo patterns (case-insensitive). No patterns means every repository.

type CloudAgents added in v0.4.0

type CloudAgents struct {
	// Identities map vendor logins to Halos users, so a cloud task is
	// attributed, ring-resolved and killed like its human.
	Identities []CloudIdentity `yaml:"identities,omitempty" json:"identities,omitempty"`
	// Server is the halo-server base URL rendered workflows exchange their
	// OIDC token at (identity: workload).
	Server string `yaml:"server,omitempty" json:"server,omitempty"`
	// HaloVersion is the halo release tag rendered workflows install to do
	// that exchange (identity: workload).
	HaloVersion string `yaml:"haloVersion,omitempty" json:"haloVersion,omitempty"`

	Claude  *CloudAgent `yaml:"claude,omitempty" json:"claude,omitempty"`
	Codex   *CloudAgent `yaml:"codex,omitempty" json:"codex,omitempty"`
	Copilot *CloudAgent `yaml:"copilot,omitempty" json:"copilot,omitempty"`
	Cursor  *CloudAgent `yaml:"cursor,omitempty" json:"cursor,omitempty"`
	Gemini  *CloudAgent `yaml:"gemini,omitempty" json:"gemini,omitempty"`
}

CloudAgents governs background coding agents that run on vendor or CI infrastructure instead of a developer machine (ADR-0034): Claude Code (the GitHub Action and cloud sessions), Codex cloud tasks, the Copilot cloud agent, Cursor cloud agents and the Gemini CLI GitHub Action. A vendor is governed when its key is present. Credentials never live here.

func (*CloudAgents) UserForGitHub added in v0.4.0

func (c *CloudAgents) UserForGitHub(login string) (string, bool)

UserForGitHub is the Halos user a GitHub login maps to (case-insensitive, as GitHub logins are).

func (*CloudAgents) Vendor added in v0.4.0

func (c *CloudAgents) Vendor(name string) *CloudAgent

Vendor returns one vendor's policy (nil when ungoverned).

type CloudConcurrency added in v0.4.0

type CloudConcurrency struct {
	PerUser  int `yaml:"perUser,omitempty" json:"perUser,omitempty"`
	PerGroup int `yaml:"perGroup,omitempty" json:"perGroup,omitempty"`
	// Groups that each get a PerGroup counter (required with perGroup).
	Groups []string `yaml:"groups,omitempty" json:"groups,omitempty"`
}

CloudConcurrency caps running tasks per user and per group.

type CloudIdentity added in v0.4.0

type CloudIdentity struct {
	// User is the Halos user id (what rings, experiments and budgets key on).
	User string `yaml:"user" json:"user"`
	// GitHub is the GitHub login that triggers GitHub-hosted agents
	// (Claude and Gemini actions, Copilot cloud agent).
	GitHub string `yaml:"github,omitempty" json:"github,omitempty"`
}

CloudIdentity is one person's vendor logins.

type CloudNetwork added in v0.4.0

type CloudNetwork struct {
	// Mode: none (no egress), allowlist (only Allow, plus the vendor's
	// package-registry list when Recommended) or full (any host; warned).
	Mode string `yaml:"mode" json:"mode"`
	// Allow are extra domains (a leading "*." matches subdomains).
	Allow []string `yaml:"allow,omitempty" json:"allow,omitempty"`
	// Recommended keeps the vendor's own dependency allowlist (allowlist only).
	Recommended bool `yaml:"recommended,omitempty" json:"recommended,omitempty"`
}

CloudNetwork is the sandbox egress mode.

type Credential

type Credential struct {
	Env    string `yaml:"env,omitempty" json:"env,omitempty"`
	File   string `yaml:"file,omitempty" json:"file,omitempty"`
	Scheme string `yaml:"scheme,omitempty" json:"scheme,omitempty"`
}

Credential locates a secret on the halo-proxy host (kinds openai, azure-openai, gemini): an environment variable name or a file path (exactly one). Scheme is api-key (default for azure-openai) or bearer (default for openai).

type CredentialRef added in v0.4.0

type CredentialRef struct {
	// IDs is the credential's jti, then its ancestors' (parent first).
	IDs []string
	// IssuedAt is the credential's iat (unix seconds).
	IssuedAt int64
}

CredentialRef identifies a halo-server-signed credential for revocation checks.

type DLP added in v0.4.0

type DLP struct {
	// Secrets: outbound secret detection on request content.
	Secrets DLPSecrets `yaml:"secrets,omitempty" json:"secrets,omitzero"`
	// Injection: prompt-injection signals on tool results entering the model.
	Injection DLPInjection `yaml:"injection,omitempty" json:"injection,omitzero"`
	// Rules override the actions per ring and org unit; the first rule whose
	// rings and units both match the caller wins.
	Rules []DLPRule `yaml:"rules,omitempty" json:"rules,omitempty"`
	// MaxScanBytes bounds how much of one request body is scanned (default
	// 4 MiB). A larger body is scanned up to the cap and counted as truncated.
	MaxScanBytes int64 `yaml:"maxScanBytes,omitempty" json:"maxScanBytes,omitempty"`
	// ScanResponses scans streamed and plain responses for echoed secrets
	// (monitor only, halo-proxy). Default true.
	ScanResponses *bool `yaml:"scanResponses,omitempty" json:"scanResponses,omitempty"`
}

DLP is the gateway's data-loss prevention and prompt-injection defense (ADR-0033). halo-proxy and halo-kong scan model-call request bodies; halo-proxy also scans streamed responses (monitor only).

func (*DLP) Actions added in v0.4.0

func (d *DLP) Actions(ring string, units []string) (secrets, injection string)

Actions returns the secret and injection actions for a caller in ring and unit chain units (the caller's unit first, then its ancestors): the first matching rule's non-empty actions over the defaults. off disables a scan.

func (*DLP) Responses added in v0.4.0

func (d *DLP) Responses() bool

Responses reports whether responses are scanned (default true).

func (*DLP) ScanLimit added in v0.4.0

func (d *DLP) ScanLimit() int64

ScanLimit is MaxScanBytes with the default applied.

type DLPInjection added in v0.4.0

type DLPInjection struct {
	// Action: monitor (count and audit) or block (refuse at or above
	// Threshold). Default monitor. Model content is never modified.
	Action string `yaml:"action,omitempty" json:"action,omitempty"`
	// Threshold is the score at which a tool result counts as an injection
	// (default 4).
	Threshold int `yaml:"threshold,omitempty" json:"threshold,omitempty"`
}

DLPInjection configures prompt-injection scoring.

func (*DLPInjection) ScoreThreshold added in v0.4.0

func (d *DLPInjection) ScoreThreshold() int

ScoreThreshold is Threshold with the default applied.

type DLPPattern added in v0.4.0

type DLPPattern struct {
	ID    string `yaml:"id" json:"id"`
	Regex string `yaml:"regex" json:"regex"`
}

DLPPattern is one org-defined secret detector.

type DLPRule added in v0.4.0

type DLPRule struct {
	Rings     []string `yaml:"rings,omitempty" json:"rings,omitempty"`
	Units     []string `yaml:"units,omitempty" json:"units,omitempty"`
	Secrets   string   `yaml:"secrets,omitempty" json:"secrets,omitempty"`
	Injection string   `yaml:"injection,omitempty" json:"injection,omitempty"`
}

DLPRule overrides the actions for callers in Rings and Units (empty = any). A unit matches its descendants too.

type DLPSecrets added in v0.4.0

type DLPSecrets struct {
	// Action: monitor (count and audit), redact (replace each secret with
	// [REDACTED:<detector>:<hash8>]) or block (refuse the request). Default monitor.
	Action string `yaml:"action,omitempty" json:"action,omitempty"`
	// Disable turns built-in detectors off by id.
	Disable []string `yaml:"disable,omitempty" json:"disable,omitempty"`
	// Patterns are org detectors (RE2). The whole match is the secret unless
	// the regex has a capture group, then the first group is.
	Patterns []DLPPattern `yaml:"patterns,omitempty" json:"patterns,omitempty"`
}

DLPSecrets configures the secret detectors.

type Delegation added in v0.3.0

type Delegation struct {
	// AllowHooks lets units add hooks.items. Default false: a hook is a shell
	// command every member's harness runs, so a unit owner could otherwise run
	// code on any member's machine.
	AllowHooks bool `yaml:"allowHooks,omitempty" json:"allowHooks,omitempty"`
	// EnvKeys are the env names units may add (exact). Provider, base-URL,
	// proxy and runtime-injection names (see UnitForbiddenEnv) are refused
	// even when listed.
	EnvKeys []string `yaml:"envKeys,omitempty" json:"envKeys,omitempty"`
}

Delegation is what the org lets units add on top of the baseline (halos.yaml delegation:). Anything else a unit adds is a loosening.

type DelegationChain added in v0.3.0

type DelegationChain struct {
	// Actors is the chain: the root's own delegate first, the current actor last.
	Actors []Actor
	// Models narrows the model aliases the credential may call; nil = not
	// narrowed by the credential (policy still applies).
	Models []string
	// ID is the delegated credential's jti (audit correlation).
	ID string
	// AuthTime is when the root authenticated (unix seconds, the signed
	// auth_time claim): a human root's session revocation after it kills the chain.
	AuthTime int64
}

DelegationChain is set on a Subject when an agent acts for it. Subject.ID, Groups and Kind stay the root principal's, so cohort, ring, budgets and posture are the root's.

func (*DelegationChain) Chain added in v0.3.0

func (d *DelegationChain) Chain() string

Chain renders the actor chain root-side first, e.g. "planner>reviewer".

func (*DelegationChain) Current added in v0.3.0

func (d *DelegationChain) Current() Actor

Current is the agent making the call.

func (*DelegationChain) Depth added in v0.3.0

func (d *DelegationChain) Depth() int

Depth is the number of agents in the chain.

type Egress

type Egress struct {
	// AllowedDomains is the allowlist: exact hosts or *.domain (subdomains only).
	AllowedDomains []string `yaml:"allowedDomains,omitempty" json:"allowedDomains,omitempty"`
	// Default is allow | deny for hosts no rule matches. Unset: deny when
	// anything is allowed (allowedDomains or presets), allow otherwise.
	Default string `yaml:"default,omitempty" json:"default,omitempty"`
	// DeniedDomains always lose: a deny rule wins over any allow rule. Unions
	// down an extends chain and across units.
	DeniedDomains []string `yaml:"deniedDomains,omitempty" json:"deniedDomains,omitempty"`
	// Presets are named domain sets added to the allowlist (EgressPresets):
	// package-registries | vcs | model-providers.
	Presets []string `yaml:"presets,omitempty" json:"presets,omitempty"`
}

Egress is the network egress policy for the agent's shell and fetch tools (ADR-0032). See Egress.Rules for how the fields combine.

func (Egress) Restricted added in v0.4.0

func (e Egress) Restricted() bool

Restricted reports whether the profile says anything about egress.

func (Egress) Rules added in v0.4.0

func (e Egress) Rules() EgressRules

Rules resolves e: presets join the allow list after allowedDomains (lower-cased, de-duplicated, first occurrence kept), and an unset default is deny when anything is allowed (allowedDomains has always meant "the only egress permitted") and allow otherwise.

type EgressRules added in v0.4.0

type EgressRules struct {
	Default string   `json:"default"` // allow | deny
	Allow   []string `json:"allow,omitempty"`
	Deny    []string `json:"deny,omitempty"`
}

EgressRules is an Egress with its default decided and presets expanded: what every adapter renders and what `halo egress test` evaluates.

func (EgressRules) Decide added in v0.4.0

func (r EgressRules) Decide(host string) (allowed bool, rule string)

Decide is the egress decision for host and the rule that made it: a deny rule wins over an allow rule, and the default decides the rest.

type Experiment

type Experiment struct {
	Meta   `yaml:",inline"`
	Type   ExperimentType `yaml:"type" json:"type"`
	Axis   Axis           `yaml:"axis" json:"axis"`
	Status string         `yaml:"status,omitempty" json:"status,omitempty"` // draft | running | paused | concluded
	// Rings the experiment draws users from.
	Rings    []string  `yaml:"rings" json:"rings"`
	Variants []Variant `yaml:"variants" json:"variants"`
	// Shadow only: fraction of eligible requests mirrored (0-1).
	SampleRate float64 `yaml:"sampleRate,omitempty" json:"sampleRate,omitempty"`
	// Shadow only: which turns of a sampled session are mirrored. "first"
	// (default) mirrors the opening request; "all" mirrors every step of the
	// session, each replayed on the real prefix (ADR-0014).
	Turns    string   `yaml:"turns,omitempty" json:"turns,omitempty"`
	Metrics  Metrics  `yaml:"metrics" json:"metrics"`
	Stopping Stopping `yaml:"stopping" json:"stopping"`
	// Salt keeps assignment independent across experiments; defaults to Name.
	Salt string `yaml:"salt,omitempty" json:"salt,omitempty"`
}

func (*Experiment) Arms

func (e *Experiment) Arms() (control, treatment *Variant, ok bool)

Arms returns the backing experiment's control and treatment variants.

func (*Experiment) AssignVariant added in v0.4.0

func (e *Experiment) AssignVariant(s Subject, ring string) *Variant

AssignVariant is the variant a subject in ring is (or was) assigned while the experiment runs, whatever its status now; nil when the ring is not enrolled. Analyses of a paused or concluded experiment use it to recover the arms exactly as ResolveVariant served them.

func (*Experiment) ResolveVariant

func (e *Experiment) ResolveVariant(s Subject, ring string) *Variant

ResolveVariant returns the experiment variant for a subject, or nil if the subject's ring is not enrolled or the experiment isn't running.

type ExperimentType

type ExperimentType string

ExperimentType is how an experiment exposes users.

const (
	ExperimentAB     ExperimentType = "ab"
	ExperimentCanary ExperimentType = "canary"
	ExperimentShadow ExperimentType = "shadow"
)

type ExtensionSet added in v0.3.0

type ExtensionSet struct {
	// Deny names that must not load. Unions down an extends chain.
	Deny []string `yaml:"deny,omitempty" json:"deny,omitempty"`
	// PluginOnly allows only extensions that arrive via plugins or managed
	// settings (the allowlist is then the allowed marketplaces); user and
	// project sources are blocked.
	PluginOnly bool `yaml:"pluginOnly,omitempty" json:"pluginOnly,omitempty"`
}

ExtensionSet governs a user-authored extension kind (skills or subagents).

type Extensions added in v0.3.0

type Extensions struct {
	// Marketplaces is the allowlist of plugin marketplace sources; empty = any.
	Marketplaces []Marketplace `yaml:"marketplaces,omitempty" json:"marketplaces,omitempty"`
	// BlockedMarketplaces are sources users may never use. Unions down an extends chain.
	BlockedMarketplaces []Marketplace `yaml:"blockedMarketplaces,omitempty" json:"blockedMarketplaces,omitempty"`
	Plugins             Plugins       `yaml:"plugins,omitempty" json:"plugins,omitempty"`
	Skills              ExtensionSet  `yaml:"skills,omitempty" json:"skills,omitempty"`
	Subagents           ExtensionSet  `yaml:"subagents,omitempty" json:"subagents,omitempty"`
	// OutputStyle selects the harness's output style by name.
	OutputStyle string `yaml:"outputStyle,omitempty" json:"outputStyle,omitempty"`
	// StatusLine runs a managed command to render the status line.
	StatusLine StatusLine `yaml:"statusLine,omitempty" json:"statusLine,omitempty"`
	// Artifacts names halos.yaml supplyChain.artifacts this profile delivers
	// (ADR-0028). Unions down an extends chain.
	Artifacts []string `yaml:"artifacts,omitempty" json:"artifacts,omitempty"`
}

Extensions is the plugin/skill/subagent governance surface. Unset means unrestricted; each adapter renders what its harness can enforce and warns about the rest.

type FieldSource added in v0.3.0

type FieldSource struct {
	Path   string `json:"path"`
	Value  string `json:"value"`
	Source string `json:"source"`
}

FieldSource is one leaf of an effective profile and the layer that set it.

type FreezeStatus added in v0.4.0

type FreezeStatus struct {
	Frozen bool           `json:"frozen"`
	Active []ActiveFreeze `json:"active,omitempty"`
	// NextOpen is the first instant no window covers the change. Zero while
	// an incident freeze holds, or when windows chain for over a year.
	NextOpen time.Time `json:"nextOpen,omitzero"`
}

FreezeStatus is whether a change may go out now.

func (*FreezeStatus) AddIncident added in v0.4.0

func (s *FreezeStatus) AddIncident(by, reason string)

AddIncident adds the incident freeze (halo-server) to s.

func (FreezeStatus) String added in v0.4.0

func (s FreezeStatus) String() string

String is the refusal message: every active window and the next open time.

type FreezeWindow added in v0.4.0

type FreezeWindow struct {
	Name   string      `yaml:"name" json:"name"`
	Reason string      `yaml:"reason" json:"reason"`
	Scope  ChangeScope `yaml:"scope,omitempty" json:"scope,omitzero"`
	// Timezone is an IANA zone (America/New_York); default UTC. Start, End and
	// Weekly are wall-clock times there, so DST moves the instant, not the time.
	Timezone string `yaml:"timezone,omitempty" json:"timezone,omitempty"`
	// Start and End bound a one-off window [start, end): "2026-12-20T18:00",
	// "2026-12-20" (midnight) or RFC 3339 with an offset.
	Start string `yaml:"start,omitempty" json:"start,omitempty"`
	End   string `yaml:"end,omitempty" json:"end,omitempty"`
	// Weekly repeats every week from From to To.
	Weekly *WeeklyWindow `yaml:"weekly,omitempty" json:"weekly,omitempty"`
}

FreezeWindow is one change-freeze window: one-off (start, end) or recurring every week (weekly), in Timezone.

type Gateway

type Gateway struct {
	Meta `yaml:",inline"`
	// BaseURL is what clients are pointed at (Kong's public listener).
	BaseURL string `yaml:"baseURL" json:"baseURL"`
	// Protocol per harness: anthropic-messages | bedrock-invoke | openai-responses | gemini.
	Protocols map[string]string `yaml:"protocols,omitempty" json:"protocols,omitempty"`
	// Auth: how clients obtain a credential for the auth gateway.
	Auth GatewayAuth `yaml:"auth" json:"auth"`
	// Models maps a stable alias (what clients request) to upstream targets.
	Models map[string]ModelRoute `yaml:"models" json:"models"`
	// Upstreams are named backends (e.g. the orchestrator, direct Anthropic).
	Upstreams map[string]Upstream `yaml:"upstreams" json:"upstreams"`
	// Engine is the data plane that enforces this policy: halo-proxy (default),
	// kong, or external (a gateway Halos does not run, e.g. a company's own API
	// gateway: clients are rendered with each alias's provider model id, since
	// nothing translates aliases). Multi-target routes (weights, failover) are
	// halo-proxy-only; Kong uses each route's first target.
	Engine string `yaml:"engine,omitempty" json:"engine,omitempty"`
	// Pricing is USD per million tokens keyed by provider model id (the model a
	// route target names). It costs gateway usage for Budgets.
	Pricing map[string]ModelPrice `yaml:"pricing,omitempty" json:"pricing,omitempty"`
	// Budgets cap spend per user, group, ring or org (ADR-0017). halo-proxy
	// enforces them; halo-server holds the counters.
	Budgets []Budget `yaml:"budgets,omitempty" json:"budgets,omitempty"`
	// DLP is data-loss prevention and prompt-injection defense on model calls (ADR-0033).
	DLP *DLP `yaml:"dlp,omitempty" json:"dlp,omitempty"`
}

Gateway describes how harness traffic reaches models.

func (*Gateway) HarnessWire

func (g *Gateway) HarnessWire(h string) string

HarnessWire is the wire harness h speaks to this gateway ("" = the gateway does not route it).

func (*Gateway) WireFit

func (g *Gateway) WireFit(alias, wire string) (WireFitness, []string)

WireFit reports whether alias routes to an upstream that answers wire, and lists each candidate target as "upstream (kind)". It is the check `halo validate` applies to a harness's start model (guardHarnessWire).

type GatewayAuth

type GatewayAuth struct {
	// HelperCommand prints a short-lived token (used as Claude apiKeyHelper, Codex env_key source, ...).
	HelperCommand string `yaml:"helperCommand,omitempty" json:"helperCommand,omitempty"`
	TTLSeconds    int    `yaml:"ttlSeconds,omitempty" json:"ttlSeconds,omitempty"`
	// IdentityHeader is the header the auth gateway sets with the verified user id.
	IdentityHeader string `yaml:"identityHeader,omitempty" json:"identityHeader,omitempty"`
}

type GitSource added in v0.4.0

type GitSource struct {
	URL    string `yaml:"url" json:"url"`
	Commit string `yaml:"commit" json:"commit"`
	// Path within the repository ("" = its root).
	Path string `yaml:"path,omitempty" json:"path,omitempty"`
}

GitSource is a git repository at an exact commit.

type Governance added in v0.4.0

type Governance struct {
	// Freezes are change-freeze windows. While one covers a change, the rollout
	// controller does not advance, and promotion and advance proposals are
	// refused. Rollbacks, pauses and the kill switch are always allowed.
	Freezes []FreezeWindow `yaml:"freezes,omitempty" json:"freezes,omitempty"`
	// Approvals are approver quorums. Every rule whose scope covers a change
	// must be met before it goes out.
	Approvals []ApprovalRule `yaml:"approvals,omitempty" json:"approvals,omitempty"`
}

Governance is halos.yaml governance: (ADR-0030): when changes may go out (freeze windows) and who must approve them (approval rules).

type Grant added in v0.5.0

type Grant struct {
	Role   string    `json:"role"`
	Scope  RoleScope `json:"scope"`
	Source string    `json:"source"`
}

Grant is one console role a subject holds over a scope, and the policy field it comes from (for /me, `halo whoami` and audit entries).

type Guardrail

type Guardrail func(*Org) []Issue

Guardrail is an org-wide policy check. ponytail: guardrails are plain Go funcs, not OPA (ADR-0007). An org-specific evaluator (Rego later) plugs in through ExtraGuardrails; no other code changes.

type HarnessSpec

type HarnessSpec struct {
	// Version is an exact CLI version pin (e.g. "2.1.280"). Required for rings.
	Version string `yaml:"version" json:"version"`
	// Model is this harness's default model alias, replacing models.default
	// for it (e.g. codex-default, so codex does not ask for a Claude alias).
	// Must be a gateway.models alias, and in models.allowed when enforced.
	Model string `yaml:"model,omitempty" json:"model,omitempty"`
	// Overrides are raw, harness-native keys merged last. Escape hatch; the Go guardrails still check them (ADR-0007).
	Overrides map[string]any `yaml:"overrides,omitempty" json:"overrides,omitempty"`
}

type Hook

type Hook struct {
	// Event: PreToolUse | PostToolUse | SessionStart | Stop | UserPromptSubmit
	Event   string `yaml:"event" json:"event"`
	Matcher string `yaml:"matcher,omitempty" json:"matcher,omitempty"`
	Command string `yaml:"command" json:"command"`
}

type Hooks

type Hooks struct {
	ManagedOnly bool   `yaml:"managedOnly,omitempty" json:"managedOnly,omitempty"`
	Hooks       []Hook `yaml:"items,omitempty" json:"items,omitempty"`
}

type Identity

type Identity struct {
	Issuer   string `yaml:"issuer,omitempty" json:"issuer,omitempty"`
	ClientID string `yaml:"clientID,omitempty" json:"clientID,omitempty"`
	// Audience expected in gateway bearer tokens; defaults to ClientID.
	Audience string `yaml:"audience,omitempty" json:"audience,omitempty"`
	// UserClaim / GroupsClaim name the JWT claims for subject id and groups.
	UserClaim   string `yaml:"userClaim,omitempty" json:"userClaim,omitempty"`     // default "email"
	GroupsClaim string `yaml:"groupsClaim,omitempty" json:"groupsClaim,omitempty"` // default "groups"
	// AdminGroups hold halo-server's admin role; same as Roles.Admin (kept for
	// existing policy repos).
	AdminGroups []string `yaml:"adminGroups,omitempty" json:"adminGroups,omitempty"`
	// Roles maps halo-server roles to IdP groups (ADR-0015). omitzero keeps
	// the JSON form, and so existing snapshot digests, unchanged when unset.
	Roles RoleGroups `yaml:"roles,omitempty" json:"roles,omitzero"`
	// RoleBindings grant a role over a scope of rings and units to groups
	// and users (ADR-0037); identity.roles and adminGroups stay global.
	// Omitted when empty, so existing snapshot digests are unchanged.
	RoleBindings []RoleBinding `yaml:"roleBindings,omitempty" json:"roleBindings,omitempty"`
	// GroupsOverage is what halo-server's console login does when the ID
	// token says the user's groups did not fit in it (Entra ID past 200
	// groups: _claim_names / hasgroups): deny (default) refuses the login with
	// a clear error; graph reads them from Microsoft Graph with the login's
	// access token (ADR-0036). Never a login with no groups.
	GroupsOverage string `yaml:"groupsOverage,omitempty" json:"groupsOverage,omitempty"`
	// RequireAcr: the console login's ID token acr must be one of these.
	// RequireAmr: its amr must contain at least one of these (e.g. [mfa]).
	// halo-server console login only; gateways do not check them (ADR-0036).
	RequireAcr []string `yaml:"requireAcr,omitempty" json:"requireAcr,omitempty"`
	RequireAmr []string `yaml:"requireAmr,omitempty" json:"requireAmr,omitempty"`
}

Identity is the org's OIDC provider. Any compliant IdP works (Okta, Entra ID, Google, Keycloak, Ping, Auth0...). Used by halo-server (portal login), halo-proxy / halo-kong (verifying the caller) and halod enrollment.

func (Identity) Grants added in v0.5.0

func (id Identity) Grants(s Subject) []Grant

Grants returns every console role s holds under id: the global grants of identity.adminGroups and identity.roles (in ConsoleRoles order), then each identity.roleBindings entry that selects s by group or user, in file order. Exact duplicates (same role and scope) are dropped.

func (Identity) RoleBindingGroups added in v0.5.0

func (id Identity) RoleBindingGroups() []string

RoleBindingGroups is every group a role binding selects (halo-server keeps these in the session, like the identity.roles groups).

type ImportOptions added in v0.4.0

type ImportOptions struct {
	ID     string // registry id; default derived from the server.json name
	Remote bool   // pin the remote (streamable HTTP) endpoint, not a package
	SHA256 string // image digest, required for an oci package
}

ImportOptions picks what ImportServerJSON pins.

type Issue

type Issue struct {
	Severity Severity
	Path     string
	Message  string
}

Issue is one validation finding. Path is like "rings[ring1].membership.percent".

func SecurityIssues added in v0.3.0

func SecurityIssues(o *Org) []Issue

SecurityIssues returns o's error-severity Validate issues on security fields (securityPaths). halo-server and the gateways run it on every policy load and refuse to activate a policy that has any, keeping the last good one: `halo validate` and `halo release build` are not the only gate.

func TrafficOverlaps

func TrafficOverlaps(o *Org) []Issue

TrafficOverlaps is the guard's finding for gateways, which log it when they load a snapshot that was never validated (they still route first by name).

func (Issue) String

func (i Issue) String() string

type Kind

type Kind string

Kind names a policy document type.

const (
	KindProfile    Kind = "Profile"
	KindRing       Kind = "Ring"
	KindExperiment Kind = "Experiment"
	KindGateway    Kind = "Gateway"
)
const KindRollout Kind = "Rollout"

KindRollout is a phased rollout of one change (see Rollout).

const KindToggle Kind = "Toggle"

KindToggle is a feature toggle document: one capability a platform team can turn on for a targeted cohort, and kill instantly, without a new release.

const KindUnit Kind = "Unit"

KindUnit is a node of the org hierarchy (ADR-0018): a business unit or team with delegated owners, a member selector and an overlay applied on top of the org baseline (the profile a user's ring, or experiment variant, ships).

type Loosening added in v0.3.0

type Loosening struct {
	Field   string // e.g. "permissions.sandbox"
	Message string
}

Loosening is one enforced field an overlay weakens.

type MCP

type MCP struct {
	// ManagedOnly blocks every server not listed here.
	ManagedOnly bool        `yaml:"managedOnly,omitempty" json:"managedOnly,omitempty"`
	Servers     []MCPServer `yaml:"servers,omitempty" json:"servers,omitempty"`
	Denied      []string    `yaml:"denied,omitempty" json:"denied,omitempty"`
}

type MCPAuth added in v0.4.0

type MCPAuth struct {
	// Mode: none (default) | oauth | enterprise | halos.
	Mode string `yaml:"mode,omitempty" json:"mode,omitempty"`
	// Issuer is the server's authorization server issuer (oauth, enterprise).
	Issuer string `yaml:"issuer,omitempty" json:"issuer,omitempty"`
	// ClientID is a pre-registered OAuth client id (oauth: at the server's
	// authorization server; enterprise: at the org IdP).
	ClientID string `yaml:"clientID,omitempty" json:"clientID,omitempty"`
	// ClientIDMetadataURL is the https URL of a Client ID Metadata Document
	// the org hosts (oauth): the URL is the client id.
	ClientIDMetadataURL string   `yaml:"clientIDMetadataURL,omitempty" json:"clientIDMetadataURL,omitempty"`
	Scopes              []string `yaml:"scopes,omitempty" json:"scopes,omitempty"`
}

MCPAuth is how clients authorize to a remote server.

type MCPFinding added in v0.4.0

type MCPFinding struct {
	Harness string `json:"harness"`
	File    string `json:"file"`
	Server  string `json:"server"`
	// Kind: unapproved | version-drift.
	Kind string `json:"kind"`
	// Transport: stdio | http.
	Transport string `json:"transport,omitempty"`
	// Host of an http server's URL (no userinfo, path or query).
	Host string `json:"host,omitempty"`
	// DefinitionSHA256 is the hex sha256 of the full user definition (URL, or
	// command and arguments), to tell definitions apart without showing them.
	DefinitionSHA256 string `json:"definitionSha256,omitempty"`
	Detail           string `json:"detail"`
	Fix              string `json:"fix"`
}

MCPFinding is one MCP server halod found in a user config that the release's registry does not approve, or approves at another version. It leaves the device, so it never carries the user's command, arguments or URL beyond its host: any of them can hold a credential.

type MCPPackage added in v0.4.0

type MCPPackage struct {
	// Type: npm | pypi | oci | binary.
	Type string `yaml:"type" json:"type"`
	// Identifier is the package name (npm, pypi) or image reference without
	// tag or digest (oci). Unused for binary.
	Identifier string `yaml:"identifier,omitempty" json:"identifier,omitempty"`
	// Version is the exact version (npm, pypi; no ranges, no "latest"), or the
	// image tag for information (oci).
	Version string `yaml:"version,omitempty" json:"version,omitempty"`
	// SHA256 is "sha256:<hex>": the image digest (oci, required) or the
	// executable's digest (binary, required; halod checks it).
	SHA256 string `yaml:"sha256,omitempty" json:"sha256,omitempty"`
	// Args are appended to the derived command line.
	Args []string `yaml:"args,omitempty" json:"args,omitempty"`
}

MCPPackage pins a stdio server's code.

func (MCPPackage) Command added in v0.4.0

func (p MCPPackage) Command() []string

Command is the pinned command line a package runs as: npx, uvx or docker with the exact version or digest. nil for binary (its command is explicit).

type MCPPin added in v0.4.0

type MCPPin struct {
	ID      string      `json:"id"`
	URL     string      `json:"url,omitempty"`
	Command []string    `json:"command,omitempty"`
	Package *MCPPackage `json:"package,omitempty"`
	// Units, when set, are the only units (and units below them) that get it;
	// empty = every member of the ring.
	Units []string `json:"units,omitempty"`
}

MCPPin is one registry server as a ring release manifest carries it for halod's drift check: identity, pin and the units it is limited to.

type MCPRegistry added in v0.4.0

type MCPRegistry struct {
	// Mode: allowlist (default) | warn.
	Mode    string           `yaml:"mode,omitempty" json:"mode,omitempty"`
	Servers []RegistryServer `yaml:"servers" json:"servers"`
}

MCPRegistry is halos.yaml mcp.

func (*MCPRegistry) Allowlist added in v0.4.0

func (r *MCPRegistry) Allowlist() bool

Allowlist reports whether the registry is enforced as an allowlist.

func (*MCPRegistry) MCPPins added in v0.4.0

func (r *MCPRegistry) MCPPins(ring string) []MCPPin

MCPPins lists the registry servers ring's members (or some of its units' members) get, for the release manifest.

func (*MCPRegistry) Server added in v0.4.0

func (r *MCPRegistry) Server(id string) *RegistryServer

Server returns the registry server id, or nil.

type MCPServer

type MCPServer struct {
	Name    string            `yaml:"name" json:"name"`
	URL     string            `yaml:"url,omitempty" json:"url,omitempty"`
	Command []string          `yaml:"command,omitempty" json:"command,omitempty"`
	Headers map[string]string `yaml:"headers,omitempty" json:"headers,omitempty"`
	// Tools and Auth (ADR-0029) are usually filled from the halos.yaml mcp
	// registry; omitzero keeps existing snapshot digests unchanged when unset.
	Tools MCPTools `yaml:"tools,omitempty" json:"tools,omitzero"`
	Auth  MCPAuth  `yaml:"auth,omitempty" json:"auth,omitzero"`
}

type MCPTokenDecision added in v0.4.0

type MCPTokenDecision struct {
	Allow  bool
	Server string
	Ring   string
	Unit   string
	Reason string
}

MCPTokenDecision is halo-server's verdict on minting a token for a server.

type MCPTools added in v0.4.0

type MCPTools struct {
	Allow []string `yaml:"allow,omitempty" json:"allow,omitempty"`
	Deny  []string `yaml:"deny,omitempty" json:"deny,omitempty"`
}

MCPTools narrows a server's tools. Deny wins over Allow; empty Allow = all.

type Marketplace added in v0.3.0

type Marketplace struct {
	// Source: github | git | url | hostPattern
	Source      string `yaml:"source" json:"source"`
	Repo        string `yaml:"repo,omitempty" json:"repo,omitempty"`
	URL         string `yaml:"url,omitempty" json:"url,omitempty"`
	Ref         string `yaml:"ref,omitempty" json:"ref,omitempty"`
	HostPattern string `yaml:"hostPattern,omitempty" json:"hostPattern,omitempty"`
}

Marketplace is one plugin marketplace source.

type Membership

type Membership struct {
	// Users are explicit user ids always in this ring (checked before Groups).
	Users []string `yaml:"users,omitempty" json:"users,omitempty"`
	// Groups are IdP groups always in this ring.
	Groups []string `yaml:"groups,omitempty" json:"groups,omitempty"`
	// Percent of remaining users hashed into this ring (0-100, basis points precision).
	Percent float64 `yaml:"percent,omitempty" json:"percent,omitempty"`
	// Default marks the catch-all ring (GA).
	Default bool `yaml:"default,omitempty" json:"default,omitempty"`
	// OptIn lets developers join this ring themselves from the portal
	// (recorded as a group membership request; admins may auto-approve).
	OptIn bool `yaml:"optIn,omitempty" json:"optIn,omitempty"`
}

type Meta

type Meta struct {
	APIVersion string            `yaml:"apiVersion" json:"apiVersion"`
	Kind       Kind              `yaml:"kind" json:"kind"`
	Name       string            `yaml:"name" json:"name"`
	Labels     map[string]string `yaml:"labels,omitempty" json:"labels,omitempty"`
}

Meta is common to every document.

type MetricDef

type MetricDef struct {
	Name        string
	Unit        string // unit of the value, e.g. ratio, ms, usd
	Description string
	Sources     map[string]string
	// Source lists where values come from. When a metric has both online
	// sources, internal/promote prefers the gateway for an experiment that has
	// gateway data (exact cohort attribution), else the CLI events.
	Source []SignalSource
	// PerUnit defines one analysis sample: experiments compare the per-unit
	// values of the two arms. CLI unit = user.id (Codex: user.account_id, else
	// conversation.id; Gemini: installation.id); gateway unit = salted hash
	// of the session id (else of the verified subject).
	PerUnit string
	// Better is the direction of improvement ("increase" | "decrease"); when
	// set, goals on this metric must use it. Empty = not enforced.
	Better string
}

MetricDef is a normalised halo.* metric. The telemetry collector maps each harness's native signal (Sources, keyed by adapter name) onto Name.

func LookupMetric

func LookupMetric(name string) (MetricDef, bool)

LookupMetric returns the registry entry for name.

func (MetricDef) HasSource

func (m MetricDef) HasSource(s SignalSource) bool

HasSource reports whether the metric can be measured by s.

func (MetricDef) ShadowOnly

func (m MetricDef) ShadowOnly() bool

ShadowOnly reports whether the metric is only measured on shadow pairs.

type MetricGoal

type MetricGoal struct {
	// Metric is a normalised halo.* metric name, e.g. halo.task.success, halo.cost.usd_per_session.
	Metric string `yaml:"metric" json:"metric"`
	// Direction: increase | decrease
	Direction string `yaml:"direction" json:"direction"`
	// MaxRegression (guardrails): relative worsening that aborts, e.g. 0.05 = 5%.
	MaxRegression float64 `yaml:"maxRegression,omitempty" json:"maxRegression,omitempty"`
}

type Metrics

type Metrics struct {
	Primary    MetricGoal   `yaml:"primary" json:"primary"`
	Guardrails []MetricGoal `yaml:"guardrails,omitempty" json:"guardrails,omitempty"`
}

type ModelChoice

type ModelChoice []string

ModelChoice is one model id, or several in failover order.

func (*ModelChoice) UnmarshalYAML

func (m *ModelChoice) UnmarshalYAML(n *yaml.Node) error

UnmarshalYAML accepts a scalar or a list of scalars.

type ModelPrice added in v0.3.0

type ModelPrice struct {
	InputPerMTok      float64 `yaml:"inputPerMTok" json:"inputPerMTok"`
	OutputPerMTok     float64 `yaml:"outputPerMTok" json:"outputPerMTok"`
	CacheReadPerMTok  float64 `yaml:"cacheReadPerMTok,omitempty" json:"cacheReadPerMTok,omitempty"`
	CacheWritePerMTok float64 `yaml:"cacheWritePerMTok,omitempty" json:"cacheWritePerMTok,omitempty"`
}

ModelPrice is what one provider model costs, in USD per million tokens. Cache prices of 0 fall back to InputPerMTok: over-counting a cache hit is the safe direction for a budget.

type ModelRoute

type ModelRoute struct {
	Upstream string `yaml:"upstream,omitempty" json:"upstream,omitempty"`
	// Model is the provider model id / Bedrock inference profile ARN.
	Model   string        `yaml:"model,omitempty" json:"model,omitempty"`
	Targets []RouteTarget `yaml:"targets,omitempty" json:"targets,omitempty"`
}

ModelRoute is one alias's routing: either a single upstream+model, or an ordered/weighted list of Targets (mutually exclusive).

func (ModelRoute) Candidates

func (r ModelRoute) Candidates() []RouteTarget

Candidates returns the route's targets (a single-target route is one).

func (ModelRoute) Primary

func (r ModelRoute) Primary() ModelRoute

Primary is the route reduced to its first-priority target, for consumers that cannot fail over (halo-kong, halo-shadow).

type Models

type Models struct {
	Default string   `yaml:"default" json:"default"`
	Allowed []string `yaml:"allowed,omitempty" json:"allowed,omitempty"`
	// Enforce rejects models outside Allowed rather than just hiding them.
	Enforce bool `yaml:"enforce,omitempty" json:"enforce,omitempty"`
}

type Org

type Org struct {
	Name        string
	Identity    Identity
	SelfService SelfService
	Gateway     *Gateway
	Profiles    map[string]*Profile
	Rings       []*Ring // ordered, earliest ring first
	Experiments []*Experiment
	Toggles     []*Toggle `json:",omitempty"` // omitted when empty: existing snapshots keep their digest

	// Rollouts are omitted when empty, like Toggles.
	Rollouts []*Rollout `json:",omitempty"`

	// Units are the org hierarchy (ADR-0018), omitted when empty like Toggles.
	Units []*Unit `json:",omitempty"`

	// Delegation is what units may add beyond the baseline (ADR-0018); omitted
	// when unset, like Toggles.
	Delegation *Delegation `json:",omitempty"`

	// Sources records which repo-relative file declared each document, so
	// Validate can keep unit directories (delegated by CODEOWNERS) to Unit
	// documents. Not part of the snapshot.
	Sources []Source `json:"-"`

	// Workloads federates CI / cloud workload identities (ADR-0023); omitted
	// when unset, like Toggles.
	Workloads *Workloads `json:",omitempty"`

	// Agents governs delegated and remote agents (ADR-0024); omitted when
	// unset, like Toggles.
	Agents *Agents `json:",omitempty"`

	// VendorAdmin drives the vendors' cloud admin consoles (ADR-0025); omitted
	// when unset, like Toggles.
	VendorAdmin *VendorAdmin `json:",omitempty"`

	// CloudAgents governs vendor-hosted background agents (ADR-0034); omitted
	// when unset, like Toggles.
	CloudAgents *CloudAgents `json:",omitempty"`

	// Governance holds change-freeze windows and approval quorums (ADR-0030);
	// omitted when unset, like Toggles.
	Governance *Governance `json:",omitempty"`

	// SupplyChain declares the extension artifacts releases deliver
	// (ADR-0028); omitted when unset, like Toggles.
	SupplyChain *SupplyChain `json:",omitempty"`

	// ArtifactContent is each artifact's resolved content, keyed by name:
	// path sources read by Load, remote sources fetched by the release
	// build. Not part of the snapshot.
	ArtifactContent map[string]*ArtifactContent `json:"-"`

	// MCP is the approved MCP server registry (ADR-0029); omitted when unset,
	// like Toggles.
	MCP *MCPRegistry `json:",omitempty"`

	// Deprecations are load-time warnings (one per file still on a deprecated
	// apiVersion) that Validate reports. Not part of the snapshot.
	Deprecations []Issue `json:"-"`
}

Org is the fully loaded policy repo.

func Load

func Load(dir string) (*Org, error)

Load reads every *.yaml/*.yml under dir (multi-document files supported), dispatches by kind, and returns the assembled Org. Decoding is strict: unknown fields are errors, reported with the yaml line ("line N") and file.

func ParseSnapshot

func ParseSnapshot(b []byte) (*Org, error)

ParseSnapshot reads a Compile envelope (verifying version and digest) or, for backwards compatibility, a bare json.Marshal(*Org) document.

func TightenWorkloads added in v0.3.0

func TightenWorkloads(good, next *Org) (*Org, []string)

TightenWorkloads is the part of a refused policy next (SecurityIssues found errors) that still applies over good, the last policy that passed: workload rule removals, so an unrelated error elsewhere cannot block the emergency removal of a compromised rule. The result is good's rules that next still has unchanged (equal, under an issuer of the same name), and each dropped rule left in its place as a deny tombstone: it still matches, first-match-wins, but grants nothing. So removing a narrow rule never lets its tokens fall through to a broader rule below it, and every token's grant is a subset of its last-good grant. Additions and modifications are not applied, nor is anything else in next. It returns good itself when no rule is dropped, else a shallow copy of good with its own Workloads, plus the names of the dropped rules. Neither input is modified.

func (*Org) AgentScopeFor added in v0.3.0

func (o *Org) AgentScopeFor(s Subject) (AgentScope, error)

AgentScopeFor resolves s's ring, profile and unit and returns the effective agent limits. An error means the effective profile could not be computed (a policy that Validate rejects); callers must fail closed.

func (*Org) AgentScopeIn added in v0.3.0

func (o *Org) AgentScopeIn(ring, unit string) (AgentScope, error)

AgentScopeIn is AgentScopeFor for a caller already resolved to ring and unit ("" = none). It depends only on the policy and the two names, so a caller may memoise it per policy snapshot.

func (*Org) ApprovalRules added in v0.4.0

func (o *Org) ApprovalRules(target ChangeTarget) []ApprovalRule

ApprovalRules returns the rules covering target.

func (*Org) CheckTicket added in v0.4.0

func (o *Org) CheckTicket(target ChangeTarget, ticket string) error

CheckTicket validates a change's ticket against every rule covering target that requires one.

func (*Org) ClientExperiments

func (o *Org) ClientExperiments(ring string) []*Experiment

ClientExperiments returns the running client-axis experiments that enroll ring, in policy order. Validate allows at most one per ring.

func (*Org) CodeOwners added in v0.3.0

func (o *Org) CodeOwners(prefix string, defaults []string) ([]byte, error)

CodeOwners renders a CODEOWNERS file delegating each unit's directory to its owners, then its ancestors' owners (any one may approve), after a catch-all line for defaults (the central team, which keeps halos.yaml, profiles and rings). A top-level unit's own file goes to defaults alone (required then): its members draw from the whole org. prefix is the policy directory relative to the repository root ("" or "." when they are the same). Lines are sorted by path, so a nested unit's line, and a unit's file line, follow and override the directory line above them.

func (*Org) DecideMCPToken added in v0.4.0

func (o *Org) DecideMCPToken(s Subject, resource string) MCPTokenDecision

DecideMCPToken decides whether s may get a Halos access token for the registry server at resource: the server must use auth mode halos and be rendered for s's ring or unit.

func (*Org) DecideRemote added in v0.3.0

func (o *Org) DecideRemote(s Subject, d *DelegationChain, name, target, skill string) RemoteDecision

DecideRemote decides whether s (with delegation d, may be nil) may call remote agent name (or, when name is empty, the agent whose AgentCard is served from target's origin) and, when skill is set, invoke that skill.

func (*Org) EffectiveProfile added in v0.3.0

func (o *Org) EffectiveProfile(base *Profile, unit string) (*Profile, error)

EffectiveProfile returns base with the overlays of unit's chain applied, root first. base is not modified; unit "" returns a copy of base.

func (*Org) ExplainEffective added in v0.3.0

func (o *Org) ExplainEffective(profile, unit string) (*Profile, []FieldSource, error)

ExplainEffective returns profile's resolved config with unit's overlays applied (unit may be empty) and, for every leaf, the layer that set it: "profile <name>" along the extends chain (root first), then "unit <name>" along the unit chain. A value no single layer holds (appended instructions) names every layer that contributed, joined with " + ".

func (*Org) FindArtifact added in v0.4.0

func (o *Org) FindArtifact(name string) (Artifact, bool)

FindArtifact returns the artifact declared under name.

func (*Org) FreezeAt added in v0.4.0

func (o *Org) FreezeAt(t time.Time, target ChangeTarget) FreezeStatus

FreezeAt reports the policy windows covering target at t. The incident freeze lives on halo-server: callers add it (AddIncident).

func (*Org) Loosenings added in v0.3.0

func (o *Org) Loosenings(parent, child *Profile) []Loosening

Loosenings lists the enforced fields child weakens relative to parent. The enforced fields are the org's security baseline: permissions (mode, sandbox, sandboxRequired, disableBypass, deny, allow, ask), mcp.denied and mcp.managedOnly, hooks.managedOnly, telemetry (enabled, endpoint, protocol, logPrompts, existing attributes), egress (allowlist, default, deny list), sandbox (writable roots, protected paths), existing env keys, models.allowed while the parent enforces it, and agents (maxDepth may only go down, remote may only narrow; ADR-0024). Additions that run code or reroute traffic on members' machines are loosenings too, unless o allows them: hooks (delegation.allowHooks), env keys (delegation.envKeys; provider, proxy and runtime-injection keys never), MCP servers (an exact selfService.catalog entry), and any extensions change.

func (*Org) ProfileReach added in v0.5.0

func (o *Org) ProfileReach(name string) []string

ProfileReach is every ring an edit to profile name reaches: each ring whose profile is name or extends it (at any depth), and every ring of each experiment with a variant whose profile does. A chain that does not resolve counts as reaching (fail closed). Ring order follows o.Rings.

func (*Org) Quorum added in v0.4.0

func (o *Org) Quorum(target ChangeTarget, requester, ticket string, approvals []Approval) *QuorumResult

Quorum evaluates approvals of a change requested by requester against every rule covering target; nil when none does. The requester's own approval and repeat approvals never count, and an approver counts for a rule only while holding one of its groups. ticket is the change's ticket reference ("" = none). Scoped approvals never count here: use QuorumFor with what the change touches.

func (*Org) QuorumFor added in v0.5.0

func (o *Org) QuorumFor(res *Resource, target ChangeTarget, requester, ticket string, approvals []Approval) *QuorumResult

QuorumFor is Quorum for a change that touches res (ADR-0037): an approval made under scoped approve bindings counts only when one of its scopes covers res, and never when res is nil (unknown reach, fail closed).

func (*Org) ResolveProfile

func (o *Org) ResolveProfile(name string) (*Profile, error)

ResolveProfile returns the named profile with its extends chain applied (root ancestor first, child last). Merge semantics:

  • scalars: child wins when set (non-zero). ponytail: zero means "unset", so a child cannot turn a parent's true bool back to false; add pointer bools if needed.
  • slices: child replaces the parent's list when non-empty, EXCEPT permissions.deny, mcp.denied, hooks.items, extensions.artifacts, the extensions deny/blocked lists, egress.deniedDomains and sandbox.protectedPaths, which union (see below).
  • sandbox.mode and permissions.sandbox are one setting: the result carries the effective mode in both.
  • maps: merged per key; struct values (e.g. harnesses) merge recursively, other values (e.g. overrides) are child-wins per key.

The result is a deep-ish copy: mutating it does not affect the Org. Extends cycles and missing parents are errors.

func (*Org) ResolveRing

func (o *Org) ResolveRing(s Subject) *Ring

ResolveRing returns the ring a subject belongs to. Rings are walked in order: explicit user ids win, then explicit group membership, then each ring claims its Percent of the hash space (cumulatively, so ring1 5% + ring2 25% = buckets [0,500) and [500,3000)), then the Default ring catches everyone else.

func (*Org) ResolveUnit added in v0.3.0

func (o *Org) ResolveUnit(s Subject) *Unit

ResolveUnit returns the unit s belongs to, or nil (see MatchUnit).

func (*Org) RingTarget added in v0.4.0

func (o *Org) RingTarget(rings ...string) ChangeTarget

RingTarget is a change to rings. A ring's members can come from any unit, so it touches every unit: a unit-scoped window or rule covers ring changes.

func (*Org) ScopeWithin added in v0.5.0

func (o *Org) ScopeWithin(inner, outer RoleScope) bool

ScopeWithin reports whether every resource inner covers, outer covers too: inner's rings are outer's (or outer is ring-unbounded), and each of inner's units is one of outer's or a descendant of one. A service token's scope must be within its creator's (ADR-0037). An unknown unit is never within anything bounded.

func (*Org) SubjectResource added in v0.5.0

func (o *Org) SubjectResource(s Subject) Resource

SubjectResource is the resource a change for one member is: their ring and unit chain under o (nil Rings/Units when none resolves).

func (*Org) SubjectTarget added in v0.4.0

func (o *Org) SubjectTarget(s Subject) ChangeTarget

SubjectTarget is a change for one subject (an access request): their ring and every unit of their unit chain.

func (*Org) Unit added in v0.3.0

func (o *Org) Unit(name string) *Unit

Unit returns the named unit, or nil.

func (*Org) UnitChain added in v0.3.0

func (o *Org) UnitChain(name string) ([]*Unit, error)

UnitChain returns name's ancestry, root first and name last. Unknown units and parent cycles are errors.

func (*Org) Validate

func (o *Org) Validate() []Issue

Validate runs semantic checks over the whole Org, then the built-in guardrails and any ExtraGuardrails. It never returns nil-vs-empty ambiguity: no issues = empty slice.

func (*Org) VendorAdminRing added in v0.3.0

func (o *Org) VendorAdminRing() string

VendorAdminRing is the ring whose profile the consoles get: VendorAdmin.Ring, else the ring with the highest order (GA). Empty when there are no rings.

func (*Org) WithMCPRegistry added in v0.4.0

func (o *Org) WithMCPRegistry(p *Profile, ring, unit string) (*Profile, error)

WithMCPRegistry returns a copy of p with the registry servers admitted for ring and unit ("" = none) added to mcp.servers, and, in allowlist mode, mcp.managedOnly set. A server p already lists by name is kept as is (a ring release's profile already holds the ring's servers when its units are rendered). p is not modified.

type Overlay added in v0.3.0

type Overlay struct {
	Models       Models            `yaml:"models,omitempty" json:"models,omitempty"`
	Permissions  Permissions       `yaml:"permissions,omitempty" json:"permissions,omitempty"`
	MCP          MCP               `yaml:"mcp,omitempty" json:"mcp,omitempty"`
	Hooks        Hooks             `yaml:"hooks,omitempty" json:"hooks,omitempty"`
	Telemetry    Telemetry         `yaml:"telemetry,omitempty" json:"telemetry,omitempty"`
	Egress       Egress            `yaml:"egress,omitempty" json:"egress,omitempty"`
	Sandbox      Sandbox           `yaml:"sandbox,omitempty" json:"sandbox,omitzero"`
	Instructions string            `yaml:"instructions,omitempty" json:"instructions,omitempty"`
	Env          map[string]string `yaml:"env,omitempty" json:"env,omitempty"`
	// Agents may only lower maxDepth and narrow remote (ADR-0024).
	Agents AgentLimits `yaml:"agents,omitempty" json:"agents,omitzero"`
}

Overlay is the part of a Profile a unit may change. Harness pins and overrides stay central (they ship per ring). Merge onto the baseline: scalars win when set; permissions.deny, mcp.denied and hooks.items union; egress.deniedDomains and sandbox.protectedPaths union; mcp.servers are added (a name already provided above is an error); models.allowed, permissions.allow/ask, egress.allowedDomains/presets and sandbox.writableRoots replace; env and telemetry.attributes merge per key; instructions are appended.

type Permissions

type Permissions struct {
	// Mode: default | acceptEdits | plan | auto
	Mode          string   `yaml:"mode,omitempty" json:"mode,omitempty"`
	Allow         []string `yaml:"allow,omitempty" json:"allow,omitempty"`
	Deny          []string `yaml:"deny,omitempty" json:"deny,omitempty"`
	Ask           []string `yaml:"ask,omitempty" json:"ask,omitempty"`
	DisableBypass bool     `yaml:"disableBypass,omitempty" json:"disableBypass,omitempty"`
	// Sandbox: harness sandbox policy — off | workspace-write | read-only
	Sandbox string `yaml:"sandbox,omitempty" json:"sandbox,omitempty"`
	// SandboxRequired fails closed: the harness must refuse to run (Claude:
	// sandbox.failIfUnavailable) rather than fall back to unsandboxed
	// execution when its sandbox dependencies are missing.
	SandboxRequired bool `yaml:"sandboxRequired,omitempty" json:"sandboxRequired,omitempty"`
}

func SafetyPreset

func SafetyPreset(name string) (Permissions, bool, error)

SafetyPreset returns the permission, MCP and hook settings of preset name ("" = standard). No preset ever sets bypassPermissions, and all set disableBypass.

type Plugins added in v0.3.0

type Plugins struct {
	// Enabled plugins are required on.
	Enabled []string `yaml:"enabled,omitempty" json:"enabled,omitempty"`
	// Blocked plugins are required off. Unions down an extends chain.
	Blocked []string `yaml:"blocked,omitempty" json:"blocked,omitempty"`
}

Plugins pins individual plugins, keyed PLUGIN@MARKETPLACE.

type Profile

type Profile struct {
	Meta    `yaml:",inline"`
	Extends string `yaml:"extends,omitempty" json:"extends,omitempty"`

	// Harnesses enabled by this profile, keyed by adapter name.
	Harnesses map[string]HarnessSpec `yaml:"harnesses" json:"harnesses"`

	Models      Models      `yaml:"models" json:"models"`
	Permissions Permissions `yaml:"permissions" json:"permissions"`
	MCP         MCP         `yaml:"mcp" json:"mcp"`
	Hooks       Hooks       `yaml:"hooks" json:"hooks"`
	Telemetry   Telemetry   `yaml:"telemetry" json:"telemetry"`
	Egress      Egress      `yaml:"egress" json:"egress"`
	// Sandbox is the agent sandbox: mode, writable roots, protected paths (ADR-0032).
	Sandbox Sandbox `yaml:"sandbox,omitempty" json:"sandbox,omitzero"`
	// Extensions governs agent-extension surfaces: plugins and their marketplaces,
	// skills, subagents, output style and status line.
	Extensions Extensions `yaml:"extensions,omitempty" json:"extensions,omitempty"`
	// Agents lowers the delegation depth and narrows the remote agents for
	// this profile's members (ADR-0024). Gateway and halo-server enforced.
	Agents AgentLimits `yaml:"agents,omitempty" json:"agents,omitzero"`
	// Login restricts how users authenticate: subscription (claude.ai / ChatGPT /
	// Google login) | api (API key or gateway credential). Empty = unrestricted.
	Login string `yaml:"login,omitempty" json:"login,omitempty"`
	// Instructions is org-wide memory (managed CLAUDE.md / AGENTS.md / GEMINI.md).
	Instructions string `yaml:"instructions,omitempty" json:"instructions,omitempty"`
	// Env is extra environment applied to every harness.
	Env map[string]string `yaml:"env,omitempty" json:"env,omitempty"`

	// Resolved is filled by Org.ResolveProfile from Extensions.Artifacts:
	// the artifacts this profile delivers, with content (ADR-0028).
	Resolved []ResolvedArtifact `yaml:"-" json:"-"`
}

Profile is desired harness behaviour, independent of any one CLI.

type Provider

type Provider struct {
	Name    string `yaml:"name" json:"name"`
	Region  string `yaml:"region,omitempty" json:"region,omitempty"`
	Project string `yaml:"project,omitempty" json:"project,omitempty"`
	URL     string `yaml:"url,omitempty" json:"url,omitempty"`
}

Provider names a model provider and its settings.

func (*Provider) UnmarshalYAML

func (p *Provider) UnmarshalYAML(n *yaml.Node) error

UnmarshalYAML accepts a bare name or a mapping, strictly (unknown keys are errors).

type QuorumResult added in v0.4.0

type QuorumResult struct {
	Rules []RuleResult `json:"rules"`
	Met   bool         `json:"met"`
	// Ticket is the change's ticket reference ("" = none).
	Ticket string `json:"ticket,omitempty"`
	// Ignored explains approvals that did not count.
	Ignored []string `json:"ignored,omitempty"`
}

QuorumResult is every covering rule's state; Met when all are.

func (*QuorumResult) String added in v0.4.0

func (q *QuorumResult) String() string

String summarises what is missing, for refusals and gate details.

type RegistryServer added in v0.4.0

type RegistryServer struct {
	// ID is the server name rendered into every harness (and the <server> of
	// its tools' names, e.g. mcp__<id>__<tool>): lowercase letters, digits, '-'.
	ID string `yaml:"id" json:"id"`
	// RegistryName and Version are the official MCP Registry's server.json name
	// and version this entry was imported from (`halo mcp registry import`).
	RegistryName string `yaml:"registryName,omitempty" json:"registryName,omitempty"`
	Version      string `yaml:"version,omitempty" json:"version,omitempty"`
	// Exactly one transport: URL (remote, https), Package (stdio; npm, pypi and
	// oci derive the command line) or Command (stdio; required with a binary package).
	URL     string            `yaml:"url,omitempty" json:"url,omitempty"`
	Command []string          `yaml:"command,omitempty" json:"command,omitempty"`
	Package *MCPPackage       `yaml:"package,omitempty" json:"package,omitempty"`
	Headers map[string]string `yaml:"headers,omitempty" json:"headers,omitempty"`
	Tools   MCPTools          `yaml:"tools,omitempty" json:"tools,omitzero"`
	Auth    MCPAuth           `yaml:"auth,omitempty" json:"auth,omitzero"`
	// Rings and Units it is rendered for: a member of a listed ring, or of a
	// listed unit or one below it. Both empty = rendered for nobody (approved
	// only: a toggle naming it may deliver it).
	Rings []string `yaml:"rings,omitempty" json:"rings,omitempty"`
	Units []string `yaml:"units,omitempty" json:"units,omitempty"`
}

RegistryServer is one approved MCP server.

func ImportServerJSON added in v0.4.0

func ImportServerJSON(b []byte, o ImportOptions) (s RegistryServer, notes []string, err error)

ImportServerJSON turns a server.json (or a registry API response wrapping one in "server") into a pinned registry entry: the remote URL, or the first npm, pypi or oci stdio package at its exact version. notes lists what the entry does not carry (arguments, environment, headers) and still needs a human: rings or units, auth, tools.

func (RegistryServer) Resolved added in v0.4.0

func (s RegistryServer) Resolved() MCPServer

Resolved is the server as harness adapters render it: the package's derived command line when it has no explicit one.

type RemoteAgent added in v0.3.0

type RemoteAgent struct {
	Name string `yaml:"name" json:"name"`
	// CardURL is the AgentCard, usually https://host/.well-known/agent-card.json.
	CardURL string `yaml:"cardURL" json:"cardURL"`
	// Digest pins the card: "sha256:<hex>" over its RFC 8785 canonical JSON, as
	// `halo agents verify` prints it. A changed card is a policy change.
	Digest string `yaml:"digest" json:"digest"`
	// PublicKey (PEM) or JWKSURL verifies the card's JWS signatures. With
	// either set, an unsigned card or a bad signature fails verification.
	PublicKey string `yaml:"publicKey,omitempty" json:"publicKey,omitempty"`
	JWKSURL   string `yaml:"jwksURL,omitempty" json:"jwksURL,omitempty"`
	// Skills are the card's skill ids members may invoke; empty = any.
	Skills []string `yaml:"skills,omitempty" json:"skills,omitempty"`
	// Rings and Units that may call it: a caller in a listed ring, or in a
	// listed unit or one below it, may. Both empty = nobody.
	Rings []string `yaml:"rings,omitempty" json:"rings,omitempty"`
	Units []string `yaml:"units,omitempty" json:"units,omitempty"`
}

RemoteAgent is one allowlisted A2A agent (https://a2a-protocol.org).

type RemoteDecision added in v0.3.0

type RemoteDecision struct {
	Allow  bool   `json:"allow"`
	Agent  string `json:"agent,omitempty"`
	Digest string `json:"digest,omitempty"`
	Reason string `json:"reason"`
	Ring   string `json:"ring,omitempty"`
	Unit   string `json:"unit,omitempty"`
}

RemoteDecision is the egress verdict for one call to a remote agent.

type ResolvedArtifact added in v0.4.0

type ResolvedArtifact struct {
	Name, Kind, Source, Digest, Signer string
	Files                              supplychain.Tree
}

ResolvedArtifact is an artifact a resolved profile delivers. Files is nil when the content was not resolved (a remote source outside a release build).

type Resource added in v0.5.0

type Resource struct {
	// Rings it affects; AllRings: members of every ring, and of none (an
	// org-wide toggle, a profile shared by several rings, a change whose
	// reach is unknown).
	Rings    []string `json:"rings,omitempty"`
	AllRings bool     `json:"allRings,omitempty"`
	// Units is the unit chain (root first) of the one member it concerns
	// (a device, an access request). AllUnits: members of every unit and of
	// none: rings, experiments, rollouts and toggles draw from the whole org
	// (ADR-0018), so no unit owns them.
	Units    []string `json:"units,omitempty"`
	AllUnits bool     `json:"allUnits,omitempty"`
}

Resource is what an authorization decision is about: the rings and units whose members it affects. halo-server derives it from current policy and its own records, never from the client (a ring in a request body or a device's self-reported ring is not trusted).

func RingResource added in v0.5.0

func RingResource(rings ...string) Resource

RingResource is a change to rings: every unit's members, as RingTarget.

type Ring

type Ring struct {
	Meta    `yaml:",inline"`
	Order   int    `yaml:"order" json:"order"`
	Profile string `yaml:"profile" json:"profile"`
	// Release pins the ring to an immutable published release digest; empty = build from Profile.
	Release    string     `yaml:"release,omitempty" json:"release,omitempty"`
	Membership Membership `yaml:"membership" json:"membership"`
	// Posture gates gateway access on the device posture halod reports to
	// halo-server (fresh, drift-free, on this ring's release): off | warn | enforce.
	// Empty means warn. Takes effect only on gateways wired to halo-server.
	Posture string `yaml:"posture,omitempty" json:"posture,omitempty"`
	// VersionGate checks the CLI version in the User-Agent against the ring's
	// pinned harness version: off | warn | enforce. Empty means warn.
	VersionGate string `yaml:"versionGate,omitempty" json:"versionGate,omitempty"`
}

Ring is an ordered rollout cohort pointing at a profile.

type RoleBinding added in v0.5.0

type RoleBinding struct {
	Role   string    `yaml:"role" json:"role"`
	Groups []string  `yaml:"groups,omitempty" json:"groups,omitempty"`
	Users  []string  `yaml:"users,omitempty" json:"users,omitempty"`
	Scope  RoleScope `yaml:"scope,omitempty" json:"scope,omitzero"`
}

RoleBinding grants Role over Scope to every listed group and user (identity.roleBindings). An empty scope is global: the binding means exactly what the same group under identity.roles means.

type RoleGroups added in v0.3.0

type RoleGroups struct {
	Viewer   []string `yaml:"viewer,omitempty" json:"viewer,omitempty"`     // read fleet, releases, rollouts, experiments, toggles, policy
	Operator []string `yaml:"operator,omitempty" json:"operator,omitempty"` // viewer + kill switch, experiment status
	Approver []string `yaml:"approver,omitempty" json:"approver,omitempty"` // viewer + decide access requests
	Auditor  []string `yaml:"auditor,omitempty" json:"auditor,omitempty"`   // audit log only
	Admin    []string `yaml:"admin,omitempty" json:"admin,omitempty"`       // everything, incl. devices, sessions, toggle config
}

RoleGroups lists, per halo-server role, the IdP groups that hold it. A user holds every role any of their groups maps to. Strict YAML decoding rejects an unknown role name, so a typo cannot silently grant nothing.

type RoleScope added in v0.5.0

type RoleScope struct {
	Rings []string `yaml:"rings,omitempty" json:"rings,omitempty"`
	Units []string `yaml:"units,omitempty" json:"units,omitempty"`
}

RoleScope bounds a binding: the rings and the units (each with its descendants) whose resources it covers. Within a list any value matches; across the two lists both must, so {rings: [canary], units: [payments]} is payments' resources in canary. Empty is global.

Deliberately not ChangeScope: an approval rule's scope selects changes it covers (any listed ring OR unit, ADR-0030); a role scope bounds authority, so it is the narrower conjunction and a resource must fall entirely inside it.

func (RoleScope) Covers added in v0.5.0

func (s RoleScope) Covers(r Resource) bool

Covers reports whether r lies entirely inside s. A bounded dimension never covers a resource that is unbounded, or unknown, in it: deny by default when the scope of a resource cannot be determined.

func (RoleScope) Global added in v0.5.0

func (s RoleScope) Global() bool

Global reports whether s is unbounded.

func (RoleScope) String added in v0.5.0

func (s RoleScope) String() string

String is s for audit entries and the CLI: "global", or "rings=a,b units=c" (sorted, so equal scopes print the same).

type Rollout

type Rollout struct {
	Meta `yaml:",inline"`
	Axis Axis `yaml:"axis" json:"axis"`
	// Status: draft | active | paused | aborted | completed ("" = draft).
	Status string `yaml:"status,omitempty" json:"status,omitempty"`
	// Step is the live step's name, set by merged advance PRs ("" = not started).
	Step string `yaml:"step,omitempty" json:"step,omitempty"`
	// Experiment is the backing two-arm experiment: exposure, evidence, kill
	// switch. Optional when every step is progressive or blue-green.
	Experiment string          `yaml:"experiment,omitempty" json:"experiment,omitempty"`
	Change     RolloutChange   `yaml:"change" json:"change"`
	Baseline   RolloutBaseline `yaml:"baseline,omitempty" json:"baseline,omitempty"`
	Steps      []RolloutStep   `yaml:"steps" json:"steps"`
}

Rollout describes how ONE change rolls out: ordered steps, each with a target ring and percent, a minimum bake, gates, and a failure action.

Exposure inside a ring is carried by the backing Experiment (its control and treatment weights, or its shadow sample rate), so assignment stays in internal/assign and the existing kill switch is the traffic-axis rollback. Ring-wide steps move Ring.release. Every edit is proposed as a PR.

func (*Rollout) EffectiveStatus

func (r *Rollout) EffectiveStatus() string

EffectiveStatus is Status with the draft default.

func (*Rollout) StepIndex

func (r *Rollout) StepIndex(name string) int

StepIndex returns the index of the named step, or -1.

type RolloutBaseline

type RolloutBaseline struct {
	// Release is the client-axis switch-back target (required with ring-wide steps).
	Release string `yaml:"release,omitempty" json:"release,omitempty"`
}

RolloutBaseline is the known-good state a rollback returns to.

type RolloutChange

type RolloutChange struct {
	// Client axis: the release digest ring pointers move to, and its label.
	Release string `yaml:"release,omitempty" json:"release,omitempty"`
	Version string `yaml:"version,omitempty" json:"version,omitempty"`
	// Traffic axis: the gateway model alias whose route becomes the treatment
	// variant's route for it when the rollout completes.
	Alias string `yaml:"alias,omitempty" json:"alias,omitempty"`
}

RolloutChange is what rolls out.

type RolloutGates

type RolloutGates struct {
	// Guardrails compare the backing experiment's arms (internal/stats
	// sequential test). A breach triggers OnFailure at any time.
	Guardrails []MetricGoal `yaml:"guardrails,omitempty" json:"guardrails,omitempty"`
	// Scorecard reads a `halo eval run --output json` scorecard.
	Scorecard *ScorecardGate `yaml:"scorecard,omitempty" json:"scorecard,omitempty"`
	// Approval holds the step until a human advances it (`halo rollout advance`).
	Approval bool `yaml:"approval,omitempty" json:"approval,omitempty"`
}

RolloutGates must all pass before a step advances.

type RolloutPreset

type RolloutPreset struct {
	Name  string
	Rings []Ring
	// Canary is the treatment ramp on the backing experiment, in order.
	Canary []RolloutStep
	// TeamBake is the bake on ring0; RingBake on each later ring before GA.
	TeamBake, RingBake string
}

RolloutPreset is a named ring layout plus the step template intent commands stamp into Rollouts. Rings carry no team members (Expand fills ring0).

func LookupRolloutPreset

func LookupRolloutPreset(name string) (RolloutPreset, error)

LookupRolloutPreset returns preset name ("" = standard).

type RolloutStep

type RolloutStep struct {
	Name     string          `yaml:"name" json:"name"`
	Strategy RolloutStrategy `yaml:"strategy" json:"strategy"`
	// Ring is required for progressive/blue-green. Percent steps expose the
	// backing experiment's rings; Ring, if set, must be one of them.
	Ring string `yaml:"ring,omitempty" json:"ring,omitempty"`
	// Percent: canary = treatment share; holdout = share kept on control;
	// dark-launch = share of requests mirrored; progressive/blue-green = 100.
	Percent float64 `yaml:"percent,omitempty" json:"percent,omitempty"`
	// Bake is the minimum time at this step: Go duration or N"d" (e.g. "14d").
	Bake       string       `yaml:"bake,omitempty" json:"bake,omitempty"`
	MinSamples int          `yaml:"minSamples,omitempty" json:"minSamples,omitempty"`
	Gates      RolloutGates `yaml:"gates,omitempty" json:"gates,omitempty"`
	// OnFailure: rollback | pause (default rollback).
	OnFailure string `yaml:"onFailure,omitempty" json:"onFailure,omitempty"`
}

RolloutStep is one phase of a rollout.

func (RolloutStep) BakeDuration

func (s RolloutStep) BakeDuration() (time.Duration, error)

BakeDuration parses Bake ("" = 0).

func (RolloutStep) EffectiveGuardrails

func (s RolloutStep) EffectiveGuardrails(exp *Experiment) []MetricGoal

EffectiveGuardrails is what step s is judged on: the backing experiment's guardrails, each overridden by the step's guardrail on the same metric, then the step's other guardrails. An active rollout owns its experiment (the experiment loop skips it), so dropping the experiment's guardrails here would silently disable their auto-rollback. Ring-wide steps have no control arm and keep only their own (none: validated).

func (RolloutStep) EffectivePercent

func (s RolloutStep) EffectivePercent() float64

EffectivePercent is Percent with the ring-wide default (100) applied.

func (RolloutStep) FailureAction

func (s RolloutStep) FailureAction() string

FailureAction is OnFailure with its default.

func (RolloutStep) RingWide

func (s RolloutStep) RingWide() bool

RingWide reports whether the step moves a ring pointer instead of experiment weights.

type RolloutStrategy

type RolloutStrategy string

RolloutStrategy is how one step exposes the change.

const (
	// StrategyProgressive moves Step.Ring's release pointer to Change.Release (client axis).
	StrategyProgressive RolloutStrategy = "progressive"
	// StrategyCanary gives Percent of the backing experiment's users the treatment.
	StrategyCanary RolloutStrategy = "canary"
	// StrategyBlueGreen switches Step.Ring from the pre-staged standby
	// Change.Release atomically; rollback switches back to Baseline.Release.
	StrategyBlueGreen RolloutStrategy = "blue-green"
	// StrategyDarkLaunch mirrors Percent of requests to the treatment via
	// halo-shadow; no user sees it (traffic axis).
	StrategyDarkLaunch RolloutStrategy = "dark-launch"
	// StrategyHoldout keeps Percent of users on control for Bake to measure
	// the long-term effect; everyone else gets the treatment.
	StrategyHoldout RolloutStrategy = "holdout"
)

type Root

type Root struct {
	APIVersion string `yaml:"apiVersion,omitempty" json:"apiVersion,omitempty"`
	Kind       string `yaml:"kind,omitempty" json:"kind,omitempty"`
	Org        string `yaml:"org" json:"org"`
	// Identity configures OIDC for the portal, halo-proxy and enrollment (any IdP).
	Identity Identity `yaml:"identity,omitempty" json:"identity,omitempty"`
	// SelfService configures the developer portal (kiosk).
	SelfService SelfService `yaml:"selfService,omitempty" json:"selfService,omitempty"`
	// Workloads federates CI / cloud agents' OIDC tokens (ADR-0023).
	Workloads *Workloads `yaml:"workloads,omitempty" json:"workloads,omitempty"`
	// Agents governs delegated and remote (A2A) agents (ADR-0024).
	Agents *Agents `yaml:"agents,omitempty" json:"agents,omitempty"`
	// VendorAdmin drives the vendors' cloud admin consoles (ADR-0025).
	VendorAdmin *VendorAdmin `yaml:"vendorAdmin,omitempty" json:"vendorAdmin,omitempty"`
	// CloudAgents governs vendor-hosted background agents (ADR-0034).
	CloudAgents *CloudAgents `yaml:"cloudAgents,omitempty" json:"cloudAgents,omitempty"`
	// SupplyChain pins, scans and signs extension artifacts (ADR-0028).
	SupplyChain *SupplyChain `yaml:"supplyChain,omitempty" json:"supplyChain,omitempty"`
	// MCP is the approved MCP server registry (ADR-0029).
	MCP *MCPRegistry `yaml:"mcp,omitempty" json:"mcp,omitempty"`
	// Delegation is what units may add beyond the baseline (ADR-0018).
	Delegation *Delegation `yaml:"delegation,omitempty" json:"delegation,omitempty"`
	// Governance holds change-freeze windows and approval quorums (ADR-0030).
	Governance *Governance `yaml:"governance,omitempty" json:"governance,omitempty"`
	// Simple mode fields (tools, provider, models, ...): see simple.go.
	Simple `yaml:",inline"`
}

Root is the halos.yaml document.

func ReadRoot

func ReadRoot(dir string) (*Root, error)

ReadRoot strictly decodes dir's halos.yaml.

func (*Root) Expand

func (r *Root) Expand() (*Org, error)

Expand returns the documents simple mode generates for root: the Gateway, the "default" Profile and the preset Rings. It is pure and deterministic; the result still has to pass Validate (Load overlays explicit documents first).

type RouteTarget

type RouteTarget struct {
	Upstream string  `yaml:"upstream" json:"upstream"`
	Model    string  `yaml:"model" json:"model"`
	Weight   float64 `yaml:"weight,omitempty" json:"weight,omitempty"`
	Priority int     `yaml:"priority,omitempty" json:"priority,omitempty"`
	// TimeoutSeconds bounds connect + wait for response headers (default: the
	// proxy's upstreamHeaderTimeout). Streams are not time-limited afterwards.
	TimeoutSeconds int `yaml:"timeoutSeconds,omitempty" json:"timeoutSeconds,omitempty"`
}

RouteTarget is one provider target of a route. Targets sharing a Priority (lower first; default 0) form a tier: within a tier Weight splits traffic (sticky per identity+session) and the rest of the tier is the failover order; later tiers are tried only after earlier ones fail.

type RuleResult added in v0.4.0

type RuleResult struct {
	Rule           string   `json:"rule"`
	Need           int      `json:"need"`
	Have           int      `json:"have"`
	Groups         []string `json:"groups"`
	DistinctGroups bool     `json:"distinctGroups,omitempty"`
	Approvers      []string `json:"approvers"`
	Ticket         string   `json:"ticket,omitempty"` // the pattern the change's ticket must match
	TicketOK       bool     `json:"ticketOK"`
	Met            bool     `json:"met"`
}

RuleResult is one approval rule's state for a change.

type Sandbox added in v0.4.0

type Sandbox struct {
	// Mode: off | workspace-write | read-only. Same field as permissions.sandbox
	// (either may be set; ResolveProfile keeps the two equal).
	Mode string `yaml:"mode,omitempty" json:"mode,omitempty"`
	// WritableRoots are absolute (or ~/) directories writable beyond the
	// workspace, e.g. a build cache. A child list replaces its parent's, like
	// allowedDomains; a unit adding one is a loosening.
	WritableRoots []string `yaml:"writableRoots,omitempty" json:"writableRoots,omitempty"`
	// ProtectedPaths may never be read or written by the agent: credentials
	// (~/.ssh, ~/.aws) and secrets files (.env*). Absolute, ~/ or a
	// workspace-relative glob. Unions down an extends chain and across units.
	ProtectedPaths []string `yaml:"protectedPaths,omitempty" json:"protectedPaths,omitempty"`
}

Sandbox is the agent sandbox policy (ADR-0032): how far the harness's shell and tools may write, and which paths they may never touch.

type ScorecardGate

type ScorecardGate struct {
	// File is the scorecard JSON, relative to the policy dir.
	File    string `yaml:"file" json:"file"`
	Variant string `yaml:"variant" json:"variant"`
	// MinPass1 is the absolute pass@1 floor (0 = none).
	MinPass1 float64 `yaml:"minPass1,omitempty" json:"minPass1,omitempty"`
	// MaxRegression is the largest allowed pass-rate drop vs the scorecard's
	// control (0.02 = 2pp); a significantly worse comparison always fails.
	MaxRegression float64 `yaml:"maxRegression,omitempty" json:"maxRegression,omitempty"`
}

ScorecardGate passes when Variant's eval scorecard clears the bars.

type SelfService

type SelfService struct {
	Enabled bool `yaml:"enabled,omitempty" json:"enabled,omitempty"`
	// Launchers offered to developers: devcontainer | codespaces | coder | laptop.
	Launchers []string `yaml:"launchers,omitempty" json:"launchers,omitempty"`
	// Requestable items: kinds of access developers may request (approval opens a policy PR).
	Requestable []string `yaml:"requestable,omitempty" json:"requestable,omitempty"` // mcp-server | model | ring-opt-in | harness
	// Catalog lists MCP servers developers may request that aren't in their profile yet.
	Catalog []MCPServer `yaml:"catalog,omitempty" json:"catalog,omitempty"`
	// EnrollmentTTLSeconds bounds one-time laptop bootstrap tokens (default 900).
	EnrollmentTTLSeconds int `yaml:"enrollmentTTLSeconds,omitempty" json:"enrollmentTTLSeconds,omitempty"`
}

SelfService configures the developer portal ("kiosk").

type ServerJSON added in v0.4.0

type ServerJSON struct {
	Name     string `json:"name"`
	Version  string `json:"version"`
	Packages []struct {
		RegistryType string `json:"registryType"`
		Identifier   string `json:"identifier"`
		Version      string `json:"version"`
		Transport    struct {
			Type string `json:"type"`
		} `json:"transport"`
		PackageArguments     []serverJSONArg `json:"packageArguments"`
		RuntimeArguments     []serverJSONArg `json:"runtimeArguments"`
		EnvironmentVariables []struct {
			Name       string `json:"name"`
			IsRequired bool   `json:"isRequired"`
		} `json:"environmentVariables"`
	} `json:"packages"`
	Remotes []struct {
		Type    string `json:"type"`
		URL     string `json:"url"`
		Headers []struct {
			Name string `json:"name"`
		} `json:"headers"`
	} `json:"remotes"`
}

ServerJSON is the part of the official MCP Registry's server.json (https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json) that `halo mcp registry import` reads.

type Severity

type Severity string

Severity of a validation Issue.

const (
	SeverityError   Severity = "error"
	SeverityWarning Severity = "warning"
)

type SignalSource

type SignalSource string

SignalSource is where a registry metric's values come from.

const (
	// SourceOnlineCLI: the harness's own OTEL export (metrics and log events),
	// attributed to a cohort by the halo.* resource attributes the profile sets.
	SourceOnlineCLI SignalSource = "online-cli"
	// SourceOnlineGateway: halo-proxy's per-request OTLP metrics. The gateway
	// assigns ring/variant itself, so it is the only online source for
	// traffic-axis experiments (the CLI never learns a gateway-assigned variant).
	SourceOnlineGateway SignalSource = "online-gateway"
	// SourceEval: offline replay (`halo eval`) only; never observed on live traffic.
	SourceEval SignalSource = "eval"
	// SourceOnlineEval: `halo eval online` LLM-judge grades of halo-shadow
	// pairs, received on the collector's authenticated eval receiver
	// (halo.source=eval). Pairs exist only for shadow experiments (and the
	// dark-launch steps of rollouts, which run their experiment as shadow).
	// Trusted for verdicts and PRs; never for the kill switch (gateway only).
	SourceOnlineEval SignalSource = "online-eval"
)

type Simple

type Simple struct {
	// Tools enables harnesses, each pinned to an exact CLI version: a bare
	// version, or {version, model} where model is one of the Models aliases
	// that tool starts on instead of "default" (e.g. codex on an OpenAI model).
	Tools map[string]ToolSpec `yaml:"tools,omitempty" json:"tools,omitempty"`
	// Provider is where models run: a name (anthropic | bedrock | vertex |
	// openai | gemini | multi) or {name, region, project, url}.
	Provider *Provider `yaml:"provider,omitempty" json:"provider,omitempty"`
	// Models maps an alias (what clients ask for; "default" is required) to a
	// provider model id, or to a list of ids tried in failover order. An id may
	// be prefixed "<provider>/" to pick another provider (required with multi).
	Models map[string]ModelChoice `yaml:"models,omitempty" json:"models,omitempty"`
	// Gateway is the base URL clients are pointed at (halo-proxy).
	Gateway string `yaml:"gateway,omitempty" json:"gateway,omitempty"`
	// GatewayEngine is what serves Gateway: halo-proxy (default), kong, or
	// external (your own API gateway; clients are sent provider model ids).
	GatewayEngine string `yaml:"gatewayEngine,omitempty" json:"gatewayEngine,omitempty"`
	// Telemetry is the OTLP/HTTP endpoint (telemetry is always on).
	Telemetry string `yaml:"telemetry,omitempty" json:"telemetry,omitempty"`
	// Team is ring0: IdP groups, or user ids containing "@". Default:
	// identity.adminGroups, else "<org>-ai-platform".
	Team []string `yaml:"team,omitempty" json:"team,omitempty"`
	// Safety preset: strict | standard (default) | relaxed. None bypasses permissions.
	Safety string `yaml:"safety,omitempty" json:"safety,omitempty"`
	// Rollout preset: fast | standard (default) | careful. Sets the rings and
	// the step template `halo upgrade start` and `halo model switch --canary` use.
	Rollout string `yaml:"rollout,omitempty" json:"rollout,omitempty"`
}

Simple is halos.yaml's simple mode: a handful of intent-level fields that Load expands, deterministically and in memory, into the Gateway, the "default" Profile and the preset Rings. Explicit documents of the same kind/name are overlaid on the expansion field by field (see overlay), so a repo can drop to full power one document at a time; `halo explain` prints the result and `halo eject` writes it out.

func (*Simple) Enabled

func (s *Simple) Enabled() bool

Enabled reports whether any simple-mode field is set.

type Source added in v0.3.0

type Source struct {
	Path string
	Kind Kind
}

Source is the repo-relative file that declared one document.

type StatusLine added in v0.3.0

type StatusLine struct {
	Command string `yaml:"command,omitempty" json:"command,omitempty"`
	Padding int    `yaml:"padding,omitempty" json:"padding,omitempty"`
}

StatusLine is a managed status-line command.

type Stopping

type Stopping struct {
	// Method: msprt | fixed
	Method      string  `yaml:"method" json:"method"`
	Alpha       float64 `yaml:"alpha,omitempty" json:"alpha,omitempty"`
	MinSamples  int     `yaml:"minSamples,omitempty" json:"minSamples,omitempty"`
	MaxDays     int     `yaml:"maxDays,omitempty" json:"maxDays,omitempty"`
	MaxSpendUSD float64 `yaml:"maxSpendUSD,omitempty" json:"maxSpendUSD,omitempty"`
}

type Subject

type Subject struct {
	ID     string
	Groups []string
	// Kind is "" for a human and SubjectWorkload for a CI / cloud workload.
	Kind string
	// Delegation is set when an agent acts for this subject (ADR-0024); ID,
	// Groups and Kind are then the root principal's.
	Delegation *DelegationChain
	// Credential is set when the subject was verified from a credential
	// halo-server signed (workload or delegated agent): what revocation
	// (ADR-0026) is checked against.
	Credential *CredentialRef
}

Subject is a user as seen by the control plane (from the IdP / auth gateway).

type SupplyChain added in v0.4.0

type SupplyChain struct {
	// Scan: off | warn (default) | enforce. enforce turns scan findings into
	// errors (as does `--strict`).
	Scan string `yaml:"scan,omitempty" json:"scan,omitempty"`
	// RequireSignatures refuses an artifact from outside the policy repo
	// (git or oci) unless it carries a signature by a trusted identity.
	RequireSignatures bool              `yaml:"requireSignatures,omitempty" json:"requireSignatures,omitempty"`
	Artifacts         []Artifact        `yaml:"artifacts,omitempty" json:"artifacts,omitempty"`
	TrustedIdentities []TrustedIdentity `yaml:"trustedIdentities,omitempty" json:"trustedIdentities,omitempty"`
	AllowFindings     []AllowFinding    `yaml:"allowFindings,omitempty" json:"allowFindings,omitempty"`
}

SupplyChain is the agent supply chain (ADR-0028): the skills, plugins, hooks, slash commands and agent definitions releases deliver, each pinned by a content digest, statically scanned, and optionally signature-checked.

type Telemetry

type Telemetry struct {
	Enabled      bool              `yaml:"enabled" json:"enabled"`
	OTLPEndpoint string            `yaml:"otlpEndpoint,omitempty" json:"otlpEndpoint,omitempty"`
	Protocol     string            `yaml:"protocol,omitempty" json:"protocol,omitempty"` // grpc | http/protobuf
	LogPrompts   bool              `yaml:"logPrompts,omitempty" json:"logPrompts,omitempty"`
	Attributes   map[string]string `yaml:"attributes,omitempty" json:"attributes,omitempty"`
}

type Toggle

type Toggle struct {
	Meta        `yaml:",inline"`
	Description string `yaml:"description,omitempty" json:"description,omitempty"`
	// Owner is the team or person who answers for this toggle (required).
	Owner string `yaml:"owner" json:"owner"`
	// Expires (YYYY-MM-DD) is when the toggle should be removed; past it, validate warns.
	Expires string `yaml:"expires,omitempty" json:"expires,omitempty"`
	// Default is the state when no rule matches.
	Default bool `yaml:"default" json:"default"`
	// Axis: client = a settings fragment delivered in the signed release;
	// traffic = a model route switch applied by the gateway.
	Axis Axis `yaml:"axis" json:"axis"`
	// Rules are evaluated in order; the first rule that matches decides.
	Rules []ToggleRule `yaml:"rules,omitempty" json:"rules,omitempty"`
	// Client is the payload of a client-axis toggle.
	Client *ToggleClient `yaml:"client,omitempty" json:"client,omitempty"`
	// Traffic is the payload of a traffic-axis toggle.
	Traffic *ToggleTraffic `yaml:"traffic,omitempty" json:"traffic,omitempty"`
}

Toggle is a named switch with ordered targeting rules.

func (*Toggle) Expired

func (t *Toggle) Expired(now time.Time) bool

Expired reports whether the toggle's expiry date has passed (a toggle with no or an unparsable expiry never expires; validate reports those).

type ToggleClient

type ToggleClient struct {
	Harnesses map[string]TogglePatch `yaml:"harnesses" json:"harnesses"`
}

ToggleClient maps harness name to the fragment applied when the toggle is on.

type TogglePatch

type TogglePatch struct {
	MCPServers []MCPServer       `yaml:"mcpServers,omitempty" json:"mcpServers,omitempty"`
	Hooks      []Hook            `yaml:"hooks,omitempty" json:"hooks,omitempty"`
	Env        map[string]string `yaml:"env,omitempty" json:"env,omitempty"`
	// Overrides are harness-native keys, restricted to the same allowlist as profile overrides.
	Overrides map[string]any `yaml:"overrides,omitempty" json:"overrides,omitempty"`
}

TogglePatch is what an "on" client toggle adds to one harness's resolved profile. It goes through the same adapter and guardrails as a profile, so an unsupported part produces the adapter's warning, never a silent drop.

func (TogglePatch) Apply

func (tp TogglePatch) Apply(p *Profile, h string) *Profile

Apply returns a copy of p with the patch merged in for harness h: servers and hooks are appended (deduped), env and overrides merged key by key.

type ToggleRule

type ToggleRule struct {
	Name   string   `yaml:"name,omitempty" json:"name,omitempty"`
	Rings  []string `yaml:"rings,omitempty" json:"rings,omitempty"`
	Groups []string `yaml:"groups,omitempty" json:"groups,omitempty"`
	Users  []string `yaml:"users,omitempty" json:"users,omitempty"`
	// Percent (0-100) of the matching subjects, hashed with the toggle's own
	// salt. Omitted = no percent condition; an explicit 0 matches nobody.
	Percent *float64 `yaml:"percent,omitempty" json:"percent,omitempty"`
	// Effect is the state when the rule matches: "on" (default) or "off".
	Effect string `yaml:"effect,omitempty" json:"effect,omitempty"`
}

ToggleRule matches a subject when EVERY condition it sets holds (rings, groups and users each match if the subject is in any listed value). A rule with no condition matches everyone.

type ToggleTraffic

type ToggleTraffic struct {
	Routes map[string]ModelRoute `yaml:"routes" json:"routes"`
}

ToggleTraffic maps a gateway model alias to the route used while the toggle is on.

type ToolSpec

type ToolSpec struct {
	Version string `yaml:"version" json:"version"`
	Model   string `yaml:"model,omitempty" json:"model,omitempty"`
}

ToolSpec is one tool's pin and, optionally, its own default model alias.

func (*ToolSpec) UnmarshalYAML

func (t *ToolSpec) UnmarshalYAML(n *yaml.Node) error

UnmarshalYAML accepts a bare version or {version, model}, strictly.

type TrustedIdentity added in v0.4.0

type TrustedIdentity struct {
	Name      string `yaml:"name" json:"name"`
	PublicKey string `yaml:"publicKey,omitempty" json:"publicKey,omitempty"`
	CosignKey string `yaml:"cosignKey,omitempty" json:"cosignKey,omitempty"`
}

TrustedIdentity is a key artifact signatures may be made with. Exactly one of PublicKey (an ed25519 PEM file in the policy repo) and CosignKey (a cosign public key reference: file or KMS URI) is set.

type Unit added in v0.3.0

type Unit struct {
	Meta        `yaml:",inline"`
	Description string `yaml:"description,omitempty" json:"description,omitempty"`
	// Parent is the enclosing unit; empty for a top-level unit.
	Parent string `yaml:"parent,omitempty" json:"parent,omitempty"`
	// Owners approve changes to the unit's directory in the policy repo:
	// CODEOWNERS entries such as @acme/payments-platform, @alice or an email.
	Owners []string `yaml:"owners" json:"owners"`
	// Members selects the unit's users from its parent's (or, at the top, the org's).
	Members UnitMembers `yaml:"members" json:"members"`
	// Overlay is what the unit changes on top of the baseline.
	Overlay Overlay `yaml:"overlay,omitempty" json:"overlay,omitempty"`

	// Dir is the repo-relative directory of the file that declares the unit
	// (set by Load). The unit's owners own that directory.
	Dir string `yaml:"-" json:"-"`
	// File is the repo-relative file that declares the unit (set by Load). A
	// top-level unit's file stays with the root owners: its members selector
	// draws from the whole org.
	File string `yaml:"-" json:"-"`
}

Unit is one business unit or team. Units nest through Parent; a user belongs to the deepest unit whose members selector, and every ancestor's, matches. The effective config is the ring's profile, then each ancestor's overlay (root first), then the unit's own. Enforced fields (see Loosenings) may only be tightened by an overlay; Validate rejects a loosening.

func MatchUnit added in v0.3.0

func MatchUnit(units []*Unit, s Subject) *Unit

MatchUnit returns the deepest unit whose members selector, and every ancestor's, matches s; equal depth goes to the lexically smallest name, so halod and the CLI agree without any hashing. nil when none matches.

type UnitMembers added in v0.3.0

type UnitMembers struct {
	Groups []string `yaml:"groups,omitempty" json:"groups,omitempty"`
	Users  []string `yaml:"users,omitempty" json:"users,omitempty"`
}

UnitMembers selects users by IdP group or user id (any match).

type Upstream

type Upstream struct {
	// URL is the base URL. Optional for kind vertex (derived from region).
	URL string `yaml:"url,omitempty" json:"url,omitempty"`
	// Kind: orchestrator | anthropic | bedrock | vertex | openai | azure-openai | gemini
	Kind string `yaml:"kind" json:"kind"`
	// Project is the Google Cloud project id for kind vertex.
	Project string `yaml:"project,omitempty" json:"project,omitempty"`
	// APIVersion is the api-version query for kind azure-openai; empty uses the
	// versionless /openai/v1 surface.
	APIVersion string `yaml:"apiVersion,omitempty" json:"apiVersion,omitempty"`
	// Credential names where halo-proxy reads the provider key (kinds openai,
	// azure-openai, gemini). Policy never holds the secret itself.
	Credential *Credential `yaml:"credential,omitempty" json:"credential,omitempty"`
	// Region is the AWS SigV4 region for kind bedrock. halo-proxy signs a
	// bedrock upstream only if its host ends in an AWS suffix (.amazonaws.com,
	// .amazonaws.com.cn, .api.aws) or is listed in the proxy's signHosts; it
	// never signs for other hosts. The region is this value when set, else
	// parsed from the host (e.g. bedrock-runtime.<region>.amazonaws.com).
	Region string `yaml:"region,omitempty" json:"region,omitempty"`
	// Serves lists the client wires an orchestrator upstream answers
	// (anthropic-messages | bedrock-invoke | openai-responses | gemini). An
	// orchestrator may translate, so validate only trusts what is declared here;
	// other kinds' wires are known.
	Serves []string `yaml:"serves,omitempty" json:"serves,omitempty"`
}

func (Upstream) Endpoint

func (u Upstream) Endpoint() string

Endpoint is the upstream base URL, deriving the Vertex AI host from Region when URL is unset ("global" has no region prefix).

type Variant

type Variant struct {
	Name string `yaml:"name" json:"name"`
	// Weight is the share of the experiment's users (weights are normalised).
	Weight float64 `yaml:"weight" json:"weight"`
	// Traffic axis: model alias -> route override.
	Routes map[string]ModelRoute `yaml:"routes,omitempty" json:"routes,omitempty"`
	// Client axis: profile to apply.
	Profile string `yaml:"profile,omitempty" json:"profile,omitempty"`
	Control bool   `yaml:"control,omitempty" json:"control,omitempty"`
}

type VendorAdmin added in v0.3.0

type VendorAdmin struct {
	// Ring whose profile the consoles get. Console settings are org-wide, so
	// exactly one ring's profile can win; empty means the last ring (GA).
	Ring    string        `yaml:"ring,omitempty" json:"ring,omitempty"`
	Claude  *VendorTarget `yaml:"claude,omitempty" json:"claude,omitempty"`
	Codex   *VendorTarget `yaml:"codex,omitempty" json:"codex,omitempty"`
	Gemini  *VendorTarget `yaml:"gemini,omitempty" json:"gemini,omitempty"`
	Copilot *VendorTarget `yaml:"copilot,omitempty" json:"copilot,omitempty"`
	Cursor  *VendorTarget `yaml:"cursor,omitempty" json:"cursor,omitempty"`
}

VendorAdmin drives the vendors' cloud admin consoles from this policy (ADR-0025): `halo admin plan` diffs them, `halo admin apply` writes the vendors that have a public write API, and `halo admin export` writes the artifacts an admin pastes into the rest. A vendor is managed when its key is present.

type VendorTarget added in v0.3.0

type VendorTarget struct {
	// Org is the tenant: the GitHub organization login (copilot, required),
	// or the Claude organization / ChatGPT workspace / Cursor team id recorded
	// in exported artifacts.
	Org string `yaml:"org,omitempty" json:"org,omitempty"`
	// Project is the Google Cloud project holding the Gemini Code Assist
	// settings (gemini, required).
	Project string `yaml:"project,omitempty" json:"project,omitempty"`
	// Location of the Gemini settings resources (gemini; default global).
	Location string `yaml:"location,omitempty" json:"location,omitempty"`
	// ReleaseChannel pins Gemini Code Assist's release channel (gemini):
	// stable | experimental. Empty leaves the channel unmanaged.
	ReleaseChannel string `yaml:"releaseChannel,omitempty" json:"releaseChannel,omitempty"`
}

VendorTarget names the tenant one vendor's console settings apply to. Credentials never live here: `halo admin` reads them from a file or an environment variable named on its command line.

func (VendorTarget) GeminiLocation added in v0.3.0

func (t VendorTarget) GeminiLocation() string

GeminiLocation is Location with the default (global) applied.

func (VendorTarget) String added in v0.3.0

func (t VendorTarget) String() string

String names the target for logs and plan output (never a credential).

type WeeklyWindow added in v0.4.0

type WeeklyWindow struct {
	From string `yaml:"from" json:"from"`
	To   string `yaml:"to" json:"to"`
}

WeeklyWindow is "Fri 18:00" to "Mon 06:00": a day (Mon..Sun) and a 24-hour time. A To at or before From in the week ends the following week.

type WireFitness

type WireFitness int

WireFitness is how well a model alias's route can answer a client wire.

const (
	WireNo    WireFitness = iota // no target can answer it (or the alias is unknown)
	WireMaybe                    // only orchestrators that do not declare it: they may translate
	WireYes                      // a target's provider (or a declaring orchestrator) answers it
)

type WorkloadIssuer added in v0.3.0

type WorkloadIssuer struct {
	Name string `yaml:"name" json:"name"`
	// URL is the exact iss claim; discovery is <url>/.well-known/openid-configuration.
	URL      string `yaml:"url" json:"url"`
	Audience string `yaml:"audience,omitempty" json:"audience,omitempty"`
	// TenantClaim names the claim that identifies the tenant (org, group,
	// account) a token was minted for; every rule of this issuer must match it
	// exactly. Required for an issuer Halos does not know (see TenantClaims);
	// for a known one it may only narrow the choice to one of its claims.
	TenantClaim string `yaml:"tenantClaim,omitempty" json:"tenantClaim,omitempty"`
}

WorkloadIssuer is one trusted OIDC issuer (GitHub Actions, GitLab, ...).

func (WorkloadIssuer) TenantClaims added in v0.3.0

func (i WorkloadIssuer) TenantClaims() (provider string, claims []string)

TenantClaims returns the provider name ("" for an issuer Halos does not know) and the claims a rule of issuer i may pin its tenant with (none for an unknown issuer without tenantClaim). For a known issuer tenantClaim only narrows the provider's own tenant claims: a tenantClaim that is not one of them (ref, environment, ...) names no tenant and is ignored, so a rule pinning only it never counts as pinned (Validate reports it).

type WorkloadRule added in v0.3.0

type WorkloadRule struct {
	Name   string `yaml:"name" json:"name"`
	Issuer string `yaml:"issuer" json:"issuer"` // WorkloadIssuer.Name
	// Match: claim name -> exact value or path.Match glob ("*" never crosses "/").
	// Every entry must hold; a missing claim never matches.
	Match map[string]string `yaml:"match" json:"match"`
	// Subject is the workload id; must start with "workload:". {claim}
	// expands to that (matched) claim's value, e.g. workload:gh:{repository}.
	Subject string   `yaml:"subject" json:"subject"`
	Groups  []string `yaml:"groups,omitempty" json:"groups,omitempty"`
	// MaxTTLSeconds bounds the minted credential (default 900, at most 3600).
	MaxTTLSeconds int `yaml:"maxTTLSeconds,omitempty" json:"maxTTLSeconds,omitempty"`
	// contains filtered or unexported fields
}

WorkloadRule maps matching tokens of one issuer to a workload subject.

func (WorkloadRule) TTL added in v0.3.0

func (r WorkloadRule) TTL() time.Duration

TTL is the rule's credential lifetime.

type Workloads added in v0.3.0

type Workloads struct {
	// Audience CI tokens must carry (each issuer may override). Required.
	Audience string           `yaml:"audience" json:"audience"`
	Issuers  []WorkloadIssuer `yaml:"issuers" json:"issuers"`
	// Rules are tried in order; the first whose every match entry holds wins.
	Rules []WorkloadRule `yaml:"rules" json:"rules"`
	// Experiments enrolls workloads in experiments like humans. Default false:
	// workload traffic gets control routing and no shadow, so CI runs never
	// skew experiment evidence.
	Experiments bool `yaml:"experiments,omitempty" json:"experiments,omitempty"`
}

Workloads federates headless agents (CI jobs, cloud workloads) into the gateway: halo-server exchanges a token from a trusted OIDC issuer for a short-lived gateway credential when a rule matches its claims (ADR-0023).

func (*Workloads) AllowsExperiments added in v0.3.0

func (w *Workloads) AllowsExperiments() bool

AllowsExperiments reports whether workload subjects may be enrolled in experiments (and shadowed).

func (*Workloads) AudienceFor added in v0.3.0

func (w *Workloads) AudienceFor(i WorkloadIssuer) string

AudienceFor is the aud a token from issuer i must carry.

func (*Workloads) IssuerByURL added in v0.3.0

func (w *Workloads) IssuerByURL(iss string) (WorkloadIssuer, bool)

IssuerByURL returns the trusted issuer whose URL is exactly iss.

func (*Workloads) MatchWorkload added in v0.3.0

func (w *Workloads) MatchWorkload(issuer string, claims map[string]any) (WorkloadRule, string, bool)

MatchWorkload returns the first rule for issuer whose match entries all hold on claims, plus its expanded subject. Claims are verified token claims. A rule that pins no tenant (UnpinnedRules) is skipped, whatever loaded it. A tombstone (TightenWorkloads) that matches ends the search with no rule.

func (*Workloads) UnpinnedRules added in v0.3.0

func (w *Workloads) UnpinnedRules() []string

UnpinnedRules names the rules that pin no tenant of their issuer (or name no declared issuer). Validate refuses them; MatchWorkload never matches them, so a policy that reached a server without validation still cannot admit another tenant's jobs.

Jump to

Keyboard shortcuts

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