spec

package
v3.0.0-m.8 Latest Latest
Warning

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

Go to latest
Published: Oct 2, 2026 License: Apache-2.0 Imports: 20 Imported by: 0

Documentation

Overview

Package spec defines the kit v3 descriptor: the declaration half of a kit, published as a manifest annotation on an ordinary OCI image whose layers are the kit's content.

The descriptor deliberately carries no top-level identity name, image reference, or runtime config (entrypoint, env, workdir, user, default command): identity is the reference a kit is consumed by, capability names are display labels, and runtime config lives in the image config, set by the companion Dockerfile.

Decode and Validate read a descriptor. Merge folds an ordered set of them into the one descriptor a composition publishes. Compose reconciles selected runtime declarations. SelectCapabilities evaluates atomic groups using the runtime's callback; its callers retain selected inputs for handlers that need per-Kit provenance. Ordering the set is resolve; reading the descriptors out of a registry is fetch; folding the images is assemble.

Index

Examples

Constants

View Source
const (
	OCIAnnotationTitle       = "org.opencontainers.image.title"
	OCIAnnotationDescription = "org.opencontainers.image.description"
	OCIAnnotationAuthors     = "org.opencontainers.image.authors"
	OCIAnnotationSource      = "org.opencontainers.image.source"
	OCIAnnotationLicenses    = "org.opencontainers.image.licenses"
	OCIAnnotationVersion     = "org.opencontainers.image.version"
)

Standard OCI image annotation keys the frontend populates from the descriptor, so generic registry tooling displays a kit's metadata without knowing the kit grammar. All of them are display metadata with exactly the descriptor fields' authority: self-asserted, never trust inputs.

View Source
const (
	DpkgStatusPath   = "/var/lib/dpkg/status"
	ApkInstalledPath = "/lib/apk/db/installed"
)

DpkgStatusPath and ApkInstalledPath are where the two package managers record what is installed. A kit's own filesystem is the only honest source for this: a descriptor states what its author knew, and what a base image actually shipped is knowable only by reading it.

View Source
const (
	KindWorkload = "workload"
	KindMixin    = "mixin"

	// KindSet is an authoring kind that never reaches a consumer: it
	// declares a kit whose content is the Kits list, and the frontend
	// publishes the merged result under the kind those kits imply —
	// workload when one of them is a workload, mixin when they all are.
	// Publishing "set" would oblige every consumer to learn a third kind
	// for a role that behaves exactly like one of the two that already
	// exist; deriving it also spares the author a claim about the set
	// that its kits already answer. Stating workload or mixin beside
	// Kits stays legal — it says the same thing explicitly, and the
	// frontend holds it to what the listed kits imply.
	KindSet = "set"
)

Kit kinds. A workload's layers are a root filesystem that may stand at the bottom of the stack; a mixin's are an overlay that must land on one.

View Source
const (
	// CapabilityNetworkPolicy is the phase-scoped egress policy; config
	// decodes to PhasedNetwork.
	CapabilityNetworkPolicy = "com.docker.sandbox/network-policy@1"

	// CapabilityNetworkPolicyV2 is the phase-scoped egress policy with
	// HTTP rules; config decodes to PhasedNetworkV2. A descriptor
	// declares one network-policy version, never both: the two describe
	// the same grant, and a host merging them would have to guess which
	// one bounds the other.
	CapabilityNetworkPolicyV2 = "com.docker.sandbox/network-policy@2"

	// CapabilityGitIdentity shares runtime-provided Git attribution, not credentials.
	CapabilityGitIdentity = "com.docker.sandbox/git-identity@1"

	// CapabilityCredential is one service the workload authenticates to;
	// config decodes to Credential. Instance-shaped, keyed by
	// (service, phase).
	CapabilityCredential = "com.docker.sandbox/credential@1"

	// CapabilitySSHAgent exposes an SSH agent to one phase, relaying to a
	// backing agent of the runtime's choosing; config decodes to SSHAgent.
	// Instance-shaped, keyed by phase.
	CapabilitySSHAgent = "com.docker.sandbox/ssh-agent@1"

	// CapabilityVolume is one persistent (or tmpfs) path; config decodes to
	// Volume. Instance-shaped, keyed by path.
	CapabilityVolume = "com.docker.sandbox/volume@1"

	// CapabilityHostMount shares a runtime-owned host directory across
	// sandboxes of one Kit. Its path key is shared with CapabilityVolume.
	CapabilityHostMount = "com.docker.sandbox/host-mount@1"

	// CapabilityPort is one in-container port to publish; config decodes to
	// Port. Instance-shaped, keyed by container port/transport.
	CapabilityPort = "com.docker.sandbox/port@1"

	// CapabilityAgentSkills declares one discovery path for bundled and
	// available host-shared skills; config decodes to AgentSkills.
	// Instance-shaped, keyed by path: a composition hosting two agents
	// that read skills from different places declares one entry each.
	CapabilityAgentSkills = "com.docker.sandbox/agent-skills@1"

	// CapabilityAgentSkill exposes one image-bundled skill, keyed by its effective name.
	CapabilityAgentSkill = "com.docker.sandbox/agent-skill@1"

	// CapabilityUSBDevice is one device passthrough request; config decodes
	// to USBDevice. Instance-shaped.
	CapabilityUSBDevice = "com.docker.sandbox/usb-device@1"

	// CapabilityResources constrains CPU, memory, and GPU; config decodes to
	// Resources.
	CapabilityResources = "com.docker.sandbox/resources@1"

	// CapabilityPrivileged requests elevated privilege; no config. Present or
	// absent — the host may refuse.
	CapabilityPrivileged = "com.docker.sandbox/privileged@1"

	// CapabilityLifecycle asks the engine to execute the kit's setup
	// across the sandbox's life: install hooks once at create, startup
	// hooks every boot, files written at start, and the interactive
	// argv tail for TTY sessions. A capability rather than
	// grammar fields so a host that cannot execute them refuses the kit
	// by name at preflight instead of silently never running its setup.
	// Like agent-sessions, not permission surface: hooks and files run
	// inside the sandbox on the entrypoint's trust plane.
	CapabilityLifecycle = "com.docker.sandbox/lifecycle@1"

	// CapabilityAgentContext asks the host to surface written instruction
	// content to the agent (the AGENTS.md family). A host with no such
	// concept skips an optional declaration or refuses a required one.
	CapabilityAgentContext = "com.docker.sandbox/agent-context@1"

	// CapabilityAgentSessions declares the workload's session control surface:
	// the argv shapes a harness uses to drive the agent — run one prompt
	// headlessly, list past sessions, resume or continue one. Not a
	// grant: the host consumes it to operate the agent, the way it
	// consumes the image config's entrypoint. Workload kits only in
	// practice — the agent the verbs drive is the workload's.
	CapabilityAgentSessions = "com.docker.sandbox/agent-sessions@1"

	// CapabilitySbx declares that a workload targets the sandbox agent
	// platform: the host launches the agent rather than letting the image
	// entrypoint be PID 1, honors the identity the image config states,
	// and provides the persistent-environment file the image names. The
	// kit's half is the floor that makes those duties performable — a
	// shell, bash, and a resolvable user — which the kit conformance
	// suite judges from the artifact. Not a grant: nothing crosses the
	// boundary the gate guards. No config; the identity lives in the
	// image config, where images already express it.
	CapabilitySbx = "com.docker.sandbox/sbx@1"

	// CapabilityLongRunning keeps a workload running independently of client
	// sessions. Workloads and mixins can request it; it takes no config.
	CapabilityLongRunning = "com.docker.sandbox/long-running@1"

	// CapabilityKitRegistry requests the runtime's kit registry — the daemon's
	// registry facade over the engine image store, where kit builds push
	// results and pull the kit frontend. Reaching it is the host's
	// answer (routing, address, transport), never an address the kit
	// guesses; the request is surfaced because image-store access is a
	// grant, not a side effect of network egress. No config.
	CapabilityKitRegistry = "com.docker.sandbox/kit-registry@1"
)

Well-known capability types. Policy-shaped types appear at most once per descriptor; instance-shaped types (credential, volume, host-mount, port, usb-device, agent-skills) appear once per thing requested.

View Source
const (
	SkillsReadOnly  = "readonly"
	SkillsReadWrite = "readwrite"
)

Skills access modes.

View Source
const (
	SessionPromptPlaceholder = "{{.Prompt}}"
	SessionIDPlaceholder     = "{{.SessionID}}"
)

Placeholders the host substitutes into agent-sessions argv before execution. The {{.X}} shape matches the credential-file structure placeholders — one substitution vocabulary across the grammar.

View Source
const (
	// SizeWarnBytes is the advisory budget: crossing it is a warning.
	SizeWarnBytes = 64 * 1024
	// SizeErrorBytes is the hard budget: crossing it fails validation.
	SizeErrorBytes = 512 * 1024
)

Size limits for the published descriptor. OCI puts no limit on annotation values, but the manifest as a whole meets practical registry ceilings around 4 MB; the grammar exiles everything bulky, so a rich descriptor measures in single-digit kilobytes and these bounds exist so no kit ever discovers a registry's limit in production.

View Source
const (
	DebNamespace = "deb"
	ApkNamespace = "apk"
)

DebNamespace and ApkNamespace hold what a kit's own filesystem says it carries, rather than what its author wrote down: publishing reads the package databases and states one entry per installed package (§9.6).

Their own namespaces, and not com.docker.kit, because they are facts of a different kind and a different authority. A bare `node` is a claim an author makes about what the kit offers; `deb/nodejs-24` is what dpkg records, and the two must not collide in one namespace where a requirement could match either.

View Source
const AnnotationBuiltBy = "vnd.docker.sandbox.kit.built-by"

AnnotationBuiltBy names the frontend build that published the kit, as compact JSON decoding into BuiltBy. It identifies the tool that produced the artifact rather than the source the artifact was produced from — unlike revision, which §9.3 refuses. Absent on kits published before the annotation existed, so readers must tolerate it missing; and self-asserted, like every annotation here, so it answers "what claims to have built this", never "what is this allowed to do".

View Source
const AnnotationCapabilities = "vnd.docker.sandbox.kit.capabilities"

