vendoradmin

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Oct 24, 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 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) 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.

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

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