Help us shape the future of Go! Take the 2026 Go Developer Survey

api

package
v0.14.0 Latest Latest
Warning

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

Go to latest
Published: Oct 23, 2026 License: AGPL-3.0 Imports: 15 Imported by: 0

Documentation

Overview

Package api defines the seven adapter interfaces. Definitions only, no implementations.

The app declares requirements; adapters translate (R-250). Core never learns a provider's vocabulary (R-251). If a Docker-shaped concept appears in a signature here, the design has failed. Everything is compiled in-tree — these are ordinary Go interfaces, freely refactorable, and there is no wire protocol. See design 03.

Index

Constants

View Source
const (
	// AuditFormatNative is the archive's line: the row as Pando stores it.
	AuditFormatNative = "native"
	// AuditFormatOCSF is OCSF 1.3.0, mapped by core's table.
	AuditFormatOCSF = "ocsf"
)

Audit event formats an adapter may ask for (R-384, design 12 §6).

View Source
const (
	ChallengeHTTP01 = "http-01"
	ChallengeDNS01  = "dns-01"
)

Challenge types an ACME order is proved with (R-169).

View Source
const (
	RefCallbackURL = "callback_url" // where the provider sends the browser back: the OIDC redirect URI, the SAML ACS URL
	RefEntityID    = "entity_id"    // the SAML service provider's entity ID, also its metadata URL
	RefLoginURL    = "login_url"    // where the provider starts a sign-in from its dashboard: OIDC's initiate login URI
)

Refs a form supplies for a step's text and links, beside the settings: an identity provider's addresses, which the person registers with the provider before Pando has saved it.

View Source
const CredentialWebhookSecret = "webhook_secret"

CredentialWebhookSecret is the credential field a connection whose capabilities say Webhook keeps the secret its webhook is signed with under.

View Source
const ImageLabelBundle = "io.pando.built-for"

ImageLabelBundle is the image label a builder sets to the app an image was built for, and a runtime reads to remove an app's images when it is destroyed. Its value is the BundleID. An image label rather than a tag, because a rebuild moves the tag and leaves the previous image untagged.

Its own key, not the one a runtime marks a bundle's containers with: a container inherits its image's labels, and a throwaway trial container started from the image must not look like one of the app's workloads.

View Source
const LookupLimit = 20

LookupLimit is the most results one lookup returns. Core asks its stores for no more, and aikit cuts a longer answer to it, so what a lookup puts in a prompt does not grow with the installation (O-54).

View Source
const PresetOther = "other"

PresetOther stands, in a step's Presets, for a provider none of the kind's presets names.

Variables

Categories is every category, in R-252's order: the order the reference lists them in.

Functions

func IsAssignable

func IsAssignable(f AIFunction) bool

IsAssignable reports whether f is a function an adapter can be assigned.

func Offers

func Offers(list []EdgeConfig, c EdgeConfig) bool

Offers reports whether a list of edge configurations includes c. An empty list offers only the shared mount, which is what every runtime offered before the field existed.

func ScopedRead

func ScopedRead[T any](ctx context.Context, key any, read func() (T, error)) (T, error)

ScopedRead is read's answer, made once per read scope and key. Outside a scope, or when read fails, nothing is kept and every call reads.

func WithReadScope

func WithReadScope(ctx context.Context) context.Context

WithReadScope opens a read scope for the reads made with the context it returns.

Types

type AIAdapter

type AIAdapter interface {
	Adapter
	Capabilities(ctx context.Context) (AICapabilities, error)

	// RepairPlan reads a proposal that failed and returns amendments that might
	// make it work. Any kind in the closed set may be proposed.
	//
	// It must honor ctx cancellation and the budget in req. Returning an error
	// is legitimate and costs nothing but the repair: the failure is shown as
	// it was (R-107).
	RepairPlan(ctx context.Context, req ScreenRequest) (ScreenResult, error)

	// AnswerQuestions answers what it can of req.Questions from the repository.
	// Only AmendAnswerQuestion is accepted from it; core refuses anything else.
	AnswerQuestions(ctx context.Context, req ScreenRequest) (ScreenResult, error)

	// RevisePlan acts on req.Instruction, what a person reviewing the plan
	// asked for, checking it against the repository. Any kind in the closed
	// set may be proposed, and ScreenResult.Reply says what it did and why —
	// including that the repository does not support what was asked.
	RevisePlan(ctx context.Context, req ScreenRequest) (ScreenResult, error)

	// DraftAccess drafts a role, a group, or both (R-343). Verbs come from
	// req.Verbs only; core refuses anything else.
	DraftAccess(ctx context.Context, req AccessRequest) (AccessDraft, error)

	// DraftPolicy proposes changes to host policy (R-344).
	DraftPolicy(ctx context.Context, req PolicyRequest) (PolicyDraft, error)

	// SearchAudit turns a question into one audit filter (R-345).
	SearchAudit(ctx context.Context, req AuditSearchRequest) (AuditSearch, error)

	// SummarizeAudit summarizes the records core found with that filter,
	// from those records alone.
	SummarizeAudit(ctx context.Context, req AuditSummaryRequest) (AuditSummary, error)

	// AnswerReference answers a question from the generated reference (R-346).
	AnswerReference(ctx context.Context, req ReferenceRequest) (ReferenceAnswer, error)
}

AIAdapter is optional supporting functionality, never required (R-106).

Every method may fail, and no failure of one is a failure of the thing that called it: R-335 says a screening that cannot run leaves the deterministic proposal exactly as it was.

type AIAssignment

type AIAssignment struct {
	Function   AIFunction `json:"function"`
	AdapterRef string     `json:"adapter_id"`
	Model      string     `json:"model,omitempty"`
}

AIAssignment is which adapter performs one AI function, and on which model (R-259). Model is empty to use the adapter's own.

type AICapabilities

type AICapabilities struct {
	Functions []AIFunction

	// Model is shown in the review. "Anthropic (claude-opus-5-5) read 7 files and
	// changed 3 things" is the honest account of what happened, and it is not
	// available if the model is a config value core never sees.
	//
	// It is the adapter's own default. An assignment may name another model
	// for one function when ChoosesModel is set (R-259); the model that ran is
	// then the one each result reports.
	Model string

	// ChoosesModel says an assignment may name a model other than Model for
	// one function, so lighter work can go to a cheaper model. An assignment
	// naming a model on an adapter that cannot choose is refused (R-259).
	ChoosesModel bool

	// Models, when set, are the only models an assignment may name. Empty
	// with ChoosesModel set means any model the provider serves; a name it
	// does not serve fails the call, not the assignment.
	Models []string

	// MaxFiles and MaxBytes are what this adapter will read from a source. Core
	// lowers them to the install's own limits; it never raises them.
	MaxFiles int
	MaxBytes int64

	// LooksUp says the adapter calls tools in a loop during the
	// administrative functions, so core hands it a Lookup to find people,
	// apps and groups with instead of a list of them (O-54). Without it core
	// searches the words of the request and sends the top matches only.
	LooksUp bool
}

AICapabilities is what an AI adapter can do, as data.

func (AICapabilities) Does

func (c AICapabilities) Does(f AIFunction) bool

Does reports whether the adapter advertises a function.

type AIFunction

type AIFunction string

AIFunction names something an AI adapter can do.

Data, not a type assertion (R-254, R-259). An adapter that does not screen is skipped with a reason in the proposal, rather than by an assertion the caller cannot plan around.

const (
	// AIFunctionRepairPlan reads a proposal that failed (a trial run that
	// crashed, or a repository no detector could read) and proposes amendments
	// that might make it work. R-106's "proposing repairs from a failed build
	// log", applied to detection.
	AIFunctionRepairPlan AIFunction = "repair_plan"

	// AIFunctionAnswerQuestions answers detection's outstanding questions from
	// the repository (R-338). Answers only: nothing else in the plan changes.
	AIFunctionAnswerQuestions AIFunction = "answer_questions"

	// AIFunctionRevisePlan changes a plan because a person reviewing it asked:
	// "you missed the database", "it serves on 8080". It reads the repository
	// again to check, and replies (R-336's third trigger).
	AIFunctionRevisePlan AIFunction = "revise_plan"

	// AIFunctionReadReadme is R-106's remaining name. Declared so the reason
	// this is a list is visible; not built.
	AIFunctionReadReadme AIFunction = "read_readme"

	// AIFunctionDraftAccess drafts a custom role, a group, or both, from a
	// description of who should be able to do what (R-343). A draft only: an
	// administrator reviews it and creates it through the ordinary endpoints.
	AIFunctionDraftAccess AIFunction = "draft_access"

	// AIFunctionDraftPolicy proposes changes to host policy from a description
	// (R-344). Core refuses any change to a field fixed in the startup
	// configuration, whatever the model says.
	AIFunctionDraftPolicy AIFunction = "draft_policy"

	// AIFunctionSearchAudit turns a question into audit filters, and
	// summarizes the records core found with them (R-345). The adapter never
	// reads the audit log (R-027): core runs the query.
	AIFunctionSearchAudit AIFunction = "search_audit"

	// AIFunctionAnswerReference answers "How can I…" from the generated API,
	// CLI and MCP reference (R-346). It describes; it never acts.
	AIFunctionAnswerReference AIFunction = "answer_reference"
)

func AIFunctions

func AIFunctions() []AIFunction

AIFunctions is every function Pando calls an AI adapter for, in the order the console lists them. read_readme is absent: nothing calls it, so there is nothing to assign it to (R-259).

func (AIFunction) Title

func (f AIFunction) Title() string

Title is how a function is named in a sentence a person reads, following the R-105 standard: "Audit search is already handled by …".

type AccessDraft

type AccessDraft struct {
	Role  *RoleDraft  `json:"role,omitempty"`
	Group *GroupDraft `json:"group,omitempty"`

	// Reply says what was drafted and why, or why nothing was, to the person
	// who asked. Held to R-105.
	Reply string `json:"reply"`
	Model string `json:"model,omitempty"`
}

AccessDraft is what DraftAccess proposes. Either part may be absent.

type AccessRequest

type AccessRequest struct {
	Description string

	// Verbs are every verb the requester could grant. A draft naming one not
	// listed is refused by core, so a model cannot widen what a person asked
	// it to draft past what that person could do themselves.
	Verbs []VerbInfo
	Roles []RoleInfo

	// Groups and People are not every group and account (O-54). They are the
	// ones the draft so far names and, for an adapter that cannot look
	// things up, those matching words in Description — at most LookupLimit
	// of each.
	Groups []GroupInfo
	People []PersonInfo

	// Lookup finds anyone else, for an adapter that LooksUp. Nil otherwise.
	Lookup *Lookup

	// Current is the draft so far, when a person is refining one: what an
	// earlier call drafted, with whatever they changed by hand. Description is
	// then the change they asked for, and the answer is the whole draft again.
	Current *AccessDraft

	Model string
}

AccessRequest asks for a role and group draft (R-343).

type Action added in v0.10.0

type Action struct {
	Text string `json:"text"`

	// Table is shown under the text, for something with columns, such as an
	// API token's permissions.
	Table *Table `json:"table,omitempty"`
}

Action is one thing to do in a step.

type Adapter

type Adapter interface {
	Kind() string
	Category() Category
	Configure(ctx context.Context, raw json.RawMessage) error

	// HealthCheck lets the planner refuse to plan against an unhealthy adapter
	// and return ADAPTER_UNAVAILABLE, rather than failing mid-deploy (R-254).
	HealthCheck(ctx context.Context) error
}

Adapter is implemented by every adapter in every category.

type Amendment

type Amendment struct {
	Kind AmendmentKind `json:"kind"`

	// Workload names which workload, for the kinds that touch one. Empty means
	// the primary.
	Workload string `json:"workload,omitempty"`

	// Key is an environment key, a slot key, or a question key.
	Key string `json:"key,omitempty"`

	// Value is the new value, meaning what Kind says it means.
	Value string `json:"value,omitempty"`

	Command []string `json:"command,omitempty"`

	// Path is a path in the source, or a mount path for AmendAddVolume.
	Path string `json:"path,omitempty"`

	Port int `json:"port,omitempty"`

	SlotType spec.SlotType `json:"slot_type,omitempty"`

	// Required is honored only when the trial run crashed. O-4's [P] fallback,
	// unchanged: an unfilled required slot blocks a deploy (R-132), so a
	// screener marking slots required freely turns "might not start" into
	// "cannot be deployed" — a blocker where configuration would do (R-104).
	Required bool `json:"required,omitempty"`

	// Reason is held to R-105: self-contained, and readable by someone who
	// cannot see the repository.
	Reason string `json:"reason"`

	// Evidence names the files in the source this rests on, and is required
	// (R-334). The paths are checked to exist. A model that names a file that
	// is not there is refused with that as the reason — the cheapest available
	// check on whether it read the repository or recalled a framework.
	Evidence []string `json:"evidence"`
}

Amendment is one proposed change to a draft spec.

One struct with kind-dependent fields rather than a union, because this crosses a JSON boundary twice — into the provider's tool schema and out to the console — and a union does neither well.

type AmendmentKind

type AmendmentKind string

AmendmentKind is the closed set (R-332, design 10 §3).

The obvious interface returns a whole spec and is wrong — not because a model might be careless, but because a returned spec can express every field there is, including the isolation floor host policy sets (R-272). That would leave a denylist in core as the only thing between a model's output and a lowered policy floor, and a denylist is a list somebody has to remember to extend when the spec grows a field.

Nothing below says policy, isolation, adapter selection, routing, resources, egress, grants, or a secret's value.

const (
	// AmendSetCommand sets a workload's command. A wrong or missing start
	// command is the most common reason an app builds and does not come up.
	AmendSetCommand AmendmentKind = "set_command"

	// AmendSetEnv sets one literal environment entry. Never a secret: an
	// EnvEntry has exactly one of Value, SlotRef and SecretRef, and this writes
	// Value. HOST=0.0.0.0 on an app that binds loopback is the case this earns
	// its place on.
	AmendSetEnv AmendmentKind = "set_env"

	// AmendSetPort sets the port a workload serves HTTP on, and only where the
	// trial run observed nothing (R-333).
	AmendSetPort AmendmentKind = "set_port"

	// AmendSetHealth sets the health path and port.
	AmendSetHealth AmendmentKind = "set_health"

	// AmendAddSlot declares a dependency Pando's detectors missed. Finding a
	// DATABASE_URL in a file nothing reads is finding a declaration (R-021);
	// deciding an app needs Postgres because it imports an ORM is not, and this
	// cannot express that — a slot needs a key, and a key comes from the source.
	AmendAddSlot AmendmentKind = "add_slot"

	// AmendSetBuildContext, AmendSetDockerfile and AmendSetStaticDir are
	// R-106's "disambiguating monorepo entrypoints".
	AmendSetBuildContext AmendmentKind = "set_build_context"
	AmendSetDockerfile   AmendmentKind = "set_dockerfile"
	AmendSetStaticDir    AmendmentKind = "set_static_dir"

	// AmendAddVolume declares persistent storage at a path (R-201).
	AmendAddVolume AmendmentKind = "add_volume"

	// AmendAnswerQuestion answers one of detection's outstanding questions.
	// R-103 is the metric: a question answered from the repository is one a
	// person did not have to carry to an assistant and back.
	AmendAnswerQuestion AmendmentKind = "answer_question"

	// AmendAddWarning says something and changes nothing.
	AmendAddWarning AmendmentKind = "add_warning"
)

type AppInfo

type AppInfo struct {
	ID   string `json:"id"`
	Name string `json:"name"`
	Slug string `json:"slug,omitempty"`
}

AppInfo is an app, so a question can name one.

type AuditBatch

type AuditBatch struct {
	// Events are encoded in Capabilities().Format, one JSON value each,
	// oldest first.
	Events []json.RawMessage

	// IDs are the events' ids, in the same order, for a destination that
	// takes an idempotency key.
	IDs []int64

	// Actions are the events' actions, in the same order, for a transport
	// that carries one outside the body — syslog's MSGID.
	Actions []string
}

AuditBatch is one Send's events.

type AuditFilter

