agentsurface

package
v0.14.0-rc.21 Latest Latest
Warning

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

Go to latest
Published: Aug 7, 2026 License: Apache-2.0 Imports: 1 Imported by: 0

Documentation

Overview

Package agentsurface is the single source of truth for which Burrow capabilities the `burrow-agent` binary carries and which ones it deliberately does not.

It exists to serve two readers from ONE table:

  • The surface-guard test (cmd/burrow-agent/agent_surface_guard_test.go) reads the agent-surface entries as its closed allow-list, so a command that appears in the binary without an entry here fails the build.
  • `guard` (on both CLIs) reads the entries that are NOT on the agent surface and reports them to the agent, with what each capability is and who can perform it ([ADR-0065](../../docs/adr/0065-what-belongs-on-the-agent-surface.md) §7).

Why the absent capabilities are reported at all

ADR-0065 §5 is the reasoning, and it is a security argument rather than a usability one. Burrow has two ways to keep a capability away from an agent: leave the verb out of the binary (tier 1) or ship it with a guardrail that denies it (tier 2). They fail differently. A denied verb produces `denied: app.delete` — a legible refusal the agent can relay to a human, and one it can see coming by reading `guard` first. An ABSENT verb produces `unknown command`, which is a dead end; and a dead end is exactly what pushes an agent to route around the control channel entirely, reaching for `kubectl` or a shell. That is the failure [ADR-0021](../../docs/adr/0021-guardrails-require-control-plane-only-agent-access.md) says Burrow cannot close from the inside: once the agent is off the channel, the guardrails are not in the path at all.

So a verb that is absent AND legible is a refusal the agent can relay — "removing an add-on is not something I can do, and here is who can" — which is what makes tier 1 tolerable rather than merely safe (ADR-0065 §7).

Reporting this enumerates the surface to anything that can read it. ADR-0065 §7 accepts that outright: the CLI is open source and `--help` already reveals it, so the read carries no access control and no capability is withheld from the answer.

Why one table rather than two lists

The absent list must not be a second, hand-maintained copy of the surface-guard test's allow-list, because two lists drift and the drift is silent. Instead every capability appears once, tagged with the surface that carries it, so moving a verb across the line is a single edit and both readers follow it. AbsentFrom goes further for the agent binary itself: it subtracts the paths the running command tree actually registers, so a verb REMOVED from the binary becomes legible in `guard` immediately, with no second edit anywhere.

Index

Constants

View Source
const WhoOperator = "the burrow operator CLI, run by a human with the cluster's admin kubeconfig"

WhoOperator is the answer to "who can do this instead" for every capability held back from the agent binary. It is one sentence on purpose: the agent relays it verbatim to a person, so it names the tool, the human, and the credential that tool needs.

Variables

This section is empty.

Functions

func AgentSurface

func AgentSurface() map[string]string

AgentSurface returns the capabilities compiled into `burrow-agent`, keyed by command path with the one-line reason each qualifies. It is the closed allow-list the surface-guard test enforces: a command registered without an entry here fails that test, and an entry here with no registered command fails it too.

Types

type AbsentCapabilitiesReport

type AbsentCapabilitiesReport struct {
	AbsentCapabilities []Capability `json:"absent_capabilities"`
}

