vendoradmin

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: 18 Imported by: 0

Documentation

Overview

Package vendoradmin renders one Halos policy into the settings of the vendors' cloud admin consoles (Claude for Teams/Enterprise, ChatGPT workspaces, Gemini for Google Cloud, GitHub Copilot, Cursor), reads the live settings where a vendor publishes a read API, diffs them, and applies the diff where it publishes a write API (ADR-0025). Everything else is exported as an artifact with manual steps.

Render is pure. Read and Apply go through a Client that takes a base URL, so tests run against httptest fakes. Credentials are never part of policy; errors are redacted of them.

Index

Constants

View Source
const (
	GeminiBaseURL = "https://cloudaicompanion.googleapis.com"
	GitHubBaseURL = "https://api.github.com"
)

Default API base URLs.

Variables

View Source
var ErrStale = errors.New("the console changed since the plan was confirmed; nothing written, re-run `halo admin plan`")

ErrStale means the console changed between the confirmed plan and apply.

Functions

func CheckBaseURL

func CheckBaseURL(raw string) error

CheckBaseURL accepts an https API base URL, or http only to a loopback host (a local fake or proxy): a credential never leaves in clear text.

func MCPTokenEnv

func MCPTokenEnv(vendor string) string

MCPTokenEnv is the environment variable the MCP admin_plan tool reads (only with `halo mcp serve --admin-read`). It differs from TokenEnv so a CLI admin token in a shell profile is never picked up by an agent's server.

func MergeList

func MergeList(want []string, cur any) []any

MergeList is want's strings (sorted, deduplicated) followed by the non-string entries of the live list cur, in their live order.

func Sanitize

func Sanitize(s string) string

Sanitize drops control (Cc) and format (Cf) characters from s, so a policy or console value cannot move the cursor, recolour or hide text in a terminal (ESC sequences, CR) or reorder it (bidi overrides) in STEPS.md.

func StatusCode added in v0.4.0

func StatusCode(err error) int

StatusCode is the HTTP status of a vendor API error from a Client, or 0.

func Targets

func Targets(va *policy.VendorAdmin) map[string]policy.VendorTarget

Targets maps each vendor name to its configured target.

func TokenEnv

func TokenEnv(vendor string) string

TokenEnv is the environment variable `halo admin` reads a vendor's credential from by default.

func Vendors

func Vendors() []string

Vendors lists the vendor names Render knows, in render order.

Types

type Access

type Access string

Access says what Halos can do with a console setting.

const (
	AccessWrite  Access = "write"  // public read and write API: plan diffs, apply writes
	AccessRead   Access = "read"   // public read API only: plan diffs, a human changes it
	AccessManual Access = "manual" // no API: exported, a human sets it, drift is unverifiable
)

type Artifact

type Artifact struct {
	Name    string `json:"name"`
	Content string `json:"content"`
}

Artifact is a file an admin pastes into a console that has no write API.

type Change

type Change struct {
	Key     string `json:"key"`
	Current any    `json:"current"` // nil = unset
	Desired any    `json:"desired"`
	Access  Access `json:"access"`
	// Loosens is true when applying it would weaken a control the console
	// has now. apply refuses these: a human makes that call in the console.
	Loosens bool `json:"loosens,omitempty"`
}

Change is one setting whose live value differs from the desired one.

type Client

type Client struct {
	Vendor  string
	BaseURL string
	Target  policy.VendorTarget
	// Token is the bearer credential. It is sent only in the Authorization
	// header and scrubbed from every error.
	Token string
	HTTP  *http.Client
}

Client reads and writes one vendor's console through its public API.

func NewClient

func NewClient(vendor, base, token string, t policy.VendorTarget) *Client

NewClient returns the API client for vendor, or nil when it has no public read API (claude, codex, cursor: see ADR-0025). base "" uses the vendor's.

func (*Client) Apply

func (c *Client) Apply(ctx context.Context, p *Plan) ([]Result, error)

Apply writes p's writable changes, p being the plan a human confirmed. It refuses the whole plan, sending no write, when a writable change loosens, a value is forbidden, a conflicting resource governs the target, or a fresh read of the console no longer matches the plan (ErrStale). It returns one Result per writable change it attempted, in order; a change after a failed write is not attempted.

func (*Client) Get added in v0.4.0

func (c *Client) Get(ctx context.Context, path string, out any, hdr ...string) (http.Header, error)

Get is an authenticated GET under the same rules as the console calls: the token only in the Authorization header and scrubbed from errors, no redirects, https (or loopback http) only, body capped at 1 MiB. The session ledger's vendor sources (internal/ledger) read audit APIs through it.

func (*Client) Read