type AuditFilter struct {
	// Actions are action names or prefixes, any of which matches.
	Actions       []string   `json:"actions,omitempty"`
	AppID         string     `json:"app_id,omitempty"`
	PrincipalID   string     `json:"principal_id,omitempty"`
	PrincipalKind string     `json:"principal_kind,omitempty"`
	TargetKind    string     `json:"target_kind,omitempty"`
	TargetID      string     `json:"target_id,omitempty"`
	Involving     string     `json:"involving,omitempty"`
	Since         *time.Time `json:"since,omitempty"`
	Until         *time.Time `json:"until,omitempty"`
}

AuditFilter is one audit query, as the audit screen's filters express it. Every field narrows; an empty one does not.

type AuditRecordView

type AuditRecordView struct {
	At            time.Time      `json:"at"`
	Action        string         `json:"action"`
	PrincipalKind string         `json:"principal_kind"`
	PrincipalID   string         `json:"principal_id"`
	AppID         string         `json:"app_id,omitempty"`
	TargetKind    string         `json:"target_kind,omitempty"`
	TargetID      string         `json:"target_id,omitempty"`
	Detail        map[string]any `json:"detail,omitempty"`
}

AuditRecordView is one audit record as a summary sees it. Detail carries no secret value (R-194): none is ever written to the log.

type AuditSearch

type AuditSearch struct {
	Filter AuditFilter `json:"filter"`

	// Note says what the filter cannot answer — that successful use of an
	// app is not recorded, say — so the summary is not read as more than it
	// is.
	Note  string `json:"note,omitempty"`
	Model string `json:"model,omitempty"`
}

AuditSearch is the filter a question became.

type AuditSearchRequest

type AuditSearchRequest struct {
	Question string

	// Now is when the question was asked, in UTC, so "last month" means
	// something.
	Now time.Time

	// People and Apps are those matching words in Question, for an adapter
	// that cannot look things up; empty otherwise (O-54). At most
	// LookupLimit of each.
	People []PersonInfo
	Apps   []AppInfo

	// Actions are every action Pando records, from its own catalog rather
	// than from the log (O-54).
	Actions []string

	// Lookup finds people and apps, for an adapter that LooksUp.
	Lookup *Lookup

	Model string
}

AuditSearchRequest asks for audit filters (R-345).

type AuditSinkAdapter

type AuditSinkAdapter interface {
	Adapter

	AuditSinkCapabilities() AuditSinkCapabilities

	// Send delivers a batch, in order. A nil error means the destination
	// accepted every event in it; any error means none is counted, and core
	// sends the same batch again later. Delivery is at least once, so a
	// destination may see an event twice; its id is the idempotency key.
	Send(ctx context.Context, b AuditBatch) error
}

AuditSinkAdapter delivers batches of audit events to one destination.

type AuditSinkCapabilities

type AuditSinkCapabilities struct {
	// MaxBatch is the most events core puts in one Send. Zero means 500.
	MaxBatch int `json:"max_batch"`

	// Format is what core encodes events as: AuditFormatNative or
	// AuditFormatOCSF.
	Format string `json:"format"`

	// Transport is "syslog" or "https", and Endpoint the host and port it
	// sends to — never with a credential. The console names both beside the
	// audit log (R-385): nobody reading the log should miss that a copy of it
	// is leaving.
	Transport string `json:"transport"`
	Endpoint  string `json:"endpoint"`

	// Actions and Exclude are action prefixes narrowing what core sends: an
	// event matching one of Actions (or any, when empty) and none of Exclude
	// (R-384). Configured on the adapter and reported here, so core, the
	// console and the archiver read one answer.
	Actions []string `json:"actions,omitempty"`
	Exclude []string `json:"exclude,omitempty"`

	// StartAtNow says a new destination's first delivery starts after the
	// newest event, rather than at the oldest in the live log. Read once,
	// when core first sees the destination; changing it later moves nothing.
	StartAtNow bool `json:"start_at_now,omitempty"`
}

AuditSinkCapabilities is how core feeds the adapter, as data (R-254).

type AuditSummary

type AuditSummary struct {
	Summary string `json:"summary"`
	Model   string `json:"model,omitempty"`
}

AuditSummary is a few sentences about the records, from the records alone.

type AuditSummaryRequest

type AuditSummaryRequest struct {
	Question string
	Filter   AuditFilter
	Records  []AuditRecordView

	// Truncated is set when more records matched than were sent.
	Truncated bool

	// People and Apps are those the records and the filter name, by ID, and
	// that the person who asked may see: bounded by the records, not by the
	// installation (O-54).
	People []PersonInfo
	Apps   []AppInfo

	Model string
}

AuditSummaryRequest asks for a summary of the records a filter found.

type Authorization

type Authorization struct {
	Mode AuthorizationMode

	// UserCode and VerificationURL are what the person is shown in device
	// mode: "Go to VerificationURL and enter UserCode."
	UserCode        string
	VerificationURL string

	// AuthorizeURL is where to send the person's browser in web mode.
	AuthorizeURL string

	// Form, when set, is submitted by the browser instead of opening
	// AuthorizeURL.
	Form *AuthorizeForm

	// Interval is how often to poll in device mode.
	Interval time.Duration

	// ExpiresAt is when the code stops working.
	ExpiresAt time.Time

	// Flow is what CompleteAuthorization needs back — the device code, the
	// PKCE verifier. Core keeps it and returns it; the adapter remembers
	// nothing between the two calls (R-027).
	Flow secret.Value
}

Authorization is an OAuth authorization in progress.

type AuthorizationCompletion

type AuthorizationCompletion struct {
	Mode        AuthorizationMode
	Flow        secret.Value
	Code        string
	RedirectURL string
}

AuthorizationCompletion finishes an OAuth authorization.

type AuthorizationMode

type AuthorizationMode string

AuthorizationMode is which OAuth flow runs.

const (
	// AuthorizationDevice shows the person a code to enter on the
	// provider's site. Pando needs no address the provider can reach, so it
	// works from a laptop, the CLI and an assistant over MCP.
	AuthorizationDevice AuthorizationMode = "device"

	// AuthorizationWeb sends the person's browser to the provider and back
	// to Pando's callback.
	AuthorizationWeb AuthorizationMode = "web"

	// AuthorizationManual connects by hand: begun, it returns a link to set
	// the integration up at the provider; completed, it takes the credential
	// that gives (a Slack bot token) as Code and checks it. For a provider
	// Pando cannot be sent back from — a Pando without an https address.
	AuthorizationManual AuthorizationMode = "manual"

	// AuthorizationInstall finishes a GitHub App's installation: the
	// provider sends the browser to SetupURL with the installation's ID,
	// which CompleteAuthorization takes as Code. Never begun; core reaches
	// it only from the setup address.
	AuthorizationInstall AuthorizationMode = "install"
)

type AuthorizationRequest

type AuthorizationRequest struct {
	Mode AuthorizationMode

	// RedirectURL is Pando's callback, for web mode.
	RedirectURL string

	// State is what the provider returns to the callback, for web mode.
	// Core makes it and checks it; the adapter only passes it along.
	State string

	// SetupURL is where the provider sends the browser once an App is
	// installed, HomepageURL is Pando's own address, and WebhookURL is the
	// connection's webhook — empty when the provider could not reach it, as
	// on a private address. For a connection that creates a GitHub App.
	SetupURL    string
	HomepageURL string
	WebhookURL  string

	// Grant is a credential the person gives to begin, used for this call
	// only and never stored: a Slack configuration token, which lets Pando
	// create its app.
	Grant secret.Value
}

AuthorizationRequest starts an OAuth authorization.

type AuthorizationResult

type AuthorizationResult struct {
	// Pending says the person has not approved a device authorization yet.
	// SlowDown says the provider asked for polling to slow down.
	Pending  bool
	SlowDown bool

	// Credentials are what to store, keyed like the adapter's credential
	// fields: an access token and, where the provider issues one, a refresh
	// token.
	Credentials map[string]secret.Value

	// ExpiresAt is when the access token expires, if it does.
	ExpiresAt time.Time

	// ContinueURL is where the browser goes next when the authorization has
	// another step at the provider: a GitHub App just created is installed
	// next. Empty when it is finished.
	ContinueURL string
}

AuthorizationResult is the outcome of CompleteAuthorization.

type AuthorizeForm added in v0.11.0

type AuthorizeForm struct {
	Action string            `json:"action"`
	Fields map[string]string `json:"fields"`
}

AuthorizeForm is a form the person's browser submits to the provider, for a flow that starts with a POST rather than a link: a GitHub App manifest.

type BackupAdapter

type BackupAdapter interface {
	Adapter

	Capabilities() BackupCapabilities

	// Writer opens a bundle for writing. The caller closes it, and a bundle is
	// only considered present once Close returns without error — a destination
	// that can write atomically should do so, because a half-written bundle
	// that looks whole is the failure R-215 exists to catch.
	Writer(ctx context.Context, name string) (io.WriteCloser, error)

	// Reader opens a stored bundle.
	Reader(ctx context.Context, name string) (io.ReadCloser, error)

	// List returns what the destination is holding. Restore does not need it —
	// Pando records every bundle it wrote — but reconciling that record against
	// reality is how an operator learns a destination expired something out
	// from under them, which is the failure a DR plan cannot afford to discover
	// during a disaster (R-216).
	List(ctx context.Context) ([]StoredBundle, error)

	// Delete removes a bundle. Called only when Pando owns retention; see
	// BackupCapabilities.OwnsRetention.
	Delete(ctx context.Context, name string) error
}

BackupAdapter stores and retrieves DR bundles and per-app backups (R-217).

The eighth category, added in phase 9. It reverses an earlier decision that a destination was a byte sink rather than a category — design 03 §8.1 records the reversal and what the old argument got right. The short reason it is a category: an object store expires and versions objects on its own schedule and a filesystem path does not, so who owns retention is a real question with a real wrong answer, and R-254 says a question like that is answered by a capabilities struct rather than by a type assertion.

Bundles are opaque here. The adapter never sees plaintext: encryption happens in core under a passphrase supplied at backup time (R-213), so a destination that is compromised yields ciphertext and a manifest-shaped blob.

type BackupCapabilities

type BackupCapabilities struct {
	// OwnsRetention says the destination expires bundles on its own schedule —
	// an object-store lifecycle rule, say. When true Pando records
	// retain_until and does not enforce it: pruning what the store has already
	// locked fails every time, and the failure looks like a Pando bug.
	//
	// When false (the local filesystem) Pando prunes, which is R-211's daily
	// retention doing what it says.
	OwnsRetention bool

	// Immutable says a written bundle cannot be deleted or overwritten before
	// its retention expires — a compliance lock. Pando must not offer to delete
	// one, because the offer would fail.
	Immutable bool

	// MaxBundleBytes is 0 when the destination has no limit it knows about. A
	// DR bundle contains a pg_dump and every app volume, so this is a real
	// constraint on small destinations, and it is a plan-time question rather
	// than a discovery to make at byte 4,294,967,296.
	MaxBundleBytes int64
}

BackupCapabilities is what the destination can do, as data (R-254).

type BeginRequest

type BeginRequest struct {
	Endpoints

	// State is opaque, generated by core and bound to the browser. The adapter
	// sends it to the provider (OIDC state, SAML RelayState) and it comes back
	// on the callback, which is how core finds the flow.
	State string
}

BeginRequest starts a redirect sign-in.

type Bid

type Bid struct {
	Confidence float64
	Strategy   BuildStrategy

	// Evidence is human-readable and shown in the review UI, so the user can
	// see the auction rather than being handed a verdict (R-102).
	Evidence []string

	// Questions are what the bidder could not determine (R-102).
	Questions []Question
}

Bid is a builder's claim on a source tree.

type BuildPlan

type BuildPlan struct {
	// Files is every generated file, by path relative to the repository root.
	// The spec carries them (R-020).
	Files map[string]string

	// Entry is the file the build starts from, one of Files.
	Entry string

	// Context is the directory the plan builds from, relative to the one it
	// was planned in: "../.." for a crate whose path dependencies are two
	// directories up. Empty is the directory itself. Files and Entry are
	// relative to the context.
	Context string

	// Start is the command the built image runs, when the plan says. Empty
	// means the plan does not know, and detection asks.
	Start string

	// Ports are the ports the plan says the app serves on, if any.
	Ports []int

	// Declared is what in the repository dictated the plan, or nil when
	// convention-matching chose it.
	Declared *PlanDeclaration
}

BuildPlan is a plan a builder made for a repository without building it.

The files are the builder's vocabulary and opaque to core (R-251): a Dockerfile, a Railpack plan, whatever the builder replays. What detection needs to know about how the app runs is said here in Pando's terms, so detection never has to read a planner's file format.

type BuildRequest

type BuildRequest struct {
	Source     SourceView
	Strategy   BuildStrategy
	Dockerfile string
	Context    string
	Args       map[string]string

	// Target is the stage of a multi-stage Dockerfile to build. Empty builds
	// the last stage, which is what a Dockerfile with no target means.
	Target string

	// GeneratedFiles are build inputs the spec carries, keyed by path relative
	// to the context. Written into the checkout before the build.
	//
	// Present means "use these" — the builder does not plan again. That is what
	// makes a reviewed plan the one that runs, and an edited one take effect.
	GeneratedFiles map[string]string

	// StaticDir is the directory to serve, for the static strategy.
	//
	// What "serving" means is the builder's business, not core's. Core says
	// "this app is a directory of files"; the builder decides what image serves
	// them (R-250, R-251). Empty means the repository root.
	StaticDir string

	// StartCommand is how the app starts, when the spec says so: a shell
	// command line. A builder that plans a build from convention uses it in
	// place of guessing one, and a builder that has no use for it ignores it.
	//
	// It exists because detection asks for the start command when it cannot
	// work one out, and the answer used to stop at the workload. A buildpack
	// plan made at build time then failed with "No start command could be
	// found" on exactly the apps whose owners had just typed one (issue #55).
	StartCommand string

	IsolationFloor IsolationClass
	Timeout        time.Duration
	EgressMode     EgressMode
	EgressAllow    []string

	// CacheNamespace is per-app (R-117), so one app's build cache cannot be
	// read by another's build.
	CacheNamespace string

	// LogSink streams build output to the console live.
	LogSink io.Writer

	// ImageSink receives the built image as a stream.
	//
	// R-111: Pando hands the builder source and receives an image. Writing it
	// to a sink rather than returning it means the image never has to be held
	// in memory or staged on disk — the caller pipes it straight into the
	// runtime's ImportImage.
	//
	// Exactly one of ImageSink and Push is set.
	ImageSink io.Writer

	// Push sends the image to a registry instead, for a runtime that pulls
	// (RuntimeCapabilities.ImageDelivery). Only set for a builder with
	// SupportsPush. BuildResult.Digest is then the pushed manifest's digest
	// and BuildResult.ImageRef is Repository@Digest.
	Push *PushTarget

	// Tag names this build's image, unique to the build: core passes the
	// deployment ID. Never a tag the next build moves, so an image refused
	// after it was built is never mistaken for the one that runs (R-146).
	// Empty lets the builder choose.
	Tag string

	// Steer lets a builder that planned this build from convention revise
	// that plan when it fails in a way the builder recognizes, and build
	// again (a bounded number of times). The plan it built from in the end
	// comes back in BuildResult.GeneratedFiles, so the caller can record it:
	// the spec stays the sole record of how the app is built (R-020).
	//
	// Only detection sets it, where the plan is still being made and the
	// revision is recorded before anyone accepts it. A deploy builds exactly
	// the plan the spec carries, and fails if that plan does.
	Steer bool
}

BuildRequest asks for an image.

type BuildResult

type BuildResult struct {
	ImageRef string
	Digest   string

	// GeneratedFiles is the plan the image was built from, when it is not the
	// one the request carried: a builder asked to Steer revised it after a
	// build of it failed. Nil when the build used the plan it was given.
	GeneratedFiles map[string]string
}

BuildResult is what a build produced.

It carries no trial-run output, though design 03 §3 put ObservedPorts and ObservedWrites here. Producing them means starting a container, starting a container means a container runtime socket, and R-112 forbids the build path ever having one — categorically, with an integration test asserting the build container's mount list. The trial run is RuntimeAdapter.Trial instead, called as its own step, which is what Sequence A described all along.

type BuildStrategy

type BuildStrategy = spec.BuildStrategy