AnnotationCapabilities lists the requested capability types as a comma-separated string, deduplicated and sorted — e.g. "com.docker.sandbox/credential@1,com.docker.sandbox/network-policy@1". An index, not a source: the descriptor annotation stays authoritative, this exists so existence checks ("does this kit want privileged@1?") and policy filters read one small value without parsing the descriptor. Commas cannot appear in a type string, so splitting is unambiguous. Absent when the kit requests nothing.

View Source
const AnnotationDescriptor = "vnd.docker.sandbox.kit.descriptor"

AnnotationDescriptor is the OCI manifest annotation carrying the published descriptor as compact JSON. Authoring is YAML; the published form is a derived artifact (args expanded, contentFile rewritten), serialized as JSON to match the manifest it rides in. Consumers decode it with the same YAML decoder as authored files — YAML accepts JSON as a subset — so older kits whose annotations carry YAML keep decoding.

View Source
const AnnotationSchemaVersion = "vnd.docker.sandbox.kit.schema-version"

AnnotationSchemaVersion carries the descriptor's schemaVersion beside it, so tooling can dispatch on the grammar version from the manifest alone — no YAML parse — and a future schema bump is visible in a registry listing. Always equal to the schemaVersion field inside the descriptor.

View Source
const DefaultCapabilityNamespace = "com.docker.kit"

DefaultCapabilityNamespace qualifies bare capability names, the way docker.io/library qualifies bare image names: `gh` and `com.docker.kit/gh` are the same capability, while a third party's `com.example/gh` never matches either. Deliberately a sibling of com.docker.sandbox, not the same namespace — com.docker.sandbox names contracts the RUNTIME answers (capability types), com.docker.kit names vocabulary KITS provide.

View Source
const MethodAny = "ANY"

MethodAny is the NetworkEntry.Methods entry naming every method. It exists so a path-scoped entry can say "any method" explicitly rather than by omitting the field, which would leave the entry unbounded.

View Source
const SchemaVersion = "3"

SchemaVersion is the only schema version this package decodes.

Variables

This section is empty.

Functions

func AgentSkillName

func AgentSkillName(s AgentSkill) string

AgentSkillName returns the directory component used at every destination.

func CanonicalRequire

func CanonicalRequire(r Require) string

CanonicalRequire renders a parsed require in one spelling, so two entries meaning the same thing compare equal however they were written: "shell >= 1.0.0" and "shell>=1.0.0" and a bare name beside its namespace-qualified form are one relation, and the order two constraints were listed in is not part of what they say.

func CapabilityTypes

func CapabilityTypes(capabilities []Capability) string

CapabilityTypes renders AnnotationCapabilities's value: the requested types, deduplicated, sorted, comma-joined. Sorted rather than authored order because the value is an existence index where order carries no meaning — sorting makes it canonical, so kits requesting the same types carry byte-identical values. Empty for an empty list, which callers translate to omitting the annotation.

func CheckReExport

func CheckReExport(kitArg string, kit Arg, setName string, set Arg, declared bool) error

CheckReExport holds a set's arg declaration to the contract of the kit's arg it stands in for.

A re-export means the set's declaration is the only one an installer ever sees: the kit's is answered and gone. So the set's has to say at least as much — otherwise a required arg becomes optional and its reference goes unresolved at create, or an enum becomes anything at all and a value the kit would have refused reaches it with the kit's own declaration no longer there to refuse it.

Restating the constraint is the set author's work, and cheap: it is the one place an installer can read what the value has to be.

func CompareVersions

func CompareVersions(a, b string) int

CompareVersions compares dotted numeric versions, returning -1, 0, or 1. Non-numeric segments compare lexically, after numeric segments of equal position compare numerically — enough for release tags without importing a semver dependency.

func ContainsArgRef

func ContainsArgRef(s string) bool

ContainsArgRef reports whether s references any kit arg.

func ContainsEnvRef

func ContainsEnvRef(s string) bool

ContainsEnvRef reports environment template syntax, including malformed references that must fail instead of becoming literal runtime policy.

func ContainsKitPlaceholder

func ContainsKitPlaceholder(s string) bool

ContainsKitPlaceholder recognizes Kit template openers even when the name or closing braces are malformed. Inserted values must remain literal.

func DecodeCapabilityConfig

func DecodeCapabilityConfig(n Capability, out any) error

DecodeCapabilityConfig decodes one entry's config into a well-known type's config struct, rejecting unknown fields.

func DerivedProvides

func DerivedProvides(namespace string, listings [][]Package) []string

DerivedProvides renders the provides entries a namespace's package listings agree on, one per package, sorted.

Every listing has to hold the package at one version. A kit publishes one descriptor for every platform it builds, so it may only state what is true of all of them: a package only some platforms carry, or one whose version moves between them, is something a single entry cannot say without lying about one of them. Those are dropped rather than refused, because an architecture-specific package is ordinary.

The same rule folds a multiarch database, where one name appears once per architecture within a single listing.

func DisplayCapabilityName

func DisplayCapabilityName(name string) string

DisplayCapabilityName renders a normalized name for humans: the default namespace is implied and stripped, anything else stays qualified — mirroring how image names display.

func EntryMethods

func EntryMethods(e NetworkEntry) []string

EntryMethods renders an entry's methods as enforcement reads them: MethodAny when it named none, the stated tokens otherwise.

func EntryPaths

func EntryPaths(e NetworkEntry) []string

EntryPaths renders an entry's paths as enforcement reads them: every path when it named none, the stated globs otherwise.

func ExpandBuildArgs

func ExpandBuildArgs(raw []byte, decls map[string]Arg, values map[string]string) ([]byte, error)

ExpandBuildArgs expands build-phase references structurally, preserving the authored YAML's comments and ordering. Create-phase references remain.

func ExpandCreateArgs

func ExpandCreateArgs(raw []byte, decls map[string]Arg, values map[string]string) ([]byte, error)

ExpandCreateArgs expands references to create-phase args (those NOT declaring buildArg:) through the descriptor, producing the EFFECTIVE descriptor: the document enforcement, the lock, and the permission gate judge. Expansion walks the decoded document — every string value and every map key — and re-serializes as JSON, so a value carrying quotes or backslashes survives instead of breaking the document it lands in. A string that is nothing but a reference adopts the value's own type, so a numeric arg can still land in a typed config field (`container: ${{ kit.args.port }}`) the way v2's substitution could. The published descriptor is never rewritten — callers expand a copy — and the declared enum/pattern constraints, which ride in the signed descriptor, bound what values can materialize. A reference with no resolved value is an error: a hole in effective policy must fail loudly, not enforce an empty string.

func ExpandString

func ExpandString(s string, values map[string]string) (string, error)

ExpandString substitutes ${{ kit.args.<name> }} references in s from values. Referencing a name absent from values is an error rather than an empty string, so a typo fails loudly instead of producing a hook that runs with a hole in it.

func FormatWidenings

func FormatWidenings(ws []Widening) string

FormatWidenings renders a widening list for an approval prompt.

func HTTPMethods

func HTTPMethods() []string

HTTPMethods returns the method tokens a NetworkEntry may name, excluding MethodAny, sorted. The published schema's enum is pinned against it.

func HasCapability

func HasCapability(needs []Capability, typ string) bool

HasCapability reports whether a type is requested.

func HasEnvReferences

func HasEnvReferences(items []Capability) bool

HasEnvReferences reports references in decoded configuration strings and keys, including group members. Checking serialized bytes would miss escaped whitespace.

func HasGroups

func HasGroups(items []Capability) bool

HasGroups reports whether selection is needed before reconciliation.

func IsDerivedProvide

func IsDerivedProvide(name string) bool

IsDerivedProvide reports whether a normalized capability name is one publishing derived from image content rather than one an author wrote.

Three callers need the distinction, all of them because a derived entry is evidence and an authored one is a claim: the version annotation must not be drowned out by hundreds of package versions, the derivation must replace its own previous output rather than accumulate it, and a report reads better when it can say which is which.

func IsVersion

func IsVersion(s string) bool

IsVersion reports whether s is version-shaped by the one rule every consumer shares (tag ordering, provides matching, version inference).

func IsWholeArgRef

func IsWholeArgRef(s string) bool

IsWholeArgRef reports whether s is exactly one arg reference.

func KitArgValues

func KitArgValues(decls map[string]Arg, supplied map[string]string) (map[string]string, error)

KitArgValues resolves one listed kit's create-phase args from the values its set supplied, separating the two kinds of value a set may give.

A literal value is validated against the kit's own enum or pattern and expanded into the merged descriptor, which is what pins the set. A value that is itself a `${{ kit.args.* }}` reference cannot be validated here — it has no value yet — so it lands where the kit's placeholder was and the set's own declaration bounds it at create. That is the same two-phase judgment a parameterized capability entry gets, applied to a listed kit's inputs.

func KnownCapabilities

func KnownCapabilities() []string

KnownCapabilities lists types understood by this version of the library. Understanding a schema is not a claim that a runtime implements it.

func LatestVersion

func LatestVersion(tags []string) string

LatestVersion returns the highest version-shaped tag (TagVersion rules, so a conventional "v" prefix counts), or "" when none parses as a version. The returned value is the tag as spelled — it names something pullable — while ordering compares the normalized versions. It is the one tag-ordering rule shared by the resolver ("unpinned means latest") and the gate ("moving from X to Y"), defined here so both agree on what "latest" means.

func NormalizeCapabilityName

func NormalizeCapabilityName(name string) string

NormalizeCapabilityName qualifies a bare capability name with the default namespace; already-qualified names pass through. Matching, locks, and surfaces speak normalized names; authored short forms are display sugar.

func OCIAnnotations

func OCIAnnotations(d *Descriptor) map[string]string

OCIAnnotations maps a published descriptor onto the standard org.opencontainers.image.* annotations it can populate: title, description, authors, source, and licenses straight from their fields, and version from the version: fallback or — when every versioned provides entry agrees — that one version. Empty fields yield no key, so absence means "not declared". Builder inputs (created, revision, base.*) are never emitted here: the descriptor cannot answer them. A builder may separately emit created from SOURCE_DATE_EPOCH per §9.3; this projection has no timestamp input and never reads the wall clock.

func OwnedProvides