AbsentCapabilitiesReport is what `burrow agent capabilities --json` answers: the capabilities the agent binary does not carry, on their own rather than beside a policy they have nothing to do with (issue #445).

It carries the same key under the same name as GuardReport.AbsentCapabilities so the two answers cannot drift into two spellings of one list, and it is never omitted when empty for the same reason that field is not: a missing key reads as "unknown" where an empty list reads as "none".

func NewAbsentCapabilitiesReport

func NewAbsentCapabilitiesReport(absent []Capability) AbsentCapabilitiesReport

NewAbsentCapabilitiesReport wraps an absent-capability list for the standalone report, normalizing a nil list to an empty one so the JSON shape is stable.

type Capability

type Capability struct {
	// Path is the command path the capability has, or WOULD have, on the agent surface —
	// space-separated, binary name omitted (e.g. "addon remove"). For an agent capability it is
	// the registered path and matches the surface-guard allow-list key. For an operator
	// capability it is the path the verb would occupy if it were compiled in, which is what makes
	// "is it registered?" a meaningful question to ask of the running command tree.
	Path string `json:"capability"`
	// Surface is which CLI carries it.
	Surface Surface `json:"-"`
	// What describes the capability in one line. For an agent capability it doubles as the reason
	// it qualifies for the surface under ADR-0065 §1 — the claim a reviewer checks.
	What string `json:"what"`
	// Why is the reason an operator capability is held back from the agent binary. Empty for an
	// agent capability.
	Why string `json:"why,omitempty"`
	// Who names who can perform it instead. Defaults to WhoOperator for an operator capability.
	Who string `json:"who,omitempty"`
	// Command is the operator CLI invocation, so the agent can hand a person something to run.
	Command string `json:"operator_command,omitempty"`
}

Capability is one Burrow capability and the surface that carries it.

The JSON field names are the shape `guard` reports for an absent capability, so an agent that hits a wall has, in one object, what the capability is (What), why it is not on its surface (Why), who can perform it (Who), and the exact command that person would run (Command).

func AbsentFrom

func AbsentFrom(registered []string) []Capability

AbsentFrom returns the capabilities in the catalogue that `registered` does not contain, each with what it is, why it is not on the agent surface, and who can perform it (ADR-0065 §7).

The subtraction is what keeps this answer honest. Passing the paths a running command tree actually registers means a verb removed from the binary becomes legible here immediately, with no second edit: it stops matching a registered path and starts being reported as absent. Capability paths that are not commands at all — `addon remove --delete-data`, a flag variant — never match anything and so are always reported.

func AbsentFromAgentSurface

func AbsentFromAgentSurface() []Capability

AbsentFromAgentSurface returns the capabilities absent from `burrow-agent` as DECLARED by the catalogue, for callers that cannot walk the agent binary's command tree — the operator CLI, which is a different binary. The two answers agree whenever the surface-guard test passes, since that test is what pins the declaration to the real command tree.

type GuardReport

type GuardReport struct {
	// Scope echoes what the answer is ABOUT when it is about something narrower than the whole
	// cluster, and is omitted otherwise. It is what makes each entry's `source` legible: `deny` with
	// `"source":"name"` means nothing on its own, and means "denied for the app website in prod, by a
	// disposition set for that app" beside `{"env":"prod","name":"website"}`. Without it a reader has
	// to remember the arguments the call was made with to interpret the answer, which an agent
	// relaying the answer to a human generally cannot (ADR-0085 §4, ADR-0065 §7).
	Scope      *GuardReportScope  `json:"scope,omitempty"`
	Guardrails []client.Guardrail `json:"guardrails"`
	// AbsentCapabilities is never omitted, even when empty: an agent reading this answer is asking
	// what it cannot do, and a missing key reads as "unknown" where an empty list reads as "none".
	AbsentCapabilities []Capability `json:"absent_capabilities"`
}

GuardReport is what `guard` answers on both CLIs: the guardrail dispositions the control plane enforces, plus the capabilities absent from the agent binary (ADR-0065 §7).

The two groups are separate fields on purpose. A denied verb and an absent verb are different answers — the first is a limit an operator can relax with `guard set`, the second is not on the binary at all — and an agent must be able to tell them apart without parsing prose.

func NewGuardReport

func NewGuardReport(scope client.GuardScope, guardrails []client.Guardrail, absent []Capability) GuardReport

NewGuardReport pairs guardrail dispositions with absent capabilities for the scope they were read for, normalizing a nil absent list to an empty one so the JSON shape is stable.

A scope that names nothing produces no `scope` member: the report is then the global policy, every entry's Source is empty because there is no tier to distinguish, and an object saying so would be noise on the answer every agent reads first.

type GuardReportScope

type GuardReportScope struct {
	Env  string `json:"env,omitempty"`
	Name string `json:"name,omitempty"`
}

GuardReportScope names the environment and the one app or add-on instance a scoped report answers for (ADR-0085 §1). Both members are omitted when empty, so the global listing carries no scope at all rather than an object of blanks.

type Surface

type Surface string

Surface names which of Burrow's two CLIs carries a capability ([ADR-0049](../../docs/adr/0049-burrow-agent-scoped-cli-control-channel.md)).

const (
	// Agent marks a capability compiled into `burrow-agent`: app lifecycle, scoped to the app the
	// agent was asked about, and gated server-side by the control-plane guardrails.
	Agent Surface = "agent"
	// Operator marks a capability the agent binary deliberately does not carry. It lives on the
	// human `burrow` CLI only, and `guard` reports it so the agent can say what it is and who can
	// run it instead of failing with `unknown command` (ADR-0065 §7).
	Operator Surface = "operator"
)

Jump to

Keyboard shortcuts

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