RoutingMode and BuildStrategy are the spec's, not the adapter's. An adapter that needed its own vocabulary for these would be translating in the wrong direction (R-251).

type BuilderAdapter

type BuilderAdapter interface {
	Adapter
	Capabilities(ctx context.Context) (BuilderCapabilities, error)

	// Bid inspects the source and returns a confidence score plus a draft spec
	// fragment. Part of the detector auction (R-093).
	Bid(ctx context.Context, src SourceView) (Bid, error)

	Build(ctx context.Context, req BuildRequest) (BuildResult, error)

	// Forget removes what the builder keeps for an app between builds — its
	// build cache — once the app is deleted. The namespace is the app's ID,
	// the prefix of every CacheNamespace it built under. Idempotent: an app
	// that never built, or was already forgotten, is not an error.
	Forget(ctx context.Context, namespace string) error
}

BuilderAdapter turns source into a runnable image.

type BuilderCapabilities

type BuilderCapabilities struct {
	// IsolationClass is independent of the runtime's (R-114): how isolated a
	// build must be is a different question from how isolated the app must be.
	IsolationClass IsolationClass

	Strategies                []BuildStrategy
	SupportsCache             bool
	SupportsEgressRestriction bool // R-118

	// SupportsPush means the builder can push what it built to a registry,
	// with a credential it is handed per build and keeps nowhere
	// (BuildRequest.Push). A runtime that only pulls from a registry needs a
	// builder with it (RuntimeCapabilities.ImageDelivery).
	SupportsPush bool
}

BuilderCapabilities describes what a builder adapter can do.

func (BuilderCapabilities) Supports

func (c BuilderCapabilities) Supports(strategy BuildStrategy) bool

Supports reports whether the builder advertises a strategy.

type BundleEvent

type BundleEvent struct {
	Kind     BundleEventKind
	BundleID string
	Workload string

	// At is when the runtime says it happened. Zero when it does not say.
	At time.Time
}

BundleEvent is one thing that happened to one workload.

type BundleEventKind

type BundleEventKind string

BundleEventKind is what happened to a bundle's workload.

const (
	// BundleEventWatching: the stream is open, and every change from now on
	// is reported. Anything before it may have been missed. No BundleID.
	BundleEventWatching BundleEventKind = "watching"

	// BundleEventMissed: changes to any bundle may have gone unreported since
	// the last event — part of the stream was lost and is back. No BundleID.
	// Also sent for a change the runtime could not attribute to a bundle.
	BundleEventMissed BundleEventKind = "missed"

	BundleEventExited    BundleEventKind = "exited"
	BundleEventOOMKilled BundleEventKind = "oom_killed"
	BundleEventStarted   BundleEventKind = "started"
	BundleEventRemoved   BundleEventKind = "removed"
	BundleEventHealth    BundleEventKind = "health_changed"
)

type BundleHandle

type BundleHandle struct {
	BundleID string
	Handle   string
}

BundleHandle is the adapter's own identifier for a bundle.

type BundlePlan

type BundlePlan struct {
	// BundleID is stable across deploys of one app.
	BundleID  string
	Workloads []WorkloadPlan
	Volumes   []VolumePlan
	Network   NetworkPlan
	Labels    map[string]string

	// FirstDeploy is true when the app has never had a successful deploy, so
	// nothing of it can be anywhere a runtime cannot see right now. A runtime
	// that places bundles may then place it while part of itself does not
	// answer; for any other bundle it must not, because a copy might already
	// run in the part that is silent (O-46). False is the safe value, and
	// what the reconciler sends.
	FirstDeploy bool
}

BundlePlan is a fully-resolved description of what should exist.

type BundleRef

type BundleRef struct {
	BundleID string
}

BundleRef identifies an app's bundle to an adapter.

type BundleUsage

type BundleUsage struct {
	Workloads []WorkloadUsage
	Volumes   []VolumeUsage
	Reported  time.Time
}

BundleUsage is one reading of what an app's workloads are using (R-245).

type Capacity

type Capacity struct {
	TotalCPUMillis   int
	TotalMemoryBytes int64
	TotalDiskBytes   int64

	// RunningWorkloads is how many workloads are running now, Pando's or
	// not; -1 when the runtime cannot say.
	RunningWorkloads int

	// LargestFit is the CPU and memory of the roomiest single place a new
	// workload could go now, by committed limits. On a runtime spread over
	// several machines the total can have room that no one machine has. Nil
	// when the runtime is one place, where the totals already say it
	// (notes-multi-host-docker-issue-72.md).
	LargestFit *Fit

	// Details is anything else the runtime reports about itself, in its own
	// shape — version, storage driver. Shown as it is, never interpreted:
	// Pando does not own its schema.
	Details map[string]any

	Reported time.Time
}

Capacity is what the adapter reports about itself (R-243): the readings every runtime gives in the same shape, so the console and the planner can read them without knowing which runtime answered, and Details for the rest.

A total of 0 means the runtime does not know it. The planner then does not check that resource, and the console says it is not reported rather than drawing an empty meter.

type Category

type Category string

Category names an adapter category.

const (
	CategoryIdentity Category = "identity"
	CategoryRouting  Category = "routing"
	CategoryBuilder  Category = "builder"
	CategoryRuntime  Category = "runtime"
	CategorySecrets  Category = "secrets"
	CategoryServices Category = "services"
	CategoryNotify   Category = "notify"

	// CategoryBackup is the eighth (R-252), added in phase 9. It reverses a
	// decision recorded in design 03 §8.1 through phase 8; that section now
	// carries the reversal and the reasoning, rather than the change being
	// visible only here.
	CategoryBackup Category = "backup"

	// CategoryScanner is the ninth (R-317), added with the security score.
	// Pando translates a scanner's findings into a number and a policy
	// decision; it does not implement one. See design 09.
	CategoryScanner Category = "scanner"

	// CategoryAI is the tenth (R-258), and the first that is optional in the
	// strong sense: an install with none configured is not degraded, because
	// everything the auction produces is produced either way (R-106).
	// Design 10 has the argument for why it is a category at all.
	CategoryAI Category = "ai"

	// CategorySource is the eleventh (R-091): a connection to a place
	// repositories live, which apps from private repositories are cloned
	// with. See source.go.
	CategorySource Category = "source"

	// CategoryImageRegistry is the twelfth (R-252, issue #153): the registry
	// Pando pushes a build to when the runtime pulls rather than imports. It
	// was install configuration until the providers' differences — which
	// create a repository on push, which mint a password — turned out to be
	// the planner's questions. See imageregistry.go.
	CategoryImageRegistry Category = "image_registry"

	// CategoryAuditSink is the thirteenth (R-252, R-382, issue #129): a
	// destination the audit log is pushed to as it is written — syslog, a
	// SIEM's HTTPS ingest. Built from its row on each delivery pass, like the
	// image registry, so a rotated token is used on whichever replica leads.
	// See auditsink.go and design 12.
	CategoryAuditSink Category = "audit_sink"
)

type CertificateIssue

type CertificateIssue struct {
	// Email is the ACME account's address.
	Email string

	// Challenge is ChallengeHTTP01, answered by Pando's proxy on every
	// hostname it issues for, or ChallengeDNS01 through DNSProvider.
	Challenge string

	// DNSProvider is the DNS-01 provider's code, as Traefik and lego name it,
	// and DNSCredentials its variables. secret.Value so they cannot reach a
	// log line (R-194).
	DNSProvider    string
	DNSCredentials map[string]secret.Value

	// Orders are the certificates wanted. Each is kept under its Name.
	Orders []CertificateOrder
}

CertificateIssue is what a routing adapter asks Pando to issue for its edge, in the terms its settings already use (R-169).

type CertificateOrder

type CertificateOrder struct {
	Name    string
	Domains []string
}

CertificateOrder is one certificate: the name the edge's routes refer to it by, and the names it covers, the first being its subject.

type Condition

type Condition struct {
	Key    string   `json:"key"`
	Values []string `json:"values"`
}

Condition is a setting's value a form checks before showing another.

type Credential

type Credential struct {
	// Username and Password for inline adapters.
	Username string
	Password secret.Value

	// For redirect adapters: the parameters the provider sent back, as query
	// or form values; the Flow its Begin returned; and the endpoints the flow
	// used. Flow is nil for a sign-in the provider started (SAML
	// IdP-initiated), which an adapter refuses unless configured to allow it.
	Callback url.Values
	Flow     []byte
	Endpoints
}

Credential is inbound authentication material.

type DestroyOptions

type DestroyOptions struct {
	// KeepVolumes is the default posture. Volumes outlive the apps that mount
	// them (R-204), and destroying them is a separate, explicit act.
	KeepVolumes bool
}

DestroyOptions controls teardown.

type EdgeCertificate

type EdgeCertificate struct {
	Name    string
	CertPEM []byte
	KeyPEM  secret.Value
}

EdgeCertificate is an issued certificate and its key, as the edge serves it.

type EdgeConfig

type EdgeConfig string

EdgeConfig is one way an edge receives its routes.

const (
	// EdgeConfigSharedMount: a directory Pando writes and the edge mounts
	// (EdgeMount.SharedWithPando). Single-host Docker.
	EdgeConfigSharedMount EdgeConfig = "shared_mount"
	// EdgeConfigKubernetesAPI: objects in the cluster's API that the edge
	// watches — Traefik's IngressRoutes. The Kubernetes runtime.
	EdgeConfigKubernetesAPI EdgeConfig = "kubernetes_api"
)

type EdgeMount

type EdgeMount struct {
	Path string

	// SharedWithPando is a path in Pando's own filesystem whose storage the
	// edge mounts too — the directory the Traefik adapter writes routes into.
	// The runtime resolves what that storage is (R-251); core never learns.
	SharedWithPando string

	// Volume is storage the edge owns, kept when the edge is recreated — an
	// ACME certificate store, so a restart does not re-issue every certificate.
	Volume string

	ReadOnly bool
}

EdgeMount is storage the edge sees at Path. Exactly one of SharedWithPando and Volume is set.

type EdgePlan

type EdgePlan struct {
	// Name is unique within the install. The runtime names what it creates
	// from it and finds it again by it.
	Name string

	Image string
	Args  []string

	// Env reaches the edge's configuration and nowhere else. Credentials —
	// a DNS provider's key, a tunnel token — are secret.Value so they cannot
	// reach a log line on the way (R-194).
	Env map[string]secret.Value

	// Ports are host ports bound to the edge. They are the only ports Pando
	// ever publishes: an app's workloads publish none (R-026).
	Ports []EdgePort

	Mounts []EdgeMount

	// ProxyAlias is the host name the edge dials to reach Pando's proxy — the
	// host part of ProxyUpstream. The runtime makes Pando answer to it on the
	// network it shares with the edge, and joins the edge to nothing else.
	ProxyAlias string

	// ReadsRoutesFrom is how this edge receives its routes, when it reads them
	// from the runtime's API (EdgeConfigKubernetesAPI): the runtime then gives
	// it the identity that may read them and nothing else. Empty for an edge
	// that reads a shared mount or holds its configuration remotely
	// (cloudflared), which is given no API identity at all.
	ReadsRoutesFrom EdgeConfig

	// Issue is the certificates this edge needs Pando to issue, when it does
	// not issue its own (notes-kubernetes-runtime-issue-72.md: on Kubernetes
	// several replicas share one set, so Pando's leader is the one issuer).
	// Nil for an edge that issues its own or serves none.
	Issue *CertificateIssue

	// Certificates are the issued certificates the edge serves, filled in by
	// core from Issue before the plan reaches the runtime. The runtime
	// stores them where the edge reads them, under each one's Name.
	Certificates []EdgeCertificate
}

EdgePlan is the workload a routing adapter needs running.

type EdgePort

type EdgePort struct {
	Host      int
	Container int
	Protocol  string // "tcp" when empty
}

EdgePort publishes one port of the edge on the host.

type EdgeRequest

type EdgeRequest struct {
	// Ref is the adapter configuration's ID. An edge is named for it, so two
	// configured Traefiks would be two edges rather than one fought over.
	Ref string

	// ProxyUpstream is Pando's proxy, as the edge must reach it (R-023) — the
	// same value every RouteRequest carries.
	ProxyUpstream string

	// EdgeConfig is how the runtime that will run the edge lets it receive
	// routes (RuntimeCapabilities.EdgeConfig). Empty means the runtime did not
	// say, which is read as EdgeConfigSharedMount.
	EdgeConfig []EdgeConfig
}

EdgeRequest is what core tells a routing adapter when asking for its edge.

type EdgeState

type EdgeState struct {
	Present bool
	Running bool

	// Detail says what is wrong in words an operator can act on, when Present
	// or Running is false for a reason the runtime can see.
	Detail string
}

EdgeState is an edge as found.

type EgressMode

type EgressMode = spec.EgressMode

RoutingMode and BuildStrategy are the spec's, not the adapter's. An adapter that needed its own vocabulary for these would be translating in the wrong direction (R-251).

type EgressRules

type EgressRules = egress.Rules

EgressRules is what decides whether a workload's connection may leave.

type Endpoints

type Endpoints struct {
	// CallbackURL is the OIDC redirect_uri, or the SAML Assertion Consumer
	// Service URL.
	CallbackURL string

	// EntityID is the SAML service provider's entity ID, which is also the URL
	// its metadata is published at. Unused by OIDC.
	EntityID string
}

Endpoints are where Pando receives a provider's responses.

Core computes them from the install's external URL and hands them over. An adapter never works out its own address: behind a TLS-terminating proxy it would get it wrong, and a redirect URI must match the registered one to the character.

type ExecRequest

type ExecRequest struct {
	Command []string
	TTY     bool
	Env     map[string]string
}

ExecRequest opens a session in a workload.

type ExecSession

type ExecSession interface {
	io.ReadWriteCloser
	Resize(rows, cols uint16) error
	ExitCode() (int, bool)
}

ExecSession is an open exec session.

type Field

type Field struct {
	Key   string `json:"key"`
	Label string `json:"label"`
	Help  string `json:"help,omitempty"`

	// Type is "string", "int", "bool" or "select": how a form should ask for
	// it and how the value goes into the configuration's JSON. A select's value
	// is a string.
	Type string `json:"type"`

	// Options are what a "select" offers.
	Options []Option `json:"options,omitempty"`

	// Other lets a "select" take a value not among Options, typed in — a DNS
	// provider Pando does not name, say.
	Other bool `json:"other,omitempty"`

	// Multiline asks for a text area: a credential given as KEY=value lines.
	Multiline bool `json:"multiline,omitempty"`

	// List asks for any number of values of a "string" setting in one field:
	// each one typed is kept as it is entered, as a form sees fit — the
	// console's a chip. They are sent as one string, separated by commas, so
	// an adapter reads a list the same way whichever surface set it.
	List bool `json:"list,omitempty"`

	// ShownWhen hides the setting unless another setting has one of the
	// given values — DNS provider fields only when certificates use DNS. A
	// form that ignores it still works; it only shows more than it needs to.
	ShownWhen *Condition `json:"shown_when,omitempty"`

	Required bool `json:"required,omitempty"`

	// Advanced marks a setting most people never change: a form asks for it
	// under "Advanced settings" rather than beside the ones that matter, and
	// the CLI lists it under its own heading. It means less common, not
	// restricted — anyone who may configure the adapter may set it. An advanced
	// setting is never required, never a credential, and states its Default,
	// so leaving every one empty sets the adapter up (Validate).
	Advanced bool `json:"advanced,omitempty"`

	// Credential marks a secret such as an API key. It is sent in the create
	// request's write-only "credentials", stored encrypted, and never shown
	// again (R-190) — never in "config", which the database refuses it in.
	Credential bool `json:"credential,omitempty"`

	// Default is the value used when the setting is left empty — the
	// adapter's own default, stated so a form can show it rather than an
	// example that might be mistaken for it.
	Default string `json:"default,omitempty"`

	// Placeholder is an example value, for a setting with no default.
	Placeholder string `json:"placeholder,omitempty"`

	// Presets are the providers the setting is for, by preset ID — a Google
	// Workspace domain for Google only. Guided setup shows it to no one else.
	// Empty is every provider.
	Presets []string `json:"presets,omitempty"`

	// Suggest is the value a form starts a new adapter's setting at, with
	// {key} standing for another setting's value: "pando.{zone}". It follows
	// that setting until the setting is edited, and is then the person's own.
	// Unlike Default it is sent as if typed; the adapter never applies it.
	Suggest string `json:"suggest,omitempty"`
}