func OwnedProvides(contributions []Contribution) map[string][]OwnedProvide

OwnedProvides indexes what the contributions offer, by name, keeping the contribution each entry came from.

Exported alongside SatisfiedByOther because ownership is the whole of the rule: Resolve skips self, so anything judging a merge has to skip it the same way, and two implementations of that would drift.

func PackageVersion

func PackageVersion(raw string) string

PackageVersion is the version a derived provide carries: the leading x.y.z core of what a package database records, with everything a distribution wrapped around it removed. Empty when the record holds no version this model can name, which drops the package rather than publishing it under the kit's own version.

What comes off is packaging bookkeeping rather than a point in the upstream order — a Debian revision, a binNMU, a backport suffix, an apk release — and a consumer writing `deb/openssl >= 3.5` cannot be asked to know about `-1~deb13u2+dhi1`. Fewer than three parts stays as it is and is never padded: binutils really is 2.44, and 2.44.0 would invent precision the distribution never stated.

func PortKey

func PortKey(p Port) string

PortKey identifies a port request for uniqueness.

func PortTransport

func PortTransport(p Port) string

PortTransport reports the transport a port request names, resolving the default. An omitted transport and an explicit "tcp" are the same request, so everything that identifies a port — the permission surface, the uniqueness check — has to see one spelling.

func Positions

func Positions(raw []byte) map[string]Position

Positions maps every element of a descriptor document to its source location, keyed by dotted path with zero-based sequence indices — "capabilities[0].config.service", "args.version.pattern", "provides[2]". Consumers (the frontend) join these with FieldError.Path to point a validation error at the exact lines that caused it.

func PrivilegedOf

func PrivilegedOf(needs []Capability) bool

PrivilegedOf reports whether the descriptor requests privileged execution.

func ProvidesIndex

func ProvidesIndex(contributions []Contribution) map[string][]Provide

mergeRelations unions the kit-to-kit relations, dropping the requires and integrates entries the set answers itself.

Provides are facts about content that travelled in, so they carry over: the merged kit really does offer what its parts offered, and dropping them would let the same kit compose twice under a different reference. Requires are the opposite — an entry the set satisfies internally names something the merged kit now contains, and a kit cannot satisfy its own requirement (Resolve skips self), so keeping it would publish a kit that can never resolve. ProvidesIndex maps each capability name the contributions offer to every provide offering it, with each bare entry's version materialized from its own contribution's fallback.

A name maps to a list rather than one entry because the resolver counts OWNERS, not entries: two kits providing one name is refused, but a single kit may offer it at several versions, and whichever of them satisfied another kit's constraint during resolution has to still satisfy it here.

Exported because the set's own declarations are judged against the kits it lists before the merge runs, and both readings have to agree on what a bare provide's version is — the rule lives here so it cannot be restated differently in two places.

func ReferencedArgs

func ReferencedArgs(raw []byte) []string

ReferencedArgs returns the set of arg names referenced anywhere in the raw descriptor, for validation that every reference resolves.

func RequireAuthoredProvides

func RequireAuthoredProvides(d *Descriptor) error

RequireAuthoredProvides refuses a provides entry an author must not write: §5.1 reserves the deb/ and apk/ namespaces for §9.6, where publishing fills them from the package databases in the content.

Authored form only, which is why it is not folded into Validate: the published form legitimately carries these, and a runtime revalidating a descriptor on load has to accept what publishing put there. The distinction is the whole point — an entry under these namespaces is something read off a filesystem, and one written by hand would assert a fact about content instead of offering a capability, with nothing left to catch the difference.

func RequireVersionedProvides

func RequireVersionedProvides(d *Descriptor) error

RequireVersionedProvides is the publish-time rule the frontend enforces on the expanded descriptor: every provides entry must carry a version — its own @version, or the descriptor's version: fallback. A published kit with an unversioned provide would satisfy only unconstrained requires and silently defeat version-constraint resolution; the build is where the author can still fix it. Consumption references (a version tag) can override these versions, never substitute for them. Kits with no provides publish fine: they offer nothing matchable, so there is nothing to version.

func ResolveArgs

func ResolveArgs(decls map[string]Arg, supplied map[string]string) (map[string]string, error)

ResolveArgs validates supplied values against the declarations and returns the effective value map: supplied values first, defaults for the rest. A required arg with no supplied value is an error; a supplied value failing its enum or pattern is an error; a supplied name the kit never declared is an error, because a silently ignored input is a misspelling in disguise.

func SatisfiedBy

func SatisfiedBy(provided map[string][]Provide, r Require) bool

SatisfiedBy reports whether any provide under the require's name holds it. One kit may offer a name at several versions; the requirement is met if one of them meets it.

func SatisfiedByOther

func SatisfiedByOther(owned map[string][]OwnedProvide, r Require, by string) bool

SatisfiedByOther reports whether a contribution other than by satisfies r. A contribution's own provide never does: Resolve skips self, so a requirement answered only by its own offer is one nothing can answer.

func Satisfies

func Satisfies(p Provide, r Require) bool

Satisfies reports whether the provide meets the require: same capability name, and a provided version that holds every stated constraint. An unversioned provide satisfies only an unconstrained require, so a constraint never silently matches a capability that declared no version.

func SkillsMode

func SkillsMode(s AgentSkills) string

SkillsMode reports the access a request names, resolving the default.

func SkillsWritable

func SkillsWritable(s AgentSkills) bool

SkillsWritable reports whether a request asks for write access.

Access is carried in a separate surface field rather than folded into the path string: a path is arbitrary text, so any suffix marking write could also be a real path, and a grant for one would silently cover a request for the other.

func TagVersion

func TagVersion(tag string) string

TagVersion maps a consumption-reference tag (an OCI tag, a git ref) onto the version it names: the bare version for a version-shaped tag, with git's conventional leading "v" tolerated and stripped; "" for everything else. The grammar itself admits no "v" — this is the one boundary where the outside world's spelling normalizes into the model's, so "v2.1.0" and "2.1.0" tags name the same version while only one spelling exists inside it.

func Validate

func Validate(d *Descriptor) (warnings []string, err error)

Validate checks every rule the descriptor grammar states. It returns non-fatal warnings alongside all independent errors; a nil error with warnings means the descriptor is usable but the author should look. Errors are returned as ValidationErrors. Each FieldError carries the offending element's dotted path; WithSource adds source locations when the caller has the raw bytes.

func ValidateDeclarations

func ValidateDeclarations(raw []byte, d *Descriptor) ([]string, error)

ValidateDeclarations validates all structure and per-entry configurations, leaving selection-dependent cross-entry checks to ValidateEffective.

func ValidateEffective

func ValidateEffective(raw []byte, d *Descriptor) ([]string, error)

ValidateEffective is the create-time gate on the expanded descriptor: ValidateRaw with every capability entry validated strictly (no placeholder leniency can apply, because no placeholder may remain). The effective descriptor is the enforcement input, so a surviving reference would become literal policy.

func ValidateExpandedDeclarations

func ValidateExpandedDeclarations(raw []byte, d *Descriptor) ([]string, error)

ValidateExpandedDeclarations additionally refuses unresolved arguments and environment references.

func ValidatePublished

func ValidatePublished(raw []byte, d *Descriptor) (warnings []string, err error)

ValidatePublished runs ValidateRaw plus the published-form requirement: every build-phase reference has been expanded, so provides entries are literal. Create-phase references in hooks and files legitimately remain.

func ValidateRaw

func ValidateRaw(raw []byte, d *Descriptor) (warnings []string, err error)

ValidateRaw runs Validate plus the checks that need the raw bytes: the size budget, and that every ${{ kit.args.* }} reference names a declared arg.

func WithSource

func WithSource(err error, filename string, raw []byte) error

WithSource renders every validation failure with a filename and, when available, a source position, source line, and caret. It preserves the original error tree for errors.Is/As. Callers without source bytes can use the field paths in Error() directly. Missing fields point at their containing element.

Example
raw := []byte(`schemaVersion: "3"
kind: mixin
capabilities:
  - type: com.docker.sandbox/port@1
    config: {container: 99999}
args:
  version:
    pattern: '['
`)
d, err := Decode(raw)
if err != nil {
	panic(err)
}
_, err = Validate(d)
fmt.Println(WithSource(err, "kit.yaml", raw))
Output:
kit.yaml:5:25: capabilities[0].config.container: capabilities[0]: container port 99999 out of range
  |
5 |     config: {container: 99999}
  |                         ^

kit.yaml:8:14: args.version.pattern: args.version: invalid pattern: error parsing regexp: missing closing ]: `[`
  |
8 |     pattern: '['
  |              ^

Types

type APIKey

type APIKey struct {
	// Name is the in-container env var, set to a sentinel when
	// proxy-managed. Empty declares an inject-only credential: Inject
	// rules are then required, and no variable is created for it.
	Name string `json:"name" yaml:"name"`

	// ProxyManaged keeps the real value on the host. A named key's
	// variable carries a sentinel; an inject-only key (empty Name with
	// Inject rules) has no in-container presence at all. Outbound, the
	// boundary presents the real value either way.
	ProxyManaged bool `json:"proxyManaged,omitempty" yaml:"proxyManaged,omitempty"`

	// Inject lists the outbound domains the proxy rewrites.
	Inject []Inject `json:"inject,omitempty" yaml:"inject,omitempty"`
}

APIKey configures proxy injection of an API key on outbound requests.

type AgentContext

type AgentContext struct {
	// Directory overrides workspace-sibling placement for agents whose
	// startup discovery only reads a dedicated configuration directory.
	Directory string `json:"directory,omitempty" yaml:"directory,omitempty"`
	Filename  string `json:"filename,omitempty" yaml:"filename,omitempty"`
	// ContentFile points at the context body: a context-relative path in
	// the authored descriptor, rewritten by the frontend to the staged
	// in-image path in the published one.
	ContentFile string `json:"contentFile,omitempty" yaml:"contentFile,omitempty"`
	// Content carries small instruction content inline, for content-free
	// kits.
	Content string `json:"content,omitempty" yaml:"content,omitempty"`
}

AgentContext is CapabilityAgentContext's config: instruction content the agent reads. An explicit Directory and Filename select the agent's discovery profile; a workload Filename alone supplies the legacy fallback.