func (c *Client) Read(ctx context.Context, p *Plan) (map[string]any, error)

Read returns the live values of the settings p desires, keyed like Setting.Key, plus conflictsKey when other resources govern the target.

func (*Client) Send added in v0.4.0

func (c *Client) Send(ctx context.Context, method, path string, body, out any) (http.Header, error)

Send is an authenticated request with an optional JSON body under the same rules as Get. The cloud agent cancel and pause calls (internal/cloudagents) go through it.

type Cred

type Cred struct {
	Token   string
	BaseURL string
}

Cred is one vendor's credential and API base URL ("" = the vendor's).

type Plan

type Plan struct {
	Vendor  string    `json:"vendor"`
	Target  string    `json:"target"`
	Desired []Setting `json:"desired"`
	Changes []Change  `json:"changes"`
	Read    bool      `json:"read"` // the live state was read and diffed
	// Conflicts are console resources Halos does not own that govern the same
	// target (a non-halos Gemini setting bound to the project); apply refuses.
	Conflicts []string   `json:"conflicts,omitempty"`
	Warnings  []string   `json:"warnings"`
	Manual    []string   `json:"manual,omitempty"`
	Artifacts []Artifact `json:"artifacts,omitempty"`
	Error     string     `json:"error,omitempty"`
}

Plan is one vendor's desired state and, after Diff, its drift.

func RenderProfile

func RenderProfile(vendor string, org *policy.Org, p *policy.Profile, t policy.VendorTarget) (*Plan, error)

RenderProfile renders one vendor for a profile (tests, the no-silent-drop check).

func (*Plan) CheckForbidden

func (p *Plan) CheckForbidden() error

CheckForbidden refuses a plan that would emit a permission-bypass token (invariant 1; policy.BypassToken is the one shared check).

func (*Plan) Diff

func (p *Plan) Diff(current map[string]any)

Diff fills p.Changes from current, the live values Client.Read returned (a missing key is unset). Manual settings are not diffed: nothing can read them. A list setting's live value is []any: Halos owns its string entries; any other entry (a Copilot {"if-any-match": [...]} rule) is preserved, so the desired value is the desired strings followed by those entries.

func (*Plan) HasAPI

func (p *Plan) HasAPI() bool

HasAPI reports whether a vendor has any setting Halos can read.

func (*Plan) Loosenings

func (p *Plan) Loosenings() []Change

Loosenings are the changes apply refuses: the ones Halos would write. Loosening read-only settings are shown by plan but are a human's call.

type Report

type Report struct {
	Ring     string   `json:"ring"`
	Profile  string   `json:"profile"`
	Warnings []string `json:"warnings"`
	Vendors  []*Plan  `json:"vendors"`
}

Report is every enabled vendor's plan for one policy.

func Render

func Render(org *policy.Org) (*Report, error)

Render renders every vendor vendorAdmin enables from the profile of org.VendorAdminRing(). It reads nothing outside org. The org must validate.

func (*Report) Clients

func (r *Report) Clients(targets map[string]policy.VendorTarget, creds map[string]Cred) (map[string]*Client, error)

Clients returns the API client for each vendor in r that has one, keyed by vendor. A base URL that is not https (or loopback http) is an error.

func (*Report) Drift

func (r *Report) Drift() bool

Drift reports whether any vendor's live state differs from the policy.

func (*Report) Errors

func (r *Report) Errors() []string

Errors lists the vendors whose live state could not be read.

func (*Report) ReadLive

func (r *Report) ReadLive(ctx context.Context, clients map[string]*Client, hint func(vendor string) string)

ReadLive reads and diffs every vendor that has an API client. A vendor that cannot be read gets Plan.Error (never a credential in it). hint names where the credential comes from, for the error.

type Result

type Result struct {
	Change Change `json:"change"`
	OK     bool   `json:"ok"`
	Error  string `json:"error,omitempty"`
}

Result is the outcome of one change Apply attempted.

type Setting

type Setting struct {
	Key    string `json:"key"`
	Value  any    `json:"value"`
	From   string `json:"from"` // the policy field it derives from
	Access Access `json:"access"`
	// Strict is the value that is the tighter control (a scalar setting);
	// changing a setting away from it (or from a StrictAlso value, which the
	// vendor treats the same) is a loosening. List settings loosen when an
	// entry the console has is missing from the desired list.
	Strict     any   `json:"-"`
	StrictAlso []any `json:"-"`
	// Sticky settings loosen whenever an existing value changes (a binding
	// target: moving it un-governs the old target).
	Sticky bool `json:"-"`
}

Setting is one desired console setting. Key is "<resource>.<field>" in the vendor's own naming, so a diff reads like the vendor's API.

Jump to

Keyboard shortcuts

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