Field is one setting.

type FileInfo

type FileInfo struct {
	Name  string
	Size  int64
	IsDir bool
}

FileInfo is what SourceView reports about a path.

type FilePlan

type FilePlan struct {
	Path    string
	Content string
	Mode    int
}

FilePlan is a configuration file to place in the workload before it starts.

Carried in the spec and replayed here rather than fetched from anywhere: the spec is the sole record of how an app runs (R-020). A runtime that cannot place a file says so through its capabilities, and the planner refuses before anything is created (R-254) — a workload started without its configuration is the failure that looks like success.

type Finding

type Finding struct {
	// ID is the scanner's identifier for it: CVE-2024-1234, or the rule that
	// fired. Shown verbatim, and what somebody searches for.
	ID       string   `json:"id"`
	Severity Severity `json:"severity"`
	Title    string   `json:"title"`

	// Target is where it is: the package, the file, the image layer.
	Target string `json:"target"`

	// Fix is the version that fixes it, when the scanner knows one. Empty is
	// common and is not an error — plenty of findings have no fix yet.
	Fix string `json:"fix,omitempty"`
}

Finding is one thing wrong.

type Fit

type Fit struct {
	CPUMillis   int
	MemoryBytes int64
}

Fit is an amount of CPU and memory one place has free.

type GitCredential

type GitCredential struct {
	// URL is the address to clone instead of the one the app names, when
	// the connection reaches it another way — an SSH key for a repository
	// entered as https. Empty means the app's own address. The spec keeps
	// the app's address either way.
	URL string

	// Username and Password are HTTPS basic authentication. A token goes in
	// Password; Username is whatever the host expects beside it
	// ("x-access-token", "oauth2", "x-token-auth", or the account name).
	Username string
	Password secret.Value

	// BearerToken is HTTPS bearer authentication, for a host that takes an
	// OAuth access token as "Authorization: Bearer" rather than as a
	// password — Azure DevOps with a Microsoft Entra ID token.
	BearerToken secret.Value

	// SSHUser, SSHPrivateKey and SSHPassphrase are SSH authentication.
	// SSHUser is "git" unless the host says otherwise.
	SSHUser       string
	SSHPrivateKey secret.Value
	SSHPassphrase secret.Value

	// KnownHosts pins the host's SSH key, in known_hosts format. Required
	// with an SSH key: a connection that would accept any host key is one a
	// network attacker can read the repository through.
	KnownHosts string

	// CABundle is PEM certificates trusted beside the system's for HTTPS,
	// for a self-managed host on a private certificate authority.
	CABundle []byte

	// ExpiresAt is when the credential stops working, when the provider
	// says. Zero when it does not expire or nobody knows.
	ExpiresAt time.Time

	// Rotated is credentials the adapter refreshed while producing this one
	// — an OAuth access token and its new refresh token — keyed like the
	// adapter's credential fields. Core stores them in place of the old ones
	// (R-190) before using the credential. Nil when nothing changed.
	Rotated map[string]secret.Value
}

GitCredential is what core's git client clones with. Exactly one of Password, BearerToken or SSHPrivateKey is set.

type GroupDraft

type GroupDraft struct {
	Name    string   `json:"name"`
	Members []string `json:"members,omitempty"`
}

GroupDraft is a proposed group. Members are account IDs from the request.

type GroupInfo

type GroupInfo struct {
	ID   string `json:"id"`
	Name string `json:"name"`
}

GroupInfo is an existing group.

type HTTPAnswer added in v0.5.0

type HTTPAnswer struct {
	Port int
	// Status is the HTTP status code of the answer to GET /.
	Status int
}

HTTPAnswer is one port's answer to a trial's HTTP request.

type HealthPlan

type HealthPlan struct {
	Command         []string
	Path            string
	Port            int
	IntervalSeconds int
	TimeoutSeconds  int
	Retries         int
}

HealthPlan is how to check a workload.

type HelperSpec

type HelperSpec struct {
	// Args are the pando subcommand and its flags.
	Args []string
	// Env carries what must not appear in a process listing: the database
	// URL. Rendered as [redacted] everywhere but the runtime call (R-194).
	Env map[string]secret.Value
}

HelperSpec is the short-lived workload that replaces Pando (R-359). It runs Pando's current image, so the code doing the replacing is the version already trusted, with Pando's networks, its data and the runtime's socket.

type IdentityAdapter

type IdentityAdapter interface {
	Adapter

	// Begin returns where to send the user, or nil for adapters that
	// authenticate inline, such as local username and password.
	Begin(ctx context.Context, req BeginRequest) (*Redirect, error)

	// Authenticate resolves an inbound callback or credential to a subject.
	Authenticate(ctx context.Context, c Credential) (Subject, error)

	// ServiceMetadata is what the provider is given to trust Pando — SAML
	// service provider metadata. Nil for adapters that have none.
	ServiceMetadata(ctx context.Context, e Endpoints) (*Metadata, error)

	// SessionPolicy is how R-047 is honored mechanically: each adapter declares
	// its own lifetime and revocation mode, so the console can show the real
	// window per adapter instead of implying a global guarantee.
	SessionPolicy() SessionPolicy

	// SupportsPush reports whether the adapter's provider can push changes
	// (SCIM or webhook), which is what makes immediate revocation possible
	// (R-048). Whether an installation has turned that on is core's to know:
	// a provider that can push but has no SCIM token pushes nothing, and core
	// reports its revocation as the session lifetime (R-050).
	SupportsPush() bool
}

IdentityAdapter authenticates. It does not authorize (R-044).

Stateless between calls. A redirect sign-in spans two requests, and what an adapter must remember from the first to check the second — a PKCE verifier and nonce, a SAML request ID — goes back to core as Redirect.Flow and returns in Credential.Flow. Core stores it, binds it to the browser that started the flow, and consumes it once; an adapter never touches state (R-027).

type ImageDelivery

type ImageDelivery string

ImageDelivery is one way a built image reaches a runtime.

const (
	// ImageDeliveryImport streams the image from the builder into the
	// runtime's ImportImage.
	ImageDeliveryImport ImageDelivery = "import"

	// ImageDeliveryRegistry has the builder push the image to the install's
	// registry and the runtime pull it by digest.
	ImageDeliveryRegistry ImageDelivery = "registry"
)

type ImageRegistryAdapter

type ImageRegistryAdapter interface {
	Adapter

	ImageRegistryCapabilities() ImageRegistryCapabilities

	// Target is where one build is pushed, with the credential for that push.
	// An empty workload is the app-wide image. The tag is unique to the
	// deployment, so an image refused after it was built is never mistaken
	// for the one that runs (R-146).
	Target(ctx context.Context, appID, workload, deploymentID string) (PushTarget, error)

	// Owns reports whether an image reference is one this registry holds
	// under Pando's path: a build Pando pushed. Pure: no network.
	Owns(ref string) bool

	// PullAuth is the credential for pulling an image the registry Owns,
	// minted fresh where the provider issues short-lived passwords. Nil for a
	// registry Pando reaches anonymously.
	PullAuth(ctx context.Context) (*RegistryAuth, error)

	// DeleteApp deletes every manifest of a deleted app's builds (R-224).
	// workloads names the app's separately built workloads; the app-wide
	// image is always included. It returns how many manifests were deleted.
	// The layers they referenced are freed by the registry's own garbage
	// collection (O-38).
	DeleteApp(ctx context.Context, appID string, workloads []string) (int, error)

	// Health is HealthCheck with notes: what works but will not do what the
	// operator expects, such as a credential that may push but not delete, so
	// a deleted app's images stay behind. A note never makes it unhealthy.
	Health(ctx context.Context) (notes []string, err error)
}

ImageRegistryAdapter is a registry Pando pushes builds to.

type ImageRegistryCapabilities

type ImageRegistryCapabilities struct {
	// Host is the registry's host and port, for messages.
	Host string `json:"host"`

	// CreatesRepositoriesOnPush says a push to a repository that does not
	// exist creates it. Without it every build goes to one repository the
	// operator created, tagged by app and deployment, and a repository per
	// app is refused when the adapter is configured rather than at the first
	// push.
	CreatesRepositoriesOnPush bool `json:"creates_repositories_on_push"`

	// RepositoryPerApp says each app's builds have a repository of their own,
	// so a deleted app's images are a few listings to delete.
	RepositoryPerApp bool `json:"repository_per_app"`

	// SendsEveryBuild says builds go through the registry even for a runtime
	// that can take one directly, such as Docker on one host. Off by default
	// (O-34).
	SendsEveryBuild bool `json:"sends_every_build"`
}

ImageRegistryCapabilities is what the registry does and how Pando uses it, as data (R-254).

type InUse

type InUse struct {
	CPUMillis   int
	MemoryBytes int64
	Reported    time.Time
}

InUse is what a runtime's workloads are using now, summed (R-245).

type IsolationClass

type IsolationClass = spec.IsolationClass

IsolationClass is ordered, so policy floors can be compared (R-114, R-255).

An integer rather than a string enum precisely because R-024 and R-114 need `got >= floor`. Gaps of 10 leave room to insert a class without a migration.

type KindInfo

type KindInfo struct {
	Category    Category `json:"category"`
	Kind        string   `json:"kind"`
	Name        string   `json:"name"`
	Description string   `json:"description"`
	Fields      []Field  `json:"fields"`

	// IDPrefix is the prefix an instance's ID conventionally takes, such as
	// "ai_" — so a form can suggest "ai_anthropic" rather than ask for one.
	IDPrefix string `json:"id_prefix"`

	// Presets are known providers of this kind — Okta, Microsoft Entra ID —
	// with the settings each needs filled in, so connecting one is choosing it
	// rather than knowing its quirks. A form offers them; the settings are the
	// same ones Fields describes.
	Presets []Preset `json:"presets,omitempty"`

	// Check refuses a configuration as it is saved, in the words Configure
	// would refuse it at startup — a kind that needs no connection to know.
	// It is given the settings, the credentials sent, and the names of those
	// already stored and not sent again. Nil when a kind is only checked at
	// startup.
	Check func(config json.RawMessage, credentials map[string]secret.Value, stored []string) error `json:"-"`

	// Managed marks a kind Pando sets up itself, from its install topology,
	// such as the BuildKit it runs builds on. A form does not offer to add
	// one or to edit the one there is; its settings, all advanced, are set
	// through the API or the CLI.
	Managed bool `json:"managed,omitempty"`
	// OtherPreset names the choice, beside the presets, of a provider none of
	// them is — "Another provider", "Another SIEM". Guided setup asks which
	// provider first when a kind has presets; empty means the presets cover
	// every case, as github.com and GitHub Enterprise Server do. A step for
	// that choice lists PresetOther in its Presets.
	OtherPreset string `json:"other_preset,omitempty"`

	// Steps walk someone through setting one up, one screen at a time: what to
	// do in the provider's own console, then the settings that produces. Every
	// basic setting is asked on exactly one step; advanced ones on none — a
	// form shows them on its review, closed (Validate). Choosing the kind,
	// naming the adapter and reviewing it are the form's own steps, the same
	// for every kind, and are not listed here.
	Steps []Step `json:"steps,omitempty"`

	// Warn says what in a configuration works but may not do what someone
	// wants — a console hostname Cloudflare's included certificate does not
	// cover — each in a sentence that says what to do about it. Never a
	// reason to refuse a configuration: that is the adapter's Configure.
	// Saving one returns its warnings. Nil when a kind has none to give.
	Warn func(config json.RawMessage) []string `json:"-"`
}

KindInfo describes a kind of adapter this build of Pando can run: what it is called, what it is for, and the settings configuring one takes.

Data, like capabilities (R-254). Each adapter package describes itself, and the console and the CLI build their "add an adapter" forms from it — so a kind added to Pando is configurable from every surface without either being taught about it (R-261).

func (KindInfo) Validate

func (k KindInfo) Validate() error

Validate checks what a kind says about its settings that a form relies on.

An advanced setting is one a form puts out of the way, so it must be safe to leave empty: not required, and with its default stated, so someone who never opens "Advanced settings" can still see what they get. Not a credential either: the CLI asks for secrets as it adds an adapter, and asks only for the basic settings.

type Link struct {
	Label string `json:"label"`
	URL   string `json:"url"`
}

Link is a page on the provider's site. Its URL may hold {key} for a setting's value, as Suggest does — https://{host}/settings/apps — so a link follows a self-managed host.

type ListRepositoriesRequest

type ListRepositoriesRequest struct {
	// Query matches part of a repository's full name. Empty lists all.
	Query string

	// Limit caps the result. Zero means the adapter's own default, 100.
	Limit int
}

ListRepositoriesRequest narrows a repository listing.

type LogOptions

type LogOptions struct {
	Follow bool
	Since  time.Time
	Tail   int
}

LogOptions controls a log stream.

type LogRetentionCapability

type LogRetentionCapability struct {
	// SupportsSizeCap means the runtime applies a per-workload byte cap when
	// the workload is created (R-222). False means logs are unbounded, and the
	// planner says so rather than pretending.
	SupportsSizeCap bool

	// CanChangeWithoutRecreate means a cap can be changed on a running
	// workload. False on Docker. When false, a changed cap takes effect on the
	// next deploy — which the console must say, because a setting that appears
	// to apply and does not is worse than one that says "next deploy".
	CanChangeWithoutRecreate bool

	// ReportsUsage means the runtime can say how much log space an app is
	// actually using. False on Docker, which is why R-224 is enforced against
	// committed caps rather than measured bytes.
	ReportsUsage bool

	// MinBytes is the smallest cap the runtime will honor, 0 for none. Docker
	// rounds; a runtime with a hard floor says so here so the planner can
	// refuse a spec asking for less rather than silently granting more.
	MinBytes int64
}

LogRetentionCapability is how a runtime can bound log growth (R-222–R-224).

Capabilities as data (R-254), and this is a case where the honest answer is "partly". Docker can cap a container's log at creation and cannot change it afterwards without recreating the container — and the reconciler may not recreate a container because an unrelated app turned chatty, which is destruction on a schedule. An adapter that says so lets the planner enforce what it can and say plainly what it cannot, instead of Pando promising a guarantee nothing delivers.

That was O-16. The decision recorded here is: cap per app at creation, and enforce R-224's aggregate as a **plan-time bound on the sum of caps** rather than as an observation of usage. Bounding what is committed is stronger than watching what accumulates — if every app's logs are capped and the caps sum under the budget, the total cannot exceed it — and it needs nothing from the runtime beyond the cap it already applies.

type Lookup

type Lookup struct {
	People func(ctx context.Context, query string) ([]PersonInfo, error)
	Apps   func(ctx context.Context, query string) ([]AppInfo, error)
	Groups func(ctx context.Context, query string) ([]GroupInfo, error)
}

Lookup is how an adapter finds the people, apps and groups a request names, rather than being handed every one (O-54). Each function searches by a name, username, email or slug, case-insensitively, and returns at most LookupLimit matches.

Callbacks core passes in, not a store: they run as the person who asked, and return only what that person could read through the API. An adapter sees no more by calling them than its caller could see, and never touches state or authorization itself (R-027). A nil function is a lookup the person may not make, and its tool is not offered.

Only an adapter whose capabilities say LooksUp is given one. Any other is given what core found by searching the words of the request itself (design 10 §10.6).

type Metadata

type Metadata struct {
	ContentType string
	Body        []byte
}

Metadata is a document a provider is given about Pando.

type MountPlan

type MountPlan struct {
	VolumeID string
	Path     string
	ReadOnly bool
}

MountPlan attaches a volume.

type NetworkPlan

type NetworkPlan struct {
	// Private is always true (R-026). It is a field rather than an assumption
	// so an adapter that cannot provide a private network fails loudly at the
	// capability check instead of silently placing workloads on a shared one.
	Private bool

	// Egress is the rules the app's workloads run with, already merged from
	// the installation's and the app's (R-182). A runtime enforces every
	// layer, or says it cannot (SupportsEgressRestriction) and is refused at
	// plan time (R-186). When Egress.Restricted() is false nothing may be put
	// in the app's path: an app with no restriction runs exactly as it would
	// with no egress controls at all.
	Egress EgressRules
}