func AgentContextOf

func AgentContextOf(capabilities []Capability) (*AgentContext, error)

AgentContextOf returns the agent-context declaration's config, or nil when none is declared.

func AgentContextsOf

func AgentContextsOf(capabilities []Capability) ([]AgentContext, error)

AgentContextsOf reads all selected context contributions of one Kit. Unlike AgentContextOf (for an effective singleton), it retains separate bodies from selected groups for contributor-specific runtime guidance handlers.

type AgentSessions

type AgentSessions struct {
	Prompt   []string    `json:"prompt,omitempty" yaml:"prompt,omitempty"`
	Resume   []string    `json:"resume,omitempty" yaml:"resume,omitempty"`
	Continue []string    `json:"continue,omitempty" yaml:"continue,omitempty"`
	List     CommandLine `json:"list,omitempty" yaml:"list,omitempty"`
}

AgentSessions is CapabilityAgentSessions's config. Prompt, Resume, and Continue are argv tails appended to the workload's launch command the way user-supplied args are: Prompt runs one prompt non-interactively and must reference {{.Prompt}}; Resume reopens a named session and must reference {{.SessionID}}; Continue reopens the most recent session. List is a complete command whose stdout enumerates resumable session ids, one per line, most recent first. Every verb is optional — an absent verb means the agent has no such operation — but a declaration with no verbs at all says nothing and is invalid.

func AgentSessionsOf

func AgentSessionsOf(needs []Capability) (*AgentSessions, error)

AgentSessionsOf returns the agent-sessions declaration's config, or nil when none is declared.

type AgentSkill

type AgentSkill struct {
	Path string `yaml:"path" json:"path"`
	Name string `yaml:"name,omitempty" json:"name,omitempty"`
}

AgentSkill identifies a complete skill directory in the published image. Name overrides its basename at discovery destinations, not in the source tree.

type AgentSkillCapability

type AgentSkillCapability struct {
	AgentSkill
	Optional    bool
	DisplayName string
	Description string
}

AgentSkillCapability exposes config.Name as Name and the entry label as DisplayName. Use AgentSkillName(request.AgentSkill) for the effective directory name, including the source-basename fallback when config.Name is omitted.

func AgentSkillRequestsOf

func AgentSkillRequestsOf(capabilities []Capability) ([]AgentSkillCapability, error)

AgentSkillRequestsOf reads selected, flat declarations in contribution order. It preserves optionality so a runtime can refuse or record unavailable skills.

type AgentSkills

type AgentSkills struct {
	Path string `json:"path" yaml:"path"`
	// Mode is "readonly" (default) or "readwrite".
	Mode string `json:"mode,omitempty" yaml:"mode,omitempty"`
}

AgentSkills declares Path as a destination for selected AgentSkill bundles and for the host's shared skills when available and enabled.

Only the kit knows where its agent looks: an agent shipped behind a wrapper, or one the runtime has never heard of, reads skills from a path no table on the host side can predict. Declaring the path is what makes the store reach kits the runtime does not recognize.

Mode is the most access the kit is willing to take, not a demand. The host's own setting narrows it further and can withhold the mount entirely without making the request unsatisfiable. Mode does not constrain bundled skills. Neither side can exceed the other, so a kit that only reads skills says so and never receives write access on a permissive host.

type AgentSkillsCapability

type AgentSkillsCapability struct {
	AgentSkills
	Optional    bool
	Name        string
	Description string
}

AgentSkillsCapability is one skills request with the entry fields a runtime needs to answer it.

func AgentSkillsOf

func AgentSkillsOf(needs []Capability) ([]AgentSkillsCapability, error)

AgentSkillsOf returns selected discovery destinations in declaration order. Every destination receives selected AgentSkill bundles. Optional governs capability support, not whether the host has skills available to share.

type Arg

type Arg struct {
	// Default is substituted when the caller supplies nothing. A nil
	// default with Required unset still means the arg is optional and
	// expands to the empty string only if referenced; declaring Required
	// alongside Default is an error.
	Default *string `json:"default,omitempty" yaml:"default,omitempty"`

	// Required marks a value the caller must supply.
	Required bool `json:"required,omitempty" yaml:"required,omitempty"`

	// Description is shown wherever a kit's inputs are listed.
	Description string `json:"description,omitempty" yaml:"description,omitempty"`

	// Enum restricts the value to an exact set. Exclusive with Pattern.
	Enum []string `json:"enum,omitempty" yaml:"enum,omitempty"`

	// Pattern restricts the value to an RE2 regexp matched against the
	// whole value. Exclusive with Enum.
	Pattern string `json:"pattern,omitempty" yaml:"pattern,omitempty"`

	// Env opts the resolved value into the container environment under
	// this variable name.
	Env string `json:"env,omitempty" yaml:"env,omitempty"`

	// BuildArg resolves the arg at build instead: the frontend validates
	// it, hands it to the companion dockerfile under this build-arg name,
	// and expands its references into the published descriptor.
	BuildArg string `json:"buildArg,omitempty" yaml:"buildArg,omitempty"`
}

Arg declares one installer-supplied value. Private by default: it reaches the running container only through Env, and the build only through BuildArg. The two are mutually exclusive because they resolve in different phases.

type BuiltBy

type BuiltBy struct {
	// Name is the frontend's published image, e.g. docker/sandbox-kit.
	Name string `json:"name"`
	// Version is the frontend's release, or "dev" for a build no release
	// stamped.
	Version string `json:"version"`
	// Revision is the full commit the frontend was built from, omitted
	// when the build recorded none.
	Revision string `json:"revision,omitempty"`
}

BuiltBy is the value of AnnotationBuiltBy: which frontend build published a kit. JSON rather than one packed string because the fields are read apart — a version is compared, a revision is looked up — and because an object can gain a field without every reader learning a new way to split one.

func ParseBuiltBy

func ParseBuiltBy(annotations map[string]string) (BuiltBy, bool, error)

ParseBuiltBy reads the annotation's value. The false report is for an absent annotation, which is the ordinary case for a kit published before the annotation existed, not a malformed one.

func (BuiltBy) Marshal

func (b BuiltBy) Marshal() (string, error)

Marshal renders the annotation's value: compact JSON, matching the published descriptor's own encoding, and byte-deterministic for a given build — a kit rebuilt by the same frontend from the same descriptor keeps its digest.

type Capability

type Capability struct {
	// Group is the alternative to an ordinary entry. It is not a capability
	// type and must be selected before typed accessors are used.
	Group *CapabilityGroup `json:"group,omitempty" yaml:"group,omitempty"`

	// Source preserves publisher attribution. It is untrusted diagnostic
	// metadata; the consuming artifact still determines trust.
	Source *CapabilitySource `json:"source,omitempty" yaml:"source,omitempty"`

	// Type names the capability and its config-schema version, e.g.
	// "com.docker.sandbox/network-policy@1".
	Type string `json:"type,omitempty" yaml:"type,omitempty"`

	// Name is a display label, not identity: duplicate labels are allowed
	// and renaming a request changes neither its grants nor its merge key.
	Name string `json:"name,omitempty" yaml:"name,omitempty"`

	// Optional marks a capability the kit degrades gracefully without:
	// an unknown or unprovided optional capability is skipped and recorded,
	// while a required one fails resolution closed. For credentials this
	// subsumes the older per-credential required flag.
	Optional bool `json:"optional,omitempty" yaml:"optional,omitempty"`

	// Config is the type-specific request payload. Well-known types are
	// decoded strictly against the schema their version pins; unknown
	// types carry it opaquely. Part of the permission surface: config
	// changes gate like any widening.
	Config map[string]any `json:"config,omitempty" yaml:"config,omitempty"`

	// Description is shown wherever the request is listed or prompted.
	Description string `json:"description,omitempty" yaml:"description,omitempty"`
	// contains filtered or unexported fields
}

Capability is one typed, versioned capability request. The type string is <namespace>/<name>@<version>: the namespace admits host-specific capability types without touching this grammar, and the version moves when the type's config schema does — capabilities evolve without a descriptor schema-major bump.

func CapabilityWithConfig

func CapabilityWithConfig(n Capability, config any) (*Capability, error)

CapabilityWithConfig returns the entry with its config replaced by the encoding of config, keeping everything that describes the request rather than stating it (type, optional, description). Callers that rewrite one well-known config — the frontend inlining a context body — go through this so the rewritten entry decodes exactly as an authored one would.

func DeclaredCapabilities

func DeclaredCapabilities(items []Capability) []Capability

DeclaredCapabilities enumerates ordinary requests, including unselected members. Use only for artifact inspection, never runtime application.

func MapCapabilities

func MapCapabilities(items []Capability, fn func(Capability) (Capability, error)) ([]Capability, error)

MapCapabilities visits ordinary entries, including group members, without selecting them. It copies slices and groups; fn must copy a config before changing it. Publishers use it to stage every potentially selected body.

func (Capability) ConfigStated

func (c Capability) ConfigStated() bool

ConfigStated reports whether the document carried a `config` key, including the null and empty spellings the decoded map loses.

func (*Capability) UnmarshalJSON

func (c *Capability) UnmarshalJSON(data []byte) error

UnmarshalJSON records config presence for the JSON spelling. Decoding is strict, as every other well-known config is — a custom unmarshaler does not inherit the caller's DisallowUnknownFields.

func (*Capability) UnmarshalYAML

func (c *Capability) UnmarshalYAML(unmarshal func(any) error) error

UnmarshalYAML records whether the document stated a config at all, which the decoded map cannot distinguish from an omitted key. The callback form, not the node form: a node decodes through a fresh decoder that does not inherit KnownFields, which would silently accept misspelled capability fields.

type CapabilityDecision

type CapabilityDecision struct {
	Accepted bool   `json:"accepted"`
	Message  string `json:"message,omitempty"`
}

CapabilityDecision is the runtime's answer for one expanded entry. Its zero value rejects the entry. Message explains acceptance or rejection for selection records and, on required rejection, error diagnostics.

type CapabilityGroup

type CapabilityGroup struct {
	Name         string       `json:"name,omitempty" yaml:"name,omitempty"`
	Description  string       `json:"description,omitempty" yaml:"description,omitempty"`
	Optional     bool         `json:"optional,omitempty" yaml:"optional,omitempty"`
	Capabilities []Capability `json:"capabilities" yaml:"capabilities"`
}

CapabilityGroup selects one feature's requests atomically.

func (*CapabilityGroup) UnmarshalJSON

func (g *CapabilityGroup) UnmarshalJSON(data []byte) error

func (*CapabilityGroup) UnmarshalYAML

func (g *CapabilityGroup) UnmarshalYAML(unmarshal func(any) error) error

type CapabilityItem

type CapabilityItem = Capability

CapabilityItem is the descriptor union: an ordinary Capability or Group. The alias keeps existing ordinary-entry Go literals source-compatible.

type CapabilitySource

type CapabilitySource struct {
	Kit  string `json:"kit" yaml:"kit"`
	Path string `json:"path" yaml:"path"`
}

CapabilitySource survives publication for diagnostics, not trust decisions.

func (*CapabilitySource) UnmarshalJSON

func (s *CapabilitySource) UnmarshalJSON(data []byte) error

func (*CapabilitySource) UnmarshalYAML

func (s *CapabilitySource) UnmarshalYAML(unmarshal func(any) error) error

type CommandLine

type CommandLine []string

CommandLine accepts either a YAML string (run via "sh -c") or a list of argv elements.

func (*CommandLine) UnmarshalJSON

func (c *CommandLine) UnmarshalJSON(data []byte) error

UnmarshalJSON mirrors the YAML flexibility on the JSON wire: lifecycle hooks live in a capability config, and configs reach their typed structs through a JSON round-trip (DecodeCapabilityConfig), so the string form must survive both decoders.

func (*CommandLine) UnmarshalYAML

func (c *CommandLine) UnmarshalYAML(unmarshal func(any) error) error

UnmarshalYAML implements the string-or-list flexibility.

type Constraint

type Constraint struct {
	Op      ConstraintOp
	Version string
}

Constraint is one comparison in a require: Op Version.

type ConstraintOp

type ConstraintOp string

ConstraintOp is a comparison against a version point.

const (
	OpGTE ConstraintOp = ">="
	OpGT  ConstraintOp = ">"
	OpLTE ConstraintOp = "<="
	OpLT  ConstraintOp = "<"
	OpEQ  ConstraintOp = "="
)

type ContextSource

type ContextSource struct {
	// Reference names the contributor, for the staged body's heading and
	// for errors reading it.
	Reference string

	// Path is an in-image path to read the body from; empty when the
	// body is inline.
	Path string

	// Content is the inline body; empty when Path is set.
	Content string

	// Target is set when publication preserves a separately selectable body.
	Target string
}

ContextSource is one contribution's agent-context body: either an in-image path (a published kit's rewritten contentFile) or content stated inline.

type Contribution

type Contribution struct {
	// Reference is how the contribution is named in merge errors: a
	// listed kit's consumption reference, or the set's own descriptor.
	Reference string

	// Descriptor is the declarations this contribution brings. A
	// listed kit's is its published descriptor with its create-phase
	// args already resolved (KitArgValues, ExpandCreateArgs). Publication
	// preserves final-environment references; Compose requires those to be
	// expanded before reconciliation.
	Descriptor *Descriptor
}

Contribution is one descriptor folded into a merged kit, named by the reference its diagnostics should point at.

type Credential

type Credential struct {
	// Service is the identifier in the host credential store.
	Service string `json:"service" yaml:"service"`

	// Phase accepts one phase or a non-empty list of distinct phases.
	Phase Phases `json:"phase" yaml:"phase"`

	// APIKey configures header injection of an API key.
	APIKey *APIKey `json:"apiKey,omitempty" yaml:"apiKey,omitempty"`

	// OAuth configures proxy-managed OAuth token interception.
	OAuth *OAuth `json:"oauth,omitempty" yaml:"oauth,omitempty"`
}

Credential is CapabilityCredential's config: one service the workload authenticates to and how the proxy presents proof — never where the secret lives. Phase-scoped like network: an install-only credential is injected only while install runs and is gone before the workload starts. An entry declaring both install and runtime uses the same configuration in both phases.

type CredentialCapability

type CredentialCapability struct {
	Credential

	// Required mirrors the entry's optionality: a required credential
	// fails preflight when no binding satisfies the service, an optional
	// one is skipped and recorded.
	Required bool

	// Name and Description are the entry's display label and explanation.
	Name        string
	Description string
}

CredentialCapability is one decoded credential request with its entry-level declaration state.

func CredentialsOf

func CredentialsOf(needs []Capability) ([]CredentialCapability, error)

CredentialsOf returns every credential request, in declaration order.

func CredentialsOfPhase

func CredentialsOfPhase(needs []Capability, phase string) ([]CredentialCapability, error)

CredentialsOfPhase returns the credential requests for one phase.

type CredentialFile

type CredentialFile struct {
	Path string `json:"path" yaml:"path"`
	// Structure is a declarative nested map, encoded after placeholder
	// substitution in the encoding Format selects, so its output is
	// well-formed regardless of the values. Leaf strings may reference
	// {{.AccessToken}} and {{.RefreshToken}} (strings), {{.ExpiresAt}}
	// (a number), {{.Scopes}} (an array), and {{.PrimaryApiKey}} (a
	// string whose enclosing key is omitted when no key is captured);
	// each placeholder renders in the target encoding's own type.
	// Unknown placeholders are errors.
	Structure map[string]any `json:"structure,omitempty" yaml:"structure,omitempty"`
	// Format selects the encoding of the substituted structure: "json"
	// (the default when empty) or "toml", for agents that read TOML
	// credential files (devin). Encoding stays declarative either way —
	// nested maps become TOML tables, {{.ExpiresAt}} a TOML integer, and
	// {{.Scopes}} a TOML array.
	Format string `json:"format,omitempty" yaml:"format,omitempty"`
}

CredentialFile renders sentinels into a file the agent reads.

type Descriptor

type Descriptor struct {

	// SchemaVersion must be "3".
	SchemaVersion string `json:"schemaVersion" yaml:"schemaVersion"`

	// DisplayName is what humans read; identity lives in the reference.
	DisplayName string `json:"displayName,omitempty" yaml:"displayName,omitempty"`

	// Author is who published the kit, in the freeform
	// org.opencontainers.image.authors convention ("Name <email>", commas
	// for multiples). Display metadata only, exactly like DisplayName: a
	// self-asserted claim, never identity and never a trust input —
	// publisher identity lives in the reference's registry namespace and
	// in the signature.
	Author string `json:"author,omitempty" yaml:"author,omitempty"`

	// Description is a short human-readable summary.
	Description string `json:"description,omitempty" yaml:"description,omitempty"`

	// SourceURL points at the kit's source repository or documentation.
	SourceURL string `json:"sourceUrl,omitempty" yaml:"sourceUrl,omitempty"`

	// IconURL is an https URL to an image representing the kit in catalogs
	// and pickers. Display metadata like DisplayName, and self-asserted
	// the same way — but unlike the other display fields a consumer
	// fetches and renders it, so the scheme is constrained rather than
	// free text: https only keeps javascript:, data:, and file: out of
	// whatever surface ends up rendering it.
	//
	// A URL rather than staged content because the surfaces that want an
	// icon — a registry listing, a picker — have the manifest and not the
	// layers. An icon in a layer could not be shown without pulling the
	// kit, which is the moment the icon existed to inform.
	IconURL string `json:"iconUrl,omitempty" yaml:"iconUrl,omitempty"`

	// Version is the fallback version for unversioned provides entries,
	// for kits consumed by references that carry no version of their own:
	// a local directory, a git branch or commit. It never overrides the
	// consumption reference — an OCI tag or a version-shaped git ref wins,
	// so a stale Version cannot lie to the resolver — and an explicit
	// provides@version outranks both. May reference a build-phase arg
	// (`${{ kit.args.version }}`), expanded into the published descriptor
	// like provides.
	Version string `json:"version,omitempty" yaml:"version,omitempty"`

	// Licenses lists SPDX license identifiers.
	Licenses []string `json:"licenses,omitempty" yaml:"licenses,omitempty"`

	// Kind is "workload" or "mixin". It names the role; the layer nature
	// follows from it, and it tells the frontend what to emit.
	Kind string `json:"kind" yaml:"kind"`

	// Provides lists capabilities this kit offers, optionally versioned as
	// name@version. Nothing is provided implicitly: a kit that wants to be
	// requirable by name states so here.
	Provides []string `json:"provides,omitempty" yaml:"provides,omitempty"`

	// Requires lists capabilities that must be satisfied by the resolved
	// set, as "name" or "name <op> version" (comma-AND'd constraints;
	// ops: >=, >, <=, <, =). Capability names resolve to nothing and
	// validate against something.
	Requires []string `json:"requires,omitempty" yaml:"requires,omitempty"`

	// Integrates lists capabilities the kit works with when present but
	// functions without, in the requires grammar. An absent provider is
	// fine; a PRESENT one that violates the stated constraints still fails
	// resolution — an integration that would break is incoherence, not an
	// option. A met entry orders like requires: the provider composes
	// before the kit that integrates with it.
	Integrates []string `json:"integrates,omitempty" yaml:"integrates,omitempty"`

	// Conflicts lists capability names that must be absent from the
	// resolved set.
	Conflicts []string `json:"conflicts,omitempty" yaml:"conflicts,omitempty"`

	// Capabilities declares everything the kit needs but cannot supply itself:
	// a list of typed, versioned capability requests, each answered —
	// granted, refused, or prompted — by the host. One model for every
	// capability: well-known types get strict config schemas, cross-entry
	// validation, and direction-aware gating; unknown types are the
	// extension point, refused by name when required, skipped and
	// recorded when optional. Everything the kit asks of its host lives
	// here — resource grants and engine-executed behaviors alike
	// (lifecycle hooks, agent context) — so a host answers the whole ask
	// through one mechanism, or refuses the parts it does not know.
	Capabilities []CapabilityItem `json:"capabilities,omitempty" yaml:"capabilities,omitempty"`

	// Args declares installer-supplied values, keyed by arg name. Private
	// by default: an arg reaches the container only via env:, and the
	// build only via buildArg:.
	Args map[string]Arg `json:"args,omitempty" yaml:"args,omitempty"`

	// Build is the kit's content recipe inline: literal Dockerfile text,
	// the single-file alternative to the companion <stem>.dockerfile
	// (declaring both is an error). Full Dockerfile semantics apply —
	// multi-stage, mounts, even a foreign # syntax= first line — because
	// the frontend hands the block to the Dockerfile frontend verbatim;
	// nothing is transpiled. Build-phase args reach it as --build-arg
	// under their declared buildArg names, exactly like a companion.
	// The published descriptor carries the block, so the annotation also
	// records how the content was produced.
	Build string `json:"build,omitempty" yaml:"build,omitempty"`

	// Kits is this kit's content as a list of published kits rather than
	// Dockerfile text: the fourth authoring form, for an environment
	// assembled out of kits someone already published. Mutually
	// exclusive with Build and Dockerfile — a kit's content recipe lives
	// in exactly one place — and declared by kind: set, or beside the
	// kind the listed kits imply.
	//
	// The frontend resolves every one of them, merges their layers and
	// their declarations, and publishes one ordinary kit. The published
	// descriptor retains the list with each entry pinned by Digest, the
	// way it retains an inline Build block: the artifact still records
	// how its content was produced.
	Kits []Kit `json:"kits,omitempty" yaml:"kits,omitempty"`

	// Dockerfile names the companion recipe explicitly, as a path
	// relative to the descriptor's directory. Optional sugar over the
	// filename-stem convention (<stem>.dockerfile beside the
	// descriptor): stating it makes the pairing visible in the
	// descriptor and frees the recipe's name from the stem. Unlike the
	// conventional companion — whose absence just means a
	// declaration-only kit — a named recipe that does not exist is an
	// error: an explicit reference failing silently would build the
	// wrong kit. Mutually exclusive with build:.
	Dockerfile string `json:"dockerfile,omitempty" yaml:"dockerfile,omitempty"`
	// contains filtered or unexported fields
}