NetworkPlan describes the bundle's network.

type Notification

type Notification struct {
	// Kind is one of the constants above, or, for a message a subscription
	// sent, the name of the event it is about (R-374).
	Kind       NotificationKind
	AppID      string
	Recipients []Recipient
	Subject    string
	Body       string

	// EventID is set when the notification tells of an event
	// (evt_…), so a receiver can deduplicate a delivery made twice.
	EventID string
	// Fields are the event's data, in order, for an adapter that lays them
	// out — a Slack block, a Teams fact set. Never a secret (R-194).
	Fields []NotificationField
	// Link is where in the console to look, when Pando knows its own address.
	Link string

	// Channel is the channel a subscription chose, for an adapter whose
	// capabilities say Channels; empty for every other.
	Channel string
}

Notification is one message.

type NotificationField

type NotificationField struct {
	Label string
	Value string
}

NotificationField is one labeled value.

type NotificationKind

type NotificationKind string

NotificationKind is why a notification fired.

const (
	NotifyAppFailed       NotificationKind = "app_failed"
	NotifyDeployFailed    NotificationKind = "deploy_failed"
	NotifyPolicyViolation NotificationKind = "policy_violation"
	NotifyBackupFailed    NotificationKind = "backup_failed"

	// NotifyDeployApproval: a deploy is waiting for somebody's approval, or
	// a request somebody made was answered (R-159).
	NotifyDeployApproval NotificationKind = "deploy_approval"
	// NotifyUpdateAvailable: a newer Pando is released (R-362), once per
	// version. NotifyUpgradeFailed: an in-place upgrade did not finish and
	// the previous version was put back, or could not be (R-359).
	NotifyUpdateAvailable NotificationKind = "update_available"
	NotifyUpgradeFailed   NotificationKind = "upgrade_failed"

	// NotifyAppShared: somebody was given use of an app (R-266). Off by
	// default on every channel — the launcher tile is the notification — and
	// a person may turn it on for a channel that reaches them elsewhere.
	NotifyAppShared NotificationKind = "app_shared"
	// NotifySubscriptionDisabled: Pando turned one of your event
	// subscriptions off because its endpoint kept failing (R-370).
	NotifySubscriptionDisabled NotificationKind = "subscription_disabled"
	// NotifyAuditSinkDisabled: Pando turned off an audit sink because it kept
	// failing (R-383). Sent to everybody holding install.audit.export.
	NotifyAuditSinkDisabled NotificationKind = "audit_sink_disabled"
	// NotifyAppIdle: an app nobody uses is about to be stopped or deleted,
	// or was (R-395). Sent to its owner.
	NotifyAppIdle NotificationKind = "app_idle"
	// NotifyAppDisk: an app is near or over its disk limit, or was stopped
	// for staying over it (R-403). Sent to its owner.
	NotifyAppDisk NotificationKind = "app_disk"
	// NotifyAppOrphaned: none of an app's owners is active any more, and
	// Pando stopped it or host policy left it running (R-409). Sent to
	// everyone holding the built-in Administrator role.
	NotifyAppOrphaned NotificationKind = "app_orphaned"
)

type NotifyAdapter

type NotifyAdapter interface {
	Adapter
	Capabilities() NotifyCapabilities
	Notify(ctx context.Context, n Notification) error

	// Channels lists the channels a notification can name, matching query —
	// for a person to pick one per subscription. Only when
	// NotifyCapabilities.Channels; otherwise it returns an error.
	Channels(ctx context.Context, query string) ([]NotifyChannel, error)

	// BeginAuthorization and CompleteAuthorization connect the adapter to
	// its provider — for Slack, creating Pando's app and installing it —
	// when NotifyCapabilities says it can be (WebAuthorization,
	// ManualAuthorization). What CompleteAuthorization returns is stored as
	// the adapter's credentials (R-190). An adapter with nothing to connect
	// returns an error from both.
	BeginAuthorization(ctx context.Context, req AuthorizationRequest) (Authorization, error)
	CompleteAuthorization(ctx context.Context, req AuthorizationCompletion) (AuthorizationResult, error)
}

NotifyAdapter delivers notifications.

type NotifyAudience

type NotifyAudience string

NotifyAudience is who a notify adapter's messages reach.

const (
	// AudiencePeople: the adapter reaches the recipients named on each
	// notification — the console, an email. Pando's own notifications go to
	// these, as each person's preferences allow (R-373).
	AudiencePeople NotifyAudience = "people"
	// AudienceChannel: the adapter posts to one place its configuration
	// names — a Slack channel, a Teams channel, an ntfy topic — and ignores
	// recipients. Only subscriptions send to these, because a message meant
	// for one person must not land in a room (R-373).
	AudienceChannel NotifyAudience = "channel"
)

type NotifyCapabilities

type NotifyCapabilities struct {
	Audience NotifyAudience `json:"audience"`

	// Channels says each subscription names the channel it posts to, from
	// Channels, rather than the adapter posting to one place. A channel
	// adapter only.
	Channels bool `json:"channels,omitempty"`

	// WebAuthorization says the adapter is connected in the browser, from a
	// credential the person gives once (AuthorizationRequest.Grant) and the
	// provider's approval screen. ManualAuthorization says it can instead be
	// connected by hand: a link to set it up at the provider, and the
	// credential that gives pasted back.
	WebAuthorization    bool `json:"web_authorization,omitempty"`
	ManualAuthorization bool `json:"manual_authorization,omitempty"`

	// Authorized says the adapter holds what it needs to send. One that
	// takes authorization and has not had it is false, and sends nothing.
	Authorized bool `json:"authorized"`

	// ManageURL is the provider's page for what connecting created — the
	// Slack app Pando made — or empty. IconSet says Pando gave it Pando's
	// icon; without it, the icon is uploaded on that page by hand.
	ManageURL string `json:"manage_url,omitempty"`
	IconSet   bool   `json:"icon_set,omitempty"`
}

NotifyCapabilities is what a notify adapter can do, as data (R-254).

type NotifyChannel added in v0.11.0

type NotifyChannel struct {
	ID      string `json:"id"`
	Name    string `json:"name"`
	Private bool   `json:"private,omitempty"`
}

NotifyChannel is one channel a notification can be posted to.

type ObservedBundle

type ObservedBundle struct {
	Exists    bool
	Workloads []ObservedWorkload
	Volumes   []ObservedVolume
}

ObservedBundle is what actually exists.

type ObservedVolume

type ObservedVolume struct {
	VolumeID string
	Present  bool
	Handle   string
}

ObservedVolume is a volume as found.

type ObservedWorkload

type ObservedWorkload struct {
	Name         string
	Present      bool
	Running      bool
	ImageDigest  string
	StartedAt    time.Time
	ExitCode     *int
	RestartCount int

	// Healthy is a pointer because "no health signal" and "unhealthy" are
	// different states and must not collapse (R-221). An app with no health
	// check is running, not perpetually degraded.
	Healthy *bool

	// Restarting means the workload is in a restart loop right now.
	//
	// Separate from Running because a runtime can report both at once — Docker
	// does, and a crash-looping container inspects as Running=true,
	// Restarting=true. Without this the reconciler reads a looping app as
	// running and healthy, clears its failure count, and the app never reaches
	// `failed`: every glimpse of it up undoes the progress toward giving up on
	// it. Design 05 §1.1 has always said degraded means "health failing **or**
	// restarting"; this is the signal that carries the second half.
	//
	// A runtime that cannot tell reports false, and the reconciler falls back
	// to noticing that a workload which has restarted before started again
	// moments ago.
	Restarting bool
}

ObservedWorkload is a workload as found.

Carries no environment, deliberately. Reading back resolved environment would require every runtime adapter to handle secret-bearing data, which is exactly what WorkloadPlan.Env's one-way flow avoids. Stale-environment drift is detected state-side by fingerprint instead (design 02 §2.4).

type Option

type Option struct {
	Value string `json:"value"`
	Label string `json:"label"`

	// Description says what choosing it means, for a short list shown as
	// radio buttons where the tradeoff should be visible.
	Description string `json:"description,omitempty"`

	// Recommended marks the choice to make unless there is a reason not to.
	// At most one option of a setting carries it.
	Recommended bool `json:"recommended,omitempty"`
}

Option is one choice a "select" offers.

type PersonInfo

type PersonInfo struct {
	ID string `json:"id"`

	// Username is what the person signs in with, and often the only name an
	// account has: "admin" in a question is this, not a display name.
	Username string `json:"username,omitempty"`
	Name     string `json:"name,omitempty"`
	Email    string `json:"email,omitempty"`
}

PersonInfo is an account, so "Ben Meeker" can be resolved to an ID. No credential and nothing else about the account is included.

type PlanDeclaration

type PlanDeclaration struct {
	// Source is the file that said it, relative to the repository root.
	Source string

	// Why explains the reading in one line. It is shown to a person as
	// evidence, and is held to the same standard as everything else they read.
	Why string

	// Confidence is where this reading sits on R-094's ladder: the bid a
	// detector should make when the plan came from it.
	Confidence float64
}

PlanDeclaration is what in a repository dictated its build plan.

R-094 ranks detection evidence, and the top of that ladder is the app's author saying something rather than Pando inferring it. A builder that planned from a Makefile target, a CI workflow, or a pair of declarations naming one directory knows which it was. Detection wants to say so, and cannot work it out for itself without a second implementation of the same reading — which is the duplication R-027's boundary already prevents by keeping the two packages apart.

Nil means convention-matching chose the plan, which is the bottom rung of the ladder and the usual case.

A definition rather than a mechanism, which is what belongs in this package: the builder fills it in, core reads it, and neither learns the other's vocabulary (R-251).

type PolicyDraft

type PolicyDraft struct {
	Changes map[string]json.RawMessage `json:"changes"`
	Reply   string                     `json:"reply"`
	Model   string                     `json:"model,omitempty"`
}

PolicyDraft is a proposed change to host policy: each field it would set, and the value it would set it to.

type PolicyField

type PolicyField struct {
	Key string `json:"key"`

	// Type is "boolean", "whole number", "list" or "text".
	Type string `json:"type"`

	// Meaning is what the field does, in the words the Policy screen uses, so
	// a request can be matched to the setting a person means.
	Meaning string `json:"meaning,omitempty"`

	// Fixed is set when the startup configuration sets this field (R-271).
	// Core refuses a change to it whatever the model returns; it is included
	// so the model can say so rather than propose it.
	Fixed bool `json:"fixed,omitempty"`
}

PolicyField is one host policy field as a policy draft sees it.

type PolicyRequest

type PolicyRequest struct {
	Description string

	// Current is the effective policy document, startup fields included.
	Current json.RawMessage
	Fields  []PolicyField
	Verbs   []VerbInfo

	// Draft is the document so far, when a person is refining a proposal:
	// Current with the changes already proposed and kept. Description is then
	// the change they asked for, and Changes are relative to Draft.
	Draft json.RawMessage

	Model string
}

PolicyRequest asks for a policy change (R-344).

type PortPlan

type PortPlan struct {
	Number   int
	Protocol string
}

PortPlan is a port to expose within the bundle network.

type Preset

type Preset struct {
	ID    string `json:"id"`
	Label string `json:"label"`

	// Help says where in the provider's own console to find what the form
	// still asks for.
	Help string `json:"help,omitempty"`

	// Values are settings to fill in, keyed like Fields.
	Values map[string]string `json:"values,omitempty"`
}

Preset is a starting configuration for a known provider.

type ProvisionRequest

type ProvisionRequest struct {
	AppID    string
	BundleID string
	SlotKey  string
	Type     spec.SlotType

	// ServiceID is Pando's identifier for this instance, minted by core before
	// the adapter is called.
	//
	// Supplied rather than returned so provisioning is idempotent: a deploy
	// that fails after the adapter answered and before core stored the row can
	// call again with the same ID and get the same names back, instead of
	// leaving an orphaned database nobody references.
	ServiceID string

	// ExistingSecret is the connection string a previous Provision returned for
	// this same service, empty the first time.
	//
	// Every deploy calls Provision, because the workloads it returns are what
	// keeps the service running. So every deploy after the first must produce
	// the *same* credentials: a database sets its password once, when its data
	// directory is created, and ignores the variable forever after. An adapter
	// that generated a fresh password on each call would hand the app a
	// password the database has never heard of, and the symptom — an app that
	// deployed successfully and cannot authenticate — points nowhere near the
	// cause.
	ExistingSecret secret.Value

	// Scheme is the URL scheme the app spells for this service (spec.Slot's
	// Scheme), empty for the adapter's default. An adapter that has only one
	// spelling ignores it.
	Scheme string
}

ProvisionRequest asks for a service instance.

type ProvisionResult

type ProvisionResult struct {
	Handle ServiceHandle

	// ConnectionSecret is the URL or DSN, stored as a secret (R-131).
	ConnectionSecret secret.Value

	// Workloads join the app's private bundle. A provisioned service is not
	// exposed, not addressable from outside, and not shareable with another app
	// (R-134) — sharing is two apps binding to one external target.
	Workloads []WorkloadPlan

	// Volumes the workloads mount. [P], added because a database workload with
	// nowhere to put its files is a database that loses everything on the next
	// deploy, and because R-135 says a provisioned service's data follows the
	// app's volume rules — which it can only do if it is in a Pando volume.
	//
	// The volumes are the app's, recorded and backed up like any other. That
	// is what makes R-135 true rather than aspirational.
	Volumes []VolumePlan
}

ProvisionResult is a provisioned service.

type PushTarget

type PushTarget struct {
	// Repository is the registry host and path, with no tag:
	// registry.internal:5000/pando/apps/app_01hq8.
	Repository string

	// Tag is for people reading the registry. Nothing Pando runs refers to
	// it: what runs is Repository@digest.
	Tag string

	// Auth is the push credential, for this build only. The builder keeps it
	// nowhere — not in its configuration and not on disk (R-194).
	Auth *RegistryAuth

	// Insecure permits plain HTTP to the registry. Only set when the operator
	// said so in the image registry adapter's settings (O-35).
	Insecure bool
}