Descriptor is the authored kit declaration. Published descriptors carry the same declarations with build-phase arg references (${{ kit.args.* }} for args with buildArg:) expanded, re-serialized as compact JSON.

func Compose

func Compose(contributions []Contribution) (*Descriptor, error)

Compose reconciles ordered, expanded contributions for runtime use. It shares Merge's conflict and reconciliation rules, without staging content for publication. Retain the input contributions alongside the returned descriptor: capability handlers need the individual declarations wherever composition would otherwise erase their provenance. The caller resolves dependencies and validates inputs before calling; Compose does not perform descriptor validation or registry IO.

func Decode

func Decode(data []byte) (*Descriptor, error)

Decode parses descriptor YAML strictly: unknown fields are errors, because a misspelled key silently ignored is a policy silently absent. A leading `# syntax=` line needs no special handling — it is a YAML comment.

func ExpandEnvironment

func ExpandEnvironment(d *Descriptor, environment map[string]string) (*Descriptor, error)

ExpandEnvironment copies a descriptor and substitutes final-container environment values in capability configuration strings, including groups. It never reads the process environment or expands keys or descriptor metadata. Callers validate the result before selection and composition.

type FieldError

type FieldError struct {
	Path string
	// contains filtered or unexported fields
}

FieldError is a validation failure attributed to one descriptor element. Path uses the same dotted form Positions produces, so an error and the source location it belongs to join without string parsing.

func (*FieldError) Error

func (e *FieldError) Error() string

func (*FieldError) Unwrap

func (e *FieldError) Unwrap() error

type File

type File struct {
	Path    string `json:"path" yaml:"path"`
	Content string `json:"content" yaml:"content"`
	Mode    string `json:"mode,omitempty" yaml:"mode,omitempty"`
	// Overwrite defaults to true; nil means unset. overwrite: false skips
	// the write when the file already exists (e.g. on a persistent volume).
	Overwrite   *bool  `json:"overwrite,omitempty" yaml:"overwrite,omitempty"`
	Description string `json:"description,omitempty" yaml:"description,omitempty"`
}

File is written at sandbox start because its content depends on runtime state the image cannot contain.

type HostMount

type HostMount struct {
	Path string `json:"path" yaml:"path"`
	Mode string `json:"mode,omitempty" yaml:"mode,omitempty"`
}

HostMount requests host-shared storage at an in-container path. The host location and provisioning mechanism belong to the runtime.

func HostMountsOf

func HostMountsOf(needs []Capability) ([]HostMount, error)

HostMountsOf returns every selected host-directory request, in declaration order.

type Inject

type Inject struct {
	Domain string `json:"domain" yaml:"domain"`
	Header string `json:"header,omitempty" yaml:"header,omitempty"`
	// Format renders the credential into the header value, e.g. "Bearer %s".
	Format string `json:"format,omitempty" yaml:"format,omitempty"`
	// Scheme selects a non-header presentation, e.g. "basic".
	Scheme string `json:"scheme,omitempty" yaml:"scheme,omitempty"`
	// Username is the basic-auth username when Scheme is "basic".
	Username string `json:"username,omitempty" yaml:"username,omitempty"`
}

Inject is one proxy header-injection rule.

type InstallHook

type InstallHook struct {
	Command     CommandLine `json:"command" yaml:"command"`
	User        string      `json:"user,omitempty" yaml:"user,omitempty"`
	Env         []string    `json:"env,omitempty" yaml:"env,omitempty"`
	Description string      `json:"description,omitempty" yaml:"description,omitempty"`
}

InstallHook runs once per sandbox at create. It sees only the environment it declares — deny by default — which is hygiene first and also makes its inputs enumerable.

type Kit

type Kit struct {
	// Ref is the reference this kit is consumed by, and the identity it
	// is named by in every merge diagnostic. A registry reference: a
	// local directory or a git URL names a kit that is not published
	// yet, and a set's kits have to be resolvable from the manifest
	// alone.
	Ref string `json:"ref" yaml:"ref"`

	// Digest pins this kit's manifest. Optional in the authored form —
	// where a tag is how an author names a version — and required in
	// the published one, which the frontend fills in from the reference
	// it actually resolved.
	Digest string `json:"digest,omitempty" yaml:"digest,omitempty"`

	// Args supplies this kit's create-phase args, keyed by its own arg
	// names. A literal value is expanded into the merged descriptor at
	// publish, which is what makes the set a pinned artifact. A value
	// that is itself a `${{ kit.args.* }}` reference re-exports the
	// input under the set's name: the reference lands where the
	// placeholder was, and the set's own declaration — signed with the
	// set — is what bounds the value an installer can supply.
	Args map[string]string `json:"args,omitempty" yaml:"args,omitempty"`
}

Kit is one entry in a set's Kits list: a published kit the set is composed of.

It is named the way any kit is consumed — by a registry reference — because the set's whole content comes from it, and the frontend resolving that reference is what pins it. It carries no role: whether it lands as the base or as an overlay is its own descriptor's kind, which the set does not restate.

type Lifecycle

type Lifecycle struct {
	Install []InstallHook `json:"install,omitempty" yaml:"install,omitempty"`
	Startup []StartupHook `json:"startup,omitempty" yaml:"startup,omitempty"`
	Files   []File        `json:"files,omitempty" yaml:"files,omitempty"`

	// Interactive is the argv tail appended to the workload's launch
	// command for an interactive (TTY) session — the one piece of
	// runtime config the image config has no native slot for (Cmd is
	// single-valued). It lives in the lifecycle capability because the
	// engine consumes it the way it consumes the hooks: behavior across
	// the sandbox's life, not a grant.
	Interactive []string `json:"interactive,omitempty" yaml:"interactive,omitempty"`
}

Lifecycle is CapabilityLifecycle's config: everything the engine executes on the kit's behalf across the sandbox's life. Install runs once per sandbox at create, in dependency order, with the network policy's install phase open; a hook sees only the env it declares. Startup runs every boot and must tolerate being re-run. Files are written at start rather than baked because their content depends on runtime state; ${{ kit.args.* }} expands at create, $VAR is left for the shell.

func ExpandLifecycle

func ExpandLifecycle(lc *Lifecycle, values map[string]string) (*Lifecycle, error)

ExpandLifecycle applies arg expansion where the design permits it and nowhere else: install and startup commands, and files content. The capability list is never expanded — the permission gate judges literal policy — and the descriptor itself is never rewritten; the returned copy exists only for execution.

func LifecycleOf

func LifecycleOf(capabilities []Capability) (*Lifecycle, error)

LifecycleOf returns the lifecycle declaration's config, or nil when none is declared.

type MergeOptions

type MergeOptions struct {
	// ContextPath is the in-image path the merged agent-context body
	// will be staged at, written into the merged entry's contentFile.
	// Required when any contribution declares context content.
	ContextPath string
}

MergeOptions carries what the merge cannot derive from the contributions themselves.

type MergeResult

type MergeResult struct {
	// Descriptor is the merged declaration set, under the derived kind.
	Descriptor *Descriptor

	// ContextSources are agent-context bodies in contribution order. Empty
	// when no contribution declared any context content. Sources with an
	// empty Target are concatenated into the merged entry's contentFile.
	// Each source with a Target must instead be staged separately at that
	// path, preserving its conditional selection boundary. The caller owns
	// reading and staging the bodies; this package only computes the paths.
	ContextSources []ContextSource
}

MergeResult is the merged kit: the descriptor it publishes, plus the agent-context bodies the caller has to stage, which the descriptor alone cannot carry.

func Merge

func Merge(contributions []Contribution, opts MergeOptions) (*MergeResult, error)

Merge folds ordered contributions into the one descriptor a flattened kit publishes.

The contributions arrive in composition order — providers before requirers, the set's own declarations last — because that order is what several of the rules below mean by "first" and "last", and deriving it needs the resolver, which does not belong here.

Unconditional context bodies are concatenated at opts.ContextPath; grouped publication stages each ContextSource at its separate Target. Runtime consumers use Compose and retain the input contributions.

type NetworkEntry