PushTarget is where a build pushes its image (issue #72, PR 5).

type Question

type Question struct {
	Key     string
	Prompt  string
	Why     string
	Kind    QuestionKind
	Options []string
}

Question is something detection could not determine.

Prompt carries a hard content requirement from R-105: it must be answerable by a model that cannot see the repo, because the expected workflow is pasting it into the assistant that wrote the app. "Which port?" fails review.

type QuestionKind

type QuestionKind string

QuestionKind shapes the answer control in the console.

const (
	QuestionChoice QuestionKind = "choice"
	QuestionText   QuestionKind = "text"
	QuestionPort   QuestionKind = "port"
	QuestionPath   QuestionKind = "path"
)

type Recipient

type Recipient struct {
	UserID string
	Email  string
}

Recipient is who to tell.

type Redirect

type Redirect struct {
	URL string

	// Flow is the adapter's own data for this sign-in, returned to it in
	// Credential.Flow. Opaque to core.
	Flow []byte
}

Redirect sends the user to an external identity provider.

type ReferenceAnswer

type ReferenceAnswer struct {
	Answer string `json:"answer"`

	// Cites are the endpoints, commands and tools the answer relies on, as
	// the reference names them: "POST /api/v1/groups", "pando policy set".
	Cites []string `json:"cites,omitempty"`

	// Covered is false when the reference does not cover the question, and
	// the answer says so rather than guess.
	Covered bool   `json:"covered"`
	Model   string `json:"model,omitempty"`
}

ReferenceAnswer is an answer and what it rests on.

type ReferenceRequest

type ReferenceRequest struct {
	Question string

	// Reference is the API, CLI and MCP reference as GET /reference serves it,
	// in Markdown.
	Reference string

	Model string
}

ReferenceRequest asks a question of the generated reference (R-346).

type Registry

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

Registry holds configured adapter instances.

Registration happens in main at startup, from compiled-in packages (R-253). There is no plugin protocol and none is planned — these are ordinary Go values, and a "registry" here is a map, not a discovery mechanism.

func NewRegistry

func NewRegistry() *Registry

func (*Registry) AI

func (r *Registry) AI(ref string) (AIAdapter, bool)

AI returns an AI adapter by reference (R-258).

func (*Registry) AIAssignments

func (r *Registry) AIAssignments() []AIAssignment

AIAssignments returns every assignment, in AIFunctions order.

func (*Registry) AIFor

func (r *Registry) AIFor(fn AIFunction) (AIAdapter, AIAssignment, bool)

AIFor returns the adapter assigned a function, and the assignment.

Returning found=false is an ordinary outcome, not a failure. R-335: a function nobody is assigned is off, and every caller of this has to be written that way. An assignment naming an adapter that is not running — it failed to configure, or was removed — is off in the same way.

func (*Registry) AuditSink

func (r *Registry) AuditSink(ref string) (AuditSinkAdapter, bool)

AuditSink returns an audit sink adapter by reference (R-382).

func (*Registry) Backup

func (r *Registry) Backup(ref string) (BackupAdapter, bool)

Backup returns a backup adapter by reference (R-217).

func (*Registry) Builder

func (r *Registry) Builder(ref string) (BuilderAdapter, bool)

Builder returns a builder adapter by reference.

func (*Registry) ByCategory

func (r *Registry) ByCategory(c Category) []string

ByCategory returns the references registered in a category.

func (*Registry) Default

func (r *Registry) Default(c Category) (string, bool)

Default returns a category's default reference.

func (*Registry) DefaultScanner

func (r *Registry) DefaultScanner() (ScannerAdapter, string, bool)

DefaultScanner returns the configured scanner, if there is one.

An installation with none has no scores, and a security threshold set on it is inert (R-317). That is a state the console has to be able to report, so "is there a scanner" is a question with an answer rather than a nil check at every call site.

func (*Registry) Get

func (r *Registry) Get(ref string) (Adapter, bool)

Get returns an adapter by reference.

func (*Registry) HealthCheckAll

func (r *Registry) HealthCheckAll(ctx context.Context) map[string]error

HealthCheckAll reports which adapters are unhealthy, by reference.

The planner uses this to refuse to plan against an unhealthy adapter and return ADAPTER_UNAVAILABLE, rather than failing mid-deploy (R-254).

func (*Registry) ImageRegistry

func (r *Registry) ImageRegistry(ref string) (ImageRegistryAdapter, bool)

ImageRegistry returns an image registry adapter by reference (R-252).

func (*Registry) Notify

func (r *Registry) Notify(ref string) (NotifyAdapter, bool)

Notify returns a notification adapter by reference (R-231).

func (*Registry) Refs

func (r *Registry) Refs() []string

Refs returns every registered reference.

func (*Registry) Register

func (r *Registry) Register(ref string, a Adapter) error

Register adds a configured adapter instance under a reference.

The reference is the string the spec names (R-251): rt_docker_local, never Docker arguments.

func (*Registry) Routing

func (r *Registry) Routing(ref string) (RoutingAdapter, bool)

Routing returns a routing adapter by reference.

func (*Registry) Runtime

func (r *Registry) Runtime(ref string) (RuntimeAdapter, bool)

Runtime returns a runtime adapter by reference.

The typed accessors exist so callers do not type-assert at every call site. This is not the capability discovery R-254 forbids — which adapter you have is a different question from what it can do, and the latter is always a Capabilities() call.

func (*Registry) Scanner

func (r *Registry) Scanner(ref string) (ScannerAdapter, bool)

Scanner returns a scanner adapter by reference (R-317).

func (*Registry) Secrets

func (r *Registry) Secrets(ref string) (SecretsAdapter, bool)

Secrets returns a secrets adapter by reference.

func (*Registry) Services

func (r *Registry) Services(ref string) (ServicesAdapter, bool)

Services returns a services adapter by reference (R-131).

func (*Registry) ServicesFor

func (r *Registry) ServicesFor(t spec.SlotType) (ServicesAdapter, string, bool)

ServicesFor returns the configured services adapter that can fill a slot of this type, preferring the category default.

Type-directed rather than ref-directed because a slot says what it needs, not who should supply it: an app declaring REDIS_URL has no opinion about which adapter stands a Redis up, and asking the user to pick one would be asking them to answer a question they cannot have information about.

func (*Registry) SetAIAssignments

func (r *Registry) SetAIAssignments(assignments []AIAssignment)

SetAIAssignments replaces every assignment. Called at startup, and after each change, with what the database and the startup configuration say together; checking an assignment is core's job, not the registry's.

func (*Registry) SetDefault

func (r *Registry) SetDefault(c Category, ref string) error

SetDefault marks a reference as its category's default.

One default per category, matching the unique index on adapter_configs.

type RegistryAuth

type RegistryAuth struct {
	Registry string
	Username string
	Password secret.Value
	// IdentityToken stands in for a password where a registry was signed in
	// to through a browser (a Docker login carried over, issue #41).
	IdentityToken secret.Value
}

RegistryAuth is what one image pull authenticates with, resolved by core from the app's registry credential (issue #41). Short-lived — an ECR password lasts twelve hours — so it travels with each plan and is never kept by the adapter. Nil means an anonymous pull.

type Repository

type Repository struct {
	// URL is the HTTPS address to clone, as a person would enter it.
	URL string `json:"url"`

	// FullName is the name the host shows: "acme/api",
	// "acme/platform/api", "acme/Payments/api".
	FullName string `json:"full_name"`

	DefaultBranch string `json:"default_branch,omitempty"`
	Private       bool   `json:"private"`
}

Repository is one repository a connection can read.

type ResourcePlan

type ResourcePlan struct {
	CPUMillis   int
	MemoryBytes int64

	// DiskBytes is the app's disk limit (R-240), the same for each of its
	// workloads. A runtime that can size a container's own layer caps it
	// there, so no one workload writes past the whole app's limit between
	// readings; what the app holds in aggregate is held to the limit by
	// measurement (R-403), which is the enforcement that applies everywhere.
	// Zero is no cap.
	DiskBytes int64
}

ResourcePlan caps a workload.

type RevocationMode

type RevocationMode string

RevocationMode describes how quickly an adapter can revoke.

const (
	// RevocationPush means the provider notifies Pando, so revocation is
	// immediate (R-048).
	RevocationPush RevocationMode = "push"
	// RevocationRefresh means Pando learns at the next refresh.
	RevocationRefresh RevocationMode = "refresh"
	// RevocationExpiryOnly means access ends when the session expires, and
	// nothing sooner. The console must say so rather than implying otherwise.
	RevocationExpiryOnly RevocationMode = "expiry_only"
)

type RoleDraft

type RoleDraft struct {
	Name  string   `json:"name"`
	Scope string   `json:"scope"`
	Verbs []string `json:"verbs"`
}

RoleDraft is a proposed custom role.

type RoleInfo

type RoleInfo struct {
	ID      string   `json:"id"`
	Name    string   `json:"name"`
	Scope   string   `json:"scope"`
	Builtin bool     `json:"builtin"`
	Verbs   []string `json:"verbs,omitempty"`
}

RoleInfo is an existing role, so a draft does not duplicate one or take a built-in's name (R-082).

type RouteHandle

type RouteHandle struct {
	AppID  string
	Handle string
}

RouteHandle is the adapter's own identifier for a route.

type RouteRequest

type RouteRequest struct {
	AppID      string
	Mode       RoutingMode
	Hostname   string
	PathPrefix string
	Port       int

	// ProxyUpstream is where the adapter must send traffic, and it is always
	// Pando's proxy (R-023). It is in the request rather than discovered by the
	// adapter so the contract is explicit: an adapter is told where to point.
	//
	// The address must be reachable from wherever the adapter's data plane runs
	// — which is not necessarily where Pando runs. A tunnel daemon in another
	// container dials it from there.
	ProxyUpstream string

	TLS TLSRequest
}

RouteRequest tells an adapter where to send traffic.

type RouteState

type RouteState struct {
	Present bool
	Address string
}

RouteState is a route as found.

type RoutingAdapter

type RoutingAdapter interface {
	Adapter
	Capabilities(ctx context.Context) (RoutingCapabilities, error)

	Ensure(ctx context.Context, r RouteRequest) (RouteHandle, error)
	Remove(ctx context.Context, h RouteHandle) error
	Observe(ctx context.Context, h RouteHandle) (RouteState, error)

	// Edge describes the workload this adapter needs running in front of
	// Pando, if any (R-174, edge.go). false means none — loopback, or a
	// Traefik somebody else runs. An adapter may prepare files it owns here,
	// such as the route that sends every other hostname to the console.
	Edge(ctx context.Context, r EdgeRequest) (EdgePlan, bool, error)
}

RoutingAdapter makes traffic arrive at Pando's proxy.

It never routes to the workload (design 00 §1.3). Every method describes intent rather than mechanism, which is why the interface survived being sketched against an outbound-tunnel provider as well as a local reverse proxy — see notes-cloudflare-routing-sketch.md. Adding a Reload() or a ConfigPath here would undo that.

type RoutingCapabilities

type RoutingCapabilities struct {
	Modes []RoutingMode

	// DefaultMode is what an app gets when nobody chooses (R-162). It is what
	// makes an install feel like proxy mode or per-hostname — neither topology
	// is a global setting (design 03 §4.1).
	DefaultMode RoutingMode

	// BaseDomain is what a new app's hostname is carved out of under this
	// adapter — <app>.<base domain> — when the adapter has one: a Cloudflare
	// zone, a Traefik's configured domain. Empty leaves it to the install's
	// server.base_domain. Data, like DefaultMode, so adding an app on
	// Cloudflare names it in the zone without a second setting to keep in step.
	BaseDomain string

	// Domains are every domain this adapter can serve a hostname in, the
	// BaseDomain first: a Cloudflare adapter's zones. Empty means the adapter
	// does not limit where a hostname may be. A hostname outside them is
	// refused when it is chosen rather than when the route is written.
	Domains []string

	// ReservedHostnames are hostnames this adapter serves Pando itself on,
	// such as a Traefik's console hostname. No app may be reached at one:
	// it would receive the console's traffic, on the console's origin.
	ReservedHostnames []string

	SupportsTLS         bool
	SupportsWildcardTLS bool

	// RequiresPublicReachability is false for loopback, and also false for an
	// outbound-tunnel adapter such as Cloudflare, where nothing on the host
	// needs to be reachable from outside.
	RequiresPublicReachability bool
}

RoutingCapabilities describes what a routing adapter can do.

func (RoutingCapabilities) Supports

func (c RoutingCapabilities) Supports(mode RoutingMode) bool

Supports reports whether the routing adapter advertises a mode.

type RoutingMode

type RoutingMode = spec.RoutingMode

RoutingMode and BuildStrategy are the spec's, not the adapter's. An adapter that needed its own vocabulary for these would be translating in the wrong direction (R-251).

type RuntimeAdapter

type RuntimeAdapter interface {
	Adapter
	Capabilities(ctx context.Context) (RuntimeCapabilities, error)

	// Capacity is adapter-reported, never host-inspected (R-243). The local
	// Docker adapter reports its own machine; a clustered adapter reports its
	// cluster. Core does not read /proc and has no concept of a host.
	Capacity(ctx context.Context) (Capacity, error)

	// LargestFitFor is Capacity.LargestFit as it stands for one bundle: the
	// CPU and memory of the roomiest place this bundle may run, counting what
	// the bundle already holds there as free, so a redeploy that fits where
	// it is is never refused. A runtime that keeps a bundle where it was
	// placed answers for that place only. Nil: one place, or not reported,
	// and the planner checks only Capacity's totals (R-242). The planner
	// refuses a bundle larger than this at plan time.
	LargestFitFor(ctx context.Context, bundleID string) (*Fit, error)

	// InUse reports what every workload on the runtime is using right now,
	// summed. Apart from Capacity because sampling CPU takes the runtime about
	// a second, and the planner calls Capacity on every plan without needing
	// it. Only called when ReportsUsage is true.
	InUse(ctx context.Context) (InUse, error)

	// Apply converges the bundle toward the plan. Idempotent: calling it with an
	// already-satisfied plan is a no-op.
	Apply(ctx context.Context, p BundlePlan) (BundleHandle, error)

	// Observe reports what exists. It never remediates — the reconciler decides
	// what to do (design 05). An adapter that silently restarts things makes
	// drift undetectable and breaks R-148's report path.
	Observe(ctx context.Context, ref BundleRef) (ObservedBundle, error)

	// Usage reports what each workload is using right now — CPU, memory and
	// disk — and each volume's size (R-245). A reading, not a history: Pando
	// keeps no time series (R-016). Only called when ReportsUsage is true.
	Usage(ctx context.Context, ref BundleRef) (BundleUsage, error)

	Stop(ctx context.Context, ref BundleRef) error
	Destroy(ctx context.Context, ref BundleRef, opts DestroyOptions) error

	CreateVolume(ctx context.Context, req VolumeRequest) (VolumeHandle, error)
	DestroyVolume(ctx context.Context, h VolumeHandle) error
	SnapshotVolume(ctx context.Context, h VolumeHandle, dst io.Writer) error
	RestoreVolume(ctx context.Context, h VolumeHandle, src io.Reader) error

	// ImportImage takes an image as a stream and makes it runnable, returning
	// the reference to use in a WorkloadPlan. Only called when ImageDelivery
	// includes ImageDeliveryImport.
	//
	// The reference must be immutable: the image's content-addressed ID
	// (sha256:…) rather than a tag. A deployment records it and the reconciler
	// restores from it, and a tag the next build moves would let a build that
	// was refused (by the security scan or the port check) be what a removed
	// workload is recreated from (R-146).
	ImportImage(ctx context.Context, r io.Reader) (string, error)

	// HasImage reports whether an image ImportImage returned is still on the
	// runtime. An imported image lives only in the runtime's own store, and
	// something other than Pando can remove it — a prune — so a deploy that
	// would run one detection built checks first and builds again when it is
	// gone (R-311). A runtime that takes images only from a registry answers
	// true: what it runs is in the registry, not on it.
	HasImage(ctx context.Context, ref string) (bool, error)

	Logs(ctx context.Context, ref WorkloadRef, opts LogOptions) (io.ReadCloser, error)
	Exec(ctx context.Context, ref WorkloadRef, req ExecRequest) (ExecSession, error)

	// Upstream says where Pando's proxy sends a request for one port of one
	// workload (R-023).
	//
	// The runtime answers because it is the runtime that made the workload
	// reachable: it put the workload on a private network and joined Pando to
	// it, or opened whatever path stands in for that. How a workload is
	// addressed is provider vocabulary core must never learn (R-251). The proxy
	// assembled the address itself, as a Docker container name, and any other
	// runtime would have received every request at a host that does not exist.
	//
	// Called on every proxied request. An adapter that can compute the answer
	// computes it rather than asking its provider each time.
	Upstream(ctx context.Context, ref WorkloadRef, port int) (Upstream, error)

	// Trial starts a workload in throwaway isolation and reports what it did
	// (R-097): which ports it bound, what it wrote outside its declared
	// storage, and — if it crashed — the log, which is the whole output when a
	// repository needs something it never declared (R-107).
	//
	// This is on the runtime rather than the builder, though design 03 §3 put
	// ObservedPorts and ObservedWrites on BuildResult. A builder cannot do it:
	// starting a container needs a container runtime socket, and R-112 says the
	// build path never gets one, categorically. Sequence A already had it as
	// its own step after the auction; the field placement was the error.
	//
	// Every runtime implements it, because a runtime that can Apply a bundle
	// can start one and throw it away. What varies is how much it can observe,
	// and that is declared in RuntimeCapabilities rather than discovered by
	// type assertion — a missing capability and a missing adapter must not look
	// alike.
	Trial(ctx context.Context, req TrialRequest) (TrialResult, error)

	// ApplyEdge converges an edge toward its plan: a matching edge is left
	// running, a changed plan recreates it. Only called when SupportsEdge is
	// true. A separate entry point from Apply on purpose (edge.go).
	ApplyEdge(ctx context.Context, p EdgePlan) error
	ObserveEdge(ctx context.Context, name string) (EdgeState, error)
	RemoveEdge(ctx context.Context, name string) error

	// Edges names every edge that exists, so one no routing adapter asks for
	// any more can be removed.
	Edges(ctx context.Context) ([]string, error)

	// EdgeVolumes is the storage edges own — a certificate store — for the
	// full-host backup (R-212). Each handle works with SnapshotVolume and
	// RestoreVolume, and restoring into one that does not exist yet creates it.
	EdgeVolumes(ctx context.Context) ([]VolumeHandle, error)

	// WatchBundles streams what happens to the workloads of Pando's bundles
	// to sink, for as long as ctx lives (O-52). Only called when
	// SupportsBundleEvents is true; a runtime without it returns an error at
	// once.
	//
	// It reports facts and never acts on them, as Observe does not (R-148):
	// what to do about an exited workload is the reconciler's decision.
	//
	// The first thing sent is BundleEventWatching, once the stream is open;
	// every change from then on is sent. A runtime that loses part of its
	// stream and recovers it without returning — one host of several — sends
	// BundleEventMissed after the gap. It returns when ctx ends or the stream
	// is lost; either way, anything after it returned was not seen, and the
	// caller calls it again. sink is called from one goroutine at a time and
	// must not block for long.
	WatchBundles(ctx context.Context, sink func(BundleEvent)) error
}

RuntimeAdapter owns bundles, workloads, volumes, exec, logs, and capacity.

type RuntimeCapabilities

type RuntimeCapabilities struct {
	IsolationClass IsolationClass

	SupportsPersistentVolumes bool
	SupportsExec              bool
	SupportsMultipleWorkloads bool

	// SupportsCarriedFiles is whether the runtime can place a configuration
	// file inside a workload before it starts (spec.File).
	//
	// Data, never a type assertion (R-254): a planner that cannot see the
	// answer cannot produce a readable plan-time error, and an app whose
	// Caddyfile silently did not arrive starts and serves the wrong thing.
	SupportsCarriedFiles bool

	// SupportsPrivateNetwork is required for R-026. An adapter without it is
	// unusable — it would place workloads somewhere other apps could reach.
	SupportsPrivateNetwork bool

	// SupportsResourceLimits is whether the runtime applies the CPU and
	// memory limits every app has (R-240) — applies, not accepts. Docker
	// accepts limits it then drops, on a host whose cgroups cannot hold them
	// (issue #130). False, and ResourceLimitsRemedy says why and what fixes it,
	// in the runtime's own words: the planner refuses with it as the remedy.
	SupportsResourceLimits bool
	ResourceLimitsRemedy   string
	SupportsStartThenSwap  bool // R-145

	// ReportsUsage means Usage works: the runtime can say what each workload
	// is using now (R-245). False, and the console says the runtime does not
	// report it rather than showing zeros.
	ReportsUsage bool

	// LogRetention is what the runtime can do about R-222's size cap.
	LogRetention LogRetentionCapability

	// ImageDelivery is how an image built by Pando can reach this runtime, in
	// the runtime's order of preference (issue #72, PR 5). Empty means it
	// cannot run a built image at all, only a published one.
	//
	// ImageDeliveryImport: the runtime takes the image as bytes (ImportImage).
	// R-111 says Pando "hands it source, receives an image", so on a single
	// host the built image travels back through Pando rather than via a
	// registry — which would otherwise need daemon-level configuration and
	// charge the setup cost R-002 says is paid once (O-34).
	//
	// ImageDeliveryRegistry: the runtime pulls the image by digest from the
	// install's registry, with WorkloadPlan.PullAuth. A runtime spanning
	// machines offers only this: pushing a tarball to every node is the wrong
	// shape.
	//
	// Data, never a type assertion (R-254): the planner turns a runtime, a
	// builder and the install's registry that cannot meet into a readable
	// plan-time refusal rather than a failure after the build has already run.
	ImageDelivery []ImageDelivery

	// EdgeConfig is how an edge on this runtime can receive its routes, for a
	// routing adapter that writes them (notes-kubernetes-runtime-issue-72.md).
	// Core hands it to the routing adapter in EdgeRequest; a routing adapter
	// that can use none of them refuses its edge rather than running one that
	// never learns a route. Data, never a type assertion (R-254).
	EdgeConfig []EdgeConfig

	// SupportsSelfUpgrade means this runtime runs Pando itself and can start
	// the helper that replaces it (R-355, R-359): the adapter implements
	// SelfUpgrader. Data, never a type assertion (R-254), so the Updates
	// screen can say why an in-place upgrade is not possible rather than
	// discovering it halfway.
	SupportsSelfUpgrade bool

	// MaxWorkloadsPerBundle is 0 for unlimited.
	MaxWorkloadsPerBundle int

	// SupportsTrialRun means Trial can start a throwaway workload and report
	// whether it stayed up (R-097). A runtime without it makes detection skip
	// the trial, which turns deferred questions back into real ones rather
	// than into a failure.
	SupportsTrialRun bool

	// SupportsPortObservation means Trial reports ObservedPorts. This is the
	// capability R-097 is really about — "port discovery happens by observing
	// what the process binds, not by asking" — so a runtime without it costs
	// the user a question per app.
	SupportsPortObservation bool

	// SupportsHTTPProbe means Trial also sends each observed port an HTTP
	// request and reports which answered (TrialResult.HTTPAnswers). An image
	// that listens on several ports — epmd beside a web UI, MQTT beside a
	// dashboard, Tomcat's shutdown port beside its connector — is routed to
	// the one that speaks HTTP rather than to whichever was listed first
	// (R-097, R-102). Without it, an empty HTTPAnswers says nothing.
	SupportsHTTPProbe bool

	// SupportsWriteObservation means Trial reports ObservedWrites, which is
	// what lets the persistence warning name the directory (R-202) instead of
	// being generic (R-201).
	SupportsWriteObservation bool

	// SupportsEdge means the runtime can run an edge — the install-scoped
	// workload a routing adapter puts in front of Pando (R-174, edge.go). A
	// routing adapter that needs one is refused a start on a runtime without
	// it, rather than configured and silently unreachable.
	SupportsEdge bool

	// SupportsEgressRestriction means the runtime enforces NetworkPlan.Egress
	// when it restricts anything (R-186). Without it, a plan whose rules
	// refuse something is refused, never deployed with them ignored.
	SupportsEgressRestriction bool

	// SupportsBundleEvents means WatchBundles reports what happens to bundles
	// as it happens: a workload exiting, being killed for memory, starting,
	// being removed, changing health (O-52). The reconciler then visits an app
	// the moment something happens to it and looks at a settled app only on a
	// slow sweep. Without it every app is looked at on every pass, as before.
	// Data, never a type assertion (R-254).
	SupportsBundleEvents bool

	// Platform is the operating system and CPU architecture the runtime runs
	// images for, as a registry names them: linux/amd64, linux/arm64. Empty
	// when the runtime cannot say, and then an image's platforms are not
	// checked at plan time (issue #41).
	Platform string

	// RegistryMirrors are the mirrors the runtime pulls Docker Hub images
	// through, as URLs (https://mirror.gcr.io, http://10.0.0.5:5000), in the
	// order it tries them. Pando's own registry reads go the same way, so an
	// install with a mirror does not spend Docker Hub's download limit on
	// reading an image the runtime will fetch from the mirror anyway. Empty
	// when the runtime has none or cannot say.
	RegistryMirrors []string
}

RuntimeCapabilities describes what a runtime adapter can do.

func (RuntimeCapabilities) Delivers

func (c RuntimeCapabilities) Delivers(d ImageDelivery) bool

Delivers reports whether the runtime takes a built image this way.

type ScanRequest

type ScanRequest struct {
	AppID  string
	SpecID string

	// Commit is the source commit SourceDir holds, recorded with the scan so
	// a deploy of the same commit uses it rather than scanning again.
	Commit string

	// Image is what the build produced, in the local runtime. Empty for an app
	// that has never been built.
	Image string

	// PullAuth fetches Image from the install's registry when the build was
	// pushed there rather than imported (issue #72, PR 5). Nil for an image
	// already on the scanner's host.
	PullAuth *RegistryAuth

	// SourceDir is a checkout of the app's source, for scanners that read the
	// tree rather than the image. Empty when there is none.
	SourceDir string
}

ScanRequest is what to look at.

An image and a directory, and deliberately nothing else. The scanner is given no secrets, no volumes and no network of the app's — a component whose job is to read untrusted code is the last one that should hold anything.

type ScanResult

type ScanResult struct {
	Findings []Finding

	// Scanner names what produced this, with its version. It is stored with the
	// scan and shown beside the score, because "scanned" is not a fact on its
	// own — by what, and how long ago, is the rest of it.
	Scanner string

	Ran time.Time
}

ScanResult is what a scanner found.

type ScannerAdapter

type ScannerAdapter interface {
	Adapter
	Scan(ctx context.Context, req ScanRequest) (ScanResult, error)
	ScannerCapabilities() ScannerCapabilities
}

ScannerAdapter scans what an app deploys.

type ScannerCapabilities

type ScannerCapabilities struct {
	// ScansImages is whether it can read a built image.
	ScansImages bool
	// ScansSource is whether it can read a source tree.
	ScansSource bool
}

ScannerCapabilities is what a scanner can look at.

Data, never a type assertion (R-254): the planner decides whether a scan is possible from this, and an adapter that cannot read an image says so here rather than by failing when asked.

type ScreenBudget

type ScreenBudget struct {
	MaxFiles int
	MaxBytes int64
	Timeout  time.Duration
}

ScreenBudget bounds a screening in files, bytes and wall clock (R-339).

type ScreenRequest

type ScreenRequest struct {
	// Source is read-only, structurally (R-020). Handing it over grants reading
	// and nothing else — there is no write method to withhold.
	//
	// Not a digest core assembled: the value of screening is in the files
	// Pando's detectors do not read, and core cannot pre-select those without
	// already knowing what it is looking for, which is the problem.
	Source SourceView

	// Spec is the proposal, complete, with the install's defaults applied. A
	// screener that can see Routing.Mode can say something useful about an app
	// served under a path prefix; one handed a bare draft cannot.
	Spec spec.AppSpec

	// Evidence is why the winning detector bid what it did (R-102).
	Evidence []string

	// Questions are what detection could not work out. A screener may answer
	// one, and that is an amendment like any other (R-338).
	Questions []Question

	// Trial is what the trial run saw, including the app's own output.
	//
	// R-107's repository is not rescued by this — nothing in it says it needs
	// Postgres — but on an app that failed to start the log is the single most
	// informative thing Pando has, and withholding it from the one component
	// positioned to read it would be withholding it for no reason. What it may
	// produce is bounded by the amendment set, not by keeping it secret.
	Trial TrialSummary

	// Instruction is what a person reviewing the plan asked for, for
	// RevisePlan. Empty for the other functions.
	Instruction string

	// Conversation is what the person and the adapter said before
	// Instruction, oldest first, so "no, the other one" means something.
	Conversation []Turn

	// Values are variables the deploy waits on that nobody has set (R-132),
	// in any reading of the repository. An adapter may fill one with
	// AmendSetEnv where the repository or the plan's own address settles it —
	// the app's domain, a mailto: contact — and never with a secret it made up.
	Values []string

	// Known are files an adapter already read about this proposal, in an
	// earlier call. Each call is a new conversation with the model, so it
	// starts knowing nothing; an adapter may hand these over at the start
	// rather than have the model find and read them again, one round trip at
	// a time. They count against Budget like any other read.
	Known []string

	Budget ScreenBudget

	// Model is the model this call runs on, from the function's assignment.
	// Empty means the adapter's own (R-259).
	Model string
}

ScreenRequest is a finished proposal, and the repository it came from. Both functions take it; which one is called is the whole difference.

type ScreenResult

type ScreenResult struct {
	Amendments []Amendment

	// Notes are worth saying and change nothing. They are shown in the review
	// and never become warnings — a warning that is a remark teaches people to
	// ignore warnings.
	Notes []string

	// FilesRead is what left the host, recorded in the audit event (R-337).
	//
	// Not a security boundary: an adapter that lied about it has already read
	// the file. It is the operator's record of what was sent, which is the
	// thing somebody wants after the fact and cannot reconstruct.
	FilesRead []string

	Model string

	// Reply is the adapter's answer to a person, for RevisePlan: what it
	// changed and why, or why it changed nothing. Held to R-105 like any
	// reason.
	Reply string
}

ScreenResult is what a screening produced.

type SecretRef

type SecretRef struct {
	AppID string
	Key   string
}

SecretRef names a secret within an app.

type SecretsAdapter

type SecretsAdapter interface {
	Adapter
	Put(ctx context.Context, ref SecretRef, v secret.Value) (StoredRef, error)
	Get(ctx context.Context, ref StoredRef) (secret.Value, error)
	Delete(ctx context.Context, ref StoredRef) error
}

SecretsAdapter stores secret values.

type SelfUpgrader

type SelfUpgrader interface {
	// Self reports Pando's own workload, or an error saying why Pando is not
	// running on this runtime.
	Self(ctx context.Context) (SelfWorkload, error)

	// PullImage fetches an image by digest, so a missing or unreachable image
	// fails while Pando is still running rather than after it has stopped.
	PullImage(ctx context.Context, ref string) error

	// StartHelper starts the helper and returns its ID. Pando is stopped by
	// the helper shortly after this returns.
	StartHelper(ctx context.Context, spec HelperSpec) (string, error)

	// RemoveHelpers removes helpers that have finished, once the Pando that
	// started next has read what they recorded.
	RemoveHelpers(ctx context.Context) error
}

SelfUpgrader is implemented by a runtime adapter that reports SupportsSelfUpgrade. It does nothing to Pando's state: the database copy and its restore are core's, done by the helper (R-027).

type SelfWorkload

type SelfWorkload struct {
	// ID identifies the workload to the runtime.
	ID string
	// Image is the image reference it was started from, as its deployment
	// configuration names it: trypando/pando:latest, trypando/pando:0.3.1,
	// trypando/pando@sha256:…. Whether that is a moving tag decides whether
	// an in-place upgrade is possible.
	Image string
}

SelfWorkload is how Pando's own workload runs (R-355).

type ServiceHandle

type ServiceHandle struct {
	ServiceID string
	Handle    string
}

ServiceHandle is the adapter's own identifier for a provisioned service.

type ServicesAdapter

type ServicesAdapter interface {
	Adapter
	Capabilities() ServicesCapabilities
	Supports() []spec.SlotType
	Provision(ctx context.Context, req ProvisionRequest) (ProvisionResult, error)
	Destroy(ctx context.Context, h ServiceHandle) error
	Snapshot(ctx context.Context, h ServiceHandle, dst io.Writer) error
	Restore(ctx context.Context, h ServiceHandle, src io.Reader) error
}

ServicesAdapter fills provisioned slots (R-131).

Provision must be pure and cheap. It is called on every deploy — the workloads it returns *are* the service, so a deploy that skipped it would produce a bundle with no database in it — and again on every reconcile, to work out what should be running. An implementation that talks to a provider here would be talking to it every fifteen seconds for every app.

The interface is shaped for that: Provision returns plans, and the runtime adapter is what creates anything.

type ServicesCapabilities

type ServicesCapabilities struct {
	// DataInAppVolumes says the service's data lives in volumes returned from
	// Provision, which Pando owns and already backs up.
	//
	// This is the difference between a service that runs inside the app's
	// bundle and one that lives somewhere Pando can only reach over the wire.
	// It decides how a DR bundle captures the service (R-212): a true here
	// means the volume snapshots already contain it and calling Snapshot would
	// copy the same bytes twice; a false means Snapshot is the only way to get
	// the data, and a bundle taken without calling it silently omits every
	// provisioned database in the install.
	//
	// Data, not a type assertion, so the backup path can state in its manifest
	// which services it captured and how.
	DataInAppVolumes bool
}

ServicesCapabilities is what a services adapter can do, as data (R-254).

type SessionPolicy

type SessionPolicy struct {
	MaxLifetime     time.Duration
	RevocationMode  RevocationMode
	RefreshInterval time.Duration
}

SessionPolicy is an adapter's own session behavior (R-047).

type Severity

type Severity string

Severity is how much a finding costs.

const (
	SeverityCritical Severity = "critical"
	SeverityHigh     Severity = "high"
	SeverityMedium   Severity = "medium"
	SeverityLow      Severity = "low"

	// SeverityUnknown is a finding the scanner reported without deciding how
	// bad it is. It scores as low and is shown as unknown: inventing a severity
	// is how a score stops meaning anything.
	SeverityUnknown Severity = "unknown"
)

type SourceAdapter

type SourceAdapter interface {
	Adapter

	SourceCapabilities() SourceCapabilities

	// Covers reports whether this connection is for the repository at repoURL,
	// and how specifically. Zero means it is not. A connection limited to an
	// owner, group, workspace or project scores higher than one for the whole
	// host, and core uses the highest scoring connection that covers a URL —
	// so "acme on github.com with an App" wins over "github.com with a token"
	// for acme's repositories and only for them.
	//
	// Pure: no network, no clock. It is called for every connection on every
	// app create and fetch.
	Covers(repoURL string) int

	// GitCredential is what to clone repoURL with. Minted here when the
	// provider issues short-lived tokens (a GitHub App installation token,
	// an Entra ID token for a service principal), refreshed here when an
	// OAuth token has expired.
	GitCredential(ctx context.Context, repoURL string) (GitCredential, error)

	// Refresh renews an OAuth access token that has expired or is about to,
	// and returns the credentials to store in place of the old ones, keyed
	// like the adapter's credential fields. Nil when nothing changed — every
	// method but oauth, and an oauth token still good. Core calls it before
	// ListRepositories and ListBranches and configures the connection again
	// with what it stored, because a provider that rotates refresh tokens
	// (GitLab) invalidates the old one the moment it is used: a listing that
	// refreshed on its own would lose the new one.
	Refresh(ctx context.Context) (map[string]secret.Value, error)

	// ListRepositories lists repositories the connection can read, for a
	// person to pick from rather than type. Only when
	// SourceCapabilities.ListRepositories; otherwise it returns an error saying
	// to enter the address instead.
	ListRepositories(ctx context.Context, req ListRepositoriesRequest) ([]Repository, error)

	// ListBranches lists a repository's branches, under the same capability.
	ListBranches(ctx context.Context, repoURL string) ([]string, error)

	// BeginAuthorization starts an OAuth authorization, for a connection
	// whose method is oauth, or the creation and installation of a GitHub
	// App. Device mode needs no address Pando can be reached at; web mode
	// returns a URL or a form to send the person to, which comes back to
	// req.RedirectURL.
	BeginAuthorization(ctx context.Context, req AuthorizationRequest) (Authorization, error)

	// CompleteAuthorization finishes one: polls the device code, exchanges
	// the web code, or checks an installation (AuthorizationInstall). A
	// device authorization the person has not approved yet returns a result
	// with Pending set and no error.
	CompleteAuthorization(ctx context.Context, req AuthorizationCompletion) (AuthorizationResult, error)
}

SourceAdapter is one connection to a source host.

type SourceCapabilities

type SourceCapabilities struct {
	// Method is how the connection authenticates: "token", "ssh", "app",
	// "oauth" or "service_principal". Shown, never branched on in core.
	Method string `json:"method"`

	// Host is the host the connection is for, such as "github.com" or
	// "gitlab.acme.internal". Shown beside the connection.
	Host string `json:"host"`

	// Scope is what on the host it is limited to — an owner, group,
	// workspace, organization or project — or empty for the whole host.
	Scope string `json:"scope,omitempty"`

	// ListRepositories says repositories can be picked from a list. A deploy
	// key or a bare git credential has no API access, and the address is
	// typed instead.
	ListRepositories bool `json:"list_repositories"`

	// DeviceAuthorization and WebAuthorization say which OAuth flows the
	// connection can run. Both false for every method but oauth, and for a
	// GitHub App, whose creation and installation run in the browser.
	DeviceAuthorization bool `json:"device_authorization"`
	WebAuthorization    bool `json:"web_authorization"`

	// Webhook says the connection has a webhook of its own the host sends
	// pushes to, signed with a secret the connection holds under
	// CredentialWebhookSecret (R-142): a GitHub App created with its push
	// webhook. Pando then checks the apps it reads without waiting for the
	// next poll.
	Webhook bool `json:"webhook"`

	// ManageURL is the provider's page where what the connection created is
	// managed — the settings of the GitHub App Pando created, where its logo
	// is uploaded and its repositories changed — or empty.
	ManageURL string `json:"manage_url,omitempty"`

	// Authorized says the connection holds what it needs to clone: a token,
	// a key, or a completed OAuth authorization. An oauth connection that
	// has not been authorized yet is false, and covers nothing.
	Authorized bool `json:"authorized"`
}

SourceCapabilities is what a connection can do, as data (R-254).

type SourceView

type SourceView interface {
	Open(name string) (io.ReadCloser, error)
	Stat(name string) (FileInfo, error)
	Glob(pattern string) ([]string, error)
}

SourceView is a read-only view of an app's source (R-020).

It has no write methods, structurally. Pando looks at the source and never asks it for permission.

type Step added in v0.10.0

type Step struct {
	// ID names the step for a form's progress and its "change" links.
	ID string `json:"id"`

	// Label is the step's name in a form's list of steps, a word or two:
	// "API token", "Domain".
	Label string `json:"label"`

	// Title is an instruction or a question, in sentence case: "Create an
	// API token", "Which domain should apps use?".
	Title string `json:"title"`

	// Lede is one sentence on why the step is asked, shown under the title.
	Lede string `json:"lede,omitempty"`

	// Do is what to do outside Pando, in order, one action to an item, in the
	// provider's own words. **Bold** marks the name of something on the
	// provider's screen and `backticks` a command, path or value typed
	// exactly; nothing else is markup. {key} stands for a setting's value, or
	// for one of the Refs the form supplies, such as {callback_url}.
	Do []Action `json:"do,omitempty"`

	// Links open the provider's pages the step starts on: usually one, and
	// one per way when there is more than one way to do it, as there is to
	// make a Cloudflare token.
	Links []Link `json:"links,omitempty"`

	// Fields are the keys of the settings asked on this step, in order.
	Fields []string `json:"fields"`

	// ShownWhen skips the step unless a setting asked on an earlier step has
	// one of the given values — the GitHub App steps only for a GitHub App.
	ShownWhen *Condition `json:"shown_when,omitempty"`

	// Presets are the providers the step is for, by preset ID, and
	// PresetOther for the choice of one no preset names: Splunk's token
	// instructions for Splunk only. Empty is every provider.
	Presets []string `json:"presets,omitempty"`
}

Step is one screen of guided setup.

type StoredBundle

type StoredBundle struct {
	Name      string
	SizeBytes int64
	StoredAt  time.Time
}

StoredBundle is one bundle as the destination sees it.

Deliberately thin: the destination knows a name, a size and a time. Anything about what is *inside* comes from the manifest, which is Pando's (R-215).

type StoredRef

type StoredRef struct {
	AppID      string
	Key        string
	Handle     string
	Ciphertext []byte
}

StoredRef is the adapter's own pointer to a stored secret.

type Subject

type Subject struct {
	// ExternalID is stable within this adapter. It is not what the rest of
	// Pando uses — users.id is (R-054).
	ExternalID  string
	Email       string
	DisplayName string
	Groups      []string

	// Username is the name the provider knows the person by, when it says.
	Username string

	// EmailVerified is the provider vouching that the email belongs to this
	// person. Linking by email is only ever done on it (O-1): an email a person
	// can set for themselves is a way into somebody else's account.
	EmailVerified bool

	// Attributes are the claims or attributes the provider sent, for an
	// administrator's test sign-in to show. Nothing reads them to decide
	// anything.
	Attributes map[string][]string

	// OneTimeID is an identifier the provider promises is used once — a SAML
	// assertion ID — valid until OneTimeUntil. Core refuses a second use,
	// which is what stops a captured response from being replayed.
	OneTimeID    string
	OneTimeUntil time.Time
}

Subject is who the adapter says this is.

It carries no roles, no verbs, and no permissions. Group *names* cross this boundary; what a group can *do* is Pando's (R-078).

type TLSRequest

type TLSRequest struct {
	Enabled  bool
	Hostname string
}

TLSRequest is advisory.

An adapter may satisfy it however it likes, or ignore it because its edge already terminates TLS. It is an intent, not a set of instructions — issuance is a per-adapter concern (O-5).

type Table added in v0.10.0

type Table struct {
	Columns []string   `json:"columns"`
	Rows    [][]string `json:"rows"`
}

Table is a small grid of plain text.

type TrialRequest

type TrialRequest struct {
	// TrialID names the throwaway bundle, so a trial that is interrupted leaves
	// something identifiable to clean up rather than an anonymous container.
	TrialID string

	Image      string
	Command    []string
	Entrypoint []string
	WorkingDir string
	Env        map[string]secret.Value

	// PullAuth authenticates the pull of a private image. Nil is anonymous.
	PullAuth *RegistryAuth

	// DeclaredPaths are the container paths the draft spec declares storage
	// for. Writes underneath them are expected; a write anywhere else is what
	// R-202 asks to have named in the persistence warning.
	DeclaredPaths []string

	// Timeout bounds the observation. An app that is still running when it
	// expires has passed: it started and stayed up, which is what was being
	// checked.
	Timeout        time.Duration
	IsolationFloor IsolationClass

	// Egress is the rules the trial is held to, resolved by core against host
	// policy as a deploy's are (R-097, R-182). A trial runs code nobody has
	// reviewed, so where these restrict anything the runtime enforces them as
	// it does for a workload, and a runtime that cannot refuses the trial
	// rather than run it with the rules ignored (R-186). Zero restricts
	// nothing, and the trial runs as an unrestricted app does.
	Egress EgressRules

	LogSink io.Writer
}

TrialRequest asks a runtime to start something once and watch it.

type TrialResult

type TrialResult struct {
	// Started means the workload reached a running state at all. A workload
	// that never started and one that started and exited are different
	// situations: the first is usually a bad image or command, the second is
	// usually a missing dependency (R-107).
	Started bool

	// ExitCode is nil while the workload was still running when observation
	// ended — which is the healthy outcome, not a missing value.
	ExitCode *int

	// ObservedPorts carry Source "observed" into the spec, which is the
	// distinction the review UI shows (R-097): watched, not guessed.
	ObservedPorts []int

	// LoopbackPorts are ports the app bound to 127.0.0.1 rather than to an
	// address traffic can arrive on.
	//
	// Separate from ObservedPorts because routing to one would not work, and
	// reporting none at all would be misleading. An app listening only on
	// loopback is a common and quiet failure — it is a dev-server default, the
	// container reports itself healthy, and every request times out with
	// nothing in the log to say why.
	LoopbackPorts []int

	// HTTPAnswers are the ObservedPorts that answered an HTTP request, with
	// the status each returned, in port order. Only meaningful on a runtime
	// with SupportsHTTPProbe: listening is not serving, and a port that
	// carries a database's or a broker's protocol answers a browser with
	// nothing at all.
	HTTPAnswers []HTTPAnswer

	// HTTPProbedPorts are the ObservedPorts the runtime actually asked over
	// HTTP: every one that answered, and each that was asked repeatedly and
	// never did. A probe that could not run — its helper image not pulled,
	// the deadline reached first — asks nothing, and an empty HTTPAnswers
	// then says nothing either. A port in this list and not in HTTPAnswers
	// does not serve HTTP.
	HTTPProbedPorts []int

	// ObservedWrites are directories written outside DeclaredPaths (R-202).
	ObservedWrites []string

	// ImageVolumes are the paths the image itself declares as persistent
	// storage (a Dockerfile's VOLUME), sorted. The image's author saying where
	// data lives is the same declaration a compose volume is (R-200).
	ImageVolumes []string

	// Log is captured whether or not the trial crashed, because on a crash it
	// is the entire answer Pando has and R-107 says showing it and stopping is
	// the correct outcome rather than a gap to close with inference.
	Log string
}

TrialResult is what the runtime saw.

type TrialSummary

type TrialSummary struct {
	Ran            bool
	Crashed        bool
	ObservedPorts  []int
	ObservedWrites []string
	Log            string
}

TrialSummary is the trial run's output (R-097), as a screener sees it.

type Turn

type Turn struct {
	// From is "person" or "ai".
	From string `json:"from"`
	Text string `json:"text"`
}

Turn is one message in a conversation about a plan, from a person or from the AI adapter.

type Upstream

type Upstream struct {
	// URL is the scheme, host and port the proxy forwards to, such as
	// "http://pando-app_01HQ8-web:3000". No path: the proxy keeps the request's.
	URL string

	// Dial opens the connection the proxy sends a request over. Nil: the proxy
	// dials URL's host itself, as on one Docker host.
	//
	// Set by a runtime whose workloads are not on a network Pando's container
	// is joined to — the multi-host Docker adapter, whose Dial connects to the
	// forwarding agent on the app's host (O-45, design 06 §4). The proxy has
	// already decided the request by then; Dial is transport only. Supplied by
	// the runtime so the agent's protocol stays the adapter's vocabulary
	// (R-251).
	Dial func(ctx context.Context) (net.Conn, error)

	// PoolKey groups connections the proxy may reuse: one key, one
	// destination, so a connection opened by one Dial is only reused for a
	// request this Upstream would have dialed the same way. Empty is URL's
	// host. Only read when Dial is set.
	PoolKey string
}

Upstream is how Pando's proxy reaches a workload.

A struct rather than a string so that a runtime whose workloads are not directly addressable — a remote host reached through a tunnel it opens — can answer here later without the interface changing again.

type VerbInfo

type VerbInfo struct {
	Name string `json:"name"`

	// Scope is "install" or "app". A role holds verbs of one scope (R-080).
	Scope string `json:"scope"`

	Meaning string `json:"meaning,omitempty"`
}

VerbInfo is one verb an access or policy draft may name.

type VolumeHandle

type VolumeHandle struct {
	VolumeID string
	Handle   string
}

VolumeHandle is the adapter's own identifier for a volume.

type VolumePlan

type VolumePlan struct {
	VolumeID string
	Name     string
}

VolumePlan is a volume the bundle needs.

type VolumeRequest

type VolumeRequest struct {
	VolumeID  string
	BundleID  string
	Name      string
	SizeBytes int64
}

VolumeRequest asks for storage.

type VolumeUsage

type VolumeUsage struct {
	VolumeID string
	Bytes    int64
}

VolumeUsage is how much a volume holds, -1 when the runtime cannot say.

type WorkloadPlan

type WorkloadPlan struct {
	Name       string
	Image      string
	Command    []string
	Entrypoint []string
	WorkingDir string

	// Env arrives fully resolved: slots filled, secrets injected. Adapters
	// never see a slot, never talk to the secrets adapter, and never learn that
	// a value was sensitive. That keeps R-027's boundary intact and the
	// interface small.
	Env map[string]secret.Value

	// PullAuth authenticates the pull of Image when it is private. Nil is an
	// anonymous pull. Never placed in the workload: it is how the runtime
	// fetches the image, not something the app is given.
	PullAuth *RegistryAuth

	Mounts    []MountPlan
	Files     []FilePlan
	Ports     []PortPlan
	DependsOn []string

	// Aliases are other names the workload is reachable by on the bundle's
	// private network, beside Name. Nothing outside the bundle resolves them.
	Aliases []string

	Health    *HealthPlan
	Resources ResourcePlan

	// LogBytes caps this workload's logs (R-222, R-223). Zero means the
	// runtime's own default, which is usually unbounded — so core always sets
	// it from the spec rather than leaving it to chance.
	LogBytes int64
	Exposed  bool
}

WorkloadPlan is one workload, fully resolved.

type WorkloadRef

type WorkloadRef struct {
	BundleID string
	Workload string
}

WorkloadRef identifies one workload within a bundle.

type WorkloadUsage

type WorkloadUsage struct {
	Workload string
	Running  bool

	// CPUMillis is thousandths of a core, averaged over the adapter's sample.
	CPUMillis      int
	CPULimitMillis int

	MemoryBytes      int64
	MemoryLimitBytes int64

	// DiskBytes is what the workload has written outside its volumes, -1 when
	// the runtime cannot say.
	DiskBytes int64
}

WorkloadUsage is one workload's use, beside its limits. A limit of 0 means none: the workload may use what the host has.

Jump to

Keyboard shortcuts

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