type NetworkEntry struct {
	// Hosts are the domain patterns this entry names. A bounded entry in
	// an Allow list names them literally, because a pattern cannot be
	// bounded and unbounded at once; Deny entries and unbounded entries
	// may name patterns.
	Hosts []string `json:"hosts" yaml:"hosts"`

	// Methods are uppercase HTTP method tokens, or the single entry
	// MethodAny. Stating any of them bounds the entry.
	Methods []string `json:"methods,omitempty" yaml:"methods,omitempty"`

	// Paths are globs matched against the request path. Stating one
	// requires stating Methods, so a path always says which methods it
	// bounds; omitted alongside methods, it means every path.
	Paths []string `json:"paths,omitempty" yaml:"paths,omitempty"`
}

NetworkEntry is one egress grant: the hosts it names, and optionally the methods and paths it is bounded to.

A bare entry — hosts and nothing else — grants the connection, which is the whole of what @1 could say, and is written as a plain string. An entry stating methods or paths grants only matching HTTP requests; the connection carries no other traffic, so what the runtime cannot read as HTTP it refuses.

One list rather than a host list beside a separate HTTP block: the two would name the same host twice and leave the wider grant overriding the narrower, which is the opposite of what declaring the narrower means.

func (NetworkEntry) Bounded

func (e NetworkEntry) Bounded() bool

Bounded reports whether the entry narrows its hosts to particular requests rather than granting the connection outright.

func (NetworkEntry) MarshalJSON

func (e NetworkEntry) MarshalJSON() ([]byte, error)

MarshalJSON writes the shorthand back as a string, so a descriptor round-trips to what its author wrote instead of growing an object form they never used.

A stated-but-empty list is the one thing this type cannot write back: Bounded reads it as unbounded, the shorthand has nowhere to put it, and omitempty drops it from the object form. Either way the entry would come out wider than it went in, laundering something validation rejects into something it accepts, so it is refused rather than serialized.

func (*NetworkEntry) UnmarshalJSON

func (e *NetworkEntry) UnmarshalJSON(data []byte) error

UnmarshalJSON accepts a bare host string as shorthand for an unbounded entry, so a kit that bounds nothing writes the @1 list it always wrote. Object form decodes strictly, as every other well-known config does — a custom unmarshaler does not inherit the caller's DisallowUnknownFields.

type NetworkRules

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

NetworkRules is an allow/deny pair of domain patterns. Deny wins.

type NetworkRulesV2

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

NetworkRulesV2 is one phase's egress. Deny wins, as in @1.

type OAuth

type OAuth struct {
	TokenEndpoint  *TokenEndpoint  `json:"tokenEndpoint,omitempty" yaml:"tokenEndpoint,omitempty"`
	ResourceHosts  []string        `json:"resourceHosts,omitempty" yaml:"resourceHosts,omitempty"`
	Sentinels      *Sentinels      `json:"sentinels,omitempty" yaml:"sentinels,omitempty"`
	CredentialFile *CredentialFile `json:"credentialFile,omitempty" yaml:"credentialFile,omitempty"`
	ResponseFields *ResponseFields `json:"responseFields,omitempty" yaml:"responseFields,omitempty"`
	// Passthrough returns the real token to the container: a downgrade.
	Passthrough bool `json:"passthrough,omitempty" yaml:"passthrough,omitempty"`
}

OAuth configures the proxy's OAuth interception for one service.

type OwnedProvide

type OwnedProvide struct {
	Provide
	Owner string
}

OwnedProvide is a provide with the contribution that offered it, so a requirement answered by another kit can be told from one a contribution aims at itself.

type Package

type Package struct {
	Name    string
	Version string
}

Package is one installed package as a database records it. Version is raw, exactly as written; PackageVersion is what turns it into something this model can name.

func ReadApkInstalled

func ReadApkInstalled(r io.Reader) ([]Package, error)

ReadApkInstalled lists the packages an apk installed database records.

apk keeps no removed-package records, so every stanza counts; the stanza shape is the difference, one letter per field.

func ReadDpkgStatus

func ReadDpkgStatus(r io.Reader) ([]Package, error)

ReadDpkgStatus lists the packages a dpkg status file records as installed.

Only installed ones. A removed package keeps its stanza in that file with a config-files status, so taking every stanza would have a kit provide software it no longer carries — which is the opposite of what reading the filesystem was for.

type PackageDatabase

type PackageDatabase struct {
	Namespace string
	Path      string
	Read      func(io.Reader) ([]Package, error)
}

PackageDatabase is one package manager's record of what a filesystem carries, and the namespace its records are published under.

func PackageDatabases

func PackageDatabases() []PackageDatabase

PackageDatabases are the databases §9.6 reads, in the order their entries are emitted.

One table, because two sides of publication consult it and a disagreement between them would be a conformance failure invented by the checker: the frontend derives entries from these paths, and the kit suite holds a published artifact's entries back to them.

type PhasedNetwork

type PhasedNetwork struct {
	Install *NetworkRules `json:"install,omitempty" yaml:"install,omitempty"`
	Runtime *NetworkRules `json:"runtime,omitempty" yaml:"runtime,omitempty"`
}

PhasedNetwork is CapabilityNetworkPolicy's config: egress by phase — install is open while the install hooks run and closed before the agent starts; runtime is what the agent gets.

func NetworkPolicyOf

func NetworkPolicyOf(needs []Capability) (*PhasedNetwork, error)

NetworkPolicyOf returns the network-policy@1 request's config, or nil when none is declared. A descriptor on @2 has no @1 entry and reads as nil here; callers that must see either version use NetworkPolicyV2Of.

type PhasedNetworkV2

type PhasedNetworkV2 struct {
	Install *NetworkRulesV2 `json:"install,omitempty" yaml:"install,omitempty"`
	Runtime *NetworkRulesV2 `json:"runtime,omitempty" yaml:"runtime,omitempty"`
}

PhasedNetworkV2 is CapabilityNetworkPolicyV2's config: PhasedNetwork's phases, each an allow/deny pair of entries rather than of bare hosts.

func NetworkPolicyV2Of

func NetworkPolicyV2Of(needs []Capability) (*PhasedNetworkV2, error)

NetworkPolicyV2Of returns the network-policy request's config in the @2 shape whichever version declared it, or nil when neither is. An @1 config lifts into @2 as unbounded entries, which is what it means: every method and path on the hosts it allows. Consumers that read the policy to enforce it use this, so a kit does not change what they see by moving between versions.

type Phases

type Phases []string

Phases accepts a scalar or list, preserving the scalar spelling when encoding a single-phase entry. Credentials and SSH agents share this grammar.

func (Phases) MarshalJSON

func (p Phases) MarshalJSON() ([]byte, error)

func (*Phases) UnmarshalJSON

func (p *Phases) UnmarshalJSON(data []byte) error

type Port

type Port struct {
	Name      string `json:"name,omitempty" yaml:"name,omitempty"`
	Container int    `json:"container" yaml:"container"`
	Transport string `json:"transport,omitempty" yaml:"transport,omitempty"`
}

Port is CapabilityPort's config: one in-container port to publish.

func PortsOf

func PortsOf(needs []Capability) ([]Port, error)

PortsOf returns every port request, in declaration order.

type Position

type Position struct {
	Line      int
	Column    int
	EndLine   int
	EndColumn int
}

Position is the 1-based source location of one YAML element.

func PositionFor

func PositionFor(positions map[string]Position, path string) (Position, bool)

PositionFor finds a field's location, falling back to its nearest containing element when a required field is absent. The empty path denotes the document, so even missing top-level fields have a location.

type Provide

type Provide struct {
	Name    string
	Version string // empty when unversioned
}

Provide is a parsed provides entry: a capability name with an optional version.

func ParseProvide

func ParseProvide(s string) (Provide, error)

ParseProvide parses "name" or "name@version". The parsed Name is normalized: bare names gain the default namespace.

type Require

type Require struct {
	Name        string
	Constraints []Constraint // empty when unconstrained
}

Require is a parsed requires/integrates entry: a capability name with zero or more version constraints. Empty Constraints means any provider of Name satisfies. Multiple constraints are ANDed (comma-separated in the authored form). Ranges and pins change only the Satisfies predicate: resolution stays a closed-set check, never a backtracking solver — the set already names its providers, and §5.3 admits at most one provider per capability name.

func ParseRequire

func ParseRequire(s string) (Require, error)

ParseRequire parses a requires/integrates entry:

name
name <op> version
name <op> version, <op> version, ...

where <op> is one of >=, >, <=, <, =. Whitespace around operators and commas is optional. The legacy form "name >= version" is unchanged. Unsatisfiable constraint sets (conflicting pins, inverted bounds) are rejected at parse. The parsed Name is normalized: bare names gain the default namespace.

type Resources

type Resources struct {
	CPU    float64 `json:"cpu,omitempty" yaml:"cpu,omitempty"`
	Memory string  `json:"memory,omitempty" yaml:"memory,omitempty"`
	GPU    string  `json:"gpu,omitempty" yaml:"gpu,omitempty"`
}

Resources is CapabilityResources's config: the workload's compute.

func ResourcesOf

func ResourcesOf(needs []Capability) (*Resources, error)

ResourcesOf returns the resources request's config, or nil when none is declared.

type ResponseFields

type ResponseFields struct {
	AccessToken  string `json:"accessToken,omitempty" yaml:"accessToken,omitempty"`
	RefreshToken string `json:"refreshToken,omitempty" yaml:"refreshToken,omitempty"`
	ExpiresIn    string `json:"expiresIn,omitempty" yaml:"expiresIn,omitempty"`
}

ResponseFields maps the provider's token-response field names. The refresh mapping is what lets a runtime mask the real refresh token when the provider spells the field nonstandardly (cursor's camelCase refreshToken) or reuses one field for both roles (devin's token).

type SSHAgent

type SSHAgent struct {
	Phase SSHAgentPhases `json:"phase" yaml:"phase"`
	// Unrestricted defaults to true. False requires at least one bound.
	Unrestricted *bool    `json:"unrestricted,omitempty" yaml:"unrestricted,omitempty"`
	Sign         []string `json:"sign,omitempty" yaml:"sign,omitempty"`
	Authenticate []string `json:"authenticate,omitempty" yaml:"authenticate,omitempty"`
}

SSHAgent is CapabilitySSHAgent's config. Phase accepts one phase or a non-empty list; each phase gets the same signing rules.

func (SSHAgent) Bounded

func (a SSHAgent) Bounded() bool

Bounded reports whether the entry limits signatures to named purposes.

type SSHAgentCapability

type SSHAgentCapability struct {
	SSHAgent
	Optional    bool
	Name        string
	Description string
}

SSHAgentCapability is one SSH agent request with its entry-level optionality and display metadata, which the runtime needs for preflight.

func SSHAgentsOf

func SSHAgentsOf(needs []Capability) ([]SSHAgentCapability, error)

SSHAgentsOf returns SSH agent requests in declaration order. One request may name both phases; callers enforcing one phase use SSHAgentsOfPhase.

func SSHAgentsOfPhase

func SSHAgentsOfPhase(needs []Capability, phase string) ([]SSHAgentCapability, error)

SSHAgentsOfPhase returns requests granting the agent in a given phase.

type SSHAgentPhases

type SSHAgentPhases = Phases

SSHAgentPhases is the phase list used by SSH-agent configurations.

type SelectCapability

type SelectCapability func(context.Context, Descriptor, Capability) CapabilityDecision

SelectCapability answers whether the runtime will provide one expanded entry. The descriptor is the owning Kit after argument and environment expansion, including its DisplayName and all declarations before selection. Descriptor and capability inputs are passed by value. The context carries cancellation and deadlines. It must not apply the entry: the enclosing group can still be rejected.

func Supported

func Supported(types ...string) SelectCapability

Supported constructs a selector from a runtime's claimed type list.

type Selection

type Selection struct {
	Capabilities []Capability      `json:"capabilities"`
	Selected     []SelectionRecord `json:"selected,omitempty"`
	Skipped      []SelectionRecord `json:"skipped,omitempty"`
}

Selection retains the create-time decision separately from merged grants.

func SelectCapabilities

func SelectCapabilities(ctx context.Context, d *Descriptor, selectCapability SelectCapability) (Selection, error)

SelectCapabilities validates declarations in their actual descriptor context before calling the selector and flattens only wholly selected constructs. A nil descriptor or selector is an error, never an implicit default. Required rejection returns records alongside the error for diagnostics. Cancellation returns a zero selection and the context error.

type SelectionRecord

type SelectionRecord struct {
	Path          string             `json:"path"`
	Name          string             `json:"name,omitempty"`
	Source        *CapabilitySource  `json:"source,omitempty"`
	Members       []string           `json:"members"`
	MemberSources []CapabilitySource `json:"memberSources,omitempty"`
	Rejected      []string           `json:"rejected,omitempty"`
	// Decisions follows Members order, including accepted members of skipped
	// groups. Older persisted records may omit it.
	Decisions []CapabilityDecision `json:"decisions,omitempty"`
}

SelectionRecord identifies an original item and its evaluated members. Path is in the consumed descriptor; Source, when present, is publisher metadata.

type Sentinels

type Sentinels struct {
	AccessToken  string `json:"accessToken,omitempty" yaml:"accessToken,omitempty"`
	RefreshToken string `json:"refreshToken,omitempty" yaml:"refreshToken,omitempty"`
}

Sentinels are the placeholder tokens rendered in-container.

type StartupHook

type StartupHook struct {
	Command     CommandLine `json:"command" yaml:"command"`
	User        string      `json:"user,omitempty" yaml:"user,omitempty"`
	Background  bool        `json:"background,omitempty" yaml:"background,omitempty"`
	Env         []string    `json:"env,omitempty" yaml:"env,omitempty"`
	Description string      `json:"description,omitempty" yaml:"description,omitempty"`
}

StartupHook runs every boot and must tolerate being re-run.

type Surface

type Surface struct {
	NetworkInstallAllow []string `json:"networkInstallAllow,omitempty"`
	NetworkInstallDeny  []string `json:"networkInstallDeny,omitempty"`
	NetworkRuntimeAllow []string `json:"networkRuntimeAllow,omitempty"`
	NetworkRuntimeDeny  []string `json:"networkRuntimeDeny,omitempty"`

	// The HTTP lists are what may be done over the allowed connections,
	// one atomic entry per method, host, and path as "<METHOD>
	// <host><path>". A host with no allow rule bounding it appears under
	// every method, because that is what it grants. Kept apart from the
	// host lists because the two grant differently: a host list entry
	// opens a connection, while these bound what may be done over it, so
	// a gate naming both "network" could not say whether an update opened
	// a new host or widened an existing one.
	NetworkInstallHTTPAllow []string `json:"networkInstallHTTPAllow,omitempty"`
	NetworkInstallHTTPDeny  []string `json:"networkInstallHTTPDeny,omitempty"`
	NetworkRuntimeHTTPAllow []string `json:"networkRuntimeHTTPAllow,omitempty"`
	NetworkRuntimeHTTPDeny  []string `json:"networkRuntimeHTTPDeny,omitempty"`

	CredentialsInstall []string `json:"credentialsInstall,omitempty"`
	CredentialsRuntime []string `json:"credentialsRuntime,omitempty"`

	// SSHAgent holds what the SSH agent may sign, per phase: "<phase>"
	// for an unbounded grant, "<phase> sign <namespace>" and "<phase>
	// authenticate <[user@]host>" for a bounded one. Kept apart from
	// credentials because the agent grants more than one service: an
	// unbounded grant signs with every key it holds, for anything the
	// sandbox asks.
	SSHAgent []string `json:"sshAgent,omitempty"`

	StoragePaths []string `json:"storagePaths,omitempty"`

	// HostMountPaths grant access to host-shared directories. Keeping
	// them apart from sandbox storage makes a change to host sharing a
	// widening even when the in-container destination stays the same.
	HostMountPaths []string `json:"hostMountPaths,omitempty"`

	// SkillsPaths are the in-container paths the host's shared skills
	// store is asked for. Kept apart from StoragePaths because the two
	// grant different things: a volume is storage the sandbox is given,
	// while this is a host directory the user filled with `sbx skills
	// import` being handed to a kit. A gate naming both "storage" would
	// not say which one was gained.
	SkillsPaths []string `json:"skillsPaths,omitempty"`

	// SkillsWritePaths are the subset of SkillsPaths the kit asks to
	// write. Separate from the paths themselves so access cannot be
	// confused with a path: write is the larger grant, and a sandbox that
	// can rewrite the shared store affects every later one.
	SkillsWritePaths []string `json:"skillsWritePaths,omitempty"`
	Ports            []string `json:"ports,omitempty"`
	USB              []string `json:"usb,omitempty"`
	Privileged       bool     `json:"privileged,omitempty"`

	// Services holds one entry per requested runtime-provided service:
	// the type string, with a digest of the config when one is present,
	// so a config change moves the surface the way any widening does.
	// Optional requests are surfaced too — optional only changes what
	// happens when the host cannot provide the service, not what is
	// granted when it can.
	Services []string `json:"services,omitempty"`
}

Surface is the permission-relevant projection of a descriptor: everything the host must grant, normalized (sorted, deduplicated) so two surfaces compare structurally. The gate stores a kit's surface in the lock and diffs it against a candidate update's; version movement that stays within the granted surface applies silently, movement that widens it stops for approval.

func SurfaceOf

func SurfaceOf(d *Descriptor) Surface

SurfaceOf projects a descriptor's needs onto its permission surface. Supply the selected, merged descriptor for enforcement. On an unselected declaration this conservatively inventories all members, without group grants. Well-known types decode into the direction-aware fields the gate diffs with per-category semantics; every other type — and any known entry whose config fails to decode — lands in Services as type + config digest, so nothing a kit requests can escape the surface.

type TokenEndpoint

type TokenEndpoint struct {
	Host string `json:"host" yaml:"host"`
	Path string `json:"path,omitempty" yaml:"path,omitempty"`
}

TokenEndpoint names the OAuth token endpoint the proxy intercepts.

type USBDevice

type USBDevice struct {
	VendorID  string `json:"vendorId,omitempty" yaml:"vendorId,omitempty"`
	ProductID string `json:"productId,omitempty" yaml:"productId,omitempty"`
	Class     string `json:"class,omitempty" yaml:"class,omitempty"`
}

USBDevice is CapabilityUSBDevice's config: one passthrough match by vendor/product or class.

type USBDeviceCapability

type USBDeviceCapability struct {
	USBDevice

	Optional    bool
	Name        string
	Description string
}

USBDeviceCapability is one decoded usb-device request with its entry-level declaration state.

func USBDevicesOf

func USBDevicesOf(needs []Capability) ([]USBDeviceCapability, error)

USBDevicesOf returns every usb-device request, in declaration order.

type ValidationErrors

type ValidationErrors []error

ValidationErrors contains independent failures in validation order. A validator returns nil on success, never an empty ValidationErrors. Each field failure remains accessible through errors.As and Unwrap.

func (ValidationErrors) Error

func (e ValidationErrors) Error() string

func (ValidationErrors) Unwrap

func (e ValidationErrors) Unwrap() []error

type Volume

type Volume struct {
	Path string `json:"path" yaml:"path"`
	Size string `json:"size,omitempty" yaml:"size,omitempty"`
	// Tmpfs makes the path a tmpfs mount instead of a block volume.
	Tmpfs bool `json:"tmpfs,omitempty" yaml:"tmpfs,omitempty"`
	// Mode is an octal permission string, e.g. "1777".
	Mode string `json:"mode,omitempty" yaml:"mode,omitempty"`
}

Volume is CapabilityVolume's config: one persistent (or tmpfs) path and its characteristics.

func VolumesOf

func VolumesOf(needs []Capability) ([]Volume, error)

VolumesOf returns every volume request, in declaration order.

type Widening

type Widening struct {
	// Category names the permission class, e.g. "network.runtime.allow".
	Category string `json:"category"`
	// Detail is the specific grant being requested, human-readable.
	Detail string `json:"detail"`
}

Widening is one way a candidate surface asks for more than a granted one.

func DiffWidenings

func DiffWidenings(granted, candidate Surface) []Widening

DiffWidenings reports every way candidate exceeds granted. Removals and narrowings are not reported: giving up a permission needs no approval. A removed deny entry IS a widening — the deny was part of what made the grant acceptable.

func (Widening) String

func (w Widening) String() string

Jump to

Keyboard shortcuts

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