Documentation
¶
Overview ¶
Pure structural methods on the generated/aliased spec types, relocated here from package main by the CUE-single-source cutover (WF-B THE REPOINT). A method on an aliased type (e.g. type BoxConfig = spec.Box in package main) cannot be declared in package main — the type is foreign — so every method that touches ONLY the type's own fields + the stdlib lives here, beside the type it acts on. Methods that reach package-main internals (VerbCatalog, Phase/Venue, EmitOpts, the readiness/preempt consts, …) stay in package main as free functions taking a *spec.X — they are NOT here. STANDALONE: this package imports nothing from package main.
Charly-name aliases for the generated `spec` types.
WF-B repoints package main onto this package via zero-churn `type <CharlyName> = spec.<CharlyName>` aliases. For that to compile the spec package must EXPOSE every param type under its charly name. The DEFMAP names the (cue-def → charly) mapping.
MECHANISM (RDD finding): `cue exp gengotypes` v0.16.1 does NOT propagate a def-level `@go(CharlyName)` rename to the FIELDS that reference that def — the referencing field keeps the original def name and the emitted Go dangles (uncompilable). Verified on a live spike. So the charly NAME is exposed here as a Go type alias (`type BoxConfig = Box`) instead of via a def-level attribute: identical drop-in semantics (the alias and the generated type are the SAME type), zero generated-file churn, and no broken references. The field-level `@go(GoName,…)` attributes (which DO work) carry the per-field name + pointer + type overrides in schema/*.cue.
HAND-WRITTEN — not emitted by `task cue:gen`; the reproducibility gate (gen_repro_test.go) only covers cue_types_gen.go + vocab_gen.go.
Hand-written types for shapes `cue exp gengotypes` cannot express faithfully:
- PortSpec — the candy `port:` element is a (int | "proto:port") disjunction the Go normalizer canonicalizes to {port, protocol}; gengotypes degrades the disjunction to `any`.
- VmDeployState — MACHINE-WRITTEN runtime deploy state. Its CUE def carries an open `...` tail (forward-evolving state record), which makes gengotypes degrade the whole def to `map[string]any`. The hand Go field is a CONCRETE struct, so #VmDeployState is @go(-)'d and the faithful struct (+ its nested state sub-types) is mirrored HERE. Kept in lockstep with the CUE def (schema/deploy.cue #VmDeployState) — every field here has a matching CUE field (e.g. PortForwards ⇄ port_forwards, the auto-forward record).
Ported from the package-main param structs (the source of truth); keep in lockstep until WF-B repoints package main onto this package. STANDALONE — no package-main symbol is referenced.
Scalar collapses for the CUE-single-source cutover (WF-B THE REPOINT).
CUE validates these named scalars (CalVer/EntityRef/CandyRef pattern strings, VmSize/Size/PortPin/Duration patterns, the 11 live-verb method enums, the build-format/-type enums) at load — so the Go side gains nothing from a DISTINCT named string type, and package-main code uses plain string/[]string throughout. The matching CUE defs are `@go(-)` (no generated named type); the referencing fields keep the def name, which these Go ALIASES resolve to the builtin. An alias (not a defined type) is load-bearing: `&op.Cdp` is a `*string`, `[]PortPin` IS `[]string`, and an untyped/string assignment to a CalVer/EntityRef field just works — exactly the charly drop-in semantics.
MECHANISM (RDD finding, mirrors spec/charly_names.go): `cue exp gengotypes` v0.16.1 does NOT propagate a def-level rename/suppression to the FIELDS that reference the def, so a bare `@go(-)` leaves the field dangling on the deleted name. Exposing the name as a Go alias here makes every reference compile while collapsing the Go type to the builtin.
HAND-WRITTEN — not emitted by `task cue:gen`.
Hand-written union / shorthand types for the generated `spec` package.
`cue exp gengotypes` degrades every CUE disjunction to `any` / `map[string]any` / an empty struct (the Go type system cannot express a disjunction). For the handful of schema defs whose Go shape charly relies on, the matching CUE def is annotated `@go(-)` in schema/*.cue (suppressing the lossy generated type) and the faithful type is hand-written HERE, in the SAME package, so the generated structs reference these by name. These are ported from the hand-written charly param types (the source of truth named in each comment); keep them in lockstep until WF-B repoints package main onto this package.
STANDALONE: this package builds on its own (`go build ./spec/...`) and reaches into NO package-main symbol — every method here is self-contained.
vm_box_labels.go — the VM-box metadata label contract (cutover plan task 2: the generic VM box). spec.VmBoxMetadata rides ONE OCI label — spec.LabelVmBox (ai.opencharly.vm.box) — whole-struct-marshaled as JSON, the VM analog of the per-field ai.opencharly.* label family the pod side names via deploykit's CapabilityLabelMap. Every exported field of VmBoxMetadata MUST have a VmBoxLabelMap entry: the map is the struct ↔ label sync table proving the WHOLE contract rides the one label, so a future source-less VM deploy (`charly fleet from-box vm:<ref>` / VmCapabilitiesFromLabels) can reconstruct every field from a pushed box image. Maintained alongside #VmBoxMetadata (CUE-sourced, schema/vm_box_metadata.cue) — adding a field to spec.VmBoxMetadata without an entry here trips CheckVmBoxLabelCompleteness and breaks the build.
Index ¶
- Constants
- Variables
- func BareCandyRef(ref string) string
- func BoxNamesOf(m BoxMap) []string
- func BuilderPhaseTemplate(b *Builder, phase Phase, venue Venue) string
- func CandyCapabilitiesError(layers map[string]CandyReader, order []string, missing []string) error
- func CheckLevelReaches(have, want string) bool
- func CheckRequiredCapabilities(layers map[string]CandyReader, order []string, agg *AggregatedCandyCaps) []string
- func CheckVmBoxLabelCompleteness() error
- func ChildProjectEnv(inherited []string, projectDir string) []string
- func ComputeCalVer() string
- func ComputeCalVerAt(t time.Time) string
- func ContainerName(boxName string) string
- func ContainerNameInstance(boxName, instance string) string
- func CurrentUsername() string
- func DecodePluginKindMap[T any](uf *UnifiedFile, kind string) map[string]*T
- func DedupeNonEmpty(in []string) []string
- func DefaultDeployConfigPath() (string, error)
- func DeployKey(boxName, instance string) string
- func DeriveDeploymentName(imageRef string) string
- func DistroTagChain(distro, version string) []string
- func EncodeBox(b BoxConfig) json.RawMessage
- func EncodeInlineCandy(il *InlineCandy) json.RawMessage
- func ExpandPath(path string, home string) string
- func ExternalBuilderPluginRef(word string) (string, bool)
- func FinalizeCandyRefs(m *CandyModel, v *CandyView, refs CandyRefs)
- func FirstYAMLVersionLine(data []byte) string
- func FlattenedEnvMap(base map[string]string, overrides *CandyServiceOverrides) map[string]string
- func FleetDelArgv(name string) []string
- func FormatCacheMount(dst, sharing string, uid, gid int) string
- func FormatPhaseTemplate(f *Format, phase Phase, venue Venue) string
- func FormatPortMapping(p ParsedPortMapping) string
- func GateEnabled(g Gate, opts EmitOpts) bool
- func GitRemoteIdentity(dir string) string
- func InputStr(c *Op, key string) string
- func IsExternalStepKind(k StepKind) bool
- func IsRemoteCandyRefString(ref string) bool
- func IsRemoteImageRef(ref string) bool
- func IsValidCheckLevel(level string) bool
- func LeafName(ref string) string
- func MappingRoot(n *yaml.Node) *yaml.Node
- func MergePluginKindsMap(dst *map[string]map[string]json.RawMessage, ...)
- func NormalizeGitRemoteURL(raw string) string
- func NormalizePCIVendor(s string) string
- func NormalizeRepoIdentity(s string) string
- func NormalizeRepoSpec(spc string) (repoPath, version string)
- func PackageNames(items []PackageItem) []string
- func ParseDeployKey(key string) (boxName, instance string)
- func PathIsSystemScoped(p string) bool
- func PodName(boxName string) string
- func PodNameInstance(boxName, instance string) string
- func PodQuadletFilename(boxName string) string
- func PodQuadletFilenameInstance(boxName, instance string) string
- func QuadletDir() (string, error)
- func QuadletExists(boxName string) (bool, error)
- func QuadletExistsInstance(boxName, instance string) (bool, error)
- func QuadletFilename(boxName string) string
- func QuadletFilenameInstance(boxName, instance string) string
- func RenderCacheMounts(mounts []CacheMount, uid, gid int, sep string, trailing bool) string
- func RenderCacheMountsAuto(mounts []CacheMount, uid, gid int, sep string, trailing bool) string
- func RepoIdentity(ref, baseDir string) string
- func ResolveBoxName(box string) string
- func ResolveCheckLevel(level string) string
- func ResolveHome(p *InstallPlan, home string)
- func ResolvePluginKindViaPlugin[T any](uf *UnifiedFile, kind string, resolve func(json.RawMessage) (*T, error)) map[string]*T
- func RootRepoIdentity(dir string) string
- func ScalarNode(v string) *yaml.Node
- func ServiceName(boxName string) string
- func ServiceNameInstance(boxName, instance string) string
- func SidecarContainerName(boxName, sidecarName string) string
- func SidecarContainerNameInstance(boxName, instance, sidecarName string) string
- func SidecarQuadletFilename(boxName, sidecarName string) string
- func SidecarQuadletFilenameInstance(boxName, instance, sidecarName string) string
- func SortedDeployKeys(m map[string]FleetNode) []string
- func SortedMemberKeys(members map[string]*FleetNode) []string
- func SplitNamespaceRef(ref string) (ns, rest string, ok bool)
- func SplitRepoAndSubPath(ref string) (repoPath, subPath, name string)
- func SplitVmAddress(name string) (plain string, isVm bool)
- func StampDescent(node *Deploy, traitsFor func(word string) *DeployTraits)
- func StatusFromState(state string) string
- func StripCandyRefVersion(ref string) (string, string)
- func StripPortSuffix(s string) (string, string)
- func StripURLScheme(ref string) string
- func SystemdUserDir() (string, error)
- func TrimPreview(s string) string
- func ValidateDeployRequiresBox(deploy map[string]FleetNode) error
- func ValidateDeploymentChildren(path string, node *FleetNode) error
- func ValidateDeploymentName(name, parentPath string) error
- func ValidateDeploymentTree(deploy map[string]FleetNode) error
- func VenueIsAgentProvisioned(uf *UnifiedFile, venue string) bool
- func VmDomainIdentity(deployName string) string
- func VmNameFromDeployName(deployName string) (string, error)
- func VmSshAlias(vmName string) string
- func WrapContainerCommand(script string) string
- type AUR
- type AdbEndpoint
- type Agent
- type AgentAbortControl
- type AgentDelegationEdge
- type AgentEvent
- type AgentExecSpec
- type AgentFederationRecord
- type AgentResolveInput
- type AgentResolveReply
- type AgentRunRequest
- type AgentRuntimeCapability
- type AgentSession
- type AgentSessionBinding
- type AgentTeam
- type AgentTeamMember
- type AgentTeamRecord
- type AggregatedCandyCaps
- type AggregatedCandyCapsView
- type AliasConfig
- type AliasYAML
- type AlpineBootstrap
- type Android
- type AndroidDeployVenue
- type AndroidResolveInput
- type AndroidResolveReply
- type AndroidSpec
- type ApkInstallStep
- type ApkPackageSpec
- type ArbiterInvokeInput
- type ArbiterInvokeReply
- type ArtifactRef
- type ArtifactView
- type AutoLogin
- type BakedLabelSet
- type BaseChainNode
- type BaseUser
- type Bootloader
- type Bootstrap
- type Box
- type BoxAlias
- type BoxConfig
- type BoxFetchResolveReply
- type BoxFetchResolveRequest
- type BoxMap
- type BoxMerge
- type BoxMetadata
- type BoxRefResolveReply
- type BoxRefResolveRequest
- type BuildEnsureReply
- type BuildEnsureRequest
- type BuildEnv
- type BuildFormat
- type BuildPkgReply
- type BuildPkgRequest
- type BuildReply
- type BuildRequest
- type BuildResolveBox
- type BuildResolveReply
- type BuildResolveRequest
- type BuildResolvedBox
- type BuildStageContext
- type BuildTarget
- type BuildType
- type Builder
- type BuilderCollectInput
- type BuilderCollectReply
- type BuilderConfig
- type BuilderDef
- type BuilderMap
- type BuilderPreresolved
- type BuilderResolveInput
- type BuilderResolveReply
- type BuilderReverseInput
- type BuilderReverseReply
- type BuilderRunOpts
- type BuilderStep
- type CLIArg
- type CLILeaf
- type CLIModel
- type CacheConfig
- type CacheMount
- type CacheMountSpec
- type CacheMountView
- type CalVer
- type Candy
- type CandyAlias
- type CandyArtifact
- type CandyArtifactRewrite
- type CandyCandidate
- type CandyCapabilities
- type CandyCapabilitiesView
- type CandyCapability
- type CandyData
- type CandyExtract
- type CandyHook
- type CandyMCPProvide
- type CandyModel
- type CandyReader
- type CandyRecord
- type CandyRef
- type CandyRefEntry
- type CandyRefs
- type CandyRoute
- type CandyScanner
- type CandySecret
- type CandyService
- type CandyServiceOverrides
- type CandyView
- type CandyVocab
- type CandyVolume
- type CandyYAML
- type CapabilityInitDef
- type CapabilityService
- type Check
- type CheckBedGpuPrereqReply
- type CheckBedGpuPrereqRequest
- type CheckBedMember
- type CheckBedPrereqSkip
- type CheckBedReply
- type CheckContext
- type CheckDrainEndpointCleanupsRequest
- type CheckEndpointResolveReply
- type CheckEndpointResolveRequest
- type CheckEnv
- type CheckExecutor
- type CheckGraphicsEndpoint
- type CheckHTTPRequest
- type CheckHTTPResponse
- type CheckImageLabelResolveReply
- type CheckImageLabelResolveRequest
- type CheckLoadPluginsReply
- type CheckLoadPluginsRequest
- type CheckResult
- type CheckRunMode
- type CheckRunReply
- type CheckRunRequest
- type CheckRunResults
- type CheckVenueResolveReply
- type CheckVenueResolveRequest
- type CheckVerbProvider
- type CheckVerbResult
- type CliReply
- type CliRequest
- type CloudInitRuntimeParams
- type CollectedAlias
- type Config
- func (c *Config) AllBoxNames() []string
- func (c *Config) BoxConfig(name string) (BoxConfig, bool)
- func (c *Config) BoxNames() []string
- func (c *Config) EachBox(yield func(string, BoxConfig) bool)
- func (c *Config) FindBoxByLeaf(leaf string) (string, bool)
- func (c *Config) HasBox(name string) bool
- func (c *Config) ResolveBoxRef(ref string) (BoxConfig, *Config, bool)
- func (c *Config) SetBox(name string, b BoxConfig)
- func (c *Config) WalkBaseChain(boxName string) []BaseChainNode
- func (c *Config) WalkBaseChainBuild(baseName string) []string
- func (c *Config) WalkBaseChainDistro(baseName string) []string
- type ConstructStepReply
- type ConstructStepRequest
- type Context
- type Copy
- type CredentialHealth
- type CredentialMount
- type DataYAML
- type Debootstrap
- type DebootstrapRepo
- type Deploy
- func (n *Deploy) HasChildren() bool
- func (c Deploy) IsDisposable() bool
- func (c Deploy) IsEphemeral() bool
- func (c Deploy) IsGroup() bool
- func (c Deploy) IsPreemptible() bool
- func (c Deploy) LifecycleTag() string
- func (c Deploy) PreemptionHolds() []string
- func (c Deploy) RequiredExclusive() []string
- func (c Deploy) RequiredShared() []string
- type DeployCompileReply
- type DeployCompileRequest
- type DeployContext
- type DeployDelResolveReply
- type DeployDelResolveRequest
- type DeployExecutor
- type DeployExpose
- type DeployNodeDelDispatchReply
- type DeployNodeDelDispatchRequest
- type DeployOverlayReply
- type DeployOverlayRequest
- type DeployPluginsConnectReply
- type DeployPluginsConnectRequest
- type DeployProbes
- type DeployRecord
- type DeployReply
- type DeployReplyRecord
- type DeployResolveTargetAddReply
- type DeployResolveTargetAddRequest
- type DeployResources
- type DeploySecret
- type DeployStorage
- type DeployTargetDelOpts
- type DeployTargetDispatchReply
- type DeployTargetDispatchRequest
- type DeployTargetLogsOpts
- type DeployTargetRebuildOpts
- type DeployTargetStatus
- type DeployTraits
- type DeployTraitsCarrier
- type DeployVenue
- type DeployVolume
- type Deploy_RecordWrap
- type DeploymentStatus
- type DescentDescriptor
- type DesktopCategory
- type DesktopEntry
- type DesktopIcon
- type DesktopWindow
- type DetectedDevices
- type Diagnostic
- type Diagnostics
- type DiscoverConfig
- type DiscoveredManifest
- type DiskLayout
- type DisplayManager
- type Distro
- type DistroBuilderCandidate
- type DistroConfig
- func (dc *DistroConfig) AllFormatNames() []string
- func (dc *DistroConfig) ExpandPackageInheritance(tags []string) []string
- func (dc *DistroConfig) FindFormat(name string) *Format
- func (dc *DistroConfig) ResolveDistro(distroTags []string) *ResolvedDistro
- func (dc *DistroConfig) ResolveInherits(def *ResolvedDistro, maxDepth int) *ResolvedDistro
- func (dc *DistroConfig) ValidFormat(name string) bool
- type DistroDef
- type DistroInstaller
- type DistroInstallerFile
- type DistroPackages
- type DistroResolveInput
- type DistroResolveReply
- type Dnf
- type DoMode
- type DocParser
- type DocShape
- type Duration
- type EmitOpts
- type EmitReply
- type EmitTarget
- type EncExecInput
- type EncExecReply
- type EncVolumePlan
- type EntityRef
- type EnvConfig
- type EnvDependency
- type EnvKV
- type EnvProvideEntry
- type EnvVar
- type EphemeralLifetime
- type EphemeralRegisterReply
- type EphemeralRegisterRequest
- type EphemeralRuntime
- type EphemeralTeardownReply
- type EphemeralTeardownRequest
- type ExecContext
- type ExternalPluginStep
- type ExternalStep
- type ExtractStep
- type ExtractYAML
- type FeatureEntity
- type FileStep
- type FleetConfig
- type FleetNode
- type Format
- type FormatRule
- type Gate
- type GitCache
- type GitCacheEntry
- type GoogleAccount
- type GpuProbeInput
- type GpuProbeReply
- type GpuSelector
- type GpuSwitchInput
- type GpuSwitchReply
- type HelmChart
- type HexColor
- type HolderAddr
- type HolderDescriptor
- type Hook
- type HooksConfig
- type HostContext
- type HostEnv
- type ImageUserInput
- type ImportEntry
- type ImportList
- type Incident
- type Init
- type InitConfig
- func (ic *InitConfig) ActiveInit(layers map[string]CandyReader, candyOrder []string) map[string]*ResolvedInit
- func (ic *InitConfig) DetectCandyInit(ly *CandyYAML, candyPath string) []string
- func (ic *InitConfig) InitNames() []string
- func (ic *InitConfig) ResolveInitSystem(layers map[string]CandyReader, candyOrder []string, explicit string) (string, *ResolvedInit)
- type InitDef
- type InitResolveInput
- type InitResolveReply
- type InitResolveRequest
- type InitServiceSchema
- type InlineCandy
- type InstallContext
- type InstallOpts
- type InstallOptsConfig
- type InstallPlan
- type InstallPlanView
- type InstallStep
- type InstallStepView
- type InstallerSeedContext
- type Iterate
- type IterateConfig
- type KeyValue
- type Kubernetes
- type KubernetesDeploy
- type KubernetesDeployVenue
- type KubernetesGatewayAPI
- type KubernetesGenFile
- type KubernetesGenInput
- type KubernetesGenReply
- type KubernetesGenerateKustomizeReply
- type KubernetesGenerateKustomizeRequest
- type KubernetesHostname
- type KubernetesImagesDefaults
- type KubernetesIngressDefaults
- type KubernetesObservability
- type KubernetesPatch
- type KubernetesPodDefaults
- type KubernetesResolveInput
- type KubernetesResolveReply
- type KubernetesResourceDefaults
- type KubernetesResourceValues
- type KubernetesResources
- type KubernetesSecretsBackend
- type KubernetesSpec
- type KubernetesStorage
- type LabelDataEntry
- type LabelDescriptionSet
- type LabelRouteEntry
- type LabelSecretEntry
- type LabelShellSet
- type LabelSkillEntry
- type LabelSkillReference
- type LabelVolumeEntry
- type LabeledDescription
- type LedgerConfig
- type LibvirtAudio
- type LibvirtCPU
- type LibvirtCPUCache
- type LibvirtCPUFeature
- type LibvirtCPUTopology
- type LibvirtCPUTune
- type LibvirtChannel
- type LibvirtClock
- type LibvirtConsole
- type LibvirtDevices
- type LibvirtDisk
- type LibvirtDomain
- type LibvirtEmulatorPin
- type LibvirtFeatures
- type LibvirtFilesystem
- type LibvirtGraphics
- type LibvirtGraphicsGL
- type LibvirtGraphicsListen
- type LibvirtGraphicsListeners
- type LibvirtHostdev
- type LibvirtHub
- type LibvirtHugepages
- type LibvirtHyperV
- type LibvirtIOMMU
- type LibvirtIOThreadPin
- type LibvirtInput
- type LibvirtInterface
- type LibvirtKVM
- type LibvirtLaunchSecurity
- type LibvirtListenOne
- type LibvirtMemBalloon
- type LibvirtMemTune
- type LibvirtMemnode
- type LibvirtMemoryBacking
- type LibvirtNUMACell
- type LibvirtNUMAMemory
- type LibvirtNUMATune
- type LibvirtPCIHex
- type LibvirtPanic
- type LibvirtParallel
- type LibvirtPortForward
- type LibvirtRNG
- type LibvirtRedirDev
- type LibvirtResource
- type LibvirtSecLabel
- type LibvirtSerial
- type LibvirtShmem
- type LibvirtSmartcard
- type LibvirtSound
- type LibvirtSpinlocks
- type LibvirtSysInfo
- type LibvirtTPM
- type LibvirtTimer
- type LibvirtURI
- type LibvirtUSB
- type LibvirtVCPUPin
- type LibvirtVendorID
- type LibvirtVideo
- type LibvirtVideoDriver
- type LibvirtVideoResolution
- type LibvirtVsock
- type LibvirtWatchdog
- type LifecycleOpts
- type LifecyclePrepareInput
- type LifecycleTarget
- type LoadedDoc
- type LoadedProject
- type LoaderExecutor
- type LoaderWalkRequest
- type Local
- type LocalPkg
- type LocalPkgBuildContext
- type LocalPkgInstallStep
- type LocalResolveInput
- type LocalResolveReply
- type LocalSpec
- type MCPProvideEntry
- type MCPServerYAML
- type Marketplace
- type MarketplaceFamily
- type MarketplaceMCPServer
- type MarketplaceSettings
- type MatchOpMap
- type Matcher
- type MatcherList
- type MaterializeProjectSeams
- type MaterializeSeams
- type MaterializedProject
- type Materializer
- type MergeConfig
- type MergeReply
- type MergeRequest
- type NamespaceMount
- type OCIEmitStepParams
- type Op
- type OpStep
- type OverlayBuildReply
- type OverlayBuildRequest
- type PackageItem
- type PackageSection
- type Packaging
- type PackagingFormat
- type PackagingVariant
- type Pacstrap
- type PacstrapRepo
- type ParsedNode
- type ParsedPortMapping
- type ParsedProject
- type ParsedRef
- type Phase
- type PhaseCarrier
- type PhaseSet
- type PhaseTemplates
- type Plugin
- type PluginCapability
- type Pod
- type PodAttachOpts
- type PodCmdOpts
- type PodCmdPayload
- type PodConfigBoxEngineReply
- type PodConfigBoxEngineRequest
- type PodConfigCleanDeployEntryReply
- type PodConfigCleanDeployEntryRequest
- type PodConfigContainerTunnelReply
- type PodConfigContainerTunnelRequest
- type PodConfigMigrateSecretsReply
- type PodConfigMigrateSecretsRequest
- type PodConfigRemoveReply
- type PodConfigRemoveRequest
- type PodConfigSaveFleetReply
- type PodConfigSaveFleetRequest
- type PodConfigScrubCliEnvReply
- type PodConfigScrubCliEnvRequest
- type PodConfigSetupReply
- type PodConfigSetupRequest
- type PodConfigTunnelResolveReply
- type PodConfigTunnelResolveRequest
- type PodConfigWriteReply
- type PodConfigWriteRequest
- type PodDisposableReply
- type PodDisposableRequest
- type PodExecReply
- type PodLifecyclePlan
- type PodLifecycleReply
- type PodLifecycleRequest
- type PodLiveStdioPlan
- type PodLogsOpts
- type PodLogsPayload
- type PodRemovePayload
- type PodResolveInput
- type PodResolveReply
- type PodServicePayload
- type PodShellOpts
- type PodShellPayload
- type PodSidecar
- type PodSpec
- type PodStartOpts
- type PodStartPayload
- type PodStopOpts
- type PodStopPayload
- type PodUpdatePayload
- type PortMapping
- type PortPin
- type PortScope
- type PortSpec
- type PostTeardownReply
- type PreemptLease
- type PreemptLedger
- type PreemptedHolder
- type PreemptibleConfig
- type PrepareVenueReply
- type Process
- type ProcessExecutor
- type ProcessLaunch
- type ProjectLoader
- type ProjectTemplates
- type ProjectWalker
- type ProvidesConfig
- type RCARecord
- type RawBody
- type Readiness
- type ReadinessConfig
- type RebootStep
- type RecoveryDecision
- type RecoveryParams
- type RefsCollectSeams
- type RefsDownloadInput
- type RefsDownloadReply
- type RefsDownloader
- type RemoteDownload
- type RemoteImageResolveReply
- type RemoteImageResolveRequest
- type RenderedService
- type RepoBlock
- type RepoChangeStep
- type RepoSpec
- type ResolveOpts
- type ResolvedAndroid
- type ResolvedBox
- type ResolvedBoxView
- type ResolvedDistro
- type ResolvedGpuSelector
- type ResolvedInit
- type ResolvedKubernetes
- type ResolvedLocal
- type ResolvedPod
- type ResolvedProject
- type ResolvedProjectRequest
- type ResolvedResource
- type ResolvedSidecar
- type ResolvedSidecarSecret
- type ResolvedSidecarVolume
- type ResolvedVm
- type ResolvedVolumeMount
- type Resource
- type ResourceDef
- type ResourceResolveInput
- type ResourceResolveReply
- type RetentionReply
- type RetentionRequest
- type ReverseOp
- type ReverseOpKind
- type RouteConfig
- type RouteYAML
- type SSHTarget
- type SaveDeployStateInput
- type ScanSeams
- type ScanSpec
- type ScannedCandy
- type Scope
- type ScoreSummary
- type SecretYAML
- type Security
- type SecurityConfig
- type ServiceCustomStep
- type ServiceEntry
- type ServicePackagedStep
- type ServiceRenderContext
- type ServiceRenderInput
- type ServiceRenderReply
- type ServiceWaitFor
- type Session
- type SessionDesktop
- type Shell
- type ShellEntry
- type ShellHookStep
- type ShellSnippetStep
- type ShellSpec
- type Sidecar
- type SidecarDef
- type SidecarList
- type SidecarResolveInput
- type SidecarResolveReply
- type SidecarSecret
- type SidecarVolume
- type Size
- type Skill
- type SkillReference
- type StandaloneLoad
- type Status
- type StatusNestedNode
- type StatusSubstrateReply
- type StatusSubstrateRequest
- type Step
- type StepBatch
- type StepContract
- type StepContractCarrier
- type StepEmitRequest
- type StepKeyword
- type StepKind
- type StepPass
- type StepRecord
- type StepResult
- type StepScore
- type StrMap
- type StrVal
- type StructuralKindCarrier
- type StructuralKindLoadEnv
- type SubstrateKind
- type SubstrateStatusReply
- type SubstrateStatusRequest
- type SubstrateTemplateResolveRequest
- type Subvolume
- type SystemInfo
- type SystemPackagesStep
- type TagInfo
- type TagPkgConfig
- type TargetHop
- type TargetSpec
- type TerminalExit
- type TerminalFrame
- type TerminalInput
- type TerminalKey
- type TerminalProfile
- type TerminalResize
- type TerminalSnapshot
- type Theme
- type ThemeRender
- type ThemeTokens
- type Threaded
- type ToolStatus
- type TunnelConfig
- type TunnelPort
- type TunnelYAML
- type UUIDv7
- type UnifiedDeployTarget
- type UnifiedFile
- func (uf *UnifiedFile) Android() map[string]json.RawMessage
- func (uf *UnifiedFile) BoxConfig(name string) (BoxConfig, bool)
- func (uf *UnifiedFile) BoxNames() []string
- func (uf *UnifiedFile) CheckBeds() map[string]FleetNode
- func (uf *UnifiedFile) HasBox(name string) bool
- func (uf *UnifiedFile) Kubernetes() map[string]json.RawMessage
- func (uf *UnifiedFile) Local() map[string]json.RawMessage
- func (uf *UnifiedFile) Pod() map[string]json.RawMessage
- func (uf *UnifiedFile) ProjectConfig() *Config
- func (uf *UnifiedFile) ProjectTemplates() *ProjectTemplates
- func (uf *UnifiedFile) SetBox(name string, b BoxConfig)
- func (uf *UnifiedFile) SetCandy(name string, il *InlineCandy)
- func (uf *UnifiedFile) VM() map[string]json.RawMessage
- type UpdateOpts
- type UserInfo
- type VFIOGpu
- type VFIOPCIDevice
- type VFIOReport
- type ValidateProjectReply
- type ValidateProjectRequest
- type ValidateWordSetsReply
- type ValidateWordSetsRequest
- type ValidatingKindCarrier
- type ValidationError
- type Venue
- type VenueDescriptor
- type VerbSpec
- type VerifyChecksRequest
- type Vm
- type VmBoxMetadata
- type VmBoxSource
- type VmBuildReply
- type VmBuildRequest
- type VmCharlyInstall
- type VmChecksum
- type VmCloudInit
- type VmCloudInitFile
- type VmCloudInitMirrors
- type VmCloudInitNetwork
- type VmCloudInitUser
- type VmDeployState
- type VmDisplayEndpoint
- type VmInstaller
- type VmKeyInjection
- type VmKeyInjectionResolved
- type VmNetwork
- type VmPluginEnv
- type VmResolveInput
- type VmResolveReply
- type VmResolveResult
- type VmSize
- type VmSnapInternalReq
- type VmSnapshot
- type VmSnapshotCreateOpts
- type VmSnapshotDecl
- type VmSnapshotEntry
- type VmSnapshotPolicy
- type VmSnapshotState
- type VmSource
- type VmSpec
- type VmSsh
- type VmVariant
- type VolumeMount
- type VolumeYAML
- type WalkSeams
Constants ¶
const ( // PreemptStopShutdown is the only supported stop mechanism (graceful ACPI shutdown / podman // stop; disk preserved). PreemptStopShutdown = "shutdown" // PreemptRestoreAlways restarts the holder regardless of the claim's outcome (the default). PreemptRestoreAlways = "always" // PreemptRestoreSuccess restarts the holder only if the claim released cleanly; on a failed // claim it is left stopped for operator inspection. PreemptRestoreSuccess = "on-success" )
const ( ArbiterActionAcquireExclusive = "acquire-exclusive" ArbiterActionRelease = "release-claimant" ArbiterActionStatus = "status" ArbiterActionReconcile = "reconcile-stranded" ArbiterActionClearPoison = "clear-poison" ArbiterActionResourcePoisoned = "resource-poisoned" )
const ( CheckLevelNone = "none" CheckLevelBuild = "build" CheckLevelNoAgent = "noagent" CheckLevelAgent = "agent" )
The per-box acceptance-depth rungs.
const ( EncMethodMount = "mount" // mount the not-yet-mounted volumes (init must already exist) EncMethodUnmount = "unmount" // fusermount3 -u + stop the scope unit EncMethodEnsure = "ensure" // auto-init then mount (charly start transparent setup) EncMethodPasswd = "passwd" // gocryptfs -passwd for every initialized volume )
EncMethod selects which gocryptfs operation plugin-enc runs.
const ( // GpuModeVfio — EVERY function of the GPU's IOMMU group bound to vfio-pci // (free for VM passthrough; the boot default on a passthrough host). GpuModeVfio = "vfio" // GpuModeNvidia — each function on its correct host driver (display -> nvidia // for CDI-shared pods, HDMI-audio -> snd_hda_intel). GpuModeNvidia = "nvidia" // NvidiaVendorID is the normalized PCI vendor of NVIDIA cards (the device_lock // wedge is an nvidia-driver concept). NvidiaVendorID = "0x10de" // HostDriverDisplay / HostDriverAudio / HostDriverVfio are the host drivers a // group function binds to: the display (VGA/3D, class 0x03xx) takes nvidia in // host mode; the HDMI-audio (class 0x0403) takes snd_hda_intel; everything takes // vfio-pci for passthrough. HostDriverDisplay = "nvidia" HostDriverAudio = "snd_hda_intel" HostDriverVfio = "vfio-pci" )
const ( GpuSwitchActionMode = "switch-mode" // GpuSwitchInput{Gpu?,Vendor?,Mode} -> flip the group's driver mode (Gpu set = exact card; Vendor set = tolerant vendor-select) GpuSwitchActionEnsureCDI = "ensure-cdi-root" // regenerate /etc/cdi/nvidia.yaml as ROOT (distinct from the C11 user-scope "ensure-cdi" detection action) GpuSwitchActionWedgeDetected = "wedge-detected" // read-only D-state probe -> Bool GpuSwitchActionGroupInMode = "group-in-mode" // GpuSwitchInput{Gpu,Mode} -> Bool GpuSwitchActionCurrentMode = "current-mode" // GpuSwitchInput{Gpu} -> Str (vfio|nvidia) GpuSwitchActionDisplayDriver = "display-driver" // GpuSwitchInput{Addr} -> Str (live driver of a PCI function) GpuSwitchActionPlan = "switch-plan" // GpuSwitchInput{Gpu,Mode} -> Plan (DRY-RUN commands; NO sysfs write) — the C9 cred/hw-free dispatch proof )
GPU driver-switch actions served by verb:gpu (cutover C9).
const ( LabelVersion = "ai.opencharly.version" LabelBox = "ai.opencharly.box" LabelRegistry = "ai.opencharly.registry" LabelBootc = "ai.opencharly.bootc" LabelUID = "ai.opencharly.uid" LabelGID = "ai.opencharly.gid" LabelUser = "ai.opencharly.user" LabelHome = "ai.opencharly.home" LabelPort = "ai.opencharly.port" LabelVolume = "ai.opencharly.volume" LabelAlias = "ai.opencharly.alias" LabelSecurity = "ai.opencharly.security" LabelNetwork = "ai.opencharly.network" LabelEnv = "ai.opencharly.env" LabelHook = "ai.opencharly.hook" LabelRoute = "ai.opencharly.route" LabelInit = "ai.opencharly.init" LabelInitDef = "ai.opencharly.init_def" LabelEnvCandy = "ai.opencharly.env_candy" LabelPathAppend = "ai.opencharly.path_append" LabelPortProto = "ai.opencharly.port_proto" LabelPortRelay = "ai.opencharly.port_relay" LabelSkill = "ai.opencharly.skill" LabelStatus = "ai.opencharly.status" LabelInfo = "ai.opencharly.info" LabelCandyVersion = "ai.opencharly.candy_version" LabelSecret = "ai.opencharly.secret" LabelPlatformDistro = "ai.opencharly.platform.distro" LabelPlatformFormat = "ai.opencharly.platform.format" LabelBuilderUse = "ai.opencharly.builder.use" LabelBuilderProvide = "ai.opencharly.builder.provide" LabelDataEntries = "ai.opencharly.data" LabelDataBox = "ai.opencharly.data_box" LabelEnvProvide = "ai.opencharly.env_provide" LabelEnvRequire = "ai.opencharly.env_require" LabelEnvAccept = "ai.opencharly.env_accept" LabelSecretAccept = "ai.opencharly.secret_accept" // credential-store-backed env vars this image can optionally use LabelSecretRequire = "ai.opencharly.secret_require" // credential-store-backed env vars this image must have LabelMCPProvide = "ai.opencharly.mcp_provide" LabelAgentProvide = "ai.opencharly.agent_provide" LabelTerminalProfiles = "ai.opencharly.terminal_profiles" LabelMCPRequire = "ai.opencharly.mcp_require" LabelMCPAccept = "ai.opencharly.mcp_accept" LabelDescription = "ai.opencharly.description" LabelService = "ai.opencharly.service" LabelShell = "ai.opencharly.shell" LabelCheckLevel = "ai.opencharly.check_level" // VM-box metadata contract (the cutover task 2 label): the WHOLE spec.VmBoxMetadata // marshaled as one JSON label, like LabelService does for CapabilityService. A NEW // label with no byte-freeze anchor — its json tags (CUE-sourced, vm_box_metadata.cue) // ARE the wire contract. LabelVmBox = "ai.opencharly.vm.box" )
const ( DefaultBoxDir = "box" // discovered box/<name>/ directory DefaultCandyDir = "candy" // discovered candy/<name>/ directory )
layout_paths.go — the stable per-directory discovery path constants (the discovered box/<name>/ and candy/<name>/ dirs). Relocated from sdk/kit/migrate_support.go (#55 import-purity cone-render): plain loader-result DATA the types-only spec module owns, mirroring spec.UnifiedFileName. kit re-exports them via const alias so existing kit.DefaultBoxDir / kit.DefaultCandyDir call sites (plugins + sdk) are untouched.
const ( ProjectDirEnv = "CHARLY_PROJECT_DIR" ProjectRepoEnv = "CHARLY_PROJECT_REPO" )
ProjectDirEnv / ProjectRepoEnv are the environment names that carry charly's PROJECT SCOPE — the two mutually-exclusive answers to "which project's charly.yml is this process operating on". Canonical loader DATA, sharing the home of the manifest filename they select: the charly CLI publishes them as the `-C/--dir` and `--repo` flag environments, and every module that spawns a charly child (sdk/deploykit's packaging child) must set or strip the SAME two names, so the contract lives in the one module both sides import rather than as a literal in each.
They are the DESTINATION, not yet the whole story: literal spellings still exist in the charly CLI (charly/plugin_command_prescan.go, charly/main_dir_test.go, charly/main_repo_test.go, charly/plugin_command_prescan_repo_test.go) and are converted alongside their consumers in opencharly/charly#309. The Kong struct tags in charly/main.go are the one PERMANENT exception: a Go struct tag is a string literal in the type declaration and cannot reference a constant.
const DefaultCheckLevel = CheckLevelNoAgent
DefaultCheckLevel is the rung applied when a box declares no check_level.
const DefaultProjectRepo = "github.com/opencharly/charly"
DefaultProjectRepo is the repo --repo defaults to when the spec is the literal string "default" (or when charly mcp serve auto-falls back).
const DeployConfigEnv = "CHARLY_DEPLOY_CONFIG"
DeployConfigEnv overrides the per-host deploy-config PATH. A check bed sets it so a disposable run never touches the operator's real ~/.config/charly/charly.yml.
const ExternalStepKindPrefix = "external:"
ExternalStepKindPrefix marks a StepKind string as an external (plugin-contributed) kind.
const HomeToken = "{{.Home}}"
HomeToken is the deferred-home placeholder resolved by ResolveHome at emit time.
const LedgerSchemaVersion = "2026.161.1649"
ledger_records.go — the install-ledger record shapes relocated from sdk/kit/install_ledger.go + sdk/kit/migrate_support.go (#55 value-type relocation spike, cluster 4) and CUE-sourced at schema/deploy.cue (generated into cue_types_gen.go — DeployRecord/CandyRecord/StepRecord are wire shapes, JSON-persisted to the ledger). kit.* becomes a type/const alias onto these. This file holds only the ledger-format version constant.
LedgerSchemaVersion is the install-ledger record format version (the ledger-candy-keys cutover's CalVer). It is INDEPENDENT of the project schema CalVer (spec/calver.LatestSchemaCalVer) — a non-ledger schema cutover that bumps the project HEAD must NOT invalidate the ledger gate.
const ReverseOpPluginScriptKey = "script"
ReverseOpPluginScriptKey is the Extra map key carrying a ReverseOpPluginScript's shell-script body. Exported so both the host handler and the SDK builder name the one key (R3 — no magic-string drift across the process boundary).
const SchemaFloor = "2026.174.1100"
SchemaFloor is the oldest schema version `charly migrate` can migrate FROM (schema/version.cue #SchemaFloor).
const SchemaVersion = "2026.240.1943"
SchemaVersion is the HEAD schema CalVer (schema/version.cue #SchemaVersion).
const UnifiedFileName = "charly.yml"
UnifiedFileName is the ONE box/candy manifest filename. Canonical loader DATA; ScanSpec below defaults to it. (sdk/kit keeps the sibling layout constants DefaultBoxDir/DefaultCandyDir.)
Variables ¶
var AllStepKinds = []StepKind{ StepKindSystemPackages, StepKindBuilder, StepKindOp, StepKindFile, StepKindServicePackaged, StepKindServiceCustom, StepKindShellHook, StepKindShellSnippet, StepKindRepoChange, StepKindApkInstall, StepKindLocalPkgInstall, StepKindReboot, StepKindExternalPlugin, StepKindExtract, }
AllStepKinds is the fixed InstallStep IR vocabulary — the 14 compiled-in concrete step kinds (external:<word> kinds are dynamic and not listed).
var AuthoringVerbs = []string{
"build",
"cache",
"caps",
"comment",
"content",
"context",
"copy",
"depends_on",
"description",
"download",
"env",
"eventually",
"exclude_distro",
"exit_status",
"extract",
"extract_include",
"id",
"link",
"mkdir",
"mode",
"retry_interval",
"run_as",
"setcap",
"skip",
"stderr",
"stdout",
"strip_components",
"tag",
"target",
"timeout",
"to",
"uninstall",
"unless_exists",
"write",
}
AuthoringVerbs is the AUTHORABLE #Op field vocabulary (#Op fields minus the never-authored origin/venue/intent_do/plugin/plugin_input/command).
var ContextWords = []string{
"build",
"deploy",
"runtime",
}
ContextWords is the plan-step execution contexts (#Context).
var DistroFormats = map[string]string{
"almalinux": "rpm",
"alpine": "apk",
"arch": "pac",
"archarm": "pac",
"cachyos": "pac",
"centos": "rpm",
"debian": "deb",
"endeavouros": "pac",
"fedora": "rpm",
"manjaro": "pac",
"omarchy": "pac",
"rhel": "rpm",
"rocky": "rpm",
"ubuntu": "deb",
}
DistroFormats is each distro id's native package format (#Distros[id].format). The SINGLE table — hostenv.FormatForDistroID reads it; there is no hand-written Go copy.
var DistroIDs = []string{
"almalinux",
"alpine",
"arch",
"archarm",
"cachyos",
"centos",
"debian",
"endeavouros",
"fedora",
"manjaro",
"omarchy",
"rhel",
"rocky",
"ubuntu",
}
DistroIDs is the CLOSED guest-distro id vocabulary, derived from #Distros' own keys (schema/distro_vocab.cue).
var DistroInits = map[string]string{
"almalinux": "systemd",
"alpine": "openrc",
"arch": "systemd",
"archarm": "systemd",
"cachyos": "systemd",
"centos": "systemd",
"debian": "systemd",
"endeavouros": "systemd",
"fedora": "systemd",
"manjaro": "systemd",
"omarchy": "systemd",
"rhel": "systemd",
"rocky": "systemd",
"ubuntu": "systemd",
}
DistroInits is each distro id's guest init system (#Distros[id].init) — systemd or openrc. Replaces `if distro == "alpine"` branches.
var DistroOvmfFamilies = map[string]string{
"almalinux": "fedora",
"arch": "arch",
"cachyos": "arch",
"centos": "fedora",
"debian": "debian",
"endeavouros": "arch",
"fedora": "fedora",
"manjaro": "arch",
"omarchy": "arch",
"rhel": "fedora",
"rocky": "fedora",
"ubuntu": "debian",
}
DistroOvmfFamilies is each distro id's OVMF firmware family (#Distros[id].ovmf_family), for the ids that declare one. Replaces the hand-maintained ovmf_distro_aliases: map that was kept in two byte-identical copies. An id with no entry keeps the all-families fallback.
var DistroSSHUnits = map[string]string{
"almalinux": "sshd",
"alpine": "sshd",
"arch": "sshd",
"archarm": "sshd",
"cachyos": "sshd",
"centos": "sshd",
"debian": "ssh",
"endeavouros": "sshd",
"fedora": "sshd",
"manjaro": "sshd",
"omarchy": "sshd",
"rhel": "sshd",
"rocky": "sshd",
"ubuntu": "ssh",
}
DistroSSHUnits is each distro id's OpenSSH service name (#Distros[id].ssh_unit) — `ssh` on Debian-family, `sshd` elsewhere. Replaces a hardcoded Go switch.
var DocDirectives = []string{
"cache",
"compiled_plugins",
"context_ignore_baseline",
"defaults",
"discover",
"import",
"ledger",
"ovmf_distro_aliases",
"ovmf_paths",
"providers",
"provides",
"repo",
"system",
"version",
}
DocDirectives is the reserved document directives (#NodeDoc top-level keys).
var ErrGPUSwitchWedged = errors.New("GPU driver switch wedged: nvidia .remove stuck holding the device_lock — host reboot required")
ErrGPUSwitchWedged signals that a GPU driver switch did not complete because the nvidia `.remove` is stuck holding the device_lock (deadline exceeded, or a self-detected D-state task in nv_pci_remove). candy/plugin-gpu carries the wedge as a bool over the wire (GpuSwitchReply.Wedged); the core gpu shim maps that bool back to this sentinel so callers (vm_gpu_cmd) keep matching it with errors.Is. Recovery is a host reboot.
var ErrImageNotLocal = errors.New("image not found in local storage")
ErrImageNotLocal is returned (wrapped with the image ref) when an image is not present in local container storage — a host-side signal, never crossing the plugin wire.
var ErrNotSupported = errors.New("live-stdio (interactive/stream) not supported on this venue")
ErrNotSupported is returned by RunInteractive/RunStream on a venue that cannot host a LIVE-STDIO session (a capture-only / non-interactive executor). Callers surface it as "shell/logs not supported on this substrate" — an explicit, compile-forced failure, never a silent no-op (F12; the honest form of the executor-matrix ErrNotSupported path).
var ErrStaleLocalImage = errors.New("short name resolves to a stale local image")
ErrStaleLocalImage is returned (wrapped with both refs) when a short-name resolve elects a local image that is NOT the newest build of that box, for a verb that pronounces a verdict on the artifact. Certifying an artifact older than the one on disk produces a green result that proves nothing, so the resolution is refused rather than silently taken (container.ResolveBuiltImageRef).
var ExternalizedBuilders = map[string]bool{ "cargo": true, "npm": true, "pixi": true, "aur": true, "mise": true, }
ExternalizedBuilders is THE single source of truth for which builder words are served by an EXTERNAL out-of-process plugin. A word here resolves through the registry to a *grpcProvider connected at plugin-load time. mise: the builder:mise plugin (opencharly/plugin-mise) — externalized like the four detection builders; the embedded builder: mise: vocabulary in charly selects it by detect_file (mise.toml/.tool-versions).
var InstallVerbs = map[string]bool{ "mkdir": true, "copy": true, "write": true, "link": true, "download": true, "setcap": true, "build": true, }
InstallVerbs are the verbs that render directly to a generic OpStep install step (a Containerfile directive at build, a deploy shell command at deploy). The verbs that lowered into a TYPED install step (package/service) are now extracted plugin verbs whose TypedStepProvider owns the lowering — handled by charly core's opActsInBuildDeploy, not this map.
var KindValueDefs = map[string]string{
"android": "#AndroidValue",
"candy": "#CandyValue",
"kubernetes": "#KubernetesValue",
"local": "#LocalValue",
"pod": "#PodValue",
"vm": "#VmValue",
}
KindValueDefs is the word→#<Kind>Value CUE-def map the host uses to closedness-gate a substrate/candy node's authored VALUE (validateKindValueCUE). DERIVED from the #<X>Value defs themselves (every one except the shared #DeployValue disjunct), so a new value-gated kind needs no hand-maintained map.
var KindWords = []string{}
KindWords is the reserved kind keywords (the #Node disjunction discriminators).
var OpFields = []string{
"build",
"cache",
"caps",
"command",
"comment",
"content",
"context",
"copy",
"depends_on",
"description",
"download",
"env",
"eventually",
"exclude_distro",
"exit_status",
"extract",
"extract_include",
"id",
"intent_do",
"link",
"mkdir",
"mode",
"origin",
"plugin",
"plugin_input",
"retry_interval",
"run_as",
"setcap",
"skip",
"stderr",
"stdout",
"strip_components",
"tag",
"target",
"timeout",
"to",
"uninstall",
"unless_exists",
"venue",
"write",
}
OpFields is every #Op verb/modifier field name (the flat Op vocabulary).
var OpInContext func(op *Op, ctx ExecContext) bool
OpInContext reports whether an op runs in the given exec context. Its fallback consults the kernel VerbCatalog (charly), so charly injects the impl at init.
var OpVerbs = []string{
"mkdir",
"copy",
"write",
"link",
"download",
"setcap",
"build",
"plugin",
}
OpVerbs is the verb DISCRIMINATOR vocabulary (#OpVerb) — the exactly-one-set verb subset of #Op fields (Op.Kind() + the VerbCatalog dispatch table gate against it).
var PluginEmitStepWords = map[StepKind]string{
StepKindFile: "file",
StepKindShellHook: "shell-hook",
StepKindShellSnippet: "shell-snippet",
StepKindServicePackaged: "service-packaged",
StepKindServiceCustom: "service-custom",
StepKindRepoChange: "repo-change",
StepKindApkInstall: "apk-install",
StepKindReboot: "reboot",
StepKindSystemPackages: "system-packages",
StepKindBuilder: "builder",
StepKindLocalPkgInstall: "local-pkg-install",
StepKindOp: "op",
StepKindExtract: "extract",
}
PluginEmitStepWords maps a builtin InstallStep kind to the lowercase-hyphenated class:step plugin word that serves its pod-overlay OpEmit (candy/plugin-installstep). These kinds have NO in-proc StepProvider — the OCI dispatch routes them here, serializing the step VIEW as the OpEmit payload. Their DEPLOY leg is unchanged (sdk/kit.WalkPlans renders them from the same view; reboot's is the host-side guest reboot over RunHostStep). apk-install's and reboot's plugin declares Emits=false (no build fragment); every other word Emits=true.
Shared cross-boundary (R3): BOTH the host (charly/provider_step.go's checkStepProviderBijection) and the plugin itself (candy/plugin-installstep's "oci-dispatch" word) consult the SAME map — it belongs with the fixed step-kind vocabulary (AllStepKinds) in the IR envelope.
var ResourceKinds = []string{
"pod",
"vm",
"kubernetes",
"local",
"android",
"group",
}
ResourceKinds is the DEPLOYABLE subset of the kind keywords — the kinds whose #Node arm nests a sub-ENTITY (resource) child (#ResourceKind).
var StepKeywords = []string{
"agent-check",
"agent-run",
"check",
"include",
"run",
}
StepKeywords is the plan-step intent keywords (#Step arms minus #Op fields).
var ValidateRecord = func(kind, label string, v any) error { return nil }
ValidateRecord is the egress-validation seam for install-ledger writes. The ledger validates each record against its egress schema before writing; charly injects its ValidateEgressValue here at init. Defaults to a no-op for standalone use.
var VerbCatalog = map[string]VerbSpec{ "mkdir": {ctxBuildDeploy, DoAct, false}, "copy": {ctxBuildDeploy, DoAct, true}, "write": {ctxBuildDeploy, DoAct, true}, "link": {ctxBuildDeploy, DoAct, true}, "download": {ctxBuildDeploy, DoAct, true}, "setcap": {ctxBuildDeploy, DoAct, false}, "build": {ctxBuildDeploy, DoAct, false}, "plugin": {ctxBuildDeployRuntime, DoAssert, false}, }
VerbCatalog is the single source of truth for every core install-verb's legality, default do-mode, and reversibility — one table driving validation, dispatch, and lowering. Keys match spec.OpVerbs (gated by the registry bijection in charly/reserved_registry.go).
var VmBoxLabelMap = map[string]string{ "Distro": LabelVmBox, "Arch": LabelVmBox, "Firmware": LabelVmBox, "Init": LabelVmBox, "Version": LabelVmBox, "Description": LabelVmBox, "BaseUser": LabelVmBox, "SSHUser": LabelVmBox, "CharlyInstall": LabelVmBox, "Source": LabelVmBox, "Plan": LabelVmBox, }
VmBoxLabelMap names the OCI label each spec.VmBoxMetadata field rides. All fields share the single whole-struct label (LabelVmBox) — the values are uniform by design; the map's job is the completeness gate, not per-field routing.
Functions ¶
func BareCandyRef ¶
BareCandyRef returns the map-key form of a ref (no @ prefix, no :version).
func BoxNamesOf ¶
BoxNamesOf returns the image names in a generic image map, sorted for determinism.
func BuilderPhaseTemplate ¶ added in v0.2026213.1726
BuilderPhaseTemplate is the Builder analog of FormatPhaseTemplate. The phase: block is the single source of truth — the legacy inline InstallTemplate field was removed (R5) and its content migrated into phase.install.container, so there is no fallback arm. Multi-stage builders render via their plugin's OpResolve (kit.BuilderResolve), NOT this resolver.
func CandyCapabilitiesError ¶ added in v0.2026213.1726
func CandyCapabilitiesError(layers map[string]CandyReader, order []string, missing []string) error
CandyCapabilitiesError formats a missing-capabilities error with a remediation hint pointing at which candies requested what.
func CheckLevelReaches ¶ added in v0.2026213.1726
CheckLevelReaches reports whether a box resolved to `have` runs at least as deep as `want` — e.g. CheckLevelReaches(boxLevel, CheckLevelNoAgent) gates the runtime live pass, CheckLevelReaches(boxLevel, CheckLevelAgent) gates the agent grader. Both operands are normalized through ResolveCheckLevel first.
func CheckRequiredCapabilities ¶ added in v0.2026213.1726
func CheckRequiredCapabilities(layers map[string]CandyReader, order []string, agg *AggregatedCandyCaps) []string
CheckRequiredCapabilities returns a sorted list of capability names requested via `requires_capabilities:` on any candy in `order` but not provided by the aggregated capabilities. Empty slice on success.
func CheckVmBoxLabelCompleteness ¶ added in v0.2026246.554
func CheckVmBoxLabelCompleteness() error
CheckVmBoxLabelCompleteness returns an error listing any spec.VmBoxMetadata exported field that lacks a VmBoxLabelMap entry. Called from TestVmBoxLabelCompleteness to fail the build when a field is added without a label mapping.
func ChildProjectEnv ¶ added in v0.2026230.1336
ChildProjectEnv returns inherited with BOTH project-scope variables removed, then sets ProjectDirEnv to projectDir when projectDir is non-empty. It is the one implementation of "hand a charly child process a project scope", shared by every module that spawns one.
Both names must be handled together, which is the whole reason this is a function rather than two constants each caller filters by hand: they are MUTUALLY EXCLUSIVE in the CLI, so a caller that strips ProjectDirEnv and leaves an inherited ProjectRepoEnv makes the child exit on "--repo and --dir are mutually exclusive" instead of running. Getting one right and the other wrong fails at runtime, not at compile time.
Stripping is the default because a child's project scope is almost never the parent's: the packaging child runs under the project owning the candy it packages, and the MCP server's forked charly must reach its own --repo default rather than inherit the caller's directory.
func ComputeCalVer ¶ added in v0.2026213.1726
func ComputeCalVer() string
ComputeCalVer returns the canonical build tag for the current UTC instant.
func ComputeCalVerAt ¶ added in v0.2026213.1726
ComputeCalVerAt formats t as the canonical CalVer: 4-digit year, 3-digit zero-padded day-of-year, 4-digit zero-padded HHMM. Every component is fixed-width, so a plain lexicographic sort of CalVer strings is chronological.
func ContainerName ¶ added in v0.2026213.1726
ContainerName returns the deterministic container name for an image or a `<base>/<instance>` deploy key (the `/` is canonicalized to `-`).
func ContainerNameInstance ¶ added in v0.2026213.1726
ContainerNameInstance returns the container name with an optional instance suffix.
func CurrentUsername ¶ added in v0.2026210.1121
func CurrentUsername() string
CurrentUsername returns $USER or falls back to "charly" if neither $USER nor $LOGNAME is set.
func DecodePluginKindMap ¶ added in v0.2026210.808
func DecodePluginKindMap[T any](uf *UnifiedFile, kind string) map[string]*T
DecodePluginKindMap reconstructs the typed name-keyed map[string]*T for a plugin kind from uf.PluginKinds[kind] (each body the canonical T JSON the kind plugin's Invoke produced) via a PLAIN json.Unmarshal — no plugin OpResolve round-trip, for a kind whose body is already self-contained (Builder). Compare ResolvePluginKindViaPlugin above, its sibling for kinds that DO need a plugin-side resolve leg. A bad entry is skipped rather than poisoning the whole vocabulary. Returns nil when the kind has no entities.
func DedupeNonEmpty ¶ added in v0.2026218.138
DedupeNonEmpty trims each entry and returns the input in order with blanks and repeats removed. Returns nil for an input that yields no entries (a nil-vs-empty distinction every caller relies on for its "no tokens declared" early return).
func DefaultDeployConfigPath ¶ added in v0.2026213.1726
DefaultDeployConfigPath returns the per-host deploy overlay file (~/.config/charly/charly.yml), honoring the DeployConfigEnv override.
func DeployKey ¶ added in v0.2026211.549
DeployKey builds the charly.yml deployment map key from a box name and an optional instance: "selkies-desktop" (no instance) or "selkies-desktop/foo" (instance "foo"). The inverse is ParseDeployKey.
func DeriveDeploymentName ¶ added in v0.2026211.549
DeriveDeploymentName turns "quay.io/myorg/openclaw:v1" → "openclaw" and "registry.example.com/path/foo" → "foo" — the shared default-name derivation for a source-less `charly fleet from-box` deploy (both the pod path, candy/plugin-fleet/from_box_pod.go, and the kubernetes path, candy/plugin-fleet/deploy_from_box.go — R3, one function, two callers).
func DistroTagChain ¶ added in v0.2026210.2052
DistroTagChain builds the most-specific-first distro tag chain used by the cascade resolver: [<distro>:<version>, <distro>] when a canonical version is known (e.g. ["ubuntu:24.04", "ubuntu"]), or just [<distro>] for a rolling distro with no version (arch/cachyos).
func EncodeBox ¶
func EncodeBox(b BoxConfig) json.RawMessage
EncodeBox marshals an authored BoxConfig into its opaque body.
func EncodeInlineCandy ¶ added in v0.2026210.808
func EncodeInlineCandy(il *InlineCandy) json.RawMessage
EncodeInlineCandy marshals a loader InlineCandy into its opaque body.
func ExpandPath ¶ added in v0.2026213.1726
ExpandPath expands ~, ${HOME} and $HOME in a path string to the given home directory. ${HOME} is replaced before bare $HOME so the braced form is handled (a bare $HOME ReplaceAll would not match "${HOME}").
Relocated from sdk/kit/env.go (#55 import-purity cone-render): a pure fabric path helper with no host dependency. kit.ExpandPath now aliases spec.ExpandPath (R3, one source), and spec.BuildServiceRenderContext consumes it in-package.
func ExternalBuilderPluginRef ¶ added in v0.2026218.138
ExternalBuilderPluginRef returns the canonical @github ref to the candy serving an externalized builder word, and whether the word is a first-party externalized builder. It feeds the ONE on-demand connect path — ops.InvokeProviderOpts.ExtraRef, which the host resolves through connectPluginByWordRef's Pass-2 canonical-ref fetch — so a box/<distro> submodule build that triggers a builder by DETECTION but vendors the plugin candy nowhere still connects it. Living in spec (beside the word maps it reads) lets BOTH charly core and the plugin-side render reach it over a spec-only import surface.
func FinalizeCandyRefs ¶
func FinalizeCandyRefs(m *CandyModel, v *CandyView, refs CandyRefs)
FinalizeCandyRefs projects the RICH pre-qualification refs (CandyRefs) down to their bare-string FINAL wire form — v.Require / v.IncludedCandy (CandyView) and m.BakePlugin (CandyModel) — run ONCE, after any remote-sibling qualification (loaderkit.QualifyRemoteSiblingDeps) has had the chance to set .Resolved. Mirrors the pre-move projectCandyView/projectCandyModel bare-ref projection (charly/resolved_project_host.go's bareRefs() calls, pre-W9), which ran AFTER qualifyRemoteSiblingDeps on the live *Candy. Lives in spec (not loaderkit, alongside its sibling CandyRefEntry/ToCandyRefEntries) — a pure, non-registry-coupled data-shape transform charly core must reach directly at the fix-point arbitration's final step (import-purity: core imports spec but never an sdk mechanism kit).
func FirstYAMLVersionLine ¶ added in v0.2026213.1726
FirstYAMLVersionLine extracts the value of the first top-level `version:` line.
func FlattenedEnvMap ¶ added in v0.2026213.1726
func FlattenedEnvMap(base map[string]string, overrides *CandyServiceOverrides) map[string]string
FlattenedEnvMap merges an entry's base env with its overrides' env (overrides win).
func FleetDelArgv ¶ added in v0.2026219.1057
FleetDelArgv returns the argv (everything after the charly binary) for a non-interactive `charly fleet del <name>`: the verb, the name, and the ONE valid skip-confirmation flag. Every programmatic teardown builds its command through this single helper — in-process (proc.RunCharlySubcommand), out-of-process (exec.Command), and the systemd-run TTL timer — so the flag can never drift across call sites again (R3 hoist: this was byte-identically duplicated in charly core, candy/plugin-fleet, and candy/plugin-substrate).
The flag is `--assume-yes`, NOT `--yes`/`--force`: the command:fleet plugin's `charly fleet del` Kong grammar (candy/plugin-fleet) declares its AssumeYes field `name:"assume-yes"`, with `-y` as the short form. That tag used to read `long:"yes"` — go-flags syntax Kong ignores, so the flag was only ever --assume-yes by accident of Kong's field-name derivation; the whole tree was converted to `name:` so the declaration states what ships. A `--yes`/`--force` drift — neither of which Kong accepts — once aborted teardown at arg-parse and silently leaked the resource (see CHANGELOG/); the deploy-del-flag regression test guards this.
func FormatCacheMount ¶ added in v0.2026213.1726
FormatCacheMount renders ONE cache mount as a Containerfile `--mount=type=cache,…` flag. uid>=0 → the owned form (uid/gid baked into the id namespace so different-uid builds don't collide on file ownership inside the cache volume); uid<0 → the shared form (sharing mode, defaulting to "locked" when empty — the BuildKit default for root-installed system caches). The id is derived from dst (and uid for owned), keeping the cache stable across layer-hash churn during iterative builds — the entire reason a derived id exists.
func FormatPhaseTemplate ¶ added in v0.2026213.1726
FormatPhaseTemplate looks up the template string for a (phase, venue) lookup. The phase: block is the single source of truth — the legacy top-level InstallTemplate field was removed (R5) and its content migrated into phase.install, so there is no fallback arm. A lookup returns "" when the requested cell is absent.
R3 (one canonical body, venue applied at render): the install cell is the venue-agnostic body written with `&& \` continuations (valid plain shell AND inside a Dockerfile RUN). The container venue wraps it with the BuildKit cacheMounts RUN prefix; the host venue returns it verbatim. Venue-specific OVERRIDES (host/container) take precedence when present, so a phase that genuinely differs by venue keeps its two cells without forcing a second canonical copy.
func FormatPortMapping ¶ added in v0.2026213.1726
func FormatPortMapping(p ParsedPortMapping) string
FormatPortMapping is the inverse of ParsePortMapping. Empty bindAddr / proto are omitted; trailing-zero / equal ports collapse to canonical short forms that podman accepts.
func GateEnabled ¶ added in v0.2026210.2308
GateEnabled returns whether the given gate is permitted under opts. GateNone is always enabled; named gates require the corresponding opt-in flag.
func GitRemoteIdentity ¶ added in v0.2026213.1726
GitRemoteIdentity returns the normalized `host/owner/repo` identity of dir's git `origin` remote, or "" when dir is not a git repo / has no origin / git is unavailable. Cached process-wide per directory (the identity is stable for the process lifetime).
func InputStr ¶ added in v0.2026213.1726
InputStr reads a string field from the step's desugared plugin input map.
func IsExternalStepKind ¶ added in v0.2026210.2308
IsExternalStepKind reports whether k is an external (plugin-contributed) step kind.
func IsRemoteCandyRefString ¶
IsRemoteCandyRefString reports whether a ref is an @-prefixed remote ref.
func IsRemoteImageRef ¶
IsRemoteImageRef returns true if a ref looks like a remote image reference (starts with @).
func IsValidCheckLevel ¶ added in v0.2026213.1726
IsValidCheckLevel reports whether level is one of the four canonical rungs.
func LeafName ¶
LeafName strips every namespace prefix from a (possibly qualified) ref, returning the final member name — e.g. "charly.arch-builder" -> "arch-builder", "a.b.c" -> "c", bare "fedora" -> "fedora".
func MappingRoot ¶
MappingRoot unwraps a YAML document node to its top-level mapping node (or nil). Shared by charly core and the out-of-module candy/plugin-migrate engine (R3).
func MergePluginKindsMap ¶ added in v0.2026213.1726
func MergePluginKindsMap(dst *map[string]map[string]json.RawMessage, src map[string]map[string]json.RawMessage)
MergePluginKindsMap merges src's plugin-kind entities INTO *dst under project-wins semantics: an entity already present in dst (by kind, then name) is never overwritten. A pure map/json.RawMessage merge over the plugin-kind carrier shape, relocated to the dedicated spec module (#55 2b) so the binary-embedded-defaults merge (charly/embed_defaults.go) reaches it without importing loaderkit; loaderkit re-exports it as a forwarder for its own merge path.
func NormalizeGitRemoteURL ¶ added in v0.2026213.1726
NormalizeGitRemoteURL strips the scheme / `git@` / `.git` decorations from a git remote URL, leaving `host/owner/repo`. scp-style (`git@host:owner/repo`) and scheme URLs (`https://`, `ssh://`, `git://`, with optional `user@`) are both handled. A value already in `host/owner/repo` (or bare `owner/repo`) form passes through unchanged.
func NormalizePCIVendor ¶
NormalizePCIVendor lowercases a PCI vendor id and ensures the "0x" prefix, so "10DE" / "0X10de" / "0x10de" all compare equal to the sysfs form ("0x10de").
func NormalizeRepoIdentity ¶ added in v0.2026213.1726
NormalizeRepoIdentity normalizes an explicit `repo:` value (which may be a full git URL, an scp-style ref, or a bare `owner/repo`) to the `host/owner/repo` form ParseRemoteRef produces — so an explicit declaration and a remote `@`-ref compare equal. Reuses NormalizeRepoSpec's bare-`owner/repo` → github.com rule.
func NormalizeRepoSpec ¶ added in v0.2026213.1726
NormalizeRepoSpec turns a user-supplied --repo spec into a (repoPath, version) pair suitable for a repo-cache download. Spec formats:
"default" → (DefaultProjectRepo, "")
"owner/repo" → ("github.com/owner/repo", "")
"owner/repo@ref" → ("github.com/owner/repo", "ref")
"host/owner/repo[@ref]" → used literally
An empty version means "resolve to default branch at lookup time".
func PackageNames ¶
func PackageNames(items []PackageItem) []string
PackageNames returns just the names from a PackageItem list, in order. Convenience for places that only need the install-target list.
func ParseDeployKey ¶ added in v0.2026211.549
ParseDeployKey splits a charly.yml map key back into image name and instance. "selkies-desktop" → ("selkies-desktop", "") "selkies-desktop/foo" → ("selkies-desktop", "foo")
func PathIsSystemScoped ¶ added in v0.2026210.2308
PathIsSystemScoped returns true for paths under /etc, /usr, /var, /opt, /root, /boot and similar system locations. Used to classify FileStep scope when the compiler hasn't explicitly tagged it.
func PodName ¶ added in v0.2026213.1726
PodName returns the container name for a pod's primary container.
func PodNameInstance ¶ added in v0.2026213.1726
PodNameInstance returns the container name for a pod's primary container, instance-aware.
func PodQuadletFilename ¶ added in v0.2026213.1726
PodQuadletFilename returns the quadlet filename for a pod.
func PodQuadletFilenameInstance ¶ added in v0.2026213.1726
PodQuadletFilenameInstance returns the quadlet filename for a pod with optional instance.
func QuadletDir ¶ added in v0.2026213.1726
QuadletDir returns the user-level quadlet directory.
func QuadletExists ¶ added in v0.2026213.1726
QuadletExists checks whether a .container file exists for the given image.
func QuadletExistsInstance ¶ added in v0.2026213.1726
QuadletExistsInstance checks whether a .container file exists for the given image/instance.
func QuadletFilename ¶ added in v0.2026213.1726
QuadletFilename returns the quadlet filename for an image.
func QuadletFilenameInstance ¶ added in v0.2026213.1726
QuadletFilenameInstance returns the quadlet filename for an image with optional instance.
func RenderCacheMounts ¶ added in v0.2026213.1726
func RenderCacheMounts(mounts []CacheMount, uid, gid int, sep string, trailing bool) string
RenderCacheMounts joins a slice of spec.CacheMount into one Containerfile flag string. uid<0 → shared form (sharing-locked); uid>=0 → owned form. `trailing` appends the separator after the last entry — needed by cacheMountsOwned which feeds directly into a multi-line RUN body. Single source of truth for the slice-rendering pattern that previously lived inline at four call sites (two template helpers + the former generate.go + tasks.go cmd-emitter); every multi-mount site now flows through here, every single-mount site flows through FormatCacheMount (via buildkit's CacheMount.String()) directly.
func RenderCacheMountsAuto ¶ added in v0.2026213.1726
func RenderCacheMountsAuto(mounts []CacheMount, uid, gid int, sep string, trailing bool) string
RenderCacheMountsAuto renders a MIXED list where each entry is owned (uid/gid) or shared per its own `owned:` flag — letting one builder declare both root system caches (pacman → shared/locked) and user build caches (makepkg SRCDEST, yay AUR clones → uid/gid-owned) in a single cache_mount list. uid/gid apply only to the entries flagged owned; the rest render in the shared form.
func RepoIdentity ¶ added in v0.2026213.1726
RepoIdentity returns the canonical repo identity of an import ref, or "" when it can't be determined (in which case the loader degrades to version-keyed behavior). A remote `@host/org/repo[/sub]:ver` ref yields `host/org/repo` directly (no fetch, no git); a local path yields the git `origin` identity of the target directory.
func ResolveBoxName ¶ added in v0.2026213.1726
ResolveBoxName extracts the short box name from a ref that may be a local box name or a remote ref (github.com/org/repo/box[@version]).
func ResolveCheckLevel ¶ added in v0.2026213.1726
ResolveCheckLevel normalizes an authored check_level to a canonical rung, applying the default for the empty value. An unknown value is returned verbatim so the validator flags it — never silently defaulted.
func ResolveHome ¶ added in v0.2026210.2308
func ResolveHome(p *InstallPlan, home string)
ResolveHome substitutes the deferred HomeToken with a concrete home in every home-bearing step field, in place. Each EmitTarget calls this once at emit time with the home of its real destination: img.Home for the OCI/pod-overlay build, the host home for the external local deploy, the GUEST home (SSH executor ResolveHome) for the external vm deploy. Idempotent — fields without the token are left untouched, so a second call is a no-op.
Covered fields: ShellHookStep env values + PathAdd, ShellSnippetStep Snippet + Destination + PathAppend, FileStep.Dest. OpStep cmd/content bodies are intentionally NOT touched — `~`/`$HOME` there shell-expand at runtime on the destination as the deploy user, which is already correct on every venue. BuilderStep is also untouched — its home is resolved separately by renderBuilderScript against the builder/guest home (see execBuilder).
A free function (not a method on InstallPlan) because it type-switches the concrete step vocabulary.
func ResolvePluginKindViaPlugin ¶ added in v0.2026210.808
func ResolvePluginKindViaPlugin[T any](uf *UnifiedFile, kind string, resolve func(json.RawMessage) (*T, error)) map[string]*T
ResolvePluginKindViaPlugin projects uf.PluginKinds[kind] (opaque bodies) into *T value envelopes via resolve, a per-body plugin OpResolve leg — the ONE shared loop every "resolve a plugin-kind catalog through its OpResolve leg" accessor uses (charly-core's Distros/Builders/ resolveResources/resolveAndroids/resolveInits each supply their own registry-coupled resolve callback; this loop itself touches no registry). A bad entry is skipped rather than poisoning the whole vocabulary.
func RootRepoIdentity ¶ added in v0.2026213.1726
RootRepoIdentity determines the local root project's own repo identity for cycle-break registration. An explicit `repo:` field in charly.yml is authoritative; otherwise it falls back to the `git remote origin` identity of the working tree. Returns "" when neither is available (the loader then behaves exactly as before — version-keyed, no self-identity short-circuit).
func ServiceName ¶ added in v0.2026213.1726
ServiceName returns the systemd service name for an image.
func ServiceNameInstance ¶ added in v0.2026213.1726
ServiceNameInstance returns the systemd service name for an image with optional instance.
func SidecarContainerName ¶ added in v0.2026213.1726
SidecarContainerName returns the container name for a named sidecar.
func SidecarContainerNameInstance ¶ added in v0.2026213.1726
SidecarContainerNameInstance returns the container name for a named sidecar, instance-aware.
func SidecarQuadletFilename ¶ added in v0.2026213.1726
SidecarQuadletFilename returns the quadlet filename for a sidecar container.
func SidecarQuadletFilenameInstance ¶ added in v0.2026213.1726
SidecarQuadletFilenameInstance returns the quadlet filename for a sidecar with optional instance.
func SortedDeployKeys ¶ added in v0.2026213.1726
SortedDeployKeys returns the deploy-map keys in deterministic (sorted) order.
func SortedMemberKeys ¶ added in v0.2026213.1726
SortedMemberKeys returns the member keys of a node in deterministic order.
func SplitNamespaceRef ¶
SplitNamespaceRef splits a qualified ref on its FIRST `.` into (namespace, remainder). A bare ref (no dot, or a leading/trailing dot) returns ok=false. The remainder may itself be qualified (`a.b.c` → "a", "b.c").
func SplitRepoAndSubPath ¶
SplitRepoAndSubPath splits a ref into repo path (host/org/repo), sub-path, and name. e.g. "github.com/org/repo/candy/name" -> ("github.com/org/repo", "candy/name", "name") For short refs like "pixi", returns ("", "", "pixi").
func SplitVmAddress ¶ added in v0.2026213.1726
SplitVmAddress detects the "vm:"-prefixed CLI ADDRESSING form (`charly fleet add/del vm:<name>` / `vm:<parent.child>`) and returns the address with that prefix stripped, plus whether it was present. "vm:" here is an ADDRESSING HINT — "resolve this via the vm substrate" — NEVER an identity itself; a caller that needs the plain (tree-lookup / ledger-identity) form strips it via this helper, one which needs the sanitized dc.Fleet key form still applies "vm:"+VmDomainIdentity(...) separately (a DIFFERENT canonical form).
NOT the same job as VmNameFromDeployName (which extracts the VM ENTITY and errors when the prefix is ABSENT — a different, already-established, unchanged contract) or VmDomainIdentity (which sanitizes dots/slashes for a domain-identity STRING, unconditionally, prefix or not). Relocated from sdk/vmshared (#55 vmshared Bucket B); vmshared keeps a re-export forwarder.
func StampDescent ¶ added in v0.2026213.1726
func StampDescent(node *Deploy, traitsFor func(word string) *DeployTraits)
StampDescent stamps node.Descent from the substrate's DECLARED traits (resolved by `traitsFor(node.Target)`) and recurses into the whole nested (Children) + peer (Members) subtree, so every node the deploy chain can descend into carries a descriptor. Idempotent — re-stamping with the same traitsFor writes the same value. traitsFor must never be nil; it returns nil for a word with no declared substrate traits (a targetless group / empty target), which DescentFromTraits maps to the external-in-place default.
func StatusFromState ¶
StatusFromState normalises an engine/container runtime state vocabulary to charly's DeploymentStatus.Status vocabulary. Shared by the substrate plugins' status collectors (the pod live row builder in candy/plugin-substrate) AND the host-side vm collector (charly/status_collect_vm.go, deploy-cone-coupled → stays core until K5) — single-sourced here (R3) so the live/enrich split (P14a) and the K5 vm-collector move both read ONE mapper. Pure over its input.
func StripCandyRefVersion ¶
StripCandyRefVersion splits an @-prefixed ref into its bare form and pinned version. A non-@ ref returns (ref, "").
func StripPortSuffix ¶ added in v0.2026213.1726
StripPortSuffix removes /tcp or /udp protocol suffix from a port string. "47998/udp" -> "47998", "udp"; "8000" -> "8000", ""
func StripURLScheme ¶ added in v0.2026213.1726
StripURLScheme removes http:// or https:// from a remote ref if present.
func SystemdUserDir ¶ added in v0.2026213.1726
SystemdUserDir returns the user-level systemd unit directory (~/.config/systemd/user/).
func TrimPreview ¶ added in v0.2026213.1726
TrimPreview truncates s to a 200-char preview (trailing "…") for compact check-output display. Homed in the fabric slice github.com/opencharly/spec/spec (the same fabric-primitive class as shellquote.ShellQuote) so both the executor slice (spec/exec) and kit reach the ONE copy — kit re-exports it (kit.TrimPreview) so its existing callers compile unchanged (R3, single source).
func ValidateDeployRequiresBox ¶
ValidateDeployRequiresBox enforces the 2026-05-12 schema rule: every `target: pod` deploy entry MUST declare its `box:` field. Pre-cutover the check runner silently fell back to inspecting the running container's image ref via `containerImageRef`, which read stale OCI labels off volume-pinned containers and dropped any probes added after the seed image. The hard-required field forces operator intent to be explicit; the check runner now resolves the ref ONLY from this field.
Scope: target: pod (or empty — pod is the default). target: vm uses `vm:`, target: local is candy-driven, target: kubernetes CLUSTER definitions live in the `kubernetes:` section (not deploy:).
Remediation: `charly migrate` (idempotent) walks every affected deploy and injects the field, inferring the value from the deploy key (`<base>` for `<base>/<instance>` keys; the key itself otherwise).
func ValidateDeploymentChildren ¶
ValidateDeploymentChildren recurses ValidateDeploymentName over node's nested-deployment children.
func ValidateDeploymentName ¶
ValidateDeploymentName rejects a deploy-tree key containing ".", reserved for dotted-path CLI addressing (`charly fleet add a.b.c`).
func ValidateDeploymentTree ¶
ValidateDeploymentTree enforces structural invariants on the deployments tree that can't be expressed in the YAML struct tags:
- Map keys at every level MUST NOT contain "." (dots are reserved for dotted-path CLI addressing like `charly fleet add a.b.c`).
- Every explicit pod deploy must declare `box:` (ValidateDeployRequiresBox).
Errors include the offending path so the user sees exactly which entry needs to be fixed.
func VenueIsAgentProvisioned ¶ added in v0.2026213.1726
func VenueIsAgentProvisioned(uf *UnifiedFile, venue string) bool
VenueIsAgentProvisioned reports whether the deploy node named venue (a child or member anywhere in the fleet tree) is flagged AgentProvisioned — the ONE genuinely fleet-tree-coupled predicate the check-run preflight needs to skip an agent-provisioned image's local-storage ensure.
func VmDomainIdentity ¶
VmDomainIdentity normalizes a deploy/fleet name into the per-deploy VM DOMAIN IDENTITY — the token that keys the libvirt domain (charly-<identity>), the per-domain state dir, the managed ssh-config alias, and the ssh-port ledger entry (vm:<identity>). It is DISTINCT from the kind:vm ENTITY (the disk/spec source, resolved via the deploy's `from:` cross-ref): several distinct beds may share one entity, so keying the domain by the ENTITY collided them on one libvirt domain + one disk + one host SSH port. Keying by the DEPLOY NAME instead makes distinct beds collision-free by construction (each gets its own domain, per-deploy disk overlay, and auto-allocated port).
The normalization strips a leading "vm:" prefix and flattens the instance ("/") and nested-path (".") separators to "-", so a bare bed name maps to itself (check-builder-vm → check-builder-vm), a fleet ref maps to its VM token (vm:arch → arch), and a direct `charly vm create <entity>` (whose domain identity IS the entity) is unchanged. Both the host preresolver and candy/plugin-deploy-vm call this on the SAME deploy name, so the domain they derive always agrees.
Home note (FLOOR-legal leaf): lives in package spec — the always-floor-legal wire/vocabulary leaf — so BOTH charly core and a plugin (candy/plugin-check's bed_session.go, #55 W3 B2-full) derive the domain identity without a vmshared import. stdlib-only (strings).
func VmNameFromDeployName ¶ added in v0.2026213.1726
VmNameFromDeployName extracts the VM entity name from a deploy-key in the legacy "vm:<name>[/<instance>]" form. Callers that hold a schema-v4 deploy key (whose entity comes from the node's `vm:` field) resolve the entity a different way (the node's own From field); this helper handles the prefixed form (legacy refs + the "vm:<entity>" key the del path builds for ledger/teardown keying). The `instance` suffix is preserved for future per-instance addressing but currently unused. Relocated from sdk/vmshared (#55 vmshared Bucket B) so kernel-floor charly files address VM deploys without a vmshared import; vmshared keeps a re-export forwarder.
func VmSshAlias ¶ added in v0.2026213.1726
VmSshAlias returns the canonical managed-ssh-config Host alias for a VM deployment name ("charly-" namespaced) — the token `charly vm create` / the vm deploy lifecycle writes the Host stanza under (~/.config/charly/ssh_config) and an SSHExecutor built with just `Host: VmSshAlias(id)` lets ssh(1) supply User/Port/IdentityFile. Pure naming leaf, a peer of VmDomainIdentity: lives in package spec — the always-floor-legal wire/vocabulary leaf — so the spec/exec executor-chain constructors (RootExecutorForDeployNode/ResolveDeployChain/ VmChildExecutor) derive the alias without an sdk/kit import (#55 K4). sdk/kit re-exports it for the host-coupled ssh-config-fragment writers (renderStanza/VmSshStanza) that STAY in kit.
func WrapContainerCommand ¶ added in v0.2026213.1726
WrapContainerCommand guards an in-container command-check script against stdin-consuming subcommands. The runner delivers in-container scripts to the pod shell over a stdin heredoc ("stdin-attached exec"); without this guard the FIRST subcommand that reads stdin — adb shell, ssh, read, cat — consumes the REST of the heredoc (the not-yet-executed script lines), silently truncating the check to its first command. Wrapping the whole script in a brace group with stdin redirected from /dev/null fixes it generically: the shell reads the entire group before executing it (so the heredoc is fully drained by parse time), then runs every subcommand with stdin tied to /dev/null. The host path (a plain `sh -c` argv) is unaffected.
Types ¶
type AUR ¶
type AUR struct {
Package []PackageItem `yaml:"package,omitempty" json:"package,omitempty"`
Options []string `yaml:"option,omitempty" json:"option,omitempty"`
Replaces []string `yaml:"replace,omitempty" json:"replace,omitempty"`
}
type AdbEndpoint ¶
type AdbEndpoint struct {
Host string `yaml:"host,omitempty" json:"host"`
}
adb endpoint host is "host:port" but MAY carry a ${HOST_PORT:N} var-ref (resolved at deploy time), so only non-emptiness is enforced.
type Agent ¶
type Agent struct {
Description string `yaml:"description,omitempty" json:"description,omitempty"`
Command []string `yaml:"command,omitempty" json:"command"`
PromptVia string `yaml:"prompt_via,omitempty" json:"prompt_via"`
VersionCommand []string `yaml:"version_command,omitempty" json:"version_command,omitempty"`
Timeout Duration `yaml:"timeout,omitempty" json:"timeout,omitempty"`
Env StrMap `yaml:"env,omitempty" json:"env,omitempty"`
WorkingDir string `yaml:"working_dir,omitempty" json:"working_dir,omitempty"`
Credential []CredentialMount `yaml:"credential,omitempty" json:"credential,omitempty"`
ProgressCheckInterval Duration `yaml:"progress_check_interval,omitempty" json:"progress_check_interval,omitempty"`
ProgressNoImprovementTimeout Duration `yaml:"progress_no_improvement_timeout,omitempty" json:"progress_no_improvement_timeout,omitempty"`
OutputFormat string `yaml:"output_format,omitempty" json:"output_format"`
}
type AgentAbortControl ¶
type AgentDelegationEdge ¶
type AgentEvent ¶
type AgentEvent struct {
RunID UUIDv7 `yaml:"run_id,omitempty" json:"run_id"`
Sequence int64 `yaml:"sequence,omitempty" json:"sequence"`
Type string `yaml:"type,omitempty" json:"type"`
Time string `yaml:"time,omitempty" json:"time"`
Payload map[string]any `yaml:"payload,omitempty" json:"payload,omitempty"`
}
type AgentExecSpec ¶
type AgentExecSpec struct {
Command []string `yaml:"command,omitempty" json:"command,omitempty"`
PromptVia string `yaml:"prompt_via,omitempty" json:"prompt_via,omitempty"`
VersionCommand []string `yaml:"version_command,omitempty" json:"version_command,omitempty"`
Timeout Duration `yaml:"timeout,omitempty" json:"timeout,omitempty"`
Env StrMap `yaml:"env,omitempty" json:"env,omitempty"`
WorkingDir string `yaml:"working_dir,omitempty" json:"working_dir,omitempty"`
Credential []CredentialMount `yaml:"credential,omitempty" json:"credential,omitempty"`
ProgressCheckInterval Duration `yaml:"progress_check_interval,omitempty" json:"progress_check_interval,omitempty"`
ProgressNoImprovementTimeout Duration `yaml:"progress_no_improvement_timeout,omitempty" json:"progress_no_improvement_timeout,omitempty"`
OutputFormat string `yaml:"output_format,omitempty" json:"output_format,omitempty"`
}
#AgentExecSpec is a fully-resolved long-running-CLI invocation descriptor — the resolve-to-envelope form of an agent, with Go-level defaults applied by the plugin (Timeout, PromptVia). Kind-agnostic: it describes HOW to launch a CLI, capture its version, and poll it in a plateau-bounded loop — not "agent-kind" knowledge.
type AgentFederationRecord ¶
type AgentFederationRecord struct {
ID UUIDv7 `yaml:"id,omitempty" json:"id"`
Node string `yaml:"node,omitempty" json:"node"`
Owner string `yaml:"owner,omitempty" json:"owner"`
SessionID UUIDv7 `yaml:"session_id,omitempty" json:"session_id,omitempty"`
RunID UUIDv7 `yaml:"run_id,omitempty" json:"run_id,omitempty"`
State string `yaml:"state,omitempty" json:"state"`
UpdatedAt string `yaml:"updated_at,omitempty" json:"updated_at"`
Metadata StrMap `yaml:"metadata,omitempty" json:"metadata,omitempty"`
}
type AgentResolveInput ¶
type AgentResolveInput struct {
Agents map[string]RawBody `yaml:"agents,omitempty" json:"agents,omitempty"`
Name string `yaml:"name,omitempty" json:"name,omitempty"`
}
#AgentResolveInput is candy/plugin-agent's OpResolve input: the opaque agent bodies (the name-keyed AI-CLI catalog) + the selected agent name ("" picks the sole entry, or errors when several are configured).
type AgentResolveReply ¶
type AgentResolveReply struct {
Spec *AgentExecSpec `yaml:"spec,omitempty" json:"spec,omitempty"`
Name string `yaml:"name,omitempty" json:"name,omitempty"`
}
#AgentResolveReply is the OpResolve reply: the resolved exec spec + the chosen agent's catalog name.
type AgentRunRequest ¶
type AgentRunRequest struct {
ID UUIDv7 `yaml:"id,omitempty" json:"id"`
SessionID UUIDv7 `yaml:"session_id,omitempty" json:"session_id"`
RequestID UUIDv7 `yaml:"request_id,omitempty" json:"request_id"`
IdempotencyKey string `yaml:"idempotency_key,omitempty" json:"idempotency_key"`
Prompt string `yaml:"prompt,omitempty" json:"prompt,omitempty"`
Params map[string]any `yaml:"params,omitempty" json:"params,omitempty"`
Resume bool `yaml:"resume,omitempty" json:"resume,omitempty"`
}
type AgentRuntimeCapability ¶
type AgentRuntimeCapability struct {
Provider string `yaml:"provider,omitempty" json:"provider"`
ProtocolVersion int `yaml:"protocol_version,omitempty" json:"protocol_version"`
Entrypoint []string `yaml:"entrypoint,omitempty" json:"entrypoint,omitempty"`
Profiles []string `yaml:"profiles,omitempty" json:"profiles,omitempty"`
InteractionModes []string `yaml:"interaction_modes,omitempty" json:"interaction_modes,omitempty"`
Persistence string `yaml:"persistence,omitempty" json:"persistence,omitempty"`
SemanticCompletion bool `yaml:"semantic_completion,omitempty" json:"semantic_completion,omitempty"`
}
type AgentSession ¶
type AgentSession struct {
ID UUIDv7 `yaml:"id,omitempty" json:"id"`
Runtime string `yaml:"runtime,omitempty" json:"runtime"`
Target TargetSpec `yaml:"target,omitempty" json:"target"`
State string `yaml:"state,omitempty" json:"state"`
CreatedAt string `yaml:"created_at,omitempty" json:"created_at"`
UpdatedAt string `yaml:"updated_at,omitempty" json:"updated_at"`
StorageRef string `yaml:"storage_ref,omitempty" json:"storage_ref,omitempty"`
Metadata StrMap `yaml:"metadata,omitempty" json:"metadata,omitempty"`
TerminalProfile *TerminalProfile `yaml:"terminal_profile,omitempty" json:"terminal_profile,omitempty"`
}
type AgentSessionBinding ¶
type AgentSessionBinding struct {
StorageRef string `yaml:"storage_ref,omitempty" json:"storage_ref"`
}
Provider-reported durable session correlation. Runtime-specific session state remains on the target; this is the only binding replicated to the controller.
type AgentTeam ¶
type AgentTeam struct {
Description string `yaml:"description,omitempty" json:"description,omitempty"`
Agents []AgentTeamMember `yaml:"agents,omitempty" json:"agents"`
Edges []AgentDelegationEdge `yaml:"edges,omitempty" json:"edges,omitempty"`
Coordinator string `yaml:"coordinator,omitempty" json:"coordinator,omitempty"`
Concurrency int64 `yaml:"concurrency,omitempty" json:"concurrency,omitempty"`
EvidencePolicy string `yaml:"evidence_policy,omitempty" json:"evidence_policy,omitempty"`
}
type AgentTeamMember ¶
type AgentTeamMember struct {
Name string `yaml:"name,omitempty" json:"name"`
Runtime string `yaml:"runtime,omitempty" json:"runtime"`
Role string `yaml:"role,omitempty" json:"role,omitempty"`
Target TargetSpec `yaml:"target,omitempty" json:"target,omitempty"`
TerminalProfile *TerminalProfile `yaml:"terminal_profile,omitempty" json:"terminal_profile,omitempty"`
}
type AgentTeamRecord ¶
type AgentTeamRecord struct {
ID UUIDv7 `yaml:"id,omitempty" json:"id"`
Team AgentTeam `yaml:"team,omitempty" json:"team"`
Sessions map[string]UUIDv7 `yaml:"sessions,omitempty" json:"sessions"`
CreatedAt string `yaml:"created_at,omitempty" json:"created_at"`
}
Durable control-plane projection of a declarative team. The authored graph remains #AgentTeam; this record binds each member name to the ephemeral session created for one dispatch so later delegation can re-check authority.
type AggregatedCandyCaps ¶ added in v0.2026210.1533
type AggregatedCandyCaps struct {
PreserveUser bool `yaml:"preserve_user,omitempty" json:"preserve_user,omitempty"`
NeedsRootAfterInit bool `yaml:"needs_root_after_init,omitempty" json:"needs_root_after_init,omitempty"`
InitSystemHint string `yaml:"init_system_hint,omitempty" json:"init_system_hint,omitempty"`
DataOnly bool `yaml:"data_only,omitempty" json:"data_only,omitempty"`
OCILabels map[string]string `yaml:"oci_labels,omitempty" json:"oci_labels,omitempty"`
// provided is the set of capability names declared by some candy in the composition.
// Used by CheckRequiredCapabilities to validate `requires_capabilities:` cross-candy
// requirements.
Provided map[string]bool `yaml:"provided,omitempty" json:"provided,omitempty"`
}
#AggregatedCandyCaps — the output of walking all candies in resolution order. Populated onto #ResolvedBox and consumed wherever code reads BoxConfig.Bootc/BoxConfig.DataImage/the init-system bootc parameter.
func AggregateCandyCapabilities ¶ added in v0.2026213.1726
func AggregateCandyCapabilities(layers map[string]CandyReader, order []string) (*AggregatedCandyCaps, error)
AggregateCandyCapabilities walks `order` (candy names in topological resolution order) and merges each candy's `capabilities:` contribution. Returns an error if two candies declare conflicting values for the same OCI label key — the conflict surfaces the bug rather than silently picking a winner.
type AggregatedCandyCapsView ¶
type AggregatedCandyCapsView struct {
PreserveUser bool `yaml:"preserve_user,omitempty" json:"preserve_user,omitempty"`
NeedsRootAfterInit bool `yaml:"needs_root_after_init,omitempty" json:"needs_root_after_init,omitempty"`
OCILabels map[string]string `yaml:"oci_labels,omitempty" json:"oci_labels,omitempty"`
}
#AggregatedCandyCapsView — the box-level aggregated candy capability surface the build RENDER reads (a wire projection of buildkit.AggregatedCandyCaps): the arbitrary candy-declared oci_labels + the two booleans that gate the render (preserve_user → the final-USER-reset skip + the bootc round-trip label; needs_root_after_init → the post-candy root reset). Carried so the plugin-side render (NewSpecResolvedBox) reattaches img.CandyCaps WITHOUT re-aggregating over the live *Candy graph.
type AlpineBootstrap ¶
type AlpineBootstrap struct {
MirrorURL string `yaml:"mirror_url,omitempty" json:"mirror_url,omitempty"`
}
type Android ¶
type Android struct {
Serial string `yaml:"serial,omitempty" json:"serial,omitempty"`
Device string `yaml:"device,omitempty" json:"device,omitempty"`
ApiLevel int `yaml:"api_level,omitempty" json:"api_level,omitempty"`
GoogleAccount *GoogleAccount `yaml:"google_account,omitempty" json:"google_account,omitempty"`
Plan []Step `yaml:"plan,omitempty" json:"plan,omitempty"`
// Device source — exactly one of box/adb (enforced in Go, see header).
Box string `yaml:"box,omitempty" json:"box,omitempty"`
Adb *AdbEndpoint `yaml:"adb,omitempty" json:"adb,omitempty"`
}
CUE schema for the `android` kind. #Android validates ONE kind:android entity (a device). A device is EITHER an in-pod emulator hosted by a kind:box (box) XOR a remote/physical adb endpoint (adb) — never both, never neither. Shared #Step from _common.cue.
CLOSED: the base struct (no trailing `...`) models every authored key, so an unknown key is still a typo. The box⊻adb exactly-one mutual-exclusion is enforced in GO (loaderkit.ValidateAndroidDevices, the load-time sibling of validateCheckBeds) — NOT by a trailing `& ({box:_} | {adb:_})` disjunction: `cue exp gengotypes` collapses an entity-level top-level disjunction to an EMPTY `struct{}`, which would make spec.Android useless as a drop-in for AndroidSpec. The plain closed struct below generates the full field set, and the Go rule restores the exactly-one-of-box/adb XOR.
func (*Android) EffectiveSerial ¶
EffectiveSerial returns the device serial, defaulting to the emulator serial. Shares the serial core with the *ResolvedAndroid twin (spec/substrate_template_methods.go).
func (*Android) IsEndpoint ¶
IsEndpoint reports whether the device targets a remote adb endpoint. Shares the endpoint core with the *ResolvedAndroid twin (spec/substrate_template_methods.go).
type AndroidDeployVenue ¶
type AndroidDeployVenue struct {
AdbAddr string `yaml:"adb_addr,omitempty" json:"adb_addr"`
Engine string `yaml:"engine,omitempty" json:"engine,omitempty"`
Container string `yaml:"container,omitempty" json:"container,omitempty"`
Serial string `yaml:"serial,omitempty" json:"serial,omitempty"`
GoogleEmail string `yaml:"google_email,omitempty" json:"google_email,omitempty"`
GoogleToken string `yaml:"google_token,omitempty" json:"google_token,omitempty"`
Installs []ApkPackageSpec `yaml:"installs,omitempty" json:"installs,omitempty"`
// boot_timeout / install_deadline / install_interval are the readiness +
// install-retry windows the host ships (no magic numbers in the plugin).
BootTimeout string `yaml:"boot_timeout,omitempty" json:"boot_timeout,omitempty"`
InstallDeadline string `yaml:"install_deadline,omitempty" json:"install_deadline,omitempty"`
InstallInterval string `yaml:"install_interval,omitempty" json:"install_interval,omitempty"`
}
#AndroidDeployVenue is the preresolved deploy:android substrate payload the host's android deploy preresolver produces (in DeployVenue.Substrate) and the candy/plugin-adb deploy:android provider decodes.
type AndroidResolveInput ¶
type AndroidResolveInput struct {
Android RawBody `yaml:"android,omitempty" json:"android"`
}
type AndroidResolveReply ¶
type AndroidResolveReply struct {
Resolved *ResolvedAndroid `yaml:"resolved,omitempty" json:"resolved,omitempty"`
}
type ApkInstallStep ¶ added in v0.2026210.2308
type ApkInstallStep struct {
Packages []ApkPackageSpec
CandyName string
CandyDir string // candy source dir — anchors relative committed-APK paths
}
ApkInstallStep installs Android apps onto a `kind: android` device. It is the IR form of a candy's `apk:` package section — the apk "package format" (parallel to SystemPackagesStep for rpm/deb/pac, and BuilderStep for aur).
Unlike every other step, an apk install lands on a RUNNING Android device, not the build/host filesystem. `target: android` is an EXTERNAL deploy substrate (F1): NO in-proc EmitTarget executes this step — the host-side android deploy preresolver (collectAndroidInstalls, android_deploy_preresolve.go) READS it to collect the apk install specs and ships them to the deploy:android plugin (candy/plugin-adb: apkeep + adb), which drives the device. Every EmitTarget SKIPS it — OCITarget emits nothing (no device at image-build time), and Local/Vm/Pod targets record a skip (a host/VM/pod is not an Android device). This is the same "wrong venue → skip" shape `aur:` uses off-Arch, expressed as a clean recorded skip rather than an error (an image legitimately builds without installing apps).
func (*ApkInstallStep) Kind ¶ added in v0.2026210.2308
func (s *ApkInstallStep) Kind() StepKind
func (*ApkInstallStep) RequiresGate ¶ added in v0.2026210.2308
func (s *ApkInstallStep) RequiresGate() Gate
func (*ApkInstallStep) Reverse ¶ added in v0.2026210.2308
func (s *ApkInstallStep) Reverse() []ReverseOp
Reverse returns no STATIC ledger ops — Android teardown ops are DYNAMIC, recorded from the deploy:android plugin's OpExecute reply (the uninstall reverse ops) at deploy time, replayed at `charly fleet del` like any external deploy substrate.
func (*ApkInstallStep) Scope ¶ added in v0.2026210.2308
func (s *ApkInstallStep) Scope() Scope
Scope is system — installing an app mutates device-global package state.
func (*ApkInstallStep) Venue ¶ added in v0.2026210.2308
func (s *ApkInstallStep) Venue() Venue
Venue is host-native — the android deploy preresolver reads this step from the host (the install itself runs in the deploy:android plugin over the wire).
type ApkPackageSpec ¶
type ApkPackageSpec struct {
Package string `yaml:"package,omitempty" json:"package,omitempty"`
Apk string `yaml:"apk,omitempty" json:"apk,omitempty"`
Source string `yaml:"source,omitempty" json:"source,omitempty"`
Arch string `yaml:"arch,omitempty" json:"arch,omitempty"`
AppVersion string `yaml:"app_version,omitempty" json:"app_version,omitempty"`
}
--------------------------------------------------------------------------- ApkPackageSpec — #CandyApk (android_spec.go). package XOR apk. ---------------------------------------------------------------------------
func (ApkPackageSpec) EffectiveArch ¶
func (s ApkPackageSpec) EffectiveArch() string
EffectiveArch returns the APK ABI, defaulting to x86_64.
func (ApkPackageSpec) EffectiveSource ¶
func (s ApkPackageSpec) EffectiveSource() string
EffectiveSource returns the APK source store, defaulting to apk-pure.
type ArbiterInvokeInput ¶
type ArbiterInvokeInput struct {
Action string `yaml:"action,omitempty" json:"action"`
Claimant string `yaml:"claimant,omitempty" json:"claimant,omitempty"`
Tokens []string `yaml:"tokens,omitempty" json:"tokens,omitempty"`
ClaimAddr HolderAddr `yaml:"claim_addr,omitempty" json:"claim_addr"`
Transient bool `yaml:"transient,omitempty" json:"transient,omitempty"`
Success bool `yaml:"success,omitempty" json:"success,omitempty"`
Token string `yaml:"token,omitempty" json:"token,omitempty"`
// GPU-implied-consumer projection (acquire-shared only, K-wave W3a A2): the claimant node's
// GPU-relevant traits, so verb:arbiter can union its OWN implied-consumer token onto tokens
// before applying its early-return-on-empty acquire policy — arbiter policy belongs in the
// arbiter, not the in-core proxy (the former charly/gpu_imply.go, now
// candy/plugin-preempt/gpu_imply.go). is_group/is_pod_member are pre-derived core-side (the
// in-core nodeTraits/isPodMember trait resolution — not duplicated plugin-side);
// security_devices is the claimant's raw security.devices list.
IsGroup bool `yaml:"is_group,omitempty" json:"is_group,omitempty"`
IsPodMember bool `yaml:"is_pod_member,omitempty" json:"is_pod_member,omitempty"`
SecurityDevices []string `yaml:"security_devices,omitempty" json:"security_devices,omitempty"`
}
#ArbiterInvokeInput is the action-multiplexed input the in-core proxy ships to verb:arbiter. Each action populates only the field(s) it needs (the OTHERS stay Go zero-value); claim_addr is REQUIRED rather than optional because the original hand-written type's `omitempty` on a value-typed (non-pointer) HolderAddr field was already a no-op in encoding/json — Go's omitempty never elides a struct value — so marking it required here changes nothing about the actual wire bytes, only how honestly the schema describes them.
type ArbiterInvokeReply ¶
type ArbiterInvokeReply struct {
Active bool `yaml:"active,omitempty" json:"active,omitempty"`
Bool bool `yaml:"bool,omitempty" json:"bool,omitempty"`
Ledger *PreemptLedger `yaml:"ledger,omitempty" json:"ledger,omitempty"`
Stranded []string `yaml:"stranded,omitempty" json:"stranded,omitempty"`
Error string `yaml:"error,omitempty" json:"error,omitempty"`
}
#ArbiterInvokeReply is the action-multiplexed reply from verb:arbiter.
type ArtifactRef ¶ added in v0.2026210.2308
type ArtifactRef struct {
ContainerPath string // path inside the builder stage/container
HostPath string // path on the deploy target (host fs or final-image fs)
Chown bool // whether to re-own to the target user
}
ArtifactRef describes an output path from a builder that needs to be extracted back to the host or copied into the final OCI image.
For user-scope pixi/npm/cargo on the host target: the host path is bind-mounted into the builder, so there's no "extraction" — the Artifacts list is empty because the bind-mount already put the files in place. For aur: the list contains /tmp/aur-pkgs/*.pkg.tar.zst, which the host target pulls out to a staging dir then hands to pacman.
type ArtifactView ¶
type ArtifactView struct {
ContainerPath string `yaml:"container_path,omitempty" json:"container_path,omitempty"`
HostPath string `yaml:"host_path,omitempty" json:"host_path,omitempty"`
Chown bool `yaml:"chown,omitempty" json:"chown,omitempty"`
}
#ArtifactView is the wire mirror of package main's ArtifactRef.
type BakedLabelSet ¶
type BakedLabelSet struct {
// always-present scalars (the formatter emits these unconditionally as LABEL %q).
Version string `yaml:"version,omitempty" json:"version"`
Box string `yaml:"box,omitempty" json:"box"`
UID int `yaml:"uid,omitempty" json:"uid"`
GID int `yaml:"gid,omitempty" json:"gid"`
User string `yaml:"user,omitempty" json:"user"`
Home string `yaml:"home,omitempty" json:"home"`
// conditional scalars (the formatter omits these when empty).
Registry string `yaml:"registry,omitempty" json:"registry,omitempty"`
Bootc bool `yaml:"bootc,omitempty" json:"bootc,omitempty"`
OCILabels map[string]string `yaml:"oci_labels,omitempty" json:"oci_labels,omitempty"`
Network string `yaml:"network,omitempty" json:"network,omitempty"`
// platform identity + builder-pool coordination (writeJSONLabel).
Distro []string `yaml:"distro,omitempty" json:"distro,omitempty"`
BuildFormat []string `yaml:"build_format,omitempty" json:"build_format,omitempty"`
Builder map[string]string `yaml:"builder,omitempty" json:"builder,omitempty"`
Build []string `yaml:"build,omitempty" json:"build,omitempty"`
// JSON labels (omitted when empty). env is the image-level env MAP (the label wire form).
Port []string `yaml:"port,omitempty" json:"port,omitempty"`
PortProto map[string]string `yaml:"port_proto,omitempty" json:"port_proto,omitempty"`
Volume []LabelVolumeEntry `yaml:"volume,omitempty" json:"volume,omitempty"`
Alias []CollectedAlias `yaml:"alias,omitempty" json:"alias,omitempty"`
Security Security `yaml:"security,omitempty" json:"security,omitempty"`
Env map[string]string `yaml:"env,omitempty" json:"env,omitempty"`
Hook *CandyHook `yaml:"hook,omitempty" json:"hook,omitempty"`
Description *LabelDescriptionSet `yaml:"description,omitempty" json:"description,omitempty"`
Shell *LabelShellSet `yaml:"shell,omitempty" json:"shell,omitempty"`
// init system: active name + the deploy-relevant init def + the DYNAMIC label key the
// per-init service-name list emits under (writeJSONLabel(b, labelInitDef.LabelKey, names)).
Init string `yaml:"init,omitempty" json:"init,omitempty"`
InitDef *CapabilityInitDef `yaml:"init_def,omitempty" json:"init_def,omitempty"`
InitLabelKey string `yaml:"init_label_key,omitempty" json:"init_label_key,omitempty"`
ServiceNames []string `yaml:"service_names,omitempty" json:"service_names,omitempty"`
Service []CapabilityService `yaml:"service,omitempty" json:"service,omitempty"`
PortRelay []int `yaml:"port_relay,omitempty" json:"port_relay,omitempty"`
Skills []LabelSkillEntry `yaml:"skills,omitempty" json:"skills,omitempty"`
Secret []LabelSecretEntry `yaml:"secret,omitempty" json:"secret,omitempty"`
EnvProvide map[string]string `yaml:"env_provide,omitempty" json:"env_provide,omitempty"`
EnvRequire []EnvDependency `yaml:"env_require,omitempty" json:"env_require,omitempty"`
EnvAccept []EnvDependency `yaml:"env_accept,omitempty" json:"env_accept,omitempty"`
SecretRequire []EnvDependency `yaml:"secret_require,omitempty" json:"secret_require,omitempty"`
SecretAccept []EnvDependency `yaml:"secret_accept,omitempty" json:"secret_accept,omitempty"`
MCPProvide []CandyMCPProvide `yaml:"mcp_provide,omitempty" json:"mcp_provide,omitempty"`
AgentProvide []AgentRuntimeCapability `yaml:"agent_provide,omitempty" json:"agent_provide,omitempty"`
TerminalProfiles map[string]TerminalProfile `yaml:"terminal_profiles,omitempty" json:"terminal_profiles,omitempty"`
MCPRequire []EnvDependency `yaml:"mcp_require,omitempty" json:"mcp_require,omitempty"`
MCPAccept []EnvDependency `yaml:"mcp_accept,omitempty" json:"mcp_accept,omitempty"`
Route []LabelRouteEntry `yaml:"route,omitempty" json:"route,omitempty"`
EnvCandy map[string]string `yaml:"env_candy,omitempty" json:"env_candy,omitempty"`
PathAppend []string `yaml:"path_append,omitempty" json:"path_append,omitempty"`
Status string `yaml:"status,omitempty" json:"status,omitempty"`
CheckLevel string `yaml:"check_level,omitempty" json:"check_level,omitempty"`
Info string `yaml:"info,omitempty" json:"info,omitempty"`
CandyVersion map[string]string `yaml:"candy_version,omitempty" json:"candy_version,omitempty"`
DataEntries []LabelDataEntry `yaml:"data_entries,omitempty" json:"data_entries,omitempty"`
DataImage bool `yaml:"data_image,omitempty" json:"data_image,omitempty"`
}
#BakedLabelSet — the BUILD-side wire-form carrier for the render's OCI-label emission (#67, the build_resolve RENDER-leg death). The former charly/generate.go writeLabels body split at the data/format boundary: the HOST gather (buildBakedMetadata) reads the live *Candy/*Config graph + the Collect* aggregators and produces a *spec.BakedLabelSet holding EXACTLY the data writeLabels emits, in WIRE form; the deploykit FORMATTER (WriteLabels) emits the LABEL lines from it byte-for-byte WITHOUT the live graph. It is distinct from #BoxMetadata (the DEPLOY-side hub ExtractMetadata builds field-by-field from labels): a few fields differ in shape between the bake wire form and the deploy read form — env is a map here (the label bakes a JSON object) but []string KEY=VALUE pairs in #BoxMetadata, and volume is #LabelVolumeEntry here (name+path) but #VolumeMount in #BoxMetadata — so the build carrier is its own wire-faithful type, never the deploy hub. It rides the envelope on #ResolvedBoxView.baked_metadata (build-render projection only) and re-attaches via NewSpecResolvedBox. MARSHALED (crosses core→plugin), so its tags are wire-relevant; the sub-shapes are the same CUE-sourced label types writeLabels emitted.
type BaseChainNode ¶
BaseChainNode is one image visited while walking an internal base chain. Name is the ref as it was reached (bare for a root image, namespace-qualified for a base reached across an import boundary, e.g. `cachyos.cachyos`).
type Bootloader ¶
type Bootloader struct {
InstallTemplate string `yaml:"install_template,omitempty" json:"install_template,omitempty"`
InitramfsTemplate string `yaml:"initramfs_template,omitempty" json:"initramfs_template,omitempty"`
FstabTemplate string `yaml:"fstab_template,omitempty" json:"fstab_template,omitempty"`
}
type Bootstrap ¶
type Bootstrap struct {
InstallCmd string `yaml:"install_cmd,omitempty" json:"install_cmd"`
Package []string `yaml:"package,omitempty" json:"package,omitempty"`
CacheMount []CacheMount `yaml:"cache_mount,omitempty" json:"cache_mount,omitempty"`
}
install_cmd is the bootstrap command; ubuntu sets it to "" (kept WITHOUT `& !=""` so the empty-string base case validates).
type Box ¶
type Box struct {
Name EntityRef `yaml:"name,omitempty" json:"name,omitempty"`
Version CalVer `yaml:"version,omitempty" json:"version,omitempty"`
Description string `yaml:"description,omitempty" json:"description,omitempty"`
Enabled *bool `yaml:"enabled,omitempty" json:"enabled,omitempty"`
Base string `yaml:"base,omitempty" json:"base,omitempty"`
From string `yaml:"from,omitempty" json:"from,omitempty"`
BootstrapBuilderImage string `yaml:"bootstrap_builder_image,omitempty" json:"bootstrap_builder_image,omitempty"`
Platforms []string `yaml:"platform,omitempty" json:"platform,omitempty"`
Tag string `yaml:"tag,omitempty" json:"tag,omitempty"`
Registry string `yaml:"registry,omitempty" json:"registry,omitempty"`
Distro []string `yaml:"distro,omitempty" json:"distro,omitempty"`
// BuildFormats: a scalar OR a list on the wire (#BuildFormats UnmarshalYAML
// normalizes "rpm" → ["rpm"]); the Go field is []string. gengotypes degrades
// the disjunction to `any`, so pin the Go shape explicitly (the typed enum is
// a future enhancement — collapse for now).
Build []string `yaml:"build,omitempty" json:"build,omitempty"`
Candy []CandyRef `yaml:"candy,omitempty" json:"candy,omitempty"`
User string `yaml:"user,omitempty" json:"user,omitempty"`
UID *int `yaml:"uid,omitempty" json:"uid,omitempty"`
GID *int `yaml:"gid,omitempty" json:"gid,omitempty"`
UserPolicy string `yaml:"user_policy,omitempty" json:"user_policy,omitempty"`
Builder map[string]string `yaml:"builder,omitempty" json:"builder,omitempty"`
Produce []BuildType `yaml:"produce,omitempty" json:"produce,omitempty"`
Env map[string]string `yaml:"env,omitempty" json:"env,omitempty"`
EnvFile string `yaml:"env_file,omitempty" json:"env_file,omitempty"`
Security *Security `yaml:"security,omitempty" json:"security,omitempty"`
Network string `yaml:"network,omitempty" json:"network,omitempty"`
// init — pin the box's init system instead of letting composition auto-detect it.
// Every value here must be an init the build vocabulary actually defines
// (charly.yml's `init:` entities): a word the vocabulary knows but this enum
// omits is unselectable, which is what kept `openrc` out of reach despite being
// a first-class init with its own service_template, management commands and
// unit_path_template.
Init string `yaml:"init,omitempty" json:"init,omitempty"`
DataImage bool `yaml:"data_image,omitempty" json:"data_image,omitempty"`
Bootc bool `yaml:"bootc,omitempty" json:"bootc,omitempty"`
Readiness *Readiness `yaml:"readiness,omitempty" json:"readiness,omitempty"`
// OCI entrypoint/cmd baked into the image's OCI config (emitted as the final
// Containerfile ENTRYPOINT/CMD directives). OPT-IN and empty by default: a normal
// charly image bakes no command and the deploy-time init (supervisord/systemd)
// is injected instead. Declare these ONLY when something spawns the image
// directly from its baked OCI config with no charly deploy in the loop — e.g. an
// embedded controller (like AgentTeams) spawning child manager/worker containers
// through a runtime socket. `cmd: []` clears the base image's inherited default
// command; a declared entrypoint with no `cmd:` is auto-cleared at emission, so
// the entrypoint runs bare.
Entrypoint []string `yaml:"entrypoint,omitempty" json:"entrypoint,omitempty"`
Cmd []string `yaml:"cmd,omitempty" json:"cmd,omitempty"`
Jobs *int `yaml:"jobs,omitempty" json:"jobs,omitempty"`
PodmanJobs *int `yaml:"podman_jobs,omitempty" json:"podman_jobs,omitempty"`
PodmanJobsCap *int `yaml:"podman_jobs_cap,omitempty" json:"podman_jobs_cap,omitempty"`
ContextIgnore []string `yaml:"context_ignore,omitempty" json:"context_ignore,omitempty"`
Cache string `yaml:"cache,omitempty" json:"cache,omitempty"`
KeepImages *int `yaml:"keep_images,omitempty" json:"keep_images,omitempty"`
KeepCheckRuns *int `yaml:"keep_check_runs,omitempty" json:"keep_check_runs,omitempty"`
Merge *BoxMerge `yaml:"merge,omitempty" json:"merge,omitempty"`
Alias []BoxAlias `yaml:"alias,omitempty" json:"alias,omitempty"`
Plan []Step `yaml:"plan,omitempty" json:"plan,omitempty"`
CheckLevel string `yaml:"check_level,omitempty" json:"check_level,omitempty"`
Shell *Shell `yaml:"shell,omitempty" json:"shell,omitempty"`
}
base and from are mutually exclusive; neither is also valid (scratch box). The entity-level base⊻from mutual-exclusion is enforced in GO (BoxConfig.HasBaseFromConflict, surfaced by validateBoxBaseFrom in validate.go and resolveBase in config.go) — NOT by a trailing `& ({from?: _|_} | {base?: _|_})` disjunction. `cue exp gengotypes` collapses an entity-level top-level disjunction to an EMPTY `struct{}`, which would make spec.Box useless as a drop-in for BoxConfig; the plain closed struct below generates the full field set, and the Go rule restores the XOR. The struct stays CLOSED (no `...`) so an unknown key is still a typo.
func (*Box) HasBaseFromConflict ¶
HasBaseFromConflict reports whether the box authored BOTH base: and from:.
type BoxAlias ¶
type BoxAlias struct {
Name string `yaml:"name,omitempty" json:"name"`
Command string `yaml:"command,omitempty" json:"command,omitempty"`
}
AliasConfig (config.go). CLOSED. command defaults to name when omitted.
type BoxConfig ¶
type BoxConfig = Box
--- top-level entity types ---
func BoxConfigFrom ¶
BoxConfigFrom decodes name's image config from a generic image map.
type BoxFetchResolveReply ¶
type BoxFetchResolveReply struct {
Path string `yaml:"path,omitempty" json:"path,omitempty"`
}
type BoxFetchResolveRequest ¶
type BoxFetchResolveRequest struct {
Spec string `yaml:"spec,omitempty" json:"spec"`
Refresh bool `yaml:"refresh,omitempty" json:"refresh,omitempty"`
}
#BoxFetchResolveRequest/#BoxFetchResolveReply — the "box-fetch-resolve" HostBuild seam behind candy/plugin-authoring's command:fetch/command:refresh (K3 build-tail tail, coneB-buildremnant): the former hidden core `__box-fetch`/`__box-refresh` reentries (charly/box_fetch_reentry.go, DELETED) are replaced by ONE generic host-builder wrapping the SAME host-coupled repo resolver (ResolveProjectRepo → EnsureRepoDownloaded: CHARLY_REPO_OVERRIDE + the registered refs-backend download dispatch + the command:migrate auto-migration) — none of which an sdk-only plugin can run itself. refresh=true additionally force-removes the spec's cache entry before resolving (the former BoxRefreshCmd body) so a stale cache re-clones.
type BoxMap ¶
type BoxMap = map[string]json.RawMessage
BoxMap is the generic image map type — name → opaque marshaled BoxConfig. A plain alias of map[string]json.RawMessage, so it round-trips byte-identically with charly's own (still separately declared, for the UnifiedFile side) `boxMap` alias — both resolve to the SAME underlying type.
type BoxMerge ¶
type BoxMerge struct {
Auto bool `yaml:"auto,omitempty" json:"auto,omitempty"`
MaxMB int `yaml:"max_mb,omitempty" json:"max_mb,omitempty"`
MaxTotalMB int `yaml:"max_total_mb,omitempty" json:"max_total_mb,omitempty"`
}
MergeConfig (config.go). CLOSED.
type BoxMetadata ¶
type BoxMetadata struct {
Box string `yaml:"box,omitempty" json:"box,omitempty"`
Version string `yaml:"version,omitempty" json:"version,omitempty"`
Registry string `yaml:"registry,omitempty" json:"registry,omitempty"`
Bootc bool `yaml:"bootc,omitempty" json:"bootc,omitempty"`
UID int `yaml:"uid,omitempty" json:"uid,omitempty"`
GID int `yaml:"gid,omitempty" json:"gid,omitempty"`
User string `yaml:"user,omitempty" json:"user,omitempty"`
Home string `yaml:"home,omitempty" json:"home,omitempty"`
Port []string `yaml:"port,omitempty" json:"port,omitempty"`
Volume []VolumeMount `yaml:"volume,omitempty" json:"volume,omitempty"`
Alias []CollectedAlias `yaml:"alias,omitempty" json:"alias,omitempty"`
Security Security `yaml:"security,omitempty" json:"security,omitempty"`
Network string `yaml:"network,omitempty" json:"network,omitempty"`
Tunnel *TunnelYAML `yaml:"tunnel,omitempty" json:"tunnel,omitempty"`
DNS string `yaml:"dns,omitempty" json:"dns,omitempty"`
AcmeEmail string `yaml:"acme_email,omitempty" json:"acme_email,omitempty"`
Env []string `yaml:"env,omitempty" json:"env,omitempty"`
Hook *CandyHook `yaml:"hook,omitempty" json:"hook,omitempty"`
Route []LabelRouteEntry `yaml:"route,omitempty" json:"route,omitempty"`
Init string `yaml:"init,omitempty" json:"init,omitempty"`
InitDef *CapabilityInitDef `yaml:"init_def,omitempty" json:"init_def,omitempty"`
Service []CapabilityService `yaml:"service,omitempty" json:"service,omitempty"`
ServiceNames []string `yaml:"service_names,omitempty" json:"service_names,omitempty"`
EnvCandy map[string]string `yaml:"env_candy,omitempty" json:"env_candy,omitempty"`
PathAppend []string `yaml:"path_append,omitempty" json:"path_append,omitempty"`
Engine string `yaml:"engine,omitempty" json:"engine,omitempty"`
PortProto map[string]string `yaml:"port_proto,omitempty" json:"port_proto,omitempty"`
PortRelay []int `yaml:"port_relay,omitempty" json:"port_relay,omitempty"`
Skills []LabelSkillEntry `yaml:"skills,omitempty" json:"skills,omitempty"`
Status string `yaml:"status,omitempty" json:"status,omitempty"`
Info string `yaml:"info,omitempty" json:"info,omitempty"`
CandyVersion map[string]string `yaml:"candy_version,omitempty" json:"candy_version,omitempty"`
Secret []LabelSecretEntry `yaml:"secret,omitempty" json:"secret,omitempty"`
Distro []string `yaml:"distro,omitempty" json:"distro,omitempty"`
BuildFormat []string `yaml:"build_format,omitempty" json:"build_format,omitempty"`
Builder map[string]string `yaml:"builder,omitempty" json:"builder,omitempty"`
Build []string `yaml:"build,omitempty" json:"build,omitempty"`
DataEntries []LabelDataEntry `yaml:"data_entries,omitempty" json:"data_entries,omitempty"`
DataImage bool `yaml:"data_image,omitempty" json:"data_image,omitempty"`
EnvProvide map[string]string `yaml:"env_provide,omitempty" json:"env_provide,omitempty"`
EnvRequire []EnvDependency `yaml:"env_require,omitempty" json:"env_require,omitempty"`
EnvAccept []EnvDependency `yaml:"env_accept,omitempty" json:"env_accept,omitempty"`
SecretAccept []EnvDependency `yaml:"secret_accept,omitempty" json:"secret_accept,omitempty"`
SecretRequire []EnvDependency `yaml:"secret_require,omitempty" json:"secret_require,omitempty"`
MCPProvide []CandyMCPProvide `yaml:"mcp_provide,omitempty" json:"mcp_provide,omitempty"`
AgentProvide []AgentRuntimeCapability `yaml:"agent_provide,omitempty" json:"agent_provide,omitempty"`
TerminalProfiles map[string]TerminalProfile `yaml:"terminal_profiles,omitempty" json:"terminal_profiles,omitempty"`
MCPRequire []EnvDependency `yaml:"mcp_require,omitempty" json:"mcp_require,omitempty"`
MCPAccept []EnvDependency `yaml:"mcp_accept,omitempty" json:"mcp_accept,omitempty"`
Description *LabelDescriptionSet `yaml:"description,omitempty" json:"description,omitempty"`
Shell *LabelShellSet `yaml:"shell,omitempty" json:"shell,omitempty"`
CheckLevel string `yaml:"check_level,omitempty" json:"check_level,omitempty"`
}
#BoxMetadata — the OCI-label metadata hub. NEVER whole-marshaled (ExtractMetadata builds it field-by-field), so its own tags are wire-irrelevant. Deploy-only fields (Tunnel/DNS/ AcmeEmail/Engine) are fed by MergeDeployOntoMetadata, never baked. PortProto RESHAPED map[int]string → map[string]string (the WIRE was always string-keyed; the int-key was an ExtractMetadata-internal convenience — resolveProto + ExtractMetadata rewired to match).
type BoxRefResolveReply ¶
type BoxRefResolveReply struct {
// exists_ref / pull_ref is the fully-qualified registry ref to check for local
// presence / hand to `podman pull`; "" when the identifier could not be
// resolved (no project reachable, or an unknown short name).
ExistsRef string `yaml:"exists_ref,omitempty" json:"exists_ref,omitempty"`
PullRef string `yaml:"pull_ref,omitempty" json:"pull_ref,omitempty"`
// build_fallback_short is the short name (bare or namespace-qualified, e.g.
// "fedora.fedora-builder") `charly box build` should target when the pull
// fails; "" when no local build-fallback is possible for this identifier.
BuildFallbackShort string `yaml:"build_fallback_short,omitempty" json:"build_fallback_short,omitempty"`
// produced_ref is the registry ref build_fallback_short resolves to today
// (registry+name, tag-less) — used to tag-alias a pinned-tag input ref onto
// the freshly built image.
ProducedRef string `yaml:"produced_ref,omitempty" json:"produced_ref,omitempty"`
}
type BoxRefResolveRequest ¶
type BoxRefResolveRequest struct {
Image string `yaml:"image,omitempty" json:"image"`
Dir string `yaml:"dir,omitempty" json:"dir,omitempty"`
}
#BoxRefResolveRequest / #BoxRefResolveReply — the "box-ref-resolve" HostBuild seam: resolve a short-name or full-ref image identifier against charly.yml.
type BuildEnsureReply ¶
type BuildEnsureReply struct {
Error string `yaml:"error,omitempty" json:"error,omitempty"`
}
type BuildEnsureRequest ¶
type BuildEnsureRequest struct {
Image string `yaml:"image,omitempty" json:"image"`
Dir string `yaml:"dir,omitempty" json:"dir,omitempty"`
BuildEngine string `yaml:"build_engine,omitempty" json:"build_engine,omitempty"`
RunEngine string `yaml:"run_engine,omitempty" json:"run_engine,omitempty"`
}
#BuildEnsureRequest / #BuildEnsureReply — the build:ensure word's Invoke envelope: ensure an image is present in local podman storage, falling back to a local (or remote-cached) build when the identifier maps to a project charly.yml entry.
type BuildEnv ¶
type BuildEnv struct {
Distros []string `yaml:"distros,omitempty" json:"distros,omitempty"`
Image string `yaml:"image,omitempty" json:"image,omitempty"`
DevLocalPkg bool `yaml:"dev_local_pkg,omitempty" json:"dev_local_pkg,omitempty"`
ImageBuildDir string `yaml:"image_build_dir,omitempty" json:"image_build_dir,omitempty"`
ContextRelPrefix string `yaml:"context_rel_prefix,omitempty" json:"context_rel_prefix,omitempty"`
// A pod-overlay deploy's add_candy: refs (if any) — threaded so a HOST-COUPLED word's OpEmit
// (candy/plugin-installstep's getGenerator) can widen ITS OWN "resolved-project" envelope
// re-fetch the same way ScanAllCandyWithConfigOpts' ResolveOpts.ExtraCandyRefs already widens
// the host's own overlay Generator (hostBuildOverlay). Without this, an add_candy candy the
// overlay build correctly resolved host-side is absent from this plugin's INDEPENDENT
// envelope fetch, and candyByName's REMOTE-candy fallback still misses (RCA'd K1-alpha
// regression: check-addcandy-pod's overlay-deploy path, "candy not found").
ExtraCandyRefs []string `yaml:"extra_candy_refs,omitempty" json:"extra_candy_refs,omitempty"`
}
#BuildEnv is the build-context descriptor the host puts in op.Env for an OpEmit Invoke at image-generation time: the image's distro tags + name, so a plugin can tailor its emitted Containerfile fragment per distro/arch.
type BuildPkgReply ¶
type BuildPkgRequest ¶
type BuildPkgRequest struct {
Format []string `yaml:"format,omitempty" json:"format,omitempty"`
Candy string `yaml:"candy,omitempty" json:"candy,omitempty"`
Out string `yaml:"out,omitempty" json:"out,omitempty"`
Dir string `yaml:"dir,omitempty" json:"dir,omitempty"`
}
#BuildPkgRequest / #BuildPkgReply — the build:pkg word's Invoke envelope: `charly box pkg`'s CLI→DRIVE envelope (mirrors #BuildRequest's shape, K3 build-tail move — the former hidden core `__box-pkg` reentry is DELETED, its body now runs plugin-side in candy/plugin-build).
type BuildReply ¶
type BuildReply struct {
Written []string `yaml:"written,omitempty" json:"written,omitempty"`
Error string `yaml:"error,omitempty" json:"error,omitempty"`
}
#BuildReply is what a build:box / build:generate plugin echoes back from its HostBuild call.
type BuildRequest ¶
type BuildRequest struct {
Boxes []string `yaml:"boxes,omitempty" json:"boxes,omitempty"`
ExtraCandyRefs []string `yaml:"extra_candy_refs,omitempty" json:"extra_candy_refs,omitempty"`
Tag string `yaml:"tag,omitempty" json:"tag,omitempty"`
Dir string `yaml:"dir,omitempty" json:"dir,omitempty"`
IncludeDisabled bool `yaml:"include_disabled,omitempty" json:"include_disabled,omitempty"`
DevLocalPkg bool `yaml:"dev_local_pkg,omitempty" json:"dev_local_pkg,omitempty"`
Push bool `yaml:"push,omitempty" json:"push,omitempty"`
Platform string `yaml:"platform,omitempty" json:"platform,omitempty"`
Cache string `yaml:"cache,omitempty" json:"cache,omitempty"`
NoCache bool `yaml:"no_cache,omitempty" json:"no_cache,omitempty"`
Jobs int `yaml:"jobs,omitempty" json:"jobs,omitempty"`
PodmanJobs int `yaml:"podman_jobs,omitempty" json:"podman_jobs,omitempty"`
}
#BuildRequest is the CLI→DRIVE envelope: what `charly box build` / `charly box generate` marshal into the compiled-in candy/plugin-build's Invoke (op.Params).
type BuildResolveBox ¶
type BuildResolveBox struct {
Name string `yaml:"name,omitempty" json:"name"`
FullTag string `yaml:"full_tag,omitempty" json:"full_tag,omitempty"`
Registry string `yaml:"registry,omitempty" json:"registry,omitempty"`
Platforms []string `yaml:"platforms,omitempty" json:"platforms,omitempty"`
MergeAuto bool `yaml:"merge_auto,omitempty" json:"merge_auto,omitempty"`
MergeMaxMB int64 `yaml:"merge_max_mb,omitempty" json:"merge_max_mb,omitempty"`
MergeMaxTotalMB int64 `yaml:"merge_max_total_mb,omitempty" json:"merge_max_total_mb,omitempty"`
// from/bootstrap_builder_image/distro_def/bootstrap_builder are the privileged-bootstrap
// inputs (a `from: builder:<name>` image) the candy needs to run
// buildkit.RunPrivileged itself instead of the host doing it in the buildengine-prep seam.
// bootstrap_builder is the SPECIFIC resolved #Builder for `from`'s builder name (not the
// whole per-image builder map) — the minimal slice runPrivilegedBootstrap actually reads.
// Both nil/empty for a non-bootstrap image (the common case).
From string `yaml:"from,omitempty" json:"from,omitempty"`
BootstrapBuilderImage string `yaml:"bootstrap_builder_image,omitempty" json:"bootstrap_builder_image,omitempty"`
DistroDef *ResolvedDistro `yaml:"distro_def,omitempty" json:"distro_def,omitempty"`
BootstrapBuilder *Builder `yaml:"bootstrap_builder,omitempty" json:"bootstrap_builder,omitempty"`
}
#BuildResolveBox is one image's drive descriptor: the tag to build and whether merge.auto fires for it (the candy gates the verb:oci layer-merge call on this bool; the host merge seam resolves the size knobs from config). The rendered Containerfile CONTENT is NO LONGER shipped in the reply (#67 render-DRIVE move): plugin-build renders Containerfiles itself from the resolved-project envelope (BuildResolveReply.resolved_project) via deploykit.Generator, so the candy pipes dg.Containerfiles[name] to podman — not a reply field.
type BuildResolveReply ¶
type BuildResolveReply struct {
Engine string `yaml:"engine,omitempty" json:"engine,omitempty"`
EngineName string `yaml:"engine_name,omitempty" json:"engine_name,omitempty"`
Platform string `yaml:"platform,omitempty" json:"platform,omitempty"`
Order []string `yaml:"order,omitempty" json:"order,omitempty"`
Levels [][]string `yaml:"levels,omitempty" json:"levels,omitempty"`
Boxes []BuildResolveBox `yaml:"boxes,omitempty" json:"boxes,omitempty"`
Jobs int64 `yaml:"jobs,omitempty" json:"jobs,omitempty"`
PodmanJobs int64 `yaml:"podman_jobs,omitempty" json:"podman_jobs,omitempty"`
Cache string `yaml:"cache,omitempty" json:"cache,omitempty"`
KeepImages int64 `yaml:"keep_images,omitempty" json:"keep_images,omitempty"`
Written []string `yaml:"written,omitempty" json:"written,omitempty"`
// resolved_project — the full resolved-project envelope with build-render
// caches (BakedMetadata/RenderCandyOrder/InitSystem/InitDef/ActiveInits/Caps
// on each ResolvedBoxView + GlobalOrder/ExternalizedBuilders on the project),
// so plugin-build renders Containerfiles via deploykit.Generator WITHOUT the
// live *Candy/*Config graph (#67 render-DRIVE move). Filled by the buildengine-prep
// seam (render-prep → projectResolvedProject with caches). Empty for the
// generate-only path that writes Containerfiles host-side (transitional).
ResolvedProject *ResolvedProject `yaml:"resolved_project,omitempty" json:"resolved_project,omitempty"`
Error string `yaml:"error,omitempty" json:"error,omitempty"`
}
type BuildResolveRequest ¶
type BuildResolveRequest struct {
Boxes []string `yaml:"boxes,omitempty" json:"boxes,omitempty"`
Tag string `yaml:"tag,omitempty" json:"tag,omitempty"`
Dir string `yaml:"dir,omitempty" json:"dir,omitempty"`
IncludeDisabled bool `yaml:"include_disabled,omitempty" json:"include_disabled,omitempty"`
DevLocalPkg bool `yaml:"dev_local_pkg,omitempty" json:"dev_local_pkg,omitempty"`
Push bool `yaml:"push,omitempty" json:"push,omitempty"`
Platform string `yaml:"platform,omitempty" json:"platform,omitempty"`
Cache string `yaml:"cache,omitempty" json:"cache,omitempty"`
NoCache bool `yaml:"no_cache,omitempty" json:"no_cache,omitempty"`
Jobs int64 `yaml:"jobs,omitempty" json:"jobs,omitempty"`
PodmanJobs int64 `yaml:"podman_jobs,omitempty" json:"podman_jobs,omitempty"`
GenerateOnly bool `yaml:"generate_only,omitempty" json:"generate_only,omitempty"`
}
#BuildResolveRequest carries the CLI-supplied build inputs (the former BuildRequest fields) the host cannot reconstruct from Dir alone. The host runs NewGenerator (loader) + Generate (render → .build/), the privileged builder-bootstrap, and the builder-image ensure host-side, then returns the drive-model. GenerateOnly (the `charly box generate` path) renders + returns the written Containerfile paths WITHOUT bootstrap/ensure/buildengine-prep.
type BuildResolvedBox ¶ added in v0.2026215.1748
type BuildResolvedBox struct {
ResolvedBox
// Build config (resolved per-image via charly.yml import: + the binary-embedded build vocabulary)
DistroConfig *DistroConfig `json:"-"`
DistroDef *ResolvedDistro `json:"-"`
BuilderConfig *BuilderConfig `json:"-"`
InitSystem string `json:"-"`
InitDef *ResolvedInit `json:"-"`
CandyCaps *AggregatedCandyCaps `json:"-"`
BakedMetadata *BakedLabelSet `json:"-"`
RenderCandyOrder []string `json:"-"`
ActiveInits map[string]*ResolvedInit `json:"-"`
}
build_resolved_box.go — SPIKE: the render-time embedding wrapper relocated from sdk/buildkit/resolved_box.go (#55 value-type relocation spike). Every field type here already resolves to a spec.* type (DistroConfig/BuilderConfig/ResolvedDistro/ ResolvedInit/AggregatedCandyCaps/BakedLabelSet are all CUE-generated in this same package) so the wrapper carries ZERO buildkit-only content — it exists purely because gengotypes has no json:"-" construct (the kit.CheckResult/DeadlineExceeded pattern — see /charly-internals:go "Generation coverage"). buildkit.ResolvedBox becomes a type alias onto this type so every existing buildkit/deploykit/candy call site compiles unchanged.
type BuildStageContext ¶
type BuildStageContext struct {
BuilderRef string `yaml:"builder_ref,omitempty" json:"builder_ref,omitempty"`
StageName string `yaml:"stage_name,omitempty" json:"stage_name,omitempty"`
LayerStage string `yaml:"layer_stage,omitempty" json:"layer_stage,omitempty"`
CopySrc string `yaml:"copy_src,omitempty" json:"copy_src,omitempty"`
UID int `yaml:"uid,omitempty" json:"uid,omitempty"`
GID int `yaml:"gid,omitempty" json:"gid,omitempty"`
Home string `yaml:"home,omitempty" json:"home,omitempty"`
User string `yaml:"user,omitempty" json:"user,omitempty"`
Manifest string `yaml:"manifest,omitempty" json:"manifest,omitempty"`
HasLockFile bool `yaml:"has_lock_file,omitempty" json:"has_lock_file,omitempty"`
InstallCmd string `yaml:"install_cmd,omitempty" json:"install_cmd,omitempty"`
ManylinuxFix string `yaml:"manylinux_fix,omitempty" json:"manylinux_fix,omitempty"`
CacheMounts []CacheMount `yaml:"cache_mounts,omitempty" json:"cache_mounts,omitempty"`
Packages []string `yaml:"packages,omitempty" json:"packages,omitempty"`
Options []string `yaml:"options,omitempty" json:"options,omitempty"`
HasBuildScript bool `yaml:"has_build_script,omitempty" json:"has_build_script,omitempty"`
BuildScript string `yaml:"build_script,omitempty" json:"build_script,omitempty"`
}
#BuildStageContext — data a builder's multi-stage `stage_template` renders against: builder/stage identity, scratch-stage + copy-src wiring, detected manifest/lockfile/build-script, install command, cache mounts, and (for config-detected builders like aur) packages/options.
type BuildTarget ¶
type BuildTarget struct {
Name string `yaml:"name,omitempty" json:"name,omitempty"`
Auto bool `yaml:"auto,omitempty" json:"auto,omitempty"`
}
#BuildTarget — a build-order entry (charly box list targets): the box/intermediate name + whether it is an auto-computed intermediate (the `name [auto]` output).
type Builder ¶
type Builder struct {
DetectFiles []string `yaml:"detect_file,omitempty" json:"detect_file,omitempty"`
DetectConfig string `yaml:"detect_config,omitempty" json:"detect_config,omitempty"`
RequiresSrcDir bool `yaml:"requires_src_dir,omitempty" json:"requires_src_dir,omitempty"`
Inline bool `yaml:"inline,omitempty" json:"inline,omitempty"`
CacheMount []CacheMount `yaml:"cache_mount,omitempty" json:"cache_mount,omitempty"`
Env StrMap `yaml:"env,omitempty" json:"env,omitempty"`
RuntimeEnv StrMap `yaml:"runtime_env,omitempty" json:"runtime_env,omitempty"`
ManylinuxFix string `yaml:"manylinux_fix,omitempty" json:"manylinux_fix,omitempty"`
BuildScript string `yaml:"build_script,omitempty" json:"build_script,omitempty"`
Phases *PhaseSet `yaml:"phase,omitempty" json:"phase,omitempty"`
InstallCommands map[string]string `yaml:"install_command,omitempty" json:"install_command,omitempty"`
CopyArtifacts []Copy `yaml:"copy_artifact,omitempty" json:"copy_artifact,omitempty"`
CopyBinary *Copy `yaml:"copy_binary,omitempty" json:"copy_binary,omitempty"`
PathContributions []string `yaml:"path_contribution,omitempty" json:"path_contribution,omitempty"`
Kind string `yaml:"kind,omitempty" json:"kind"`
Privileged bool `yaml:"privileged,omitempty" json:"privileged"`
OutputArtifact string `yaml:"output_artifact,omitempty" json:"output_artifact,omitempty"`
}
func (*Builder) IsBootstrap ¶
IsBootstrap reports whether the builder is a bootstrap builder.
type BuilderCollectInput ¶
type BuilderCollectInput struct {
Candy string `yaml:"candy,omitempty" json:"candy"`
Builder string `yaml:"builder,omitempty" json:"builder"`
Home string `yaml:"home,omitempty" json:"home,omitempty"`
Packages []string `yaml:"packages,omitempty" json:"packages,omitempty"`
Replaces []string `yaml:"replaces,omitempty" json:"replaces,omitempty"`
}
#BuilderCollectInput is the OpCollectContext params: the host-supplied candy descriptor an external builder plugin reads to produce its per-candy stage context.
type BuilderCollectReply ¶
type BuilderCollectReply struct {
Context map[string]any `yaml:"context,omitempty" json:"context,omitempty"`
}
#BuilderCollectReply is the OpCollectContext reply: the builder-specific stage-context keys the host merges onto the base context.
type BuilderConfig ¶ added in v0.2026210.2052
#BuilderConfig is the `builder:` build-vocabulary container — the whole builder map keyed by builder name (charly/charly.yml's `builder:` section). #55 step 3-III: relocated from sdk/buildkit for the import-purity lever (see #DistroConfig). Its methods (ValidBuilderType / BuilderNames) are pure Go METHODS and stay hand-written in spec/builder_config_methods.go.
func ProjectBuilderConfig ¶ added in v0.2026213.1726
func ProjectBuilderConfig(uf *UnifiedFile) *BuilderConfig
ProjectBuilderConfig reconstructs the *BuilderConfig (builder: section) from uf. The builder bodies decode purely (DecodePluginKindMap) — no OpResolve callback needed. Nil when no builders are configured.
func (*BuilderConfig) BuilderNames ¶ added in v0.2026210.2052
func (bc *BuilderConfig) BuilderNames() []string
BuilderNames returns sorted list of defined builder names.
func (*BuilderConfig) ValidBuilderType ¶ added in v0.2026210.2052
func (bc *BuilderConfig) ValidBuilderType(name string) bool
ValidBuilderType returns true if the given name is a defined builder.
type BuilderMap ¶ added in v0.2026210.1533
#BuilderMap — a map of build type (pixi/npm/cargo/aur) → builder image name.
func EffectiveBuilderForBox ¶ added in v0.2026213.1726
func EffectiveBuilderForBox(cfg *Config, name string, img BoxConfig) BuilderMap
EffectiveBuilderForBox computes the builder image refs an image will build against, from a RAW BoxConfig — the FETCH-path counterpart to ResolveBox's resolved-value path. Both end at the ONE canonical ResolveEffectiveBuilder.
func PickDistroBuilder ¶ added in v0.2026213.1726
func PickDistroBuilder(candidates []DistroBuilderCandidate, distroTags []string) BuilderMap
PickDistroBuilder returns the builder map of the first candidate (in the given, caller-sorted order — determinism is the caller's responsibility, so the result is stable when more than one candidate shares a distro tag) whose Distro contains a tag, walking distroTags in priority order (most specific first, e.g. ["cachyos","arch"] or ["fedora:43","fedora"]). Only candidates with a non-empty Builder are considered.
func ResolveEffectiveBuilder ¶ added in v0.2026213.1726
func ResolveEffectiveBuilder(cfg *Config, name string, distro []string, base string, isExternalBase bool, imgBuilder BuilderMap) BuilderMap
ResolveEffectiveBuilder computes an image's effective builder map via the SINGLE canonical precedence, lowest→highest: defaults.builder → distro-keyed default → direct local base → per-image override — then self-references are filtered. EVERY builder-consuming path calls this so resolution can never drift between commands.
func (BuilderMap) AllBuilder ¶ added in v0.2026210.1533
func (m BuilderMap) AllBuilder() []string
AllBuilder returns a deduplicated sorted list of builder image names.
func (BuilderMap) BuilderFor ¶ added in v0.2026210.1533
func (m BuilderMap) BuilderFor(format string) string
BuilderFor returns the builder image name for the given format, or "".
func (BuilderMap) HasBuilder ¶ added in v0.2026210.1533
func (m BuilderMap) HasBuilder(format string) bool
HasBuilder returns true if a builder is configured for the given format.
type BuilderPreresolved ¶ added in v0.2026213.1726
BuilderPreresolved is one candy×builder's pre-resolved payload: the builder-specific render Context (opaque key/value map from the builder plugin's OpCollectContext) plus its Reverse teardown ops (from OpReverse). Consumed by the deploy-plan compiler, which only reads it.
type BuilderResolveInput ¶
type BuilderResolveInput struct {
Candy string `yaml:"candy,omitempty" json:"candy"`
Builder string `yaml:"builder,omitempty" json:"builder,omitempty"`
BuilderRef string `yaml:"builder_ref,omitempty" json:"builder_ref,omitempty"`
StageName string `yaml:"stage_name,omitempty" json:"stage_name,omitempty"`
LayerStage string `yaml:"layer_stage,omitempty" json:"layer_stage,omitempty"`
CopySrc string `yaml:"copy_src,omitempty" json:"copy_src,omitempty"`
UID int `yaml:"uid,omitempty" json:"uid,omitempty"`
GID int `yaml:"gid,omitempty" json:"gid,omitempty"`
Home string `yaml:"home,omitempty" json:"home,omitempty"`
User string `yaml:"user,omitempty" json:"user,omitempty"`
Manifest string `yaml:"manifest,omitempty" json:"manifest,omitempty"`
HasLockFile bool `yaml:"has_lock_file,omitempty" json:"has_lock_file,omitempty"`
InstallCmd string `yaml:"install_cmd,omitempty" json:"install_cmd,omitempty"`
ManylinuxFix string `yaml:"manylinux_fix,omitempty" json:"manylinux_fix,omitempty"`
HasBuildScript bool `yaml:"has_build_script,omitempty" json:"has_build_script,omitempty"`
BuildScript string `yaml:"build_script,omitempty" json:"build_script,omitempty"`
Packages []string `yaml:"packages,omitempty" json:"packages,omitempty"`
Options []string `yaml:"options,omitempty" json:"options,omitempty"`
CacheMountsOwned string `yaml:"cache_mounts_owned,omitempty" json:"cache_mounts_owned,omitempty"`
CacheMountsAuto string `yaml:"cache_mounts_auto,omitempty" json:"cache_mounts_auto,omitempty"`
Inline bool `yaml:"inline,omitempty" json:"inline,omitempty"`
}
#BuilderResolveInput is the OpResolve params: the RENDER CONTEXT the host computes and hands a builder plugin so it can render its build-time multi-stage self-contained.
func BuilderResolveInputFrom ¶ added in v0.2026213.1726
func BuilderResolveInputFrom(candyName, builderName string, builderDef *BuilderDef, ctx *BuildStageContext) BuilderResolveInput
BuilderResolveInputFrom builds the serializable BuilderResolveInput a builder plugin's OpResolve leg needs, from the host-computed BuildStageContext. Cache mounts are PRE-RENDERED to flag strings here (RenderCacheMounts) with the SAME separator/trailing the former cacheMountsOwned/cacheMountsAuto template funcs used, so the plugin's rendered stage is byte-identical to the former embedded-vocabulary render. Shared by the box-build path (sdk/deploykit resolveDetectionBuilderReply) AND the pod-overlay build-emit (candy/plugin-installstep stepEmitBuilder, via the deploykit re-export) + the INLINE-builder resolve (sdk/deploykit's dg.ResolveInlineBuilder), R3 — hence exported.
type BuilderResolveReply ¶
type BuilderResolveReply struct {
Stage string `yaml:"stage,omitempty" json:"stage,omitempty"`
CopyArtifacts []string `yaml:"copy_artifacts,omitempty" json:"copy_artifacts,omitempty"`
CopyBinary string `yaml:"copy_binary,omitempty" json:"copy_binary,omitempty"`
InlineFragment string `yaml:"inline_fragment,omitempty" json:"inline_fragment,omitempty"`
}
#BuilderResolveReply is what a builder plugin returns from an OpResolve Invoke at image-generation time — the build-time BUILDER leg.
type BuilderReverseInput ¶
type BuilderReverseInput struct {
Candy string `yaml:"candy,omitempty" json:"candy"`
Builder string `yaml:"builder,omitempty" json:"builder"`
Context map[string]any `yaml:"context,omitempty" json:"context,omitempty"`
}
#BuilderReverseInput is the OpReverse params: the candy + its resolved stage-context keys.
type BuilderReverseReply ¶
type BuilderReverseReply struct {
ReverseOps []ReverseOp `yaml:"reverse_ops,omitempty" json:"reverse_ops,omitempty"`
}
#BuilderReverseReply is the OpReverse reply: the builder's teardown ops.
type BuilderRunOpts ¶
type BuilderRunOpts struct {
Engine string // "podman" or "docker"; default "podman"
BuilderImage string // full image ref, e.g. "ghcr.io/opencharly/fedora-builder:latest"
CandyDir string // absolute path to candy source (bind-mounted as /work)
ScriptBody string // shell script contents to pass to bash -c
// ResolveImage + EnsureImage inject the image-presence behavior that used to
// read *Config directly (Config stays a charly kernel type; the executor is an
// sdk mechanism reached through this seam). ResolveImage maps a short/namespaced
// builder ref to its concrete podman storage key (side-effect-free — safe in the
// dry-run path). EnsureImage guarantees the image is present, falling back to a
// local `charly box build <basename>` when it is project-buildable. The charly
// callers close over Cfg + ProjectDir; see charly/host_build_box_ref_resolve.go
// (ResolveImage) and charly/dispatch_build_ensure.go (EnsureImage).
ResolveImage func(image string) (string, error)
EnsureImage func(ctx context.Context, image string) error
// Bind-mounts. Keys are container paths; values are host paths.
// Set by the caller based on the builder kind — pixi/npm/cargo use
// the same HOME-subdir layout, aur uses a tmpdir for /tmp/aur-pkgs.
BindMounts map[string]string
// Env vars to set inside the container (in addition to HOME).
Env map[string]string
// HostHome is the invoking user's absolute home dir. Set via HOME=
// inside the container so path-baking (pixi shebangs, etc.) resolves
// to a path that's valid both inside (via bind-mount) and outside.
HostHome string
// DryRun returns the command line that would run without executing.
// Used for --dry-run deploy.
DryRun bool
// RunAsRoot spawns the container as UID 0 instead of the host's
// UID. Needed for builders whose script body uses `sudo` against
// users that don't exist in the builder image's /etc/passwd —
// e.g. AUR's makepkg+yay flow inside a non-OCI-staged builder
// image. Under rootless podman, root-in-container maps to the
// host's UID, so file ownership in bind-mounts stays correct.
RunAsRoot bool
}
BuilderRunOpts describes one `podman run <builder>` invocation. The BuilderRun implementation (host-engine builder exec) lives in sdk/kit; this options struct is spec-homed because DeployExecutor.RunBuilder carries it across the IR.
type BuilderStep ¶ added in v0.2026210.2308
type BuilderStep struct {
Builder string // "pixi" | "npm" | "cargo" | "aur"
BuilderImage string // resolved image ref (e.g. "fedora-builder:2026.04")
CandyName string // layer that triggered this builder (for ledger)
CandyDir string // absolute layer source path (bind-mounted as /work)
Phase Phase // typically PhaseInstall
Artifacts []ArtifactRef // outputs to extract (empty for user-scope pixi/npm/cargo; populated for aur)
// Builder-specific template context — the compiler populates this from
// the candy's manifest files + the embedded builder definition (charly/charly.yml).
RawStageContext map[string]any
// LocalPkg is the package format's localpkg contract, populated by the
// compiler for the `aur` builder only. The aur builder produces package
// files (.pkg.tar.zst) that the host/VM deploy targets install onto the
// venue via the SAME config-driven transfer+install leg the localpkg step
// uses (R3) — so the install command + package glob come from
// the embedded `pac.local_pkg` vocabulary, never a hardcoded literal. Nil for
// pixi/npm/cargo (home-artifact builders, no package-file install).
LocalPkg *LocalPkg
// BuilderDef is the resolved builder definition from the embedded build vocabulary (charly/charly.yml) for this builder
// (img.BuilderConfig.Builder[Builder]), populated by the compiler. The
// host-venue deploy targets render its phase.install.host cell via
// renderBuilderScript — the plain-shell analog of the container stage — so the
// host build script is config-driven, not hardcoded Go. nil only on
// synthetic test paths that don't supply a BuilderConfig.
BuilderDef *BuilderDef
// PreResolvedReverse is the builder's teardown ops, resolved HOST-SIDE in the build
// PRE-PASS (builder_preresolve.go → the externalized builder plugin's OpReverse) and stashed
// here so Reverse() is a PURE getter — the per-builder reverse-op KIND (pixi-env-remove /
// package-remove / …) is exactly the logic externalized out-of-process, and Reverse() runs
// host-side (step-view projection + RunHostStep) with no registry handle. Carried across the
// wire on InstallStepView.ReverseOps (the host-computed teardown field) and restored by
// stepFromView. Nil for a custom candy builder / a direct compile with no pre-pass.
PreResolvedReverse []ReverseOp
}
BuilderStep runs a multi-stage builder (pixi/npm/cargo/aur). On the OCI target this emits a FROM + RUN stage plus a final-stage COPY --from. On the host target this emits a `podman run <builder>` with HOME-remap and appropriate bind-mounts.
func (*BuilderStep) Kind ¶ added in v0.2026210.2308
func (s *BuilderStep) Kind() StepKind
func (*BuilderStep) RequiresGate ¶ added in v0.2026210.2308
func (s *BuilderStep) RequiresGate() Gate
func (*BuilderStep) Reverse ¶ added in v0.2026210.2308
func (s *BuilderStep) Reverse() []ReverseOp
func (*BuilderStep) Scope ¶ added in v0.2026210.2308
func (s *BuilderStep) Scope() Scope
func (*BuilderStep) Venue ¶ added in v0.2026210.2308
func (s *BuilderStep) Venue() Venue
type CLIArg ¶
type CLIArg struct {
Prop string `yaml:"prop,omitempty" json:"prop"`
Name string `yaml:"name,omitempty" json:"name"`
Type string `yaml:"type,omitempty" json:"type"`
Help string `yaml:"help,omitempty" json:"help,omitempty"`
Enum []string `yaml:"enum,omitempty" json:"enum,omitempty"`
Default string `yaml:"default,omitempty" json:"default,omitempty"`
HasDefault bool `yaml:"has_default,omitempty" json:"has_default,omitempty"`
Required bool `yaml:"required,omitempty" json:"required,omitempty"`
IsBool bool `yaml:"is_bool,omitempty" json:"is_bool,omitempty"`
IsSlice bool `yaml:"is_slice,omitempty" json:"is_slice,omitempty"`
IsMap bool `yaml:"is_map,omitempty" json:"is_map,omitempty"`
Negated bool `yaml:"negated,omitempty" json:"negated,omitempty"`
ElemType string `yaml:"elem_type,omitempty" json:"elem_type,omitempty"`
}
CLI reflection is a wire contract between command plugins, the Charly host, and MCP. It is CUE-owned like every other authored or transported shape.
type CLILeaf ¶
type CLILeaf struct {
Path string `yaml:"path,omitempty" json:"path"`
Help string `yaml:"help,omitempty" json:"help,omitempty"`
Hidden bool `yaml:"hidden,omitempty" json:"hidden,omitempty"`
Positionals []CLIArg `yaml:"positionals,omitempty" json:"positionals,omitempty"`
Flags []CLIArg `yaml:"flags,omitempty" json:"flags,omitempty"`
}
type CacheConfig ¶ added in v0.2026241.931
type CacheConfig struct {
Git GitCache `yaml:"git,omitempty" json:"git,omitempty"`
}
#CacheConfig is the top-level `cache:` block. `git` holds the git metadata cache; future caches (image layers, check runs) add their own sub-key.
type CacheMount ¶
type CacheMount struct {
Dst string `yaml:"dst,omitempty" json:"dst"`
Sharing string `yaml:"sharing,omitempty" json:"sharing,omitempty"`
Owned bool `yaml:"owned,omitempty" json:"owned,omitempty"`
}
A BuildKit cache mount. dst is the absolute in-builder cache path; sharing is the BuildKit sharing mode; owned renders a uid/gid-owned cache.
type CacheMountSpec ¶ added in v0.2026210.2308
CacheMountSpec mirrors the BuildKit cache-mount configuration carried in the embedded build vocabulary's format/builder definitions (charly/charly.yml). The OCI target renders these as `--mount=type=cache,...`; the host target ignores them.
type CacheMountView ¶
type CacheMountView struct {
Dst string `yaml:"dst,omitempty" json:"dst,omitempty"`
Sharing string `yaml:"sharing,omitempty" json:"sharing,omitempty"`
}
#CacheMountView is the wire mirror of package main's CacheMountSpec.
type Candy ¶
type Candy struct {
// --- identity (required: ADE mandates version+name+description+plan) ---
Version CalVer `yaml:"version,omitempty" json:"version"`
Name EntityRef `yaml:"name,omitempty" json:"name,omitempty"`
Description string `yaml:"description,omitempty" json:"description"`
Plan []Step `yaml:"plan,omitempty" json:"plan,omitempty"`
// --- maturity / engine ---
Status string `yaml:"status,omitempty" json:"status,omitempty"`
// engine — a candy's required run engine; per-image resolution walks the
// candy chain (engine.go ResolveBoxEngine) and cross-candy conflicts are a
// validate error. RETAINED by the schema-compaction consumer audit.
Engine string `yaml:"engine,omitempty" json:"engine,omitempty"`
// `from:` is NOT a candy field — EDGE-INHERIT cutover D: a candy: node carrying
// `base:` or `from:` is a full IMAGE (#Box, the former box:), routed there by the
// loader; a LAYER fragment has neither. So `from:` lives only on #Box.
Reboot bool `yaml:"reboot,omitempty" json:"reboot,omitempty"`
// --- composition ---
Candy []CandyRef `yaml:"candy,omitempty" json:"candy,omitempty"`
Require []CandyRef `yaml:"require,omitempty" json:"require,omitempty"`
// bake_plugin — the OUT-OF-TREE plugin candies whose pre-built provider binary
// this candy BAKES into every composing image at /usr/lib/charly/plugins/, so a
// DEPLOYED container (no candy source, no go toolchain) can run an external plugin
// its in-container charly needs at runtime — e.g. charly-mcp bakes plugin-mcp so
// `charly mcp serve` resolves the external `mcp` command in-container. The S0
// baked-plugin BUILD-side seam, the deploy-time counterpart of resolvePluginBinary's
// bakedPluginBinary fallback (plugin_loader.go). See generate.go emitBakedPlugins.
BakePlugin []CandyRef `yaml:"bake_plugin,omitempty" json:"bake_plugin,omitempty"`
RequiresCapability []string `yaml:"requires_capability,omitempty" json:"requires_capability,omitempty"`
Capability *CandyCapability `yaml:"capability,omitempty" json:"capability,omitempty"`
// plugin — declaring this block makes the candy a PLUGIN: it provides
// reserved-word Providers (built-in OR out-of-tree). The candy is otherwise
// authored, validated, built, deployed, and checked like any candy (R3 — one
// authoring surface). See provider.go / plugin_loader.go.
Plugin *Plugin `yaml:"plugin,omitempty" json:"plugin,omitempty"`
// external_builder — the reserved word of an EXTERNAL builder plugin
// (`builder:<word>`, an out-of-tree grpcProvider) this candy SELECTS to produce
// a multi-stage build artifact. At image build the generator resolves the word,
// Invokes the provider's OpResolve, and splices the returned BuilderResolveReply
// (the FROM…AS stage pre-main-FROM + the COPY --from artifacts post-main-FROM).
// The build-time BUILDER leg — the counterpart of a `run:` step's `plugin:` verb
// (the STEP leg). A builtin builder (pixi/cargo/npm/aur) is selected by detection
// files, NOT this field. See generate.go emitExternalBuilderStages.
ExternalBuilder string `yaml:"external_builder,omitempty" json:"external_builder,omitempty"`
// --- runtime env / local vars / PATH ---
// env forbids PATH (validate.go: use path_append instead). Values are
// Go-coerced scalars (#StrVal) — an unquoted `PORT: 8080` is a string. The Go
// field is map[string]string (yaml.v3 coerces the scalar), so pin the shape
// (gengotypes degrades the #StrVal value to `any`).
Env map[string]string `yaml:"env,omitempty" json:"env,omitempty"`
// var: Go field is `Vars map[string]string`; gengotypes degrades the
// pattern-keyed map to an empty struct, so pin name + shape.
Vars map[string]string `yaml:"var,omitempty" json:"var,omitempty"`
PathAppend []string `yaml:"path_append,omitempty" json:"path_append,omitempty"`
// --- package surface (Calamares-aligned) ---
Package []PackageItem `yaml:"package,omitempty" json:"package,omitempty"`
Distro map[string]*DistroPackages `yaml:"distro,omitempty" json:"distro,omitempty"`
Apk []ApkPackageSpec `yaml:"apk,omitempty" json:"apk,omitempty"`
// packaging — the native-package metadata for this candy: the SINGLE source
// of truth the `charly generate-packages` plugin (sdk/packagekit) reads to
// build distro packages (deb/rpm/apk/archlinux/ipk/msix). Any candy may
// declare it (kind-blind); the charly candy's charly.yml carries the real
// section. Replaces the old `localpkg:` source-dir map — the plugin builds
// from the released binary + plugins, not from a bundled source tree.
Packaging *Packaging `yaml:"packaging,omitempty" json:"packaging,omitempty"`
// --- networking / routing ---
// PortSpec: a plain int OR a "proto:port" string (proto ∈ http/https/tcp/…);
// the Go normalizer canonicalizes either form to the {port, protocol}
// PortSpec struct, so the Go field is []PortSpec (gengotypes degrades the
// scalar|string disjunction element to `any`).
Port []PortSpec `yaml:"port,omitempty" json:"port,omitempty"`
PortRelay []int `yaml:"port_relay,omitempty" json:"port_relay,omitempty"`
Route *CandyRoute `yaml:"route,omitempty" json:"route,omitempty"`
// --- services / volumes / aliases / extract / data ---
Service []CandyService `yaml:"service,omitempty" json:"service,omitempty"`
Volume []CandyVolume `yaml:"volume,omitempty" json:"volume,omitempty"`
Alias []CandyAlias `yaml:"alias,omitempty" json:"alias,omitempty"`
Extract []CandyExtract `yaml:"extract,omitempty" json:"extract,omitempty"`
Data []CandyData `yaml:"data,omitempty" json:"data,omitempty"`
// --- security / hooks ---
// (the candy-level `libvirt:` raw-XML-snippet field was removed — zero live Go
// consumers, retired alongside the box-level libvirt hard-cutover; migrated
// away by candy/plugin-migrate's stripCandyLibvirtField. The paired `kind: vm`
// entity's OWN `libvirt: {...}` domain config, sdk/schema/vm.cue #Vm.libvirt,
// is unrelated and unaffected.)
Security *Security `yaml:"security,omitempty" json:"security,omitempty"`
Hook *CandyHook `yaml:"hook,omitempty" json:"hook,omitempty"`
// --- env/secret/mcp dependency + provides surface ---
// All six env/secret/mcp dependency lists decode into the ONE Go type
// []EnvDependency (pinned via @go(...,type=[]EnvDependency)); the per-context
// CUE refinements (env-var name regex; the charly/ secret-key prefix) only
// TIGHTEN validation — they don't change the emitted Go type (an inline
// `#EnvDependency & {…}` would otherwise make gengotypes emit an anonymous
// struct). mcp_* keep the base shape (an MCP server name is not env-var-
// constrained).
EnvProvides StrMap `yaml:"env_provide,omitempty" json:"env_provide,omitempty"`
EnvRequire []EnvDependency `yaml:"env_require,omitempty" json:"env_require,omitempty"`
EnvAccept []EnvDependency `yaml:"env_accept,omitempty" json:"env_accept,omitempty"`
SecretAccept []EnvDependency `yaml:"secret_accept,omitempty" json:"secret_accept,omitempty"`
SecretRequire []EnvDependency `yaml:"secret_require,omitempty" json:"secret_require,omitempty"`
MCPProvide []CandyMCPProvide `yaml:"mcp_provide,omitempty" json:"mcp_provide,omitempty"`
AgentProvide []AgentRuntimeCapability `yaml:"agent_provide,omitempty" json:"agent_provide,omitempty"`
// terminal_profile keeps the authored-key convention: every collection-
// holding authored key in this block is singular (env_provide, mcp_provide,
// agent_provide, …). The baked wire/label shape is the PLURAL
// `terminal_profiles` (schema/boxmetadata.cue; spec.LabelTerminalProfiles
// "ai.opencharly.terminal_profiles") — a key burned into built images, so
// the singular-authored ↔ plural-wire mapping is deliberate and stable,
// not a typo to "align" (renaming either side is a format change).
TerminalProfiles map[string]TerminalProfile `yaml:"terminal_profile,omitempty" json:"terminal_profile,omitempty"`
MCPRequire []EnvDependency `yaml:"mcp_require,omitempty" json:"mcp_require,omitempty"`
MCPAccept []EnvDependency `yaml:"mcp_accept,omitempty" json:"mcp_accept,omitempty"`
SecretYAML []CandySecret `yaml:"secret,omitempty" json:"secret,omitempty"`
// --- shell init (same shape as box shell) ---
Shell *Shell `yaml:"shell,omitempty" json:"shell,omitempty"`
// --- operator-facing artifacts ---
Artifact []CandyArtifact `yaml:"artifact,omitempty" json:"artifact,omitempty"`
}
type CandyAlias ¶
type CandyAlias struct {
Name string `yaml:"name,omitempty" json:"name"`
Command string `yaml:"command,omitempty" json:"command"`
}
AliasYAML — for a CANDY alias `command` is REQUIRED (validateAliases); the box-level #BoxAlias keeps it optional (defaults to name).
type CandyArtifact ¶
type CandyArtifact struct {
Name string `yaml:"name,omitempty" json:"name"`
Path string `yaml:"path,omitempty" json:"path"`
RetrieveTo string `yaml:"retrieve_to,omitempty" json:"retrieve_to"`
Mode string `yaml:"mode,omitempty" json:"mode,omitempty"`
Optional bool `yaml:"optional,omitempty" json:"optional,omitempty"`
WaitSeconds int `yaml:"wait_second,omitempty" json:"wait_second,omitempty"`
Rewrite []CandyArtifactRewrite `yaml:"rewrite,omitempty" json:"rewrite,omitempty"`
// register: an optional NAME-BLIND post-retrieve processing hint (e.g. "kubeconfig") —
// what to do with the retrieved artifact beyond the generic retrieve/rewrite pipeline,
// dispatched by a host-side registry keyed on this word (retrieveArtifactsAndK3s /
// artifactRegisterHandlers). Replaces a hardcoded candy-NAME check
// (deployHasCandy(candyList, "k3s-server")) with a declaration the candy itself carries,
// so a future candy needing the same registration need not be named "k3s-server".
Register string `yaml:"register,omitempty" json:"register,omitempty"`
}
CandyArtifact — a file the candy publishes back to the operator post-setup.
type CandyArtifactRewrite ¶
type CandyCandidate ¶ added in v0.2026210.808
type CandyCandidate struct {
Scanned ScannedCandy
Version string // per-entity version (Scanned.Model.Version) — mandatory, never ""
GitTag string // fetch coordinate (the @github :vTAG)
Source string // "<repo>@<git-tag>" for warning attribution
}
CandyCandidate is one fetched materialization of a bare candy ref. The git tag is the fetch coordinate; Version is the candy's own per-entity `version:`. Relocated from loaderkit (candy_version.go) as loader-result DATA; the PickCandyVersion ARBITER that consumes it stays in loaderkit (it needs kit's semver/CalVer comparison — a mechanism).
type CandyCapabilitiesView ¶
type CandyCapabilitiesView struct {
PreserveUser bool `yaml:"preserve_user,omitempty" json:"preserve_user,omitempty"`
}
#CandyCapabilitiesView — the per-candy capability surface a consumer reads off the envelope. Carries exactly the caps the validate ENGINE consumes (preserve_user today); grow it only when a relocated rule reads a further cap. Mirrors the read half of the runtime candy's `capabilities:` block (charly AggregatedCandyCaps).
type CandyCapability ¶
type CandyCapability struct {
PreserveUser bool `yaml:"preserve_user,omitempty" json:"preserve_user,omitempty"`
NeedsRootAfterInit bool `yaml:"needs_root_after_init,omitempty" json:"needs_root_after_init,omitempty"`
InitSystemHint string `yaml:"init_system_hint,omitempty" json:"init_system_hint,omitempty"`
DataOnly bool `yaml:"data_only,omitempty" json:"data_only,omitempty"`
OCILabels map[string]string `yaml:"oci_label,omitempty" json:"oci_label,omitempty"`
}
CandyCapabilities — image-level facts a candy contributes (aggregated at resolve time). oci_label is a genuine open string→string passthrough.
type CandyData ¶
type CandyData struct {
Src string `yaml:"src,omitempty" json:"src"`
Volume string `yaml:"volume,omitempty" json:"volume"`
Dest string `yaml:"dest,omitempty" json:"dest,omitempty"`
}
DataYAML — stage candy-dir data into a volume at build, provision at deploy.
type CandyExtract ¶
type CandyExtract struct {
Source string `yaml:"source,omitempty" json:"source"`
Path string `yaml:"path,omitempty" json:"path"`
Dest string `yaml:"dest,omitempty" json:"dest"`
}
ExtractYAML — copy a path out of another OCI image into this one. source is an image ref; path (in the source image) and dest (in this image) are absolute (validateCandyContents).
type CandyHook ¶
type CandyHook struct {
PostEnable string `yaml:"post_enable,omitempty" json:"post_enable,omitempty"`
PreRemove string `yaml:"pre_remove,omitempty" json:"pre_remove,omitempty"`
}
HooksConfig — lifecycle hook scripts.
type CandyMCPProvide ¶
type CandyMCPProvide struct {
Name string `yaml:"name,omitempty" json:"name"`
URL string `yaml:"url,omitempty" json:"url"`
Transport string `yaml:"transport,omitempty" json:"transport,omitempty"`
}
MCPServerYAML — mcp_provide entry exposed to peer containers.
type CandyModel ¶
type CandyModel struct {
// --- identity ---
Name string `yaml:"name,omitempty" json:"name,omitempty"`
Version string `yaml:"version,omitempty" json:"version,omitempty"`
SourceDir string `yaml:"source_dir,omitempty" json:"source_dir,omitempty"`
Reboot bool `yaml:"reboot,omitempty" json:"reboot,omitempty"`
// --- host-precomputed predicates (#67 render-DRIVE move) ---
// has_content / has_install_files are the LIVE *Candy.HasContent() / HasInstallFiles()
// verdicts, computed HOST-SIDE (the live Candy has the env/ports/route/volumes/aliases/
// libvirt/init fields + the fs-probe caches the envelope CandyModel cannot recompute
// faithfully) and carried here so the specCandyAdapter matches the live *Candy
// byte-exactly — the candy-graph composition (ExpandCandy/ResolveCandyOrder) and the
// pixi-bound intermediate detection both gate on these. has_content does NOT count plan
// steps, so a pure-composition candy (e.g. agent-forwarding: candy:
// [gnupg,direnv,ssh-client], only a check plan) has has_content=false. Such a candy is
// still admitted to the candy graph when it carries a PLAN, so that plan bakes into the
// ai.opencharly.description label (ADE: every candy's plan is runnable acceptance); it
// contributes nothing to the build, since has_content=false implies empty RunOps. A
// content-free composing candy with no plan is skipped, as a pure grouping node.
// Admission is deliberately NOT keyed to the description: ADE makes a description
// mandatory on every candy, so that would admit every composing candy unconditionally.
// The admission predicate is deploykit's layerEntersOrder.
HasContent bool `yaml:"has_content,omitempty" json:"has_content,omitempty"`
HasInstallFiles bool `yaml:"has_install_files,omitempty" json:"has_install_files,omitempty"`
// --- plan + lowered ops ---
Plan []Step `yaml:"plan,omitempty" json:"plan,omitempty"`
RunOps []Op `yaml:"run_ops,omitempty" json:"run_ops,omitempty"`
// --- services / extract / data / apk ---
Service []CandyService `yaml:"service,omitempty" json:"service,omitempty"`
Extract []CandyExtract `yaml:"extract,omitempty" json:"extract,omitempty"`
Data []CandyData `yaml:"data,omitempty" json:"data,omitempty"`
Apk []ApkPackageSpec `yaml:"apk,omitempty" json:"apk,omitempty"`
// --- package surface (resolved) ---
TopPackages []string `yaml:"top_packages,omitempty" json:"top_packages,omitempty"`
// packaging — the candy's `packaging:` section (the single source of truth
// for distro package metadata + variants). Carried on the build model so the
// specCandyAdapter can implement CandyReader.Packaging() — the deploy-plan
// compiler reads it to emit a LocalPkgInstallStep (PackageName = packaging.name).
Packaging *Packaging `yaml:"packaging,omitempty" json:"packaging,omitempty"`
FormatSections map[string]PackageSection `yaml:"format_sections,omitempty" json:"format_sections,omitempty"`
TagSections map[string]TagPkgConfig `yaml:"tag_sections,omitempty" json:"tag_sections,omitempty"`
// --- env / vars / route / shell ---
Vars map[string]string `yaml:"vars,omitempty" json:"vars,omitempty"`
Env *EnvConfig `yaml:"env,omitempty" json:"env,omitempty"`
Route *RouteConfig `yaml:"route,omitempty" json:"route,omitempty"`
Shell *Shell `yaml:"shell,omitempty" json:"shell,omitempty"`
// --- volumes / aliases ---
Volumes []CandyVolume `yaml:"volumes,omitempty" json:"volumes,omitempty"`
Aliases []CandyAlias `yaml:"aliases,omitempty" json:"aliases,omitempty"`
// --- security / hooks (the OCI-label-collector surface: CollectSecurity/CollectHooks
// read this per-candy field then apply the box-level #ResolvedBoxView.security override) ---
Security *Security `yaml:"security,omitempty" json:"security,omitempty"`
Hook *CandyHook `yaml:"hook,omitempty" json:"hook,omitempty"`
// --- validate read-surface (candy-local config the validate ENGINE checks) ---
// external_builder: the reserved word of an EXTERNAL builder plugin this candy selects
// (from the candy manifest external_builder:). validateCandyContents reads it to accept a
// candy whose only content is an external-builder selection (deploykit EmitExternalBuilderStages).
ExternalBuilder string `yaml:"external_builder,omitempty" json:"external_builder,omitempty"`
// (the candy-level `libvirt:` field was removed here alongside #Candy — see
// candy.cue's note; CollectLibvirtSnippets, its only reader, was already dead.)
Engine string `yaml:"engine,omitempty" json:"engine,omitempty"`
PortRelayPorts []int `yaml:"port_relay,omitempty" json:"port_relay,omitempty"`
ServiceFiles []string `yaml:"service_files,omitempty" json:"service_files,omitempty"`
EnvRequire []EnvDependency `yaml:"env_require,omitempty" json:"env_require,omitempty"`
EnvAccept []EnvDependency `yaml:"env_accept,omitempty" json:"env_accept,omitempty"`
SecretRequire []EnvDependency `yaml:"secret_require,omitempty" json:"secret_require,omitempty"`
SecretAccept []EnvDependency `yaml:"secret_accept,omitempty" json:"secret_accept,omitempty"`
MCPRequire []EnvDependency `yaml:"mcp_require,omitempty" json:"mcp_require,omitempty"`
MCPAccept []EnvDependency `yaml:"mcp_accept,omitempty" json:"mcp_accept,omitempty"`
// --- W9 mass-edit interface-completeness fill: the remaining fields the 42-file
// CandyReader repoint needs, already authored on #Candy (candy.cue) but not yet
// projected onto the build model. artifact/requires_capability/secret/port mirror
// candy.cue's own fields+shapes exactly; capability widens the CandyView's narrow
// (preserve_user-only) #CandyCapabilitiesView to the full authored #CandyCapability.
Artifact []CandyArtifact `yaml:"artifact,omitempty" json:"artifact,omitempty"`
RequiresCapability []string `yaml:"requires_capability,omitempty" json:"requires_capability,omitempty"`
Capability *CandyCapability `yaml:"capability,omitempty" json:"capability,omitempty"`
Secret []CandySecret `yaml:"secret,omitempty" json:"secret,omitempty"`
// port mirrors candy.cue's own union shape exactly (a plain int OR a "proto:port" string,
// normalized Go-side to PortSpec — there is no reusable #PortSpec def, per candy.cue's own note).
Port []PortSpec `yaml:"port,omitempty" json:"port,omitempty"`
// bake_plugin: the FINAL bare-string form (post remote-sibling qualification — see
// spec.CandyRefs, the hand-written pipeline-state type carrying the rich pre-qualification
// form). generate.go's emitBakedPlugins reads this to COPY the plugin's pre-built provider
// binary into every composing image.
BakePlugin []CandyRef `yaml:"bake_plugin,omitempty" json:"bake_plugin,omitempty"`
}
#CandyModel — the serializable candy build model. Every field optional (a projection). name is the stable key. Field order mirrors the runtime *Candy accessors (deploykit.CandyModel interface).
type CandyReader ¶
type CandyReader interface {
GetName() string
GetSourceDir() string
GetVersion() string
Vars() map[string]string
PlanSteps() []Step
Reboot() bool
Apk() []ApkPackageSpec
EnvConfig() (*EnvConfig, error)
Service() []CandyService
Shell() *Shell
TopPackages() []string
FormatSection(name string) *PackageSection
// Packaging returns the candy's `packaging:` section (the single source of
// truth for distro package metadata + variants). Nil when the candy declares
// none — the deploy-plan compiler then emits no LocalPkgInstallStep for it.
Packaging() *Packaging
TagSection(tag string) *TagPkgConfig
HasFile(filename string) bool
// P8 render-delta accessors (build-mode graph/render surface). Field-backed
// members use Get* to avoid a field/method name collision on the charly *Candy
// implementer; the three already-methods keep their names.
GetIncludedCandy() []CandyRefEntry // candy: composition refs (splicing)
GetRequire() []CandyRefEntry // require: deps (ordering + resolution)
// GetBakePlugin (W9): bake_plugin: out-of-tree plugin candies baked into composing images
// (generate.go emitBakedPlugins). Same bare-string-wrapped-as-CandyRefEntry shape as
// GetRequire/GetIncludedCandy above (CandyModel.BakePlugin is the FINAL bare-string wire
// form; the adapter re-wraps it so callers keep using .Bare() uniformly).
GetBakePlugin() []CandyRefEntry
HasContent() bool
HasInstallFiles() bool // at least one install file (drives pixi-bound detection)
PixiManifest() string
GetHasPackageJson() bool
GetHasCargoToml() bool
GetExternalBuilder() string // external_builder: word (the out-of-tree builder plugin this candy selects)
HasFormatPackages() bool
GetRemote() bool // true if the candy came from a remote repo
HasExtract() bool
Extract() []CandyExtract
HasData() bool
Data() []CandyData
GetHasPixiLock() bool
GetRepoPath() string
GetSubPathPrefix() string
HasEnv() bool
HasRoute() bool
Route() (*RouteConfig, error)
// P8 init-cluster accessors (emitInitAssembly/emitInitFragmentStages/
// generateInitFragments). RelayPorts wraps the field-backed PortRelayPorts to
// avoid a field/method name collision on the charly *Candy implementer.
HasInit(initName string) bool // this candy contributes to the named init system
ServiceFiles() []string // file_copy-model service unit paths (globbed)
RelayPorts() []int // port_relay: ports (init-agnostic)
// P8 writeCandySteps accessors.
HasTasks() bool // the candy has any tasks: (runOps non-empty)
RunOps() []Op // the candy's plan lowered to build-mode run ops
// OCI-label-collector surface (CollectSecurity/CollectHooks/layer_secrets):
// per-candy security/hooks + the six env/secret/mcp dependency lists.
Security() *Security
Hooks() *CandyHook
EnvRequire() []EnvDependency
EnvAccept() []EnvDependency
SecretRequire() []EnvDependency
SecretAccept() []EnvDependency
MCPRequire() []EnvDependency
MCPAccept() []EnvDependency
// W9 mass-edit interface-completeness fill: the remaining accessors the 42-file
// repoint needs (CollectBoxAlias/CollectBoxVolume/ports.go/layer_capabilities.go/…)
// — every one of these already exists on the charly *Candy implementer (layers.go);
// this is a pure exposure widening, no new *Candy behavior. (Libvirt()/HasLibvirt()
// were added here for this same fill, to serve charly/libvirt.go's
// CollectLibvirtSnippets — that caller was dead code [box-level `libvirt:` was
// removed in the VM hard-cutover] and was deleted, and the candy-level `libvirt:`
// field itself was removed from the schema in the SAME cutover [candy.cue /
// candymodel.cue, migrated by candy/plugin-migrate's stripCandyLibvirtField] — so
// these two accessors are gone too, not merely deprecated.)
// Consumer-set audited: HasAnyInit/HasAnyPackages/HasApk/HasService/HasTagPackages
// were considered and DROPPED — zero external caller outside layers.go itself (they
// back OTHER *Candy-internal composition, e.g. HasContent/HasInstallFiles, which
// stays a host-precomputed field per the #67 predicate-carrying pattern above).
Alias() []CandyAlias
HasAliases() bool
Volume() []CandyVolume
HasVolumes() bool
Artifact() []CandyArtifact
Capabilities() *CandyCapability
RequiresCapabilities() []string
Engine() string
EnvProvides() map[string]string
MCPProvide() []CandyMCPProvide
Secret() []CandySecret
Port() ([]string, error)
PortSpecs() []PortSpec
HasEnvAccepts() bool
HasEnvProvides() bool
HasEnvRequires() bool
HasMCPAccepts() bool
HasMCPProvides() bool
HasMCPRequires() bool
HasPorts() bool
HasSecretAccepts() bool
HasSecretRequires() bool
// W9 pipeline-retype fill: the candy's OWN `plugin:` declaration (the data-read half of external
// verb/step-word recognition, plus findCommandPluginCandy) — the provider REGISTRATION itself
// stays core-M (it touches the providerRegistry), but recognizing which capability words a
// scanned candy declares is a plain data read the interface must expose once *Candy is gone. The
// validate path reads exactly these three off the resolved-project envelope and hands the
// capability strings to the host's "validate-word-sets" seam, which does the registration.
IsPluginCandy() bool
GetPluginSource() string
GetPluginProviders() []string
// W9 pipeline-retype fill: identity-view scalars multiple OCI-label-collector consumers
// (render_baked_metadata.go's candyStatus/worst-of-status + info-parts walk) need directly —
// widely-needed enough to earn a real interface method, unlike the FULL identity view
// (Description/Info/IsPlugin's sibling fields + the FormatSections/TagSections/RunOps maps)
// resolved_project_host.go alone needs via the RawCandy() escape hatch.
GetStatus() string
GetDescription() string
// W9 federated-control-plane fill: the candy-declared `agent_provide:`/`terminal_profile:`
// surfaces (56b1a6b) the OCI-label-collector (render_baked_metadata.go's per-box
// agent/terminal aggregation) needs once *Candy is gone.
AgentProvide() []AgentRuntimeCapability
HasAgentProvides() bool
TerminalProfiles() map[string]TerminalProfile
}
candy_reader.go — CandyReader, the read-only view of a runtime candy that the deploy-plan compiler (deploykit.BuildDeployPlan) and the build-render engine consume. It lives in spec (the shared contract home, alongside the CUE-generated wire envelopes) rather than sdk/deploykit so an import-clean charly file can reach it WITHOUT crossing the mechanism-kit import-purity line — matches the DocParser/ProjectWalker precedent in loader_seam.go. The concrete runtime Candy (charly core) implements it; deploykit's specCandyAdapter implements it from the CUE-generated CandyModel/CandyView wire views. Every referenced type below is already spec-native (Step/EnvConfig/EnvDependency/Op/Security/CandyHook/CandyService/Shell/…) or promoted here alongside the interface (CandyRefEntry, below) — a spec-hosted contract never needs an sdk mechanism-kit import.
type CandyRecord ¶ added in v0.2026215.1748
type CandyRecord struct {
SchemaVersion string `yaml:"schema_version,omitempty" json:"schema_version,omitempty"`
Candy string `yaml:"candy,omitempty" json:"candy"`
Version string `yaml:"version,omitempty" json:"version,omitempty"`
DeployedBy []string `yaml:"deployed_by,omitempty" json:"deployed_by"`
DeployedAt string `yaml:"deployed_at,omitempty" json:"deployed_at"`
BuilderImage string `yaml:"builder_image,omitempty" json:"builder_image,omitempty"`
Steps []StepRecord `yaml:"steps,omitempty" json:"steps,omitempty"`
ReverseOps []ReverseOp `yaml:"reverse_ops,omitempty" json:"reverse_ops,omitempty"`
}
#CandyRecord is the per-candy ledger entry. Lists concrete artifacts (packages installed, files written, services enabled, env.d file created, repo changes) so reversal doesn't need to re-compile the plan from the candy manifest.
type CandyRefEntry ¶
type CandyRefEntry struct {
Raw string // original ref, e.g. "@github.com/org/repo/candy/x:v1" or bare "x"
// Resolved is the qualified candy-map key assigned when a freshly-fetched
// remote candy's plain-name sibling deps are qualified to
// "<repo>/<subpathprefix><name>" (qualifyRemoteSiblingDeps). Empty for every
// other ref, where the map key derives from Raw.
Resolved string
}
CandyRefEntry is a single candy reference as authored in the candy manifest `require:` / `candy:` (or charly.yml `candy:`). It carries the ORIGINAL ref string — with any `@repo` prefix and `:version` suffix — as the single source of truth; the bare map-key form (.Bare()) and the pinned version (.Version()) are DERIVED on demand.
func ToCandyRefEntries ¶
func ToCandyRefEntries(raw []string) []CandyRefEntry
ToCandyRefEntries wraps raw ref strings into CandyRefEntry values. Returns nil for a nil/empty input so an absent list stays absent.
func (CandyRefEntry) Bare ¶
func (r CandyRefEntry) Bare() string
Bare returns the candy-map key (no @ prefix, no :version) — the form used for dependency resolution and graph keying.
func (CandyRefEntry) IsRemote ¶
func (r CandyRefEntry) IsRemote() bool
IsRemote reports whether this is an @-prefixed remote ref.
func (CandyRefEntry) Version ¶
func (r CandyRefEntry) Version() string
Version returns the pinned version (the ":vX" suffix), or "" for an unpinned remote ref or a local (bare-name) ref.
type CandyRefs ¶
type CandyRefs struct {
Require []CandyRefEntry
IncludedCandy []CandyRefEntry
BakePlugin []CandyRefEntry
}
CandyRefs carries the RICH require:/candy:/bake_plugin: refs (CandyRefEntry, with a mutable .Resolved) a freshly scanned candy declares.
SDD classification (hand-written, non-wire — precedent: ParsedProject/LoadedProject above, the original hand-written-contract types this seam file establishes): CandyRefs is same-process PIPELINE STATE crossing ONLY the compiled-in typed CandyScanner seam — it is never marshaled, because the loader plugin is bootstrap-critical and ALWAYS compiled-in (see the package doc above: "the loader must ALWAYS resolve... registered at init() before the first load"), so this seam never crosses a real wire the way an out-of-process plugin's gRPC envelope would. It exists only between ScanCandyManifest and the host's qualifyRemoteSiblingDeps (which sets .Resolved on a remote candy's plain-name sibling deps) and the FINAL bare-string conversion into CandyView.Require/.IncludedCandy (mirrors the pre-move projectCandyView's bareRefs() call, which ran AFTER qualification on the live *Candy — this type is what lets that same ordering survive the *Candy struct's departure). The FINAL bare-string form lands on CandyView.Require/ .IncludedCandy and CandyModel.BakePlugin (FinalizeCandyRefs, sdk/loaderkit).
type CandyRoute ¶
type CandyRoute struct {
Host string `yaml:"host,omitempty" json:"host"`
Port int `yaml:"port,omitempty" json:"port"`
}
RouteYAML — generic service-route metadata (traefik / tunnel).
type CandyScanner ¶
type CandyScanner interface {
// ParseCandyManifest is the candy-MANIFEST parse the two scan methods below take as their
// `parseManifest` seam. It relocated OUT of charly core in K-wave 2 cone R1 (A2 unit 2) into
// sdk/loaderkit, so a plugin driving its own scan (candy/plugin-build's remote-repo fetch) can
// parse manifests ITSELF instead of round-tripping to the host for every candy directory.
//
// It reaches core through this seam because charly/ may not import sdk/loaderkit (import
// purity); core's parseCandyYAML is the thin wrapper that supplies the two values the mechanism
// needs from the host — t, the registry-derived kind-recognition snapshot, and vocab, the build
// vocabulary the misplaced-section shape guard consults. The clause-B buildCandy factory is NOT
// on this path: an RDD spike over the whole 324-manifest corpus proved the pre-move
// pn->genericNode->pn round trip through it was an identity (321 node-form manifests plus all 3
// error paths, byte-identical).
ParseCandyManifest(path string, t Threaded, vocab CandyVocab) (*Candy, error)
// ProjectCandiesScanned scans or synthesizes a candy per uf.Candy entry off an ALREADY-LOADED
// project — the local candy scan's body, relocated to sdk/loaderkit in K-wave 2 cone R1 (A2 unit
// 3) so a plugin holding a *UnifiedFile no longer round-trips to the host to turn it into a
// ScannedCandy map. Reaches core through this seam for the same import-purity reason
// ParseCandyManifest does.
ProjectCandiesScanned(uf *UnifiedFile, rootDir string, parseDoc func(path string) (*Candy, error)) (map[string]ScannedCandy, error)
ScanCandyManifest(path, name, manifestName string, parseManifest func(path string) (*Candy, error)) (CandyModel, CandyView, CandyRefs, error)
// ScanInlineCandy builds the two views for a candy declared INLINE in a unified charly.yml —
// ly is already the parsed body (no manifest file, no parseManifest seam needed). sourceDir is
// the charly.yml's own directory.
ScanInlineCandy(name, sourceDir string, ly *Candy) (CandyModel, CandyView, CandyRefs)
// ScanRemoteCandy scans specific candies out of a downloaded remote repository directory —
// only the bare refs in wantRefs (each "github.com/org/repo/candy/x" form). Sets each result's
// CandyView.Remote/.RepoPath/.SubPathPrefix and runs the remote-sibling-dep qualification
// (QualifyRemoteSiblingDeps) before returning, mirroring the pre-move charly/layers.go
// ScanRemoteCandy, which did the same two things (post-scan.Remote/RepoPath/SubPathPrefix
// mutation, then qualifyRemoteSiblingDeps) on the live *Candy it had just built.
ScanRemoteCandy(repoDir, repoPath string, wantRefs map[string]bool, parseManifest func(path string) (*Candy, error)) (map[string]ScannedCandy, error)
}
CandyScanner is the swappable CANDY-SCAN seam (W9): the loader plugin candy implements it (candy/plugin-loader, delegating to loaderkit.ScanCandyManifest), and the host resolves the registered loader provider to it and calls it once per candy directory. Typed (no wire envelope) — the compiled-in placement passes a live parseManifest callback (a Go function value, exactly like WalkSeams.Parser above) since the candy-manifest parse itself is registry-coupled (it threads the registered DocParser + the registry-derived Threaded snapshot) and so stays a HOST-injected seam rather than moving into loaderkit — only the SCAN+CONSTRUCT logic (fs-probes, the bake_plugin/package-derivation/port-normalization business logic) moves. Returns the two resolved envelope views (spec.CandyModel + spec.CandyView) DIRECTLY — the same shape sdk/deploykit.NewSpecCandyModel already consumes to build a spec.CandyReader, so core never needs a concrete Candy struct to hold the scan result.
ScanCandyManifest is named distinctly from the ESTABLISHED exported charly.ScanCandy(dir) (the whole-project scan-all-candies entry point, charly/layers.go) — a similar name on a single-candy-directory scan risks confusion once both exist side by side during the cutover.
type CandySecret ¶
type CandySecret struct {
Name string `yaml:"name,omitempty" json:"name"`
Target string `yaml:"target,omitempty" json:"target,omitempty"`
Env string `yaml:"env,omitempty" json:"env,omitempty"`
}
SecretYAML — a candy-owned secret (target defaults to /run/secrets/<name>).
type CandyService ¶
type CandyService struct {
Name string `yaml:"name,omitempty" json:"name"`
UsePackaged string `yaml:"use_packaged,omitempty" json:"use_packaged,omitempty"`
Exec string `yaml:"exec,omitempty" json:"exec,omitempty"`
// distro restricts this entry to the named distros — a bare distro name
// ("debian") or a versioned tag ("debian:13"). Empty = every distro (the
// backward-compatible default). The service analogue of a check step's
// exclude_distros: — it lets ONE candy carry per-distro-DIVERGENT packaged
// units / exec daemons (the modular virtqemud.socket + virtnetworkd.socket
// on Fedora/Arch vs the monolithic libvirtd.socket on Debian/Ubuntu, whose
// libvirt is built without the split daemons) WITHOUT a <name>-host sibling
// candy (CLAUDE.md "Init-system polymorphism"; R3). Filtered at render time
// against the target distro tag chain (compileServiceSteps + generate.go).
Distro []string `yaml:"distro,omitempty" json:"distro,omitempty"`
Env StrMap `yaml:"env,omitempty" json:"env,omitempty"`
Restart string `yaml:"restart,omitempty" json:"restart,omitempty"`
WorkingDirectory string `yaml:"working_directory,omitempty" json:"working_directory,omitempty"`
User string `yaml:"user,omitempty" json:"user,omitempty"`
After []string `yaml:"after,omitempty" json:"after,omitempty"`
Before []string `yaml:"before,omitempty" json:"before,omitempty"`
WantedBy []string `yaml:"wanted_by,omitempty" json:"wanted_by,omitempty"`
Stdout string `yaml:"stdout,omitempty" json:"stdout,omitempty"`
StopTimeout string `yaml:"stop_timeout,omitempty" json:"stop_timeout,omitempty"`
Scope string `yaml:"scope,omitempty" json:"scope,omitempty"`
Enable bool `yaml:"enable,omitempty" json:"enable,omitempty"`
// exec_start_pre / exec_start_post — ordered hook commands run around a CUSTOM
// (exec:) entry's own start, rendered by the init system's service_template
// (systemd: ExecStartPre= / ExecStartPost= lines, emitted in authored order).
// They exist for guards belonging to the SERVICE's lifecycle rather than to the
// install timeline: a plan step runs ONCE, at deploy, whereas a pre-start hook
// re-asserts a host precondition on EVERY start (a restart included), and a
// post-start hook is the only place a guard runs ALONGSIDE the daemon it
// guards — the sole vantage from which that daemon's own socket/API is
// reachable. Both are init-supervised, so a failure surfaces in the unit's
// status instead of being swallowed; prefix a command with "-" for systemd's
// ignore-failure semantics.
// Canonical consumer: candy/k3s-server (the cgroup cpuset-delegation assertion
// and the CRD-establishment wedge heal).
ExecStartPre []string `yaml:"exec_start_pre,omitempty" json:"exec_start_pre,omitempty"`
ExecStartPost []string `yaml:"exec_start_post,omitempty" json:"exec_start_post,omitempty"`
Overrides *CandyServiceOverrides `yaml:"overrides,omitempty" json:"overrides,omitempty"`
Kind string `yaml:"kind,omitempty" json:"kind,omitempty"`
Events string `yaml:"event,omitempty" json:"event,omitempty"`
AutoStart *bool `yaml:"auto_start,omitempty" json:"auto_start,omitempty"`
StartRetries int `yaml:"start_retry,omitempty" json:"start_retry,omitempty"`
StartSecs int `yaml:"start_sec,omitempty" json:"start_sec,omitempty"`
StopSignal string `yaml:"stop_signal,omitempty" json:"stop_signal,omitempty"`
ExitCode string `yaml:"exit_code,omitempty" json:"exit_code,omitempty"`
Priority int `yaml:"priority,omitempty" json:"priority,omitempty"`
// ---- Portable lifecycle fields -------------------------------------------
// Every field below is expressible by at least TWO of the three init systems.
// That is the admission rule: a field only one init can honour belongs in
// unit_options: instead, where it is named as that init's own.
//
// type — systemd Type=; supervisord approximates oneshot with startsecs=0;
// OpenRC distinguishes forking via command_background.
Type string `yaml:"type,omitempty" json:"type,omitempty"`
// requires — a HARD dependency, distinct from after:'s ordering. systemd
// Requires=; OpenRC depend(){ need … }, which its template already emits for
// localmount. supervisord has no equivalent and ignores it.
Requires []string `yaml:"requires,omitempty" json:"requires,omitempty"`
// restart_sec — delay before a restart. systemd RestartSec=; OpenRC
// --respawn-delay. Accepts "5s" or a bare 5 (Go-coerced; the Go field is string,
// matching stop_timeout's shape).
RestartSec string `yaml:"restart_sec,omitempty" json:"restart_sec,omitempty"`
// watchdog_sec — liveness deadline. systemd WatchdogSec=; OpenRC
// --healthcheck-timer. Requires type: notify on systemd to be meaningful.
WatchdogSec string `yaml:"watchdog_sec,omitempty" json:"watchdog_sec,omitempty"`
// unit_options — the ONE escape hatch for init-specific directives, keyed
// init-name -> directive -> value.
//
// It exists so #ServiceRenderContext does not grow a field per init-specific
// knob. That struct is a kind-AGNOSTIC envelope rendered by every init's
// template; putting NotifyAccess, Slice, KillMode, RuntimeDirectory and a
// hardening block into it would make it systemd's typed shape wearing a generic
// name — the leak the boundary law names. With this map, a FOURTH init needs no
// schema change, exactly as the openrc init entity needed no Go change.
//
// A value may be a scalar or a list; a list emits the directive once per element,
// which is what systemd expects for repeatable directives such as
// RuntimeDirectory= or ReadWritePaths=.
UnitOptions map[string]map[string]any `yaml:"unit_options,omitempty" json:"unit_options,omitempty"`
// wait_for — block the service's start until the paths it needs exist.
//
// Nine hand-rolled versions of this loop exist in the candy corpus today
// (labwc, kde-selkies x2, chrome-cdp, wayvnc x3, swaync, waybar, waybar-labwc,
// sway), each re-deriving the same poll with its own hardcoded timeout — the
// sleeps and magic numbers R4 forbids. One of them is already wrong: labwc's
// declares TIMEOUT=30 but sleeps 0.5s per iteration while counting +1, so it
// actually gives up after 15 seconds. That is the failure mode a hand-rolled
// loop has and a declared one cannot.
//
// Each init lowers this natively — systemd ExecStartPre=, supervisord a
// rendered pre-exec ahead of the command, OpenRC start_pre() — so the wait is
// SUPERVISED: a timeout fails the unit and surfaces in its status, instead of a
// wrapper exiting non-zero and looking like a crash.
WaitFor *ServiceWaitFor `yaml:"wait_for,omitempty" json:"wait_for,omitempty"`
}
ServiceEntry (spec). use_packaged XOR exec is a Go cross-field rule (mixed-entry polymorphism allows the SAME name twice: one packaged form, one exec form) — neither is required at the CUE layer.
func (*CandyService) EffectiveScope ¶
func (e *CandyService) EffectiveScope() string
EffectiveScope returns the service scope, defaulting to "system".
func (*CandyService) IsPackaged ¶
func (e *CandyService) IsPackaged() bool
IsPackaged reports whether the service is the packaged (use_packaged) form.
type CandyServiceOverrides ¶
type CandyView ¶
type CandyView struct {
Name string `yaml:"name,omitempty" json:"name"`
Version string `yaml:"version,omitempty" json:"version,omitempty"`
Description string `yaml:"description,omitempty" json:"description,omitempty"`
Status string `yaml:"status,omitempty" json:"status,omitempty"`
Info string `yaml:"info,omitempty" json:"info,omitempty"`
Remote bool `yaml:"remote,omitempty" json:"remote,omitempty"`
RepoPath string `yaml:"repo_path,omitempty" json:"repo_path,omitempty"`
// sub_path_prefix — the parent directory within the repo for sibling ref resolution
// (e.g. "candy/"). Build-mode remote-candy COPY-source leg: RepoPath + SubPathPrefix +
// ref reconstruct a remote candy's context path. Filled by the resolve projector; the
// specCandyAdapter returns it so the plugin-side render reproduces remote COPY sources
// WITHOUT the live *Candy (K3-D render move, #67 — the GetSubPathPrefix deferral).
SubPathPrefix string `yaml:"sub_path_prefix,omitempty" json:"sub_path_prefix,omitempty"`
IsPlugin bool `yaml:"is_plugin,omitempty" json:"is_plugin,omitempty"`
// the candy's OWN declared plugin block (validatePluginCandy SUBJECT, task #60): the
// `plugin.providers:` capability strings + `plugin.source:` so the validate plugin can check each
// declared BUILTIN `<class>:<word>` is a member of ResolvedProject.ProviderCapabilities (the TARGET
// set). Empty for a non-plugin candy. is_plugin stays the cheap presence bool for inspect/list.
PluginProviders []string `yaml:"plugin_providers,omitempty" json:"plugin_providers,omitempty"`
PluginSource string `yaml:"plugin_source,omitempty" json:"plugin_source,omitempty"`
Require []CandyRef `yaml:"require,omitempty" json:"require,omitempty"`
IncludedCandy []CandyRef `yaml:"candy,omitempty" json:"candy,omitempty"`
EnvProvides map[string]string `yaml:"env_provide,omitempty" json:"env_provide,omitempty"`
MCPProvide []CandyMCPProvide `yaml:"mcp_provide,omitempty" json:"mcp_provide,omitempty"`
AgentProvide []AgentRuntimeCapability `yaml:"agent_provide,omitempty" json:"agent_provide,omitempty"`
TerminalProfiles map[string]TerminalProfile `yaml:"terminal_profiles,omitempty" json:"terminal_profiles,omitempty"`
Ports []int64 `yaml:"port,omitempty" json:"port,omitempty"`
ServiceNames []string `yaml:"service_name,omitempty" json:"service_name,omitempty"`
// per-candy list-subcommand sources: `charly box list routes|volumes|aliases|services`.
// route/volumes/aliases carry the authored detail each subcommand prints (their presence IS
// the RouteCandy/VolumeCandy/AliasCandy predicate). has_init + port_relay reconstruct the
// InitCandy predicate (HasAnyInit() || PortRelayPorts>0) for `list services`.
Route *RouteConfig `yaml:"route,omitempty" json:"route,omitempty"`
Volumes []CandyVolume `yaml:"volumes,omitempty" json:"volumes,omitempty"`
Aliases []CandyAlias `yaml:"aliases,omitempty" json:"aliases,omitempty"`
HasInit bool `yaml:"has_init,omitempty" json:"has_init,omitempty"`
// init_systems — the PER-INIT-SYSTEM trigger map (W9 finding: HasInit alone is one AGGREGATE
// "any init" bool, which cannot answer "does this candy trigger init system Y" — the question
// deploykit's HasInit(initName) / sdk/deploykit's EmitInitFragmentStages actually ask). Mirrors
// the live *Candy.InitSystems field exactly; populated by the SAME cross-candy host-completion
// pass PopulateCandyInitSystem runs (once, after every candy is scanned) — see
// sdk/deploykit.specCandyAdapter.HasInit, which reads this field directly.
InitSystems map[string]bool `yaml:"init_systems,omitempty" json:"init_systems,omitempty"`
PortRelayPorts []int `yaml:"port_relay,omitempty" json:"port_relay,omitempty"`
// capabilities — the per-candy capability surface the validate ENGINE reads (task #60,
// ruling a). The projector fills it from the candy's `capabilities:` block; the validate
// plugin re-derives it via boxPreserveUser over a box's candy order (a boolean OR of
// preserve_user, order-independent) + the PreserveUser rule (the validatePackagedServices
// box-arm). An aggregate over RESOLVED models belongs on the
// envelope, not a host route-B diagnostic — a lean envelope is not a virtue when it keeps
// an R-rule in core.
Capabilities *CandyCapabilitiesView `yaml:"capabilities,omitempty" json:"capabilities,omitempty"`
}
#CandyView — the resolved candy GRAPH node a consumer reads: identity + dep-graph + provides + ports/services (the exported surface of the runtime Candy). The Has* filesystem probes, the unexported package/service sections, and the *CandyPluginDecl stay host — this is NOT the candy BUILD model (plan-steps / package-format sections), which is the CandyModel / S-CM concern (K3-D), distinct by design. require / candy pin to the bare map-key ref form (#CandyRef is @go(-), so a list of it generates []string). name is the stable key.
type CandyVocab ¶ added in v0.2026218.138
type CandyVocab struct {
// FormatNames is the union of every distro's declared package formats (rpm/deb/pac/aur/…),
// inherited chains resolved.
FormatNames map[string]bool
// DistroNames is every distro name declared in the build vocabulary.
DistroNames map[string]bool
}
candy_vocab.go — the build VOCABULARY the candy-manifest shape guard consults (K-wave 2 cone R1, A2 unit 2). Relocated from charly/layers.go, where it was the pair of process-global caches candyYAMLFormatNames / candyYAMLDistroNames plus looksLikeDistroOrFormatKey.
It became a VALUE rather than staying a global because the parse mechanism it feeds moved to sdk/loaderkit, where both charly core (via the CandyScanner seam) and candy/plugin-build (directly) call it. A compiled-in plugin shares the host's process, so package globals would have APPEARED to work while silently coupling two modules through mutable state — and would fail open for an out-of-process placement, where the plugin's own copy is never registered. Passing the value keeps the mechanism honest in both placements.
The vocabulary is NOT hardcoded in Go: it is DERIVED at load time from the embedded build vocabulary (plus any project override) — the `distro:` section (the DistroConfig). Adding a new distro or package format is therefore purely a vocabulary edit, with no code change.
These sets are consumed ONLY by the candy-manifest shape guard, to recognize a package-format or per-distro section mistakenly placed at the candy root. The FORWARD package parser (loaderkit.derivePackageSections) needs no vocabulary at all — it routes every `distro:` sub-key structurally and lets the cascade resolver match on the image's real Distro/Pkg.
func NewCandyVocab ¶ added in v0.2026218.138
func NewCandyVocab(dc *DistroConfig) CandyVocab
NewCandyVocab derives the distro/format vocabulary from a DistroConfig. A nil config yields the zero vocabulary, under which the shape guard fails OPEN (no false positives) — byte-identical to the pre-move RegisterBuildVocabulary(nil) contract, which cleared the caches for the same reason.
func (CandyVocab) LooksLikeDistroOrFormatKey ¶ added in v0.2026218.138
func (v CandyVocab) LooksLikeDistroOrFormatKey(key string) bool
LooksLikeDistroOrFormatKey reports whether a candy-manifest top-level key is a package-format family name (pac/deb/rpm/aur) or a per-distro tag section (`debian`, `debian:13`, `debian,ubuntu`) — shapes that nest under the `distro:` map, never at the candy root. Returns false for an unpopulated vocabulary (no false positives), leaving the explicit removed-field cases to fire.
type CandyVolume ¶
type CandyVolume struct {
Name string `yaml:"name,omitempty" json:"name"`
Path string `yaml:"path,omitempty" json:"path"`
}
VolumeYAML — name is lowercase-hyphen (validateVolume); path may be home-relative (~/…) or absolute, so not anchored to /.
type CapabilityInitDef ¶
type CapabilityInitDef struct {
Entrypoint []string `yaml:"entrypoint,omitempty" json:"entrypoint,omitempty"`
FallbackEntrypoint []string `yaml:"fallback_entrypoint,omitempty" json:"fallback_entrypoint,omitempty"`
ManagementTool string `yaml:"management_tool,omitempty" json:"management_tool,omitempty"`
ManagementCommands map[string]string `yaml:"management_commands,omitempty" json:"management_commands,omitempty"`
}
#CapabilityInitDef — the deploy-relevant subset of an InitDef (ai.opencharly.init_def).
type CapabilityService ¶
type CapabilityService struct {
Name string `yaml:"name,omitempty" json:"name"`
Scope string `yaml:"scope,omitempty" json:"scope,omitempty"`
Enable bool `yaml:"enable,omitempty" json:"enable,omitempty"`
UsePackaged string `yaml:"use_packaged,omitempty" json:"use_packaged,omitempty"`
Exec string `yaml:"exec,omitempty" json:"exec,omitempty"`
Env map[string]string `yaml:"env,omitempty" json:"env,omitempty"`
Restart string `yaml:"restart,omitempty" json:"restart,omitempty"`
WorkingDirectory string `yaml:"working_directory,omitempty" json:"working_directory,omitempty"`
User string `yaml:"user,omitempty" json:"user,omitempty"`
After []string `yaml:"after,omitempty" json:"after,omitempty"`
Before []string `yaml:"before,omitempty" json:"before,omitempty"`
Stdout string `yaml:"stdout,omitempty" json:"stdout,omitempty"`
StopTimeout string `yaml:"stop_timeout,omitempty" json:"stop_timeout,omitempty"`
Kind string `yaml:"kind,omitempty" json:"kind,omitempty"`
Events string `yaml:"events,omitempty" json:"events,omitempty"`
AutoStart *bool `yaml:"auto_start,omitempty" json:"auto_start,omitempty"`
StartRetries int `yaml:"start_retries,omitempty" json:"start_retries,omitempty"`
StartSec int `yaml:"start_sec,omitempty" json:"start_sec,omitempty"`
StopSignal string `yaml:"stop_signal,omitempty" json:"stop_signal,omitempty"`
ExitCode string `yaml:"exit_code,omitempty" json:"exit_code,omitempty"`
Priority int `yaml:"priority,omitempty" json:"priority,omitempty"`
Init string `yaml:"init,omitempty" json:"init,omitempty"`
Candy string `yaml:"candy,omitempty" json:"candy,omitempty"`
}
#CapabilityService — the full structured service spec baked into ai.opencharly.service. name required; all else omitempty. Mirrors the hand struct byte-for-byte.
type Check ¶
type Check Deploy
#Check — a kind:check bed. Structurally IDENTICAL to #Deploy (same FleetNode Go struct), so it is a plain reference (R3 — no field duplication; stays CLOSED because #Deploy is closed). The bed-mode invariants the former `& (A|B|C)` disjunction expressed — disposable required + bed-legal target ∈ {pod,vm,local, android} for the deterministic/ephemeral modes, the iterate AI-benchmark mode, and the ephemeral⇒disposable promotion — are enforced in GO at load time (loaderkit.ValidateCheckBeds + the ephemeral validator beside it), which is the SINGLE source of truth for the actual fleet-form beds (a node-form check bed is a `fleet:` node validated via #DeployValue=#Deploy, so the disjunction was only ever applied to the legacy root-shape `check:` collection). Relaxing it to the alias removes that divergent parallel spec and lets gengotypes emit a real Check struct instead of an empty `struct{}`.
type CheckBedGpuPrereqReply ¶ added in v0.2026217.301
type CheckBedGpuPrereqRequest ¶ added in v0.2026217.301
type CheckBedGpuPrereqRequest struct {
Tokens []string `yaml:"tokens,omitempty" json:"tokens,omitempty"`
}
#CheckBedRequest DIED (#55 W3 B2-full): the "check-bed" op-discriminated HostBuild seam it carried is gone. The compiled-in plugin-check now self-orchestrates the whole bed session itself — flock via spec/lock, the repo-override via spec/proc, the arbiter lease via a direct InvokeProvider(verb,"arbiter") call (the vm_arbiter_shim precedent) — exactly the K5 death this type's own header already predicted. See candy/plugin-check/bed_session.go.
#CheckBedGpuPrereqRequest/#CheckBedGpuPrereqReply is the ONE narrow seam that SURVIVES: GPU host-DETECTION (gpu_allocate.go's bedGPUPrereqMissing, DetectVFIO) is the project's explicitly operator-dropped exception (no hardware to verify against; fenced from every K-wave cutover, including this one — see gpu_shim.go's own header). Threads just the claimant's resource tokens out and the GPU-unsatisfiable verdict back, so the fenced core logic stays completely unchanged.
type CheckBedMember ¶
type CheckBedMember struct {
Key string `yaml:"key,omitempty" json:"key"`
IsVM bool `yaml:"is_vm,omitempty" json:"is_vm,omitempty"`
Image string `yaml:"image,omitempty" json:"image,omitempty"`
From string `yaml:"from,omitempty" json:"from,omitempty"`
}
#CheckBedMember — one sibling member's build coordinates (the group-bed member build loop).
type CheckBedPrereqSkip ¶
type CheckBedPrereqSkip struct {
Token string `yaml:"token,omitempty" json:"token"`
Vendor string `yaml:"vendor,omitempty" json:"vendor"`
Reason string `yaml:"reason,omitempty" json:"reason"`
}
#CheckBedPrereqSkip — a bed the host skips for an absent HOST prerequisite (a GPU resource whose vendor has no matching card): a clean SKIP (exit 3), not a failure.
type CheckBedReply ¶
type CheckBedReply struct {
Calver string `yaml:"calver,omitempty" json:"calver,omitempty"`
LogDir string `yaml:"log_dir,omitempty" json:"log_dir,omitempty"`
PrereqSkip *CheckBedPrereqSkip `yaml:"prereq_skip,omitempty" json:"prereq_skip,omitempty"`
// BedDescriptor — the substrate classification + refs the plugin drives from.
IsVM bool `yaml:"is_vm,omitempty" json:"is_vm,omitempty"`
IsLocal bool `yaml:"is_local,omitempty" json:"is_local,omitempty"`
IsGroup bool `yaml:"is_group,omitempty" json:"is_group,omitempty"`
IsExternal bool `yaml:"is_external,omitempty" json:"is_external,omitempty"`
// node_json is the bed ROOT FleetNode (spec.Deploy) serialized — including its nested
// Members peer map (each member's full FleetNode, with stamped Descent) — so the plugin
// bed runner can call deploykit.PersistBedDeployOverrides PLUGIN-SIDE for the bed root AND
// each member (#55 coneC-dsh β1 — the bed-root + member persist relocate off the host seam;
// the host-side persistBedDeployOverrides wrapper + its deploykit import shed). The plugin
// supplies its own loader-threaded marshalNode + reader (deployMarshalNode/deployConfigReader
// pattern); externalInPlace for the root is IsExternal above, for a member it is derivable from
// the member's stamped Descent (Venue parent/none → in-place). Empty for a VM root (the host
// seam's !isVM guard — a VM bed runs no `charly config`, so no root persist).
NodeJSON RawBody `yaml:"node_json,omitempty" json:"node_json,omitempty"`
Image string `yaml:"image,omitempty" json:"image,omitempty"`
HasAddCandy bool `yaml:"has_add_candy,omitempty" json:"has_add_candy,omitempty"`
// bed) — bed_run.go skips --tag at the config/start steps for such a bed: the FRESH artifact to
// verify is the overlay fleet-add just built + persisted (resolved via plugin-deploy-pod's
// resolveDeployRefLocal resolved_image overlay preference), not the base
// image's own --tag build ref.
VMTemplate string `yaml:"vm_template,omitempty" json:"vm_template,omitempty"`
BedDomain string `yaml:"bed_domain,omitempty" json:"bed_domain,omitempty"`
ImageTag string `yaml:"image_tag,omitempty" json:"image_tag,omitempty"`
LocalRef string `yaml:"local_ref,omitempty" json:"local_ref,omitempty"`
VMDomains []string `yaml:"vm_domains,omitempty" json:"vm_domains,omitempty"`
CheckLiveRefs []string `yaml:"check_live_refs,omitempty" json:"check_live_refs,omitempty"`
ChildKeys []string `yaml:"child_keys,omitempty" json:"child_keys,omitempty"`
// local_child_keys is the HOST-ROOTED (kind:local) subset of child_keys, in the same order. A VM
// root deploys ONLY these host-side (mirroring the core deployNestedLocalChildren): a VM's
// nested CONTAINER children are deployed in-guest by plugin-deploy-vm's PostApply, so a host-side
// re-deploy would be wrong. The pod path uses child_keys (all); the vm path uses local_child_keys.
LocalChildKeys []string `yaml:"local_child_keys,omitempty" json:"local_child_keys,omitempty"`
// members carries each sibling member's build coordinates so a GROUP bed's plugin can drive the
// per-member image build loop (`charly vm build <from>` / `charly box build <image>` + check box)
// BEFORE the host `members-up` op deploys them (bringUpMembers assumes pre-built images).
Members []CheckBedMember `yaml:"members,omitempty" json:"members,omitempty"`
RunBuild bool `yaml:"run_build,omitempty" json:"run_build,omitempty"`
RunRuntime bool `yaml:"run_runtime,omitempty" json:"run_runtime,omitempty"`
RunAgent bool `yaml:"run_agent,omitempty" json:"run_agent,omitempty"`
}
#CheckBedReply is now a plain plugin-internal DESCRIPTOR VALUE TYPE (#55 W3 B2-full), never a wire reply — candy/plugin-check/bed_session.go constructs it directly (no HostBuild round-trip; the type stays CUE-sourced per SDD, since it is still a useful named shape the plugin builds and consumes internally). PrereqSkip set ⇒ the bed is a clean SKIP (exit 3): the plugin writes the prereq-skip summary + returns CheckSkippedError, running NO other setup step (not even teardown — nothing was acquired on the skip path).
type CheckContext ¶ added in v0.2026213.1726
type CheckContext interface {
// Exec runs commands on the venue (in-container under CheckModeLive, in a disposable
// container under CheckModeBox, or host-side depending on the executor).
Exec() CheckExecutor
// Mode is the run mode (Live vs Box).
Mode() CheckRunMode
// HTTPDo issues an HTTP request from the CHARLY HOST's network namespace, applying
// the per-request TLS / redirect / CA policy in req, and returns the status, body, and
// response headers. It REPLACES the former HTTPClient() *http.Client leg: an
// *http.Client cannot cross a process boundary, so out-of-process the REQUEST crosses
// (CheckContextService.HTTPDo) and the host dials; in-process the host builds the client
// and dials directly. The transport-level error is returned as err (a non-2xx is NOT an
// error — the caller matches resp.Status).
HTTPDo(ctx context.Context, req CheckHTTPRequest) (CheckHTTPResponse, error)
// ResolveEndpoint resolves the check target's venue (container / VM / ssh / local) and
// returns a host-reachable "host:port" address for an in-venue TCP port, opening (and
// host-side tracking, for teardown after this verb's Invoke) any ssh -L forward a VM/ssh
// venue needs. An endpoint verb (cdp/vnc/spice/…) declares its in-venue port and dials
// the returned addr — REPLACING the per-verb host preresolvers: the host owns the
// venue/podman/go-libvirt machinery the out-of-process plugin lacks. Empty addr with a
// nil error means "no live venue" (box-mode / no-box) — the verb's own no-endpoint skip
// then fires; a resolution failure is returned as err.
ResolveEndpoint(ctx context.Context, port int) (addr string, err error)
// ResolveGraphicsEndpoint resolves a VM's <graphics type='<kind>'> listener (kind =
// "vnc" | "spice") to a dialable endpoint, opening (and host-side tracking, for teardown
// after this verb's Invoke) any ssh -L forward + socket->TCP bridge the venue needs. A
// graphics verb (vnc/spice) calls it instead of the removed per-verb host preresolver;
// the host owns the go-libvirt resolution, tunnel, bridge, and credential-store password.
// CheckGraphicsEndpoint.Skip=true means the deployment declares no graphics device of that
// kind (an N/A skip). A zero CheckGraphicsEndpoint with a nil error means no live VM context.
ResolveGraphicsEndpoint(ctx context.Context, kind string) (CheckGraphicsEndpoint, error)
// ResolveImageLabel reads one OCI label value off the deployment-under-test's image — the
// host owns the podman engine + container→image resolution the out-of-process plugin cannot
// reach. A verb declares the label it needs (mcp reads ai.opencharly.mcp_provide) and parses
// the returned value; an empty string means the label is absent on the image.
ResolveImageLabel(ctx context.Context, label string) (value string, err error)
// DialTimeout is the per-dial ceiling for host-side TCP reachability probes.
DialTimeout() time.Duration
// Box / Instance are the deployment's image + instance names (empty under CheckModeBox).
Box() string
Instance() string
// Distros is the image's distro tag list (e.g. ["fedora:43","fedora"]) for
// distro-specific package-name resolution.
Distros() []string
// AddBackground registers a host-side background process PID with the active plan run
// so plan teardown reaps it (SIGTERM). A no-op when the engine has no scenario context
// (a bare-Op run) or pid<=0. Used by a verb that fire-and-forgets a host process
// (the `command` verb's background path).
AddBackground(pid int)
}
CheckContext is the live check-engine surface a host-coupled verb's RunVerb consumes. charly's *Runner implements it; a candy reaches the running deployment through it without importing charly's package main.
type CheckDrainEndpointCleanupsRequest ¶ added in v0.2026217.301
type CheckDrainEndpointCleanupsRequest struct {
}
#CheckDrainEndpointCleanupsRequest signals plugin-check to close every forward its #CheckEndpointResolveRequest handler opened since the last drain (LIFO) — the plugin-side twin of the former core-side hostVerbResolver.runEndpointCleanups (#55 W3 B7). Carries no fields: the plugin's pending-cleanup list IS the state, reset+drained per single-verb Invoke — the SAME sequential per-Invoke lifecycle guarantee the former core-side h.endpointCleanups = nil / defer h.runEndpointCleanups() bracket relied on, now owned by the plugin instead.
type CheckEndpointResolveReply ¶ added in v0.2026217.301
type CheckEndpointResolveReply struct {
Addr string `yaml:"addr,omitempty" json:"addr,omitempty"`
}
type CheckEndpointResolveRequest ¶ added in v0.2026217.301
type CheckEndpointResolveRequest struct {
Box string `yaml:"box,omitempty" json:"box"`
Instance string `yaml:"instance,omitempty" json:"instance,omitempty"`
Mode string `yaml:"mode,omitempty" json:"mode"`
Port int `yaml:"port,omitempty" json:"port"`
}
#CheckEndpointResolveRequest/#CheckEndpointResolveReply — the resolution BODY behind the fixed CheckContext.ResolveEndpoint reverse-RPC every out-of-process live-container verb (cdp/wl/vnc/dbus/mcp) dials back into (#55 W3 B7). The reverse-RPC SERVICE surface itself stays core (charly/check_endpoint_resolve.go's hostVerbResolver wraps the core-private verb-dispatch registry no out-of-process caller can bypass) — only the RESOLUTION WORK relocates, compiled-in-REQUIRED placement class (bed_session.go's precedent, #55 W3 B2-full): the venue-classify leg it calls was ALREADY plugin-native (#CheckVenueResolveRequest above), and the downstream resolution (spec/checkhost's EndpointForVenue) has zero core-private dependency of its own. Any ssh -L forward it opens is tracked in the plugin's OWN per-process pending-cleanup state, never on the wire (a live cleanup closure cannot cross ANY Invoke — compiled-in or not, the wire is JSON bytes only) — see #CheckDrainEndpointCleanupsRequest for the close-it-now signal.
type CheckEnv ¶
type CheckEnv struct {
Box string `yaml:"box,omitempty" json:"box,omitempty"`
Instance string `yaml:"instance,omitempty" json:"instance,omitempty"`
Mode string `yaml:"mode,omitempty" json:"mode,omitempty"`
ContainerName string `yaml:"container_name,omitempty" json:"container_name,omitempty"`
Distros []string `yaml:"distros,omitempty" json:"distros,omitempty"`
Venue string `yaml:"venue,omitempty" json:"venue,omitempty"`
VenueKind string `yaml:"venue_kind,omitempty" json:"venue_kind,omitempty"`
DialTimeoutNs int64 `yaml:"dial_timeout_ns,omitempty" json:"dial_timeout_ns,omitempty"`
}
#CheckEnv is the SINGLE-SOURCED scalar snapshot of a check verb's invocation context (K1-unblock W3 Unit B) — the ONE #CheckEnv def now generating the struct all THREE of its consumers share (a hand-written mirror per consumer is the exact "wire type not CUE-generated" violation SDD forbids in fresh code): (1) charly/provider_checkenv.go's host-side CheckEnv, filled by snapshotCheckEnv from a live *kit.Runner and threaded to an out-of-process verb's Invoke envelope; (2) sdk's out-of-process verb-serve decode (sdk/checkverb.go), which reconstructs a kit.CheckContext's scalar legs from this exact snapshot; (3) candy/plugin-check's InvokeProvider-backed VerbResolver (verb_resolver.go), which marshals this same shape when asking the host to dispatch a verb on its behalf. A 4th consumer (charly/plugin_dispatch_reverse.go's InvokeProvider host handler) DECODES it host-side to construct a detached kit.CheckContext for a CheckVerbProvider target — the SAME snapshot, not a second shape.
Every field is optional (a caller fills only what it has — an in-proc/live snapshot has a live Runner to read from; a box-mode run has no ContainerName/Venue; a detached construction may lack DialTimeoutNs). container_name/venue are HOST-COMPUTED-ONLY fields (never authored, carried for the out-of-process appium/vm-target verbs that need charly's naming convention without re-deriving it) — present on the wire regardless of which of the three marshal sites populates them, since all three now share this one shape.
type CheckExecutor ¶ added in v0.2026213.1726
type CheckExecutor interface {
// RunCapture runs a shell command/script on the venue, returning stdout,
// stderr, the exit code, and any execution error (NOT a non-zero exit — that
// is reported via the exit code). No root escalation; callers add sudo.
RunCapture(ctx context.Context, script string) (stdout, stderr string, exit int, err error)
// Kind classifies the venue: "host" | "container" | "image" | "vm".
Kind() string
}
CheckExecutor is the subset of DeployExecutor a check verb needs: run one command/script on the venue and capture stdout/stderr/exit separately. DeployExecutor satisfies this structurally (RunCapture + Kind have identical signatures), so *Runner.Exec is passed straight through.
type CheckGraphicsEndpoint ¶ added in v0.2026213.1726
type CheckGraphicsEndpoint struct {
Addr string
Socket string
Password string
Skip bool
SkipMessage string
}
CheckGraphicsEndpoint is the resolved, dialable VM graphics endpoint a vnc/spice verb gets from CheckContext.ResolveGraphicsEndpoint. Exactly one of Addr / Socket is set (the host bridges a UNIX socket to TCP for a TCP-only client, or forwards a remote listener, before returning). Password is the resolved ticket ("" = no auth). Skip=true (with SkipMessage) means the deployment declares no graphics device of that kind — an N/A skip, not a failure.
type CheckHTTPRequest ¶ added in v0.2026213.1726
type CheckHTTPRequest struct {
Method string
URL string
Body []byte
Headers map[string]string
Timeout string
AllowInsecure bool
NoFollowRedirects bool
CAPEM []byte
}
CheckHTTPRequest is the host-vantage HTTP request a check verb hands cc.HTTPDo. It carries the FULL request plus the per-request policy the host needs to build the client: Timeout is a Go duration string ("" = the engine's base timeout); CAPEM is the resolved CA PEM bytes (a candy reads its authored ca_file host-side and ships the bytes, so the host server needs no filesystem access). Both placements (in-proc + the CheckContextService RPC) consume the SAME struct.
type CheckHTTPResponse ¶ added in v0.2026213.1726
CheckHTTPResponse is the result of cc.HTTPDo: the status code, the response body, and the response headers as a pre-formatted "Key: value\n" blob (the host formats once — R3 — preserving multi-value headers the matcher pipeline consumes directly). A transport-level failure is returned as the HTTPDo error, not here.
type CheckImageLabelResolveReply ¶ added in v0.2026217.301
type CheckImageLabelResolveReply struct {
Value string `yaml:"value,omitempty" json:"value,omitempty"`
}
type CheckImageLabelResolveRequest ¶ added in v0.2026217.301
type CheckImageLabelResolveRequest struct {
Box string `yaml:"box,omitempty" json:"box"`
Instance string `yaml:"instance,omitempty" json:"instance,omitempty"`
Mode string `yaml:"mode,omitempty" json:"mode"`
Label string `yaml:"label,omitempty" json:"label"`
}
#CheckImageLabelResolveRequest/#CheckImageLabelResolveReply — the resolution BODY behind the fixed CheckContext.ResolveImageLabel reverse-RPC (#55 W3 B7), the sibling of #CheckEndpointResolveRequest above (same placement class; no live resource to track — a raw OCI label read is a pure podman-inspect computation).
type CheckLoadPluginsReply ¶
type CheckLoadPluginsReply struct {
}
#CheckLoadPluginsReply is empty on success — connect failures are best-effort WARNINGS on the host (mirroring resolveCheckRunnerContext's existing behavior: an unresolvable plugin fails loudly later, at actual verb dispatch, never here).
type CheckLoadPluginsRequest ¶
type CheckLoadPluginsRequest struct {
Name string `yaml:"name,omitempty" json:"name"`
Dir string `yaml:"dir,omitempty" json:"dir,omitempty"`
}
#CheckLoadPluginsRequest asks the host to connect the out-of-process plugin candies a check plan's verb words reference (K1-unblock wave — the "live" check-run arm). Verb dispatch itself crosses the wire generically via InvokeProvider (S1 — command:check's pluginVerbResolver), but InvokeProvider only resolves an ALREADY-CONNECTED provider (or a compiled-in one); connecting an out-of-process candy is the plugin-loading M-mechanism (the kernel/plugin boundary law's clause M — plugin discovery/loading/connect stays core), so this seam is the entry point a plugin calls BEFORE dispatching a plan whose verbs may need an out-of-process candy connected. The host runs the UNCHANGED core engine (LoadConfig + resolveCheckRunnerContext: ScanAllCandyWithConfigOpts + collectReferencedPluginWords + loadProjectPlugins) as a pure SIDE EFFECT on its own providerRegistry — every subsequent InvokeProvider call in this same `charly check run` invocation then resolves. Class-generic action noun "check-load-plugins" (F11 — never a substrate/provider word).
type CheckResult ¶
type CheckResult struct {
Op *Op `yaml:"op,omitempty" json:"op"`
Verb string `yaml:"verb,omitempty" json:"verb"`
Status Status `yaml:"status,omitempty" json:"status"`
Message string `yaml:"message,omitempty" json:"message"`
Elapsed time.Duration `yaml:"elapsed,omitempty" json:"elapsed"`
Attempts int `yaml:"attempts,omitempty" json:"attempts,omitempty"`
TotalElapsed time.Duration `yaml:"total_elapsed,omitempty" json:"total_elapsed,omitempty"`
CapturedValue string `yaml:"captured_value,omitempty" json:"captured_value,omitempty"`
}
CUE schema for the check-engine's per-step VERDICT envelope (FLOOR-SLIM Unit 4). NOT an authoring kind (never in #Node/#Op) — a pure generated wire/render struct, single-sourced here so `task cue:gen` produces the Go struct charly core's registry-coupled floor files (provider.go/provider_verb.go/verb_builtins.go/unified_targets.go/provider_checkenv.go) reference directly (spec.CheckResult), with zero new sdk/kit import.
#CheckResult covers every field EXCEPT the engine-internal `DeadlineExceeded` retry signal — the ONE spike-proven exception (P12, cited at sdk/kit/checkrun_seam.go and sdk/schema/seam.cue): `DeadlineExceeded bool json:"-"` has no gengotypes construct (a field that exists in memory but is excluded from marshaling). sdk/kit.CheckResult embeds this generated type and adds ONLY that one hand-written field back.
FLOOR-SLIM deliberately renames the wire keys to snake_case (Op→op, Verb→verb, Status→status, Message→message, Elapsed→elapsed) — the former hand-written type carried NO json tag on these fields, so encoding/json defaulted to the bare, inconsistent PascalCase Go field name. This is a documented, deliberate breaking wire-format fix for `--format json`/TAP consumers of `charly check box/live/run`, not an accident: it brings CheckResult in line with every other CUE-sourced wire type's snake_case convention. Every field that ALWAYS serialized before (op/verb/status/message/elapsed) stays REQUIRED (`!`) here so gengotypes omits `omitempty` — an omitempty regression would silently drop zero-valued fields from output, a SEPARATE wire change the rename must not introduce. Every field that already carried `,omitempty` (attempts/total_elapsed/captured_value) stays optional (`?`).
Status is carried as a plain int (@go(Status,type=Status) — Status is the check-engine's pass/fail/skip enum, HAND-WRITTEN in spec/status_result.go: gengotypes has no construct for an iota-based enum + String() method, so CUE owns the wire VALUE SET (an int) and Go owns the formatting behavior (String()), mirroring the #SubstrateKind split (status.cue) — there the enum is string-backed and suppressed via @go(-); here it is int-backed and referenced directly since there is no separate disjunction def to suppress.
Elapsed / TotalElapsed carry a nanosecond count wire-typed as time.Duration (@go(Elapsed,type=time.Duration)) — the RDD spike (T-P12, cited in /charly-internals:go) proved a custom-scalar @go(,type=…) override generates faithfully.
type CheckRunMode ¶ added in v0.2026213.1726
type CheckRunMode int
CheckRunMode is the mode a check runs under (charly's RunMode). It never crosses the wire — spec.CheckEnv carries the "box"/"live" string form — so it is a plain Go int.
const ( // CheckModeLive — `charly check live`, against a running container/VM (in-container probes). CheckModeLive CheckRunMode = iota // CheckModeBox — `charly check box`, against a disposable build container. CheckModeBox )
func (CheckRunMode) String ¶ added in v0.2026213.1726
func (m CheckRunMode) String() string
String renders the mode as "box" / "live" (mirrors charly's runModeName).
type CheckRunReply ¶ added in v0.2026213.1726
type CheckRunReply struct {
Steps []StepResult `yaml:"steps,omitempty" json:"steps,omitempty"`
Image string `yaml:"image,omitempty" json:"image,omitempty"`
NoSteps bool `yaml:"no_steps,omitempty" json:"no_steps,omitempty"`
Header string `yaml:"header,omitempty" json:"header,omitempty"`
Passthrough *StepPass `yaml:"passthrough,omitempty" json:"passthrough,omitempty"`
Score *CheckRunResults `yaml:"score,omitempty" json:"score,omitempty"`
}
#CheckRunReply is the plugin-resolved result of a check-run. Steps is the per-step verdict list the plugin formats (FormatStepResults*) and tallies into an exit code. Image is the resolved image ref for the "Image: <ref>" header line. NoSteps signals the image declared no plan (the plugin prints "No plan steps defined for this image." and exits 0) — distinct from an empty Steps that ran zero scored steps. Header is the pre-formatted, kind-specific banner line the plugin builds (container name, ssh user/host/port, member count) — the plugin prints it, then the formatted Steps. Passthrough carries the one non-plan-run live path — a nested pod-in-VM leaf whose check is delegated to the guest over SSH, forwarded verbatim; nil for every plan-run mode. Score is the "score"-mode reply (the AI-harness SCORING result, #CheckRunResults), nil for the box/live/feature plan-run modes that carry their verdicts in Steps. CUE-sourced (SDD): plain carrier, no json:"-".
type CheckRunRequest ¶
type CheckRunRequest struct {
Mode string `yaml:"mode,omitempty" json:"mode"`
Name string `yaml:"name,omitempty" json:"name,omitempty"`
Image string `yaml:"image,omitempty" json:"image,omitempty"`
Instance string `yaml:"instance,omitempty" json:"instance,omitempty"`
Dir string `yaml:"dir,omitempty" json:"dir,omitempty"`
Section string `yaml:"section,omitempty" json:"section,omitempty"`
Filter []string `yaml:"filter,omitempty" json:"filter,omitempty"`
Tag string `yaml:"tag,omitempty" json:"tag,omitempty"`
Strict bool `yaml:"strict,omitempty" json:"strict,omitempty"`
Agent string `yaml:"agent,omitempty" json:"agent,omitempty"`
Timeout string `yaml:"timeout,omitempty" json:"timeout,omitempty"`
NoAgent bool `yaml:"no_agent,omitempty" json:"no_agent,omitempty"`
Plan []Step `yaml:"plan,omitempty" json:"plan,omitempty"`
// --- snapshot-anchored check-run mode (§5.3.1) ---
// anchor: the snapshot name to revert before the checks (the golden disk).
// keep_venue: keep the VM between runs (batch loop). variant: the VM-config
// variant name to boot over the shared golden disk. vars: per-run variable
// passthrough (e.g. pr=9345).
Anchor string `yaml:"anchor,omitempty" json:"anchor,omitempty"`
KeepVenue bool `yaml:"keep_venue,omitempty" json:"keep_venue,omitempty"`
Variant string `yaml:"variant,omitempty" json:"variant,omitempty"`
Vars map[string]string `yaml:"vars,omitempty" json:"vars,omitempty"`
}
#CheckRunRequest is the check-run dispatch envelope (P12). command:check (candy/plugin-check) owns the `charly check` CLI + output formatting AND runs every mode itself (hostCheckRunCtx's per-mode bodies — the former "check-run" HostBuild kind is DELETED, K-wave 2 cone R4): the venue→executor construction + OCI-label plan extraction are plugin-side (loaderkit / deploykit), while the plan-walk's verb dispatch resolves through the provider registry (in-proc, or the out-of-process check-context reverse channel for the live-container verbs). The action noun "check-run" is class-generic (F11).
Mode selects the run shape (discriminated union): "box" — a pure-box run against a disposable container built from Image (RunModeBox, build-scope steps only, the CheckBoxCmd engine); "live" — a full-stack run against a running deployment resolved by Name (the plugin classifies vm/pod/local/group via its own venue.go), applying the Instance/Section/Filter selectors; "feature-box" / "feature-live" — the ADE acceptance run (SkipDeterministicRun) over Image (build scope) or the live deployment Name (deploy scope, the plugin-side agent grader wiring, gated by NoAgent/Agent/Timeout), scoped by Tag/Strict. Dir is the project dir (empty → the plugin uses its own cwd), matching LoadUnified(dir). `format` is deliberately NOT a field — the plugin formats the returned Steps itself. run-bed + iterate are NOT seam modes: the plugin drives them over HostBuild("cli").
The REPLY is CUE-sourced: #CheckRunReply / #StepResult / #StepPass (checkresult.cue) — generated to spec.CheckRunReply / spec.StepResult / spec.StepPass, byte-identical JSON to the former hand-written kit types. sdk/kit aliases each (kit.CheckRunReply = spec.CheckRunReply etc., sdk/kit/checkrun_seam.go) so the plugin reuses the kit formatters (FormatStepResults*) with byte-parity across every --format. A live `cue exp gengotypes` spike (P12) proved kit.CheckResult AS A WHOLE is genuinely inexpressible in CUE — its engine-internal `DeadlineExceeded bool json:"-"` field has no gengotypes construct — but confirmed the REST of the type (Op/Verb/Status/Message/Elapsed/Attempts/TotalElapsed/CapturedValue) generates faithfully. FLOOR-SLIM Unit 4 acted on that finding: #CheckResult (checkresult.cue) is the CUE-sourced base (→ spec.CheckResult), and kit.CheckResult is now `struct { spec.CheckResult; DeadlineExceeded bool json:"-" }` — an EMBEDDING wrapper, not a hand-duplicated type. The SDD sweep in THIS cutover extended CUE-sourcing to the reply envelope too: StepResult / CheckRunReply / StepPass are plain carriers (no json:"-"), so gengotypes expresses them faithfully — the `optional=nillable` marker emits the Passthrough / Score pointers. The exception the wire mandate's spike-proven path authorizes is now narrowed to EXACTLY the one kit.CheckResult field that forced it, not the whole type nor the reply envelope.
P12 Wave-2: the "score" mode adds Plan — a substituted, nonce-carrying scoring plan candy/plugin-check's pluginRunCheckLive walks (NOT the OCI-baked plan the "live" mode extracts; walked plugin-side directly since K1-unblock wave arm 3, no host round-trip). Its per-step scoring verdicts ride the kit.CheckRunReply.Score field (a *CheckRunResults, below).
type CheckRunResults ¶
type CheckRunResults struct {
Box string `yaml:"box,omitempty" json:"box,omitempty"`
Mode string `yaml:"mode,omitempty" json:"mode,omitempty"`
Step []StepScore `yaml:"step,omitempty" json:"step,omitempty"`
Summary ScoreSummary `yaml:"summary,omitempty" json:"summary"`
}
#CheckRunResults / #StepScore / #ScoreSummary — the AI-harness SCORING result model (originally P12 Wave-2; the mode's walk moved plugin-side in K1-unblock wave arm 3). pluginRunCheckLive returns a *CheckRunResults (the scored check:/agent-check: verdicts, keyed by step id for plateau tracking); it doubles as the `charly check box --format yaml` payload the harness scorer parses (ParseCharlyTestOutput). These are plain structs — the gengotypes workhorse — CUE-sourced so the "score"-mode reply's Score field and the plugin scorer that produces AND consumes it import ONE definition (SDD; no alias). Every field mirrors the former hand-written Go tag set: required (!) fields carry no json-omitempty (json wire byte-identical for the seam reply); optional (?) fields carry it. The retag pass adds ,omitempty to every YAML tag uniformly — inert here since ID/Status are always set and a zero Summary block only elides on an empty (0-step) result ParseCharlyTestOutput tolerates.
type CheckVenueResolveReply ¶
type CheckVenueResolveReply struct {
Descriptor VenueDescriptor `yaml:"descriptor,omitempty" json:"descriptor"`
Kind string `yaml:"kind,omitempty" json:"kind"`
Engine string `yaml:"engine,omitempty" json:"engine,omitempty"`
Name string `yaml:"name,omitempty" json:"name,omitempty"`
VMName string `yaml:"vm_name,omitempty" json:"vm_name,omitempty"`
Nested bool `yaml:"nested,omitempty" json:"nested,omitempty"`
}
#CheckVenueResolveReply is the wire-safe projection of the plugin-side CheckVenue: the generic #VenueDescriptor (the host re-materializes it into a live DeployExecutor — a live executor never crosses the wire) plus the scalar venue facts the host legs consume (kind for the vm-vs-not graphics branch + the IsContainer var-resolution gate; engine/name for container port mapping + image-label reads; vm_name for the VM graphics leg; nested marks a genuinely multi-hop target the single-hop descriptor degrades to zero for, so the host rebuilds its chain via ResolveDeployChain).
type CheckVenueResolveRequest ¶
type CheckVenueResolveRequest struct {
Name string `yaml:"name,omitempty" json:"name"`
Instance string `yaml:"instance,omitempty" json:"instance,omitempty"`
}
#CheckVenueResolveRequest asks plugin-check to CLASSIFY a check target's venue — the kind-decode (checkVmTarget/checkLocalTarget branch on the stamped venue trait) that is CHECK CAPABILITY LOGIC, not kernel fabric (#118 boundary-law self-test: a floor file switching on a kind is a leaked R-item). The host's floor reverse-legs (endpoint / graphics / gRPC exec-attach) call this INSTEAD of an in-core classifier duplicate, then re-materialize the returned generic #VenueDescriptor via kit.VenueFromDescriptor (single-hop) — or, for a nested target the flat descriptor cannot express, rebuild the N-hop chain host-side via the kind-blind deploykit.ResolveDeployChain. plugin-check reaches the merged deploy tree via its OWN InvokeProvider("build","project", OpResolve) peer-dispatch (the former HostBuild("resolved-project") seam is DELETED), so this seam carries only name+instance. Class-generic action noun (F11).
type CheckVerbProvider ¶ added in v0.2026213.1726
type CheckVerbProvider interface {
Reserved() string
RunVerb(ctx context.Context, cc CheckContext, op *Op) CheckVerbResult
}
CheckVerbProvider is the typed in-process contract a host-coupled check-verb candy implements. Reserved() is the verb word; RunVerb runs the probe against the live CheckContext and returns a CheckVerbResult. The authored plugin_input rides op.PluginInput (decode it into the candy's CUE-generated params struct).
type CheckVerbResult ¶ added in v0.2026213.1726
type CheckVerbResult struct {
Status Status
Message string
CapturedValue string // value stashed under `capture:` (recorded only on PASS)
}
CheckVerbResult is a host-coupled verb's verdict. charly converts it to its internal CheckResult (stamping the Op/Verb/timing) at the dispatch boundary.
type CliReply ¶
type CliReply struct {
Stdout string `yaml:"stdout,omitempty" json:"stdout,omitempty"`
ExitCode int `yaml:"exit_code,omitempty" json:"exit_code,omitempty"`
Error string `yaml:"error,omitempty" json:"error,omitempty"`
}
#CliReply is the "cli" host-builder reply: captured stdout (Capture=true), the exit code, and an error string on a non-zero exit that was not BestEffort-swallowed.
type CliRequest ¶
type CliRequest struct {
Argv []string `yaml:"argv,omitempty" json:"argv"`
Capture bool `yaml:"capture,omitempty" json:"capture,omitempty"`
Combined bool `yaml:"combined,omitempty" json:"combined,omitempty"`
BestEffort bool `yaml:"best_effort,omitempty" json:"best_effort,omitempty"`
}
#CliRequest is the "cli" host-builder envelope (M4): a lifecycle plugin asks the HOST to run a `charly <argv>` subcommand.
type CloudInitRuntimeParams ¶ added in v0.2026215.1748
type CloudInitRuntimeParams struct {
// SSHPublicKey is the OpenSSH authorized_keys-format public key
// line (e.g. "ssh-ed25519 AAAA..."). Empty when key injection is
// disabled or when VmSsh.KeySource == "none".
SSHPublicKey string
// InstanceID is the stable UUIDv4 cloud-init instance-id.
// Pinned at first VM create and persisted in VmDeployState.
InstanceID string
// Hostname for the guest. Defaults to the VM name when empty.
Hostname string
// InjectKeyViaCloudInit is the resolved D13 key_injection.cloud_init
// channel state. When false the renderer emits no
// ssh_authorized_keys entries even if SSHPublicKey is populated.
InjectKeyViaCloudInit bool
}
CloudInitRuntimeParams carries the runtime-resolved state needed to render cloud-init user-data: the SSH public key to inject, the instance-id (stable UUIDv4 persisted in VmDeployState), the hostname, and whether cloud-init should inject the SSH key at all (computed from D13 auto-defaults + explicit VmKeyInjection overrides).
type CollectedAlias ¶
type CollectedAlias struct {
Name string `yaml:"name,omitempty" json:"name"`
Command string `yaml:"command,omitempty" json:"command"`
}
#CollectedAlias — a command alias baked into ai.opencharly.alias (name+command required).
type Config ¶
type Config struct {
Defaults BoxConfig `yaml:"defaults" json:"defaults"`
Box BoxMap `yaml:"box" json:"box"`
// Local carries kind:local templates so remote-ref collection + validation walk their candy:
// lists symmetrically with box candy lists (kind:local templates compose remote @-ref
// candies too). Populated from UnifiedFile.Local by UnifiedFile.ProjectConfig() (sdk/loaderkit).
Local map[string]json.RawMessage `yaml:"local,omitempty" json:"local,omitempty"`
// Sidecar carries the project's sidecar-template library as OPAQUE bodies (the raw
// PluginKinds["sidecar"] map). The kernel never reads their fields — candy/plugin-sidecar's
// OpResolve owns all sidecar business logic.
Sidecar map[string]json.RawMessage `yaml:"sidecar,omitempty" json:"sidecar,omitempty"`
// Skills carries the project's skill-kind library as OPAQUE bodies (the raw
// PluginKinds["skill"] map). The kernel never reads their fields — CollectSkills
// (deploykit) projects the composed candies' skills into the ai.opencharly.skill label
// and candy/plugin-marketplace regenerates the plugins/ corpus from the same source.
Skills map[string]json.RawMessage `yaml:"skills,omitempty" json:"skills,omitempty"`
// Namespaces carries child namespaces mounted by namespaced `import:` entries (alias →
// projected sub-Config). Qualified refs like `cachyos.cachyos` resolve through here.
// Populated from UnifiedFile.Namespaces by ProjectConfig().
Namespaces map[string]*Config `yaml:"-"`
}
Config represents the charly.yml configuration projection.
func (*Config) AllBoxNames ¶
AllBoxNames returns every image name (enabled or not), sorted — the raw-map view BoxNames filters. Used by the map-killing accessors where the enabled filter is applied separately. Exported: called cross-package from several charly consumers (host_build_feature.go, resolved_project_host.go, refs.go) and in-package by spec's own distroBuilderMap (builder_resolve.go).
func (*Config) BoxConfig ¶
BoxConfig decodes the authored image config for name; ok=false when absent.
func (*Config) EachBox ¶
EachBox iterates every image as (name, decoded BoxConfig) in sorted name order — the decode-on-iterate view consumers walk INSTEAD of the kernel holding a typed map. Exported: used cross-package as a range-over-func iterator by charly's validate.go / validate_project_host.go.
func (*Config) FindBoxByLeaf ¶
FindBoxByLeaf searches for a box whose leaf (unqualified) name equals `leaf` — first in c's own root box map, then recursively in each imported namespace (deterministic alias order). Returns the fully-qualified ref under which the box is reachable from c (bare for a root hit, `ns.<...>` for a namespaced hit), or ok=false when no box with that leaf exists anywhere in the namespace tree.
This is the DISCOVERY dual of ResolveBoxRef: ResolveBoxRef resolves a KNOWN qualified ref by descent, whereas FindBoxByLeaf discovers the qualification for a bare leaf that may live in a namespace. ensure-image's build-fallback needs it because it only has the basename of a full registry ref (e.g. `arch-builder` extracted from `ghcr.io/opencharly/arch-builder:<tag>`) and must find that the box lives under the `charly` namespace to build it locally.
func (*Config) ResolveBoxRef ¶
ResolveBoxRef resolves a (possibly qualified) box name to its BoxConfig and the Config (namespace context) it lives in. Bare names resolve in c; `ns.name` descends into c.Namespaces[ns] recursively. Exported: called cross-package from charly's deploy_ref.go, check_bed_run.go, host_build_box_ref_resolve.go, validate.go, refs.go, and from sdk/buildkit's resolveBase (via the *Config it's given).
func (*Config) WalkBaseChain ¶
func (c *Config) WalkBaseChain(boxName string) []BaseChainNode
WalkBaseChain walks boxName's ROOT-INTERNAL base-image chain and returns the images in walk order (self first, then each internal base). It is the ONE shared base-chain traversal used by every chain-walking collector (CollectHooks / CollectShell / CollectDescription / CollectBoxVolume) — each previously re-implemented the identical `for { img := cfg.Box[current]; ...; current = img.Base }` loop (R3: one implementation, no divergent copies), now cycle-safe for all of them.
It deliberately does NOT descend import namespaces. A namespace-qualified base (e.g. `selkies.selkies-labwc`) is a SEPARATELY-BUILT image that owns its own baked check / hooks / shell / volume labels; re-collecting its candies into the consumer would DOUBLE-COUNT every candy the consumer also lists directly. Namespace-AWARENESS belongs to NAME resolution (ResolveBoxRef / FindBoxByLeaf), not to this per-image candy-collection walk; the distro/build VALUE walkers (WalkBaseChainDistro / WalkBaseChainBuild) cross namespaces precisely because those are inherited values, whereas candy contributions are not.
Exported: called cross-package from sdk/deploykit's BoxCandyChain.
func (*Config) WalkBaseChainBuild ¶
WalkBaseChainBuild walks the base chain through box: entries to find the first ancestor with a build: field set. Returns nil if no ancestor defines build formats or the chain reaches an external base image. Exported: called cross-package from sdk/buildkit's resolveBuild.
func (*Config) WalkBaseChainDistro ¶
WalkBaseChainDistro walks the base chain through box: entries to find the first ancestor with a distro: field set. Returns nil if no ancestor defines distro tags or the chain reaches an external base image. Exported: called cross-package from sdk/buildkit's resolveDistro / effectiveBuilderForBox.
type ConstructStepReply ¶
type ConstructStepReply struct {
Step *InstallStepView `yaml:"step,omitempty" json:"step,omitempty"`
}
type ConstructStepRequest ¶
type ConstructStepRequest struct {
Op Op `yaml:"op,omitempty" json:"op"`
CandyName string `yaml:"candy_name,omitempty" json:"candy_name"`
CandySourceDir string `yaml:"candy_source_dir,omitempty" json:"candy_source_dir,omitempty"`
CandyVars map[string]string `yaml:"candy_vars,omitempty" json:"candy_vars,omitempty"`
ResolvedUser string `yaml:"resolved_user,omitempty" json:"resolved_user,omitempty"`
PkgFormat string `yaml:"pkg_format,omitempty" json:"pkg_format,omitempty"`
DistroTags []string `yaml:"distro_tags,omitempty" json:"distro_tags,omitempty"`
}
#ConstructStepRequest/#ConstructStepReply — the "construct-step" HostBuild seam (K5-A item 1, compile-seam ctx-threading): the ONE genuinely host-only piece of the former compileActOp — resolving a `run:` act op's `plugin:` word against the PROVIDER REGISTRY (a clause-M kernel mechanism, never reachable from a plugin directly) to decide whether it lowers into a typed InstallStep (TypedStepProvider.ConstructStep, an in-proc-only Go method today — no change), an ExternalPluginStep (an out-of-process executorInvoker verb), an ExternalStep (a class:step provider's declared StepContract), or the generic OpStep fallthrough. Everything ELSE compileActOp used to read off `layer`/`img` is ALREADY portable sdk/deploykit-side (CandyModel, *buildkit.ResolvedBox, deploykit.ResolveUserSpec) and never needs to cross this wire — the caller (deploykit.BuildDeployPlan, now ctx/exec-threaded) resolves those PORTABLE pieces itself and sends only the reduced scalars the registry-consult logic actually reads. The reply carries the constructed step as an OPAQUE #InstallStepView (StepToView/StepFromView, R3 — the SAME wire-view round-trip every other step-carrying seam uses) so the caller re-materializes a real deploykit.InstallStep without a second per-kind decode path. No new #Op selector is needed: the registry-consult decision is HostBuild-KIND-dispatched (string, like every other seam in this file), not a provider-targeted Invoke — compileActOp's logic runs UNCHANGED host-side inside the seam handler.
type CredentialHealth ¶
type CredentialHealth struct {
BackendName string `yaml:"backend_name,omitempty" json:"backend_name"`
ConfiguredBackend string `yaml:"configured_backend,omitempty" json:"configured_backend"`
KeyringAvailable bool `yaml:"keyring_available,omitempty" json:"keyring_available"`
KeyringLocked bool `yaml:"keyring_locked,omitempty" json:"keyring_locked"`
PlaintextCount int `yaml:"plaintext_count,omitempty" json:"plaintext_count"`
NoSession bool `yaml:"no_session,omitempty" json:"no_session"`
CollErr string `yaml:"coll_err,omitempty" json:"coll_err,omitempty"`
HealthyColls []string `yaml:"healthy_colls,omitempty" json:"healthy_colls,omitempty"`
BrokenColls []string `yaml:"broken_colls,omitempty" json:"broken_colls,omitempty"`
IndexTotal int `yaml:"index_total,omitempty" json:"index_total"`
IndexMissing []string `yaml:"index_missing,omitempty" json:"index_missing,omitempty"`
}
#CredentialHealth is the credential-store health snapshot. Rendered into the doctor "secret storage" checks by the plugin.
type CredentialMount ¶
type Debootstrap ¶
type Debootstrap struct {
Suite string `yaml:"suite,omitempty" json:"suite,omitempty"`
Mirror string `yaml:"mirror,omitempty" json:"mirror,omitempty"`
Variant string `yaml:"variant,omitempty" json:"variant,omitempty"`
Components string `yaml:"components,omitempty" json:"components,omitempty"`
IncludePackages []string `yaml:"include_package,omitempty" json:"include_package,omitempty"`
BasePackages []string `yaml:"base_package,omitempty" json:"base_package,omitempty"`
ExtraRepos []DebootstrapRepo `yaml:"extra_repo,omitempty" json:"extra_repo,omitempty"`
}
type DebootstrapRepo ¶
type Deploy ¶
type Deploy struct {
Version CalVer `yaml:"version,omitempty" json:"version,omitempty"`
Description string `yaml:"description,omitempty" json:"description,omitempty"`
// target is DERIVED from the node's discriminator kind + cross-ref at load
// (buildFleetNode/inferFleetTarget) — NOT authored in node-form. Optional
// here so #Check's arms can pin it; the #DeployValue arm (node.cue) rejects an
// authored `target:` outright. The former default `*"pod"` is dropped (Go's
// classifyTarget supplies the empty→pod default). Generated as a plain Go
// `string` (the loader stamps it; the CUE enum still validates a pinned value).
Target string `yaml:"target,omitempty" json:"target,omitempty"`
// member_of + inside are loader-DERIVED runtime fields (never authored;
// rejected by #DeployValue): member_of marks a folded sibling-member entry,
// inside names the venue a nested resource deploys into. Generated for the Go
// tree-walker, forbidden in authoring.
MemberOf string `yaml:"member_of,omitempty" json:"member_of,omitempty"`
Inside string `yaml:"inside,omitempty" json:"inside,omitempty"`
// agent_provisioned marks a resource member/child the AI deploys at run time
// (the iterate-benchmark contract): image-less (no box:), not folded to a
// top-level entry, exempt from the box-required validators. See deploy.go.
AgentProvisioned bool `yaml:"agent_provisioned,omitempty" json:"agent_provisioned,omitempty"`
// descent is the loader-DERIVED venue-hop descriptor (the descent de-type,
// Cutover H): the substrate plugin stamps it at OpLoad (via kit.StampDescent) so
// the deploy chain (AppendHopForFlatPath) descends generically BY TRANSPORT,
// never by switching on the substrate kind word. charly-written state, never
// authored (rejected by #DeployValue).
Descent *DescentDescriptor `yaml:"descent,omitempty" json:"descent,omitempty"`
// EDGE-INHERIT cutover B: the substrate kind is the EDGE discriminator (pod:/vm:/
// kubernetes:/local:/android:/group:), so the deploy carries only NON-kind cross-refs:
//
// from — inherit a SAME-kind template by name (vm/kubernetes/local/android deploys).
// image — the box/OCI artifact a pod/kubernetes/android RUNS (the former `box:`).
//
// Per-substrate validity (image⊻from, source⊻from) is enforced in Go
// (classifyTarget / validateDeploy), not CUE, so a `vm:` node is a VmSpec template
// (source:) OR a deploy (from:) under ONE arm — the disjunction #Vm|#Deploy.
From EntityRef `yaml:"from,omitempty" json:"from,omitempty"`
Image string `yaml:"image,omitempty" json:"image,omitempty"`
Kind string `yaml:"kind,omitempty" json:"kind,omitempty"`
Replica int `yaml:"replica,omitempty" json:"replica,omitempty"`
Restart string `yaml:"restart,omitempty" json:"restart,omitempty"`
Schedule string `yaml:"schedule,omitempty" json:"schedule,omitempty"`
Tunnel *TunnelYAML `yaml:"tunnel,omitempty" json:"tunnel,omitempty"`
DNS string `yaml:"dns,omitempty" json:"dns,omitempty"`
AcmeEmail string `yaml:"acme_email,omitempty" json:"acme_email,omitempty"`
Port []PortPin `yaml:"port,omitempty" json:"port,omitempty"`
ResolvedPort []PortPin `yaml:"resolved_port,omitempty" json:"resolved_port,omitempty"`
// resolved_image: the concrete image ref the pod deploy's add_candy: overlay
// build produced (`<deploy-key>-overlay:<hash>`), persisted by PrepareVenue so
// config/start deploy EXACTLY that overlay (carrying the add_candy layers)
// instead of re-resolving the base image: short-name by a CalVer sort that the
// overlay alias can lose to the base on a same-minute build. charly-written
// state (like resolved_port), never authored; empty for a plain pod.
ResolvedImage string `yaml:"resolved_image,omitempty" json:"resolved_image,omitempty"`
Env map[string]string `yaml:"env,omitempty" json:"env,omitempty"`
EnvFile string `yaml:"env_file,omitempty" json:"env_file,omitempty"`
Network string `yaml:"network,omitempty" json:"network,omitempty"`
Engine string `yaml:"engine,omitempty" json:"engine,omitempty"`
Security *Security `yaml:"security,omitempty" json:"security,omitempty"`
Secret []DeploySecret `yaml:"secret,omitempty" json:"secret,omitempty"`
Volume []DeployVolume `yaml:"volume,omitempty" json:"volume,omitempty"`
// volume_project_checked marks that this deploy's PROJECT-declared `volume:` override has
// already been consulted ONCE (true regardless of whether the project declared one), so
// resolveDeployVolumes (candy/plugin-deploy-pod) never re-queries the project on a
// subsequent `charly config`/`charly update` merely because the per-host overlay's Volume
// field happens to be empty (that emptiness is ALSO produced by an unrelated overlay write —
// e.g. the port-resolution block — that never touched volumes at all, so key-existence alone
// cannot distinguish "checked, found nothing" from "not checked yet"). charly-written state
// (like resolved_port/resolved_image), never authored.
VolumeProjectChecked bool `yaml:"volume_project_checked,omitempty" json:"volume_project_checked,omitempty"`
// The Go type is OPAQUE (map[string]json.RawMessage): CUE still validates each
// per-deploy sidecar override against #Sidecar, but the kernel stores it as
// opaque bodies — ALL sidecar business logic lives in candy/plugin-sidecar's
// OpResolve (the sidecar de-type, Cutover D). The host never reads Sidecar fields.
Sidecar map[string]RawBody `yaml:"sidecar,omitempty" json:"sidecar,omitempty"`
ForwardGpgAgent *bool `yaml:"forward_gpg_agent,omitempty" json:"forward_gpg_agent,omitempty"`
ForwardSSHAgent *bool `yaml:"forward_ssh_agent,omitempty" json:"forward_ssh_agent,omitempty"`
Plan []Step `yaml:"plan,omitempty" json:"plan,omitempty"`
Iterate *Iterate `yaml:"iterate,omitempty" json:"iterate,omitempty"`
// record — the bed-level whole-run recording wrap (G5 follow-up): when set,
// the check-run runner starts a recording on the venue before the live
// phases and stops it after, landing the artifact in the bed's run evidence
// (recordings.yml beside summary.yml). terminal/desktop select the recorder
// lane; fps + record_env pass through to the record verb (desktop sessions
// on VM venues need XDG_RUNTIME_DIR/WAYLAND_DISPLAY).
Record *Deploy_RecordWrap `yaml:"record,omitempty" json:"record,omitempty"`
AddCandy []string `yaml:"add_candy,omitempty" json:"add_candy,omitempty"`
InstallOpts *InstallOpts `yaml:"install_opts,omitempty" json:"install_opts,omitempty"`
Host string `yaml:"host,omitempty" json:"host,omitempty"`
User string `yaml:"user,omitempty" json:"user,omitempty"`
SSHArgs []string `yaml:"ssh_arg,omitempty" json:"ssh_arg,omitempty"`
Cpus int `yaml:"cpus,omitempty" json:"cpus,omitempty"`
Ram VmSize `yaml:"ram,omitempty" json:"ram,omitempty"`
DiskSize VmSize `yaml:"disk_size,omitempty" json:"disk_size,omitempty"`
Deploy *KubernetesDeploy `yaml:"deploy,omitempty" json:"deploy,omitempty"`
Resources *DeployResources `yaml:"resources,omitempty" json:"resources,omitempty"`
Expose *DeployExpose `yaml:"expose,omitempty" json:"expose,omitempty"`
Storage []DeployStorage `yaml:"storage,omitempty" json:"storage,omitempty"`
Probes *DeployProbes `yaml:"probes,omitempty" json:"probes,omitempty"`
FromSnapshot string `yaml:"from_snapshot,omitempty" json:"from_snapshot,omitempty"`
CloudInitClean bool `yaml:"cloud_init_clean,omitempty" json:"cloud_init_clean,omitempty"`
VmState *VmDeployState `yaml:"vm_state,omitempty" json:"vm_state,omitempty"`
// snapshot — the check-bed snapshot-anchoring policy (§5.3.1): capture at
// install finalize and reset before every check run, so a batch of PR runs
// shares ONE golden disk (revert ≈ seconds vs fresh install ≈ 20-30 min).
// VM-only (the substrate-word checks reject it on other substrates).
Snapshot *VmSnapshotPolicy `yaml:"snapshot,omitempty" json:"snapshot,omitempty"`
// variants — named VM-config overrides that boot the SAME golden disk with a
// different shape (cpus/memory/video/gpu/display/devices/attachments). A
// variant may ONLY change VM shape — any change to source: or disk identity
// is rejected, because the disk comes exclusively from the shared snapshot
// chain. The unnamed default variant equals the bed's own vm: attributes.
Variants map[string]*VmVariant `yaml:"variants,omitempty" json:"variants,omitempty"`
// update_gate — the check-bed's declarative R10 fresh-update change-class
// switch: how the Step-5 acceptance gate re-verifies the bed (the declarative
// twin of `charly check run --no-rebuild`). Check-bed only (like snapshot:);
// meaningless on a plain deploy.
//
// full (default) — the canonical destroy+recreate gate: `charly
// update` rebuilds the venue from scratch (a VM
// domain is destroyed + recreated, a pod image
// rebuilt + container recreated, a group rebuilds
// every member image) and re-checks it.
// restart-only — replace the destroy/recreate with a plain venue
// RESTART (a VM BOOTs its existing clone again, a
// pod restarts its container, a group cycles its
// members WITHOUT rebuilding images) and re-runs
// the check-live pass against the restarted
// venue. The change class for runtime-PR-injection
// software evals: a rebuild cannot alter a
// runtime-injected artifact, so the full gate
// spends its destroy/recreate time (~38s in the
// measured omarchy PR-eval lane) proving nothing.
// skip — no update phase and no post-update pass (the
// declarative twin of --no-rebuild). --no-rebuild
// / --anchor still force skip structurally.
//
// In-place (local/external) beds have no restartable venue: restart-only
// leaves the gate step unchanged there (the in-place re-apply), same as full.
UpdateGate string `yaml:"update_gate,omitempty" json:"update_gate,omitempty"`
Disposable *bool `yaml:"disposable,omitempty" json:"disposable,omitempty"`
Lifecycle string `yaml:"lifecycle,omitempty" json:"lifecycle,omitempty"`
Ephemeral *EphemeralLifetime `yaml:"ephemeral,omitempty" json:"ephemeral,omitempty"`
Preemptible *PreemptibleConfig `yaml:"preemptible,omitempty" json:"preemptible,omitempty"`
RequiresExclusive []string `yaml:"requires_exclusive,omitempty" json:"requires_exclusive,omitempty"`
// nested/peer map keys carry no dots (validateDeploymentName). Loader-built
// runtime tree maps (Children = nested-inside venue; Members = brought-up
// alongside on the shared network) — gengotypes can't express a pattern-keyed
// self-referential map, so the Go type is pinned explicitly.
Children map[string]*Deploy `yaml:"nested,omitempty" json:"nested,omitempty"`
Members map[string]*Deploy `yaml:"peer,omitempty" json:"peer,omitempty"`
}
func (*Deploy) HasChildren ¶
HasChildren reports whether this node has any nested deployments.
func (Deploy) IsDisposable ¶
IsDisposable reports whether the deploy is safe to destroy + rebuild.
func (Deploy) IsEphemeral ¶
IsEphemeral reports whether this deploy is marked ephemeral.
func (Deploy) IsGroup ¶
IsGroup reports whether this is a GROUP fleet — no workload cross-ref but with sibling members (the cross-deployment subject+driver shape).
func (Deploy) IsPreemptible ¶
IsPreemptible reports whether this deploy holds a preemptible exclusive resource (graceful-stop side).
func (Deploy) LifecycleTag ¶
LifecycleTag returns the deploy's lifecycle tag.
func (Deploy) PreemptionHolds ¶
PreemptionHolds returns the exclusive-resource token(s) this deploy occupies.
func (Deploy) RequiredExclusive ¶
RequiredExclusive returns the exclusive-resource token(s) this deploy claims.
func (Deploy) RequiredShared ¶
RequiredShared returns the shared-mode resource token(s) this deploy claims.
type DeployCompileReply ¶
type DeployCompileReply struct {
PlansJSON RawBody `yaml:"plans,omitempty" json:"plans"`
Base string `yaml:"base,omitempty" json:"base,omitempty"`
CandySet []string `yaml:"candy_set,omitempty" json:"candy_set,omitempty"`
}
#DeployCompileReply is the OpCompile reply: the compiled plans as marshalled []spec.InstallPlanView (a hand-written sdk/spec wire type with no CUE def → opaque RawBody envelope; the host unmarshals into []spec.InstallPlanView and re-materializes []*spec.InstallPlan via deploykit.PlanFromView), plus the base identity (box name) and the resolved candy set (the order, for deployID + overlay-candy propagation).
type DeployCompileRequest ¶
type DeployCompileRequest struct {
Dir string `yaml:"dir,omitempty" json:"dir"`
BoxView ResolvedBoxView `yaml:"box_view,omitempty" json:"box_view,omitempty"`
Order []string `yaml:"order,omitempty" json:"order,omitempty"`
HostContextJSON RawBody `yaml:"host_context,omitempty" json:"host_context"`
Tag string `yaml:"tag,omitempty" json:"tag,omitempty"`
// The add_candy:/--add-candy ref(s) (if any) this compile call's own candy set was widened
// with host-side (scanCandiesForRef's synthetic-augmented scan, for a REMOTE ref) — threaded
// into the plugin's OWN InvokeProvider("build","project") re-fetch (as its extra_candy_refs) so
// the envelope's candy map ALSO carries them (RCA'd K1-alpha regression: the two scans were
// independent, so a remote add-candy resolved host-side never reached the envelope).
ExtraCandyRefs []string `yaml:"extra_candy_refs,omitempty" json:"extra_candy_refs,omitempty"`
// candy_ref selects the CANDY shape above (K4 unit B): the authored ref string (bare local
// name OR a `@github…` remote ref) the plugin resolves via BareRef against its own
// rp.CandyModels/rp.Candies (widened, when remote, by extra_candy_refs carrying the SAME raw
// ref — mirroring compileStandaloneCandySelection's ExtraCandyRefs: []string{ref.Raw}
// widening). Absent for the other shapes.
CandyRef string `yaml:"candy_ref,omitempty" json:"candy_ref,omitempty"`
// vm_entity selects the CANDY shape's synthetic-box kind, TOLERANTLY (mirrors the OLD host
// syntheticVmBox call site exactly — never a hard requirement): the plugin tries vm_entity
// against its own rp.Templates.VM; a HIT resolves it via the kind:vm provider's OpResolve leg
// for a guest-tuned box; a MISS (including vm_entity=="") falls through to a plain host-adhoc
// box via vmshared.DetectHostDistro. A non-vm deploy's node.From (e.g. a `local:` node's
// kind:local template ref) is ALSO threaded into vm_entity upstream (resolveVmEntity returns
// node.From unconditionally, not only for a real vm cross-ref) — so a miss here is the
// COMMON case, not an error condition. Ignored for the other shapes.
VmEntity string `yaml:"vm_entity,omitempty" json:"vm_entity,omitempty"`
// box_ref selects the BOX-REF shape above (K4 unit B box-half): the box's own name (never a
// remote ref — compileRefSelection already rejects a remote image ref before this request is
// built). The plugin's own InvokeProvider("build","project") re-fetch is asked to include_disabled
// so an explicitly-named `enabled: false` box still resolves (mirrors the OLD host
// ResolveBox(cfg, ref.Name, …) call, which never checked IsEnabled at all — enabled-filtering
// is a ResolveAllBox/listing concern, not a by-name-resolve one; zero disabled boxes exist
// repo-wide today, so this is a zero-cost future-proofing widening, not a live behavior
// change). Absent for the other shapes.
BoxRef string `yaml:"box_ref,omitempty" json:"box_ref,omitempty"`
// base_box_ref selects the ADD-CANDY-ON-BOX shape (K4 box-half completion) WHEN set alongside
// candy_ref: the primary pod/kubernetes base image's own name, read from rp.Boxes[base_box_ref] as the
// COMPILE CONTEXT the candy_ref overlay compiles against — replacing the former host-side
// buildkit.ResolveBox(baseImg) + scanCandiesForRef. Absent for every other shape (a standalone
// candy_ref with NO base_box_ref stays the CANDY shape, synthetic-box compiled).
BaseBoxRef string `yaml:"base_box_ref,omitempty" json:"base_box_ref,omitempty"`
}
#DeployCompileRequest is the per-node COMPILE seam (K4-B / K4 unit B): the host asks the command:fleet plugin's OpCompile handler to compile, in one of THREE selection SHAPES (a discriminated set, not three Ops — R3). The plugin fetches the resolved-project envelope itself via InvokeProvider("build","project", OpResolve) peer-dispatch (the former HostBuild("resolved-project") seam is DELETED — it does NOT receive the whole project in the request), loops deploykit.BuildDeployPlan over the resolved order, and returns []InstallPlanView. The host re-materializes []*InstallPlan from the views via deploykit.PlanFromView.
- BOX-VIEW selection (an already-resolved base image, ctx!=nil): box_view + order are POPULATED host-side (the host projects an ALREADY-RESOLVED base image via projectResolvedBox) and the plugin trusts them as sent. Retained for any caller that still resolves the base image host-side; the add_candy-on-pod/kubernetes path itself now uses the ADD-CANDY-ON-BOX shape below.
- ADD-CANDY-ON-BOX selection (compileCandyOnBoxSelection, the add_candy-on-pod/kubernetes shape, K4 box-half completion): candy_ref (the add_candy overlay ref) AND base_box_ref (the primary pod/kubernetes base image name) are BOTH set — the plugin reads rp.Boxes[base_box_ref] (the SAME ResolvedBoxView the primary BOX-REF shape reads, R3) as the COMPILE CONTEXT, resolves the add_candy's OWN topo order from rp.CandyModels (deploykit.ResolveCandyOrder over {BareRef(candy_ref)}, widened by extra_candy_refs for a remote overlay), prunes container-init-for-systemd, and compiles that order against the base image — replacing the former host-side buildkit.ResolveBox(baseImg) + scanCandiesForRef path.
- CANDY selection (compileStandaloneCandySelection, the target:local/vm standalone-candy shape, K4 unit B candy-half): candy_ref is set — the plugin resolves the candy key + topo order itself from its own envelope (deploykit.ResolveCandyOrder over rp.CandyModels, already pure) and builds ITS OWN synthetic box (vmshared.DetectHostDistro/DetectHostGlibc for a host target — already sdk-portable; the kind:vm provider's own OpResolve leg for a vm target, mirroring the kind:local OpResolve reuse in node_resolve.go's lookupLocalTemplate, K4 unit A).
- BOX-REF selection (compileBoxSelection, the primary pod/kubernetes image shape, K4 unit B box-half): box_ref is set — the plugin reads rp.Boxes[box_ref] (the SAME ResolvedBoxView hostBuildResolvedProject already computed to BUILD the envelope in the first place — no re-derivation, R3) directly via deploykit.NewSpecResolvedBox, and resolves the candy topo order itself from img.Candy over rp.CandyModels (deploykit.ResolveCandyOrder, same as the CANDY shape).
Exactly one of {box_view, box_ref, candy_ref-alone} is set, OR the ADD-CANDY-ON-BOX pair (candy_ref + base_box_ref) together. Neither CANDY nor BOX-REF needs LoadUnified or the provider-CONNECT registry (verified live, K4 unit B: candy/plugin-fleet's own ALREADY-EXISTING preresolveBuilderContexts, called unconditionally for every OpCompile, already S2-lazy-connects any externalized builder the resolved order+img trigger via exec.InvokeProvider — an exhaustive repo grep found zero target:local/vm or pod/kubernetes deploy anywhere needing a builder plugin outside the calling project's own candy closure, the one edge S2's Pass-1 project-scan can't cover), so neither needs a new HostBuild kind.
HostContextJSON is the marshalled spec.HostContext — the 4 host-computed WIRE scalars (MachineVenue/Distro/GlibcVersion/BuilderImage) — a hand-written spec type with no CUE def (the gengotypes spike cannot express its json:"-" in-process fields), so it rides as an opaque RawBody envelope (the VmJSON/PodConfigJSON idiom; the plugin unmarshals into spec.HostContext, which sdk/deploykit re-exports as deploykit.HostContext). ONLY the 4 scalars cross: the plugin populates the json:"-" BuilderContext (preresolveBuilderContexts over the reverse channel) + ActiveInit (off the resolved-project envelope's rp.Init) IN-PROCESS after the decode — never on the wire. Tag is the image CalVer pin (for the plan Version field when set). Dir is the project dir the plugin threads into its InvokeProvider("build","project") call (empty → plugin cwd).
type DeployContext ¶ added in v0.2026218.138
type DeployContext struct {
// Node is the dispatch-merged FleetNode. nil for a ref-based
// deploy with no charly.yml entry (e.g. `charly fleet add host ./x.yml`).
Node *FleetNode
// Name is the deploy key (the bed key / charly.yml map key, e.g.
// "check-k3s-vm"). Distinct from the kind:vm entity name (node.From).
Name string
// Dir is the project directory.
Dir string
// Cfg / DistroCfg / BuilderCfg are the configs loaded once by the
// resolve-target-add host seam. Reused by each Add so the construction
// matches what the plugin compiled plans against.
Cfg *Config
DistroCfg *DistroConfig
BuilderCfg *BuilderConfig
}
DeployContext carries everything an Add needs from the generic dispatchNode pre-stage: the dispatch-merged FleetNode (the project+operator field-level merged deploy tree — the SINGLE source of truth for node fields like Nested/Env/ephemeral/disposable, NEVER re-read inside an Add), the deploy name + project dir, the loaded image/distro/builder configs, and the resolved primary base ref. One value threaded into every target.Add so each adapter constructs its live embedded target without re-resolving config that dispatchNode already loaded.
type DeployDelResolveReply ¶
type DeployDelResolveRequest ¶
type DeployDelResolveRequest struct {
Name string `yaml:"name,omitempty" json:"name"`
// tree_json is the merged project+operator deploy tree the command:fleet plugin already
// resolved PLUGIN-SIDE (resolveTreeViaLoader, which also connects the deployment's plugins) —
// threaded as DATA so the host resolveDelNode consumes it instead of re-loading the tree
// host-side (#55 Cone A Unit 3a). Marshalled map[string]spec.Deploy; an absent/empty tree
// falls through to resolveDelNode's non-tree fallbacks (vm-prefix / pod-artifact probe), exactly
// as a nil host-tree-read result did before.
TreeJSON RawBody `yaml:"tree_json,omitempty" json:"tree_json,omitempty"`
}
#DeployDelResolveRequest/#DeployDelResolveReply — resolve a `charly fleet del` target's FleetNode (resolveDelNode: literal "host" / "vm:"-prefix legacy forms / a charly.yml tree entry / a ref-based pod-artifact probe) — needs LoadUnified + the on-disk artifact probe, so it stays host-side; the plugin's `charly fleet del` calls this FIRST.
type DeployExecutor ¶
type DeployExecutor interface {
// Venue returns a stable identifier for where this executor's
// commands physically run. Examples:
//
// "local" — ShellExecutor.
// "ssh://arch@127.0.0.1:2224" — SSHExecutor.
// "nested:podman exec stack/local" — NestedExecutor over local.
// "nested:ssh vm/local" — NestedExecutor over SSH.
//
// The string is used as a map key for per-venue ledgers, so it
// must be stable across invocations for the same logical target.
// Not a URL — don't parse it; just compare.
Venue() string
// RunSystem executes a shell script with root privileges. On the
// host, this is `sudo bash -s <<<script`; on the VM target, it's
// `ssh <user>@<host> sudo bash -s <<<script`. The script body runs
// with set -e semantics at the caller's discretion.
//
// The interpreter is NOT guaranteed to be bash. Those two implementations
// pin bash, but NestedExecutor selects the far-side interpreter at run time
// inside the target and degrades to `sh` on a base that ships no bash (see
// exec.nestedShellProbe). A script body handed to this interface must
// therefore be POSIX sh — a bashism runs on the host and silently fails
// inside a busybox container or guest.
RunSystem(ctx context.Context, script string, opts EmitOpts) error
// RunUser executes a shell script as the invoking user (no sudo).
// On the host, it's `bash -s <<<script`; on VM, `ssh <user>@<host>
// bash -s <<<script` where <user> is the unprivileged guest user.
// Same interpreter caveat as RunSystem — write POSIX sh.
RunUser(ctx context.Context, script string, opts EmitOpts) error
// RunBuilder invokes the multi-stage builder image (podman run
// <builder>) to compile pixi/npm/cargo/aur artifacts. On the host
// this calls the existing BuilderRun helper. On VM deploys, the
// builder runs *on the host* and artifacts are scp'd into the
// guest via PutFile — podman inside the guest is not required.
RunBuilder(ctx context.Context, opts BuilderRunOpts) ([]byte, error)
// PutFile places a file at a remote path. ownerRoot == true means
// the file is chown'd to root:root and chmod'd according to mode.
// On the host, this is a plain os.WriteFile (plus sudo chown when
// ownerRoot). On VM, this is scp into a tmp location followed by
// `sudo install -m <mode> -o root -g root` on the guest.
PutFile(ctx context.Context, localPath, remotePath string, mode uint32, ownerRoot bool, opts EmitOpts) error
// GetFile retrieves the contents of a file on the venue. asRoot==true
// runs the read via sudo to handle paths the deploying user cannot
// access (e.g. /etc/rancher/k3s/k3s.yaml on a k3s server). On the
// host, this is os.ReadFile (or `sudo cat` when asRoot). On VM, this
// is `ssh <host> sudo cat <path>` with stdout captured. On nested
// executors, delegates through the jump via the parent's own RunSystem
// semantics. Used by layer_artifacts.go to publish files back to the
// operator after deploy completion.
GetFile(ctx context.Context, remotePath string, asRoot bool, opts EmitOpts) ([]byte, error)
// RunCapture executes a single shell command (or short POSIX-sh script) on
// the venue and returns stdout/stderr/exit/err separately. Used by the
// declarative test runner (testrun.go) to probe target state without
// the streamed-output ergonomics of RunSystem/RunUser. No root
// escalation — callers add `sudo` explicitly when needed; mirrors the
// previous test-time Executor.Exec semantics. After the executor-
// hierarchy cutover (2026-04), this is the single capture-output
// method used by every probe across `charly check live`, `charly check box`, and
// `charly check` scoring.
RunCapture(ctx context.Context, script string) (stdout, stderr string, exit int, err error)
// RunInteractive runs a command on the venue wired to the operator's LIVE TTY:
// stdin/stdout/stderr are INHERITED from the host charly process (the reverse-channel
// server runs IN that process, so os.Std* IS the operator's terminal). The child
// (`podman exec -it` / `ssh -t`) owns the PTY, resize (SIGWINCH), and Ctrl-C. Blocks
// until the session ends; returns its exit code. NOT ctx-deadlined — the TTY owns its
// lifetime (the hostBuildCli doctrine). Stdio NEVER crosses the plugin wire — only the
// script + the exit code (F12; the live-stdio sibling of RunCapture). Consumers: `charly
// shell` (-it) and `charly cmd` (-i, stdin piped). A venue that cannot host an interactive
// session returns ErrNotSupported.
RunInteractive(ctx context.Context, script string) (exit int, err error)
// RunStream runs a command on the venue streaming stdout/stderr LIVE to the operator
// (inherited os.Stdout/os.Stderr; NO stdin). Blocks until exit. Same host-holds-the-
// terminal semantics as RunInteractive (F12). Consumer: `charly logs --follow`. Returns
// ErrNotSupported on a venue that cannot stream.
RunStream(ctx context.Context, script string) (exit int, err error)
// Kind returns a coarse classification of the venue used by the test
// runner for reporting and skip decisions. Values:
// "host" — ShellExecutor (operator's machine)
// "container" — NestedExecutor with JumpPodmanExec / JumpDockerExec
// "vm" — SSHExecutor or NestedExecutor with JumpSSH/JumpVirshConsole
// Replaces the test-time Executor.Kind() method deleted in the
// 2026-04 executor-hierarchy cutover.
Kind() string
// ResolveHome returns the absolute path of $HOME for the named user
// on the venue. Empty user means "the executor's default user" (the
// invoking operator for ShellExecutor; the SSH login user for
// SSHExecutor). Implementations consult `getent passwd` so they
// don't depend on $HOME being set in the calling environment — that
// matters for SSH executors where the operator's $HOME has nothing
// to do with the remote user's home, and for ShellExecutor when the
// caller wants a different user's home (e.g. running as root but
// resolving an unprivileged user's home).
//
// Bundled as part of the 2026-05 shell:-schema cutover. Replaces the
// `the local deploy target.HostHome = os.Getenv("HOME")` static-field
// initialization that mis-targeted SSH deploys: the operator's
// $HOME is not the remote user's home, so every shell-rc edit
// (env.d sourcing block included) was landing in the wrong place
// for `host: user@machine` deploys.
ResolveHome(ctx context.Context, user string) (string, error)
}
DeployExecutor is the in-process host-side executor: the concrete venue an InstallPlan's steps are applied against (ShellExecutor on the operator's machine, SSHExecutor over ssh, NestedExecutor through a jump). The three implementations live in sdk/kit; the InstallPlan IR (EmitOpts.ParentExec) carries one through the nested-deploy tree. DISTINCT from kit.DeployExecutor (the reverse-channel executor a plugin serves over gRPC) — same role, different wire shape (typed EmitOpts + file paths here; JSON-marshalled opts + byte content there).
func ReconstructParentExec ¶ added in v0.2026218.138
func ReconstructParentExec(ancestorPaths []string, ancestorNodes []FleetNode, derive func(string, *FleetNode, DeployExecutor) (DeployExecutor, error)) (DeployExecutor, error)
ReconstructParentExec re-derives the ancestor executor chain from ROOT-FIRST ancestor path/node lists (spec/fleet.ResolveNodePath's contract, EXCLUDING the target itself), applying derive to each ancestor. derive is the registry-coupled per-ancestor hop (core's deriveChildExecutorForPath) — the loop itself is pure, so it lives here once (K-wave 2 cone R2 bank D thin: the resolve-target-add seam's former reconstructParentExec).
type DeployExpose ¶
type DeployNodeDelDispatchReply ¶
type DeployNodeDelDispatchReply struct {
}
type DeployNodeDelDispatchRequest ¶
type DeployNodeDelDispatchRequest struct {
Name string `yaml:"name,omitempty" json:"name"`
Node *Deploy `yaml:"node,omitempty" json:"node,omitempty"`
AssumeYes bool `yaml:"assume_yes,omitempty" json:"assume_yes,omitempty"`
KeepRepoChanges bool `yaml:"keep_repo_changes,omitempty" json:"keep_repo_changes,omitempty"`
KeepServices bool `yaml:"keep_services,omitempty" json:"keep_services,omitempty"`
KeepImage bool `yaml:"keep_image,omitempty" json:"keep_image,omitempty"`
DryRun bool `yaml:"dry_run,omitempty" json:"dry_run,omitempty"`
}
#DeployNodeDelDispatchRequest/#DeployNodeDelDispatchReply — the `charly fleet del` terminal step: ResolveTarget + target.Del, honoring the teardown gates (the live ReverseRunner is still never carried on the wire — a programmatic teardown needing a specific runner is resolved host-side during dispatch).
type DeployOverlayReply ¶
type DeployOverlayReply struct {
ConfigJSON RawBody `yaml:"config_json,omitempty" json:"config_json,omitempty"`
}
#DeployOverlayReply carries the marshaled per-host FleetConfig. config_json is the JSON encoding of *deploykit.FleetConfig (nil-safe: absent/null when no per-host overlay file exists yet, matching LoadDeployConfigForRead's own nil-FleetConfig contract).
type DeployOverlayRequest ¶
type DeployOverlayRequest struct {
Context string `yaml:"context,omitempty" json:"context"`
}
#DeployOverlayRequest asks the host for the PER-HOST deploy-config overlay (K4: deploykit.LoadDeployConfigForRead — the runtime ledger at ~/.config/charly/charly.yml, NOT the project charly.yml the resolved-project envelope projects; Mode Purity keeps the two apart, same distinction #PodDisposableRequest documents). Unlike #PodDisposableRequest (a single overlay BIT), several pod-lifecycle resolvers need CROSS-DEPLOYMENT visibility (deploykit.FleetConfig's GlobalEnvForImage/OccupiedHostPorts/DeployedContainerNames all read OTHER deploys' entries, not just the caller's own), so a single-field extraction can't serve them — the host returns the WHOLE marshaled *deploykit.FleetConfig and the plugin calls the SAME already-portable deploykit methods locally. Re-fetched on EVERY call (no caching): the ledger can change between invocations (an intervening `charly config`), and PrepareVenue-time data is stale by the time OpStart/OpStop/OpShell run much later — this is NOT threaded through the one-shot LifecyclePrepareInput. Class-generic action noun "deploy-overlay" (F11 — never a substrate word).
type DeployPluginsConnectReply ¶
type DeployPluginsConnectReply struct {
Dir string `yaml:"dir,omitempty" json:"dir"`
}
type DeployPluginsConnectRequest ¶
type DeployPluginsConnectRequest struct {
Path string `yaml:"path,omitempty" json:"path"`
AddCandy []string `yaml:"add_candy,omitempty" json:"add_candy,omitempty"`
}
#DeployPluginsConnectRequest/#DeployPluginsConnectReply — the K1-LOADER RELOCATION witness (Unit D). candy/plugin-fleet now DRIVES loaderkit.LoadUnified ITSELF, plugin-side, over the reverse-channel LoaderExecutor (execLoaderExecutor → the "loader-*" host legs), to resolve the `charly fleet add` deploy tree — the host no longer runs a host-side merged-tree read for the walk. This seam is the ONE host-only PREAMBLE the plugin still needs: connect the deployment's out-of-tree plugin candies (loadDeployPlugins — registry-coupled, a core Mechanism) BEFORE ResolveTarget can route to an external substrate, and return the resolved project dir (host os.Getwd — the SAME dir the host loader uses) the plugin passes to loaderkit.LoadUnified. The plugin reads root-venue-ssh itself from the tree's stamped node.Descent (loaderkit.LoadUnified stamps it), so no host trait call — proving plugin-fleet → loaderkit.LoadUnified end-to-end.
type DeployProbes ¶
type DeployProbes struct {
Liveness *Op `yaml:"liveness,omitempty" json:"liveness,omitempty"`
Readiness *Op `yaml:"readiness,omitempty" json:"readiness,omitempty"`
Startup *Op `yaml:"startup,omitempty" json:"startup,omitempty"`
}
DeployProbes (deploy.go) — each probe is an inline Op (the check verb vocab).
type DeployRecord ¶ added in v0.2026215.1748
type DeployRecord struct {
// schema_version is the ledger-format version (the ledger-candy-keys cutover's
// CalVer). Empty means a pre-cutover record (json "layer" keys) — the read path
// rejects it with a `charly migrate` hint.
SchemaVersion string `yaml:"schema_version,omitempty" json:"schema_version,omitempty"`
DeployID string `yaml:"deploy_id,omitempty" json:"deploy_id"`
Image string `yaml:"image,omitempty" json:"image"`
Tag string `yaml:"tag,omitempty" json:"tag,omitempty"`
Target string `yaml:"target,omitempty" json:"target"`
Candy []string `yaml:"candy,omitempty" json:"candy,omitempty"`
AddCandy []string `yaml:"add_candy,omitempty" json:"add_candy,omitempty"`
DeployedAt string `yaml:"deployed_at,omitempty" json:"deployed_at"`
}
#DeployRecord is the top-level entry in deploys/<deploy-id>.json. Lists the image, tag, and the ordered candy set included in this deploy (image candies + add_candy overlays, already topo-sorted).
type DeployReply ¶
type DeployReply struct {
ReverseOps []ReverseOp `yaml:"reverse_ops,omitempty" json:"reverse_ops,omitempty"`
Record DeployReplyRecord `yaml:"record,omitempty" json:"record"`
}
#DeployReply is the structured result an external deploy provider returns from an OpExecute Invoke: the teardown ops the host records into the ledger, plus a provenance record.
type DeployReplyRecord ¶
type DeployReplyRecord struct {
Candy string `yaml:"candy,omitempty" json:"candy"`
Version string `yaml:"version,omitempty" json:"version,omitempty"`
}
#DeployReplyRecord names the ledger CandyRecord the host writes for an external deploy: the logical candy whose ReverseOps drive teardown, plus its version.
type DeployResolveTargetAddReply ¶
type DeployResolveTargetAddReply struct {
}
type DeployResolveTargetAddRequest ¶
type DeployResolveTargetAddRequest struct {
Path string `yaml:"path,omitempty" json:"path"`
DeployName string `yaml:"deploy_name,omitempty" json:"deploy_name"`
Node *Deploy `yaml:"node,omitempty" json:"node,omitempty"`
Target string `yaml:"target,omitempty" json:"target"`
Dir string `yaml:"dir,omitempty" json:"dir"`
// plans_json is the marshalled []spec.InstallPlanView (deployID + AddCandies already stamped
// plugin-side); the host re-materializes []*spec.InstallPlan via deploykit.PlanFromView.
PlansJSON RawBody `yaml:"plans_json,omitempty" json:"plans_json"`
AncestorPaths []string `yaml:"ancestor_paths,omitempty" json:"ancestor_paths,omitempty"`
AncestorNodes []Deploy `yaml:"ancestor_nodes,omitempty" json:"ancestor_nodes,omitempty"`
NodeOnly bool `yaml:"node_only,omitempty" json:"node_only,omitempty"`
Pull bool `yaml:"pull,omitempty" json:"pull,omitempty"`
Verify bool `yaml:"verify,omitempty" json:"verify,omitempty"`
WithServices bool `yaml:"with_services,omitempty" json:"with_services,omitempty"`
AllowRepoChanges bool `yaml:"allow_repo_changes,omitempty" json:"allow_repo_changes,omitempty"`
AllowRootTasks bool `yaml:"allow_root_tasks,omitempty" json:"allow_root_tasks,omitempty"`
SkipIncompatible bool `yaml:"skip_incompatible,omitempty" json:"skip_incompatible,omitempty"`
AssumeYes bool `yaml:"assume_yes,omitempty" json:"assume_yes,omitempty"`
BuilderImage string `yaml:"builder_image,omitempty" json:"builder_image,omitempty"`
// dev_local_pkg marks a DISPOSABLE CHECK BED's deploy, the deploy-side twin of `charly box
// build --dev-local-pkg`. On a bed, a localpkg candy whose package source cannot be found is
// a HARD FAILURE rather than the benign skip a normal deploy takes — a bed exists to prove
// the in-development package builds and installs, so silently installing nothing (or an
// older release) makes the bed assert something it never tested.
DevLocalPkg bool `yaml:"dev_local_pkg,omitempty" json:"dev_local_pkg,omitempty"`
}
#DeployResolveTargetAddRequest/#DeployResolveTargetAddReply — the K4-C SHAPE-2 per-node terminal step: ResolveTarget + DeployContext + target.Add for ONE tree position, reached once per node from the plugin's own walk. UNLIKE the retired #DeployNodeDispatchRequest (which had the host RE-COMPILE the plans via an in-proc OpCompile round-trip — the plugin→host→plugin double-bounce), the plugin now COMPILES the InstallPlans IN-PROC (walk.go dispatchOne → compileNodePlans → compilePlansForRequest, no OpCompile hop) and ships the ALREADY-COMPILED plans (with deployID + AddCandies stamped plugin-side, round-tripped through InstallPlanView) as plans_json. The host half does ONLY the genuine floor-M residue a plugin cannot: reconstruct the ancestor executor chain (deriveChildExecutorForPath — registry-coupled), loadConfigForDeploy (LoadUnified), ResolveTarget + DeployContext + utgt.Add.
ancestor_paths/ancestor_nodes let the host reconstruct the SAME parentExec chain the OLD in-core walk built (deriveChildExecutorForPath is pure Go over spec/kit types, re-run HOST-side) — a live DeployExecutor never crosses the wire. target is the plugin-classified substrate word (a pure spec/fleet.ClassifyNodeTarget of node+path), carried so the host synthesizes a Target-only node when node is nil (a ref-based deploy with no charly.yml entry). The gate flags are the FINAL resolved EmitOpts values (node.InstallOpts already applied over the CLI flags plugin-side); dry_run never reaches this seam — a dry-run prints the compiled plans plugin-side and returns without dispatch.
type DeployResources ¶
type DeploySecret ¶
type DeployStorage ¶
type DeployStorage struct {
Name string `yaml:"name,omitempty" json:"name"`
Size string `yaml:"size,omitempty" json:"size,omitempty"`
ClassHint string `yaml:"class_hint,omitempty" json:"class_hint,omitempty"`
Access string `yaml:"access,omitempty" json:"access,omitempty"`
Path string `yaml:"path,omitempty" json:"path,omitempty"`
}
type DeployTargetDelOpts ¶
type DeployTargetDelOpts struct {
DryRun bool `yaml:"dry_run,omitempty" json:"dry_run,omitempty"`
AssumeYes bool `yaml:"assume_yes,omitempty" json:"assume_yes,omitempty"`
KeepLedger bool `yaml:"keep_ledger,omitempty" json:"keep_ledger,omitempty"`
RemoveVolumes bool `yaml:"remove_volumes,omitempty" json:"remove_volumes,omitempty"`
KeepRepoChanges bool `yaml:"keep_repo_changes,omitempty" json:"keep_repo_changes,omitempty"`
KeepServices bool `yaml:"keep_services,omitempty" json:"keep_services,omitempty"`
KeepImage bool `yaml:"keep_image,omitempty" json:"keep_image,omitempty"`
}
#DeployTargetDelOpts is the `charly fleet del` opts type — formerly the charly-core DelOpts, now CUE-sourced (the UnifiedDeployTarget contract lives in spec/spec/deploy_target_unified.go). The three teardown gates (KeepRepoChanges/KeepServices/KeepImage) were folded into DelOpts proper in S3b, replacing the pre-S3b type-assertion in host_build_deploy_node_del_dispatch.go; the del dispatcher now passes them as plain fields.
type DeployTargetDispatchReply ¶
type DeployTargetDispatchReply struct {
Status DeployTargetStatus `yaml:"status,omitempty" json:"status,omitempty"`
Output string `yaml:"output,omitempty" json:"output,omitempty"`
ExitCode int64 `yaml:"exit_code,omitempty" json:"exit_code,omitempty"`
VenueJSON RawBody `yaml:"venue_json,omitempty" json:"venue_json,omitempty"`
ArtifactKey string `yaml:"artifact_key,omitempty" json:"artifact_key,omitempty"`
}
#DeployTargetDispatchReply carries whatever the dispatched op produces — Status for "status", Output/ExitCode for "shell"/"attach" (the F12 non-zero-exit propagation via *sdk.ExitCodeError), Venue for "add"/"update" (the spec.VenueDescriptor PrepareVenue produced or re-confirmed, threaded back to core so a substrate WITH a lifecycle hook — pod's overlay container, vm's guest — hands core the SAME live executor for --verify and every subsequent dispatch on this target, mirroring the pre-S3b `t.exec = exec` reassignment inside apply()), nothing for the rest (success is "no error").
type DeployTargetDispatchRequest ¶
type DeployTargetDispatchRequest struct {
Op string `yaml:"op,omitempty" json:"op"`
Name string `yaml:"name,omitempty" json:"name"`
Word string `yaml:"word,omitempty" json:"word"`
HasLifecycle bool `yaml:"has_lifecycle,omitempty" json:"has_lifecycle,omitempty"`
HasPreresolve bool `yaml:"has_preresolve,omitempty" json:"has_preresolve,omitempty"`
HasPlan bool `yaml:"has_plan,omitempty" json:"has_plan,omitempty"`
Node *Deploy `yaml:"node,omitempty" json:"node,omitempty"`
Dir string `yaml:"dir,omitempty" json:"dir,omitempty"`
NodeOnly bool `yaml:"node_only,omitempty" json:"node_only,omitempty"`
// ledger_root OPTIONALLY overrides the ledger root directory (kit.LedgerPaths.Root) — the
// pre-S3b externalDeployTarget carried a settable `paths *kit.LedgerPaths` field a TEST could
// redirect to a temp dir instead of the operator's real ~/.config/opencharly/installed/; this
// is the wire-safe equivalent (a bare root string, the plugin derives Deploys/Candies/LockFile
// from it exactly like kit.DefaultLedgerPaths does). Empty (the default) — kit.DefaultLedgerPaths().
LedgerRoot string `yaml:"ledger_root,omitempty" json:"ledger_root,omitempty"`
PlansJSON RawBody `yaml:"plans_json,omitempty" json:"plans_json,omitempty"`
OptsJSON RawBody `yaml:"opts_json,omitempty" json:"opts_json,omitempty"`
ChecksJSON RawBody `yaml:"checks_json,omitempty" json:"checks_json,omitempty"`
Cmd []string `yaml:"cmd,omitempty" json:"cmd,omitempty"`
TTY bool `yaml:"tty,omitempty" json:"tty,omitempty"`
// venue_json is the ALREADY-MATERIALIZED spec.VenueDescriptor for this deploy. Two distinct
// producers set it: (a) core, when this dispatch is a NESTED non-lifecycle child (a
// `local:`/`android:`/`kubernetes:` deploy under a vm/pod, tree position) — Add flattens the
// live ancestor executor (EmitOpts.ParentExec) into this field via kit.DescriptorFromExecutor
// BEFORE the very first "add" dispatch, since that live value cannot itself cross the wire
// (FIX ROUND, S3b follow-up — its absence on "add" was the R10 bed regression: every nested
// child silently applied on the operator's host instead of the parent venue); (b) core again,
// carrying FORWARD a prior dispatch's reported venue for the SAME target's lifetime
// (Update/Del/Start/Stop/Status/Logs/Shell/Attach/Rebuild, after a lifecycle substrate's "add"
// already ran PrepareVenue once). Either way the plugin re-materializes it via
// kit.VenueFromDescriptor instead of re-deriving from the node or re-running PrepareVenue.
// Absent on "add" for a ROOT (non-nested) OR lifecycle (vm/pod) target — those derive their
// own venue fresh (root: RootExecutorForDeployNode(node); lifecycle: PrepareVenue).
VenueJSON RawBody `yaml:"venue_json,omitempty" json:"venue_json,omitempty"`
// distro_cfg_json is the marshalled *buildkit.DistroConfig ("add"/"update" only) — recordDeploy's
// FillReverseUninstallCmds needs it to render an aur-builder ReverseOpPackageRemove's
// UninstallCmd host-side-equivalent, now plugin-side since buildkit.DistroConfig is a plain sdk
// type with no core-only coupling (unlike the core-only buildEngineContext wrapper it used to
// travel inside).
DistroCfgJSON RawBody `yaml:"distro_cfg_json,omitempty" json:"distro_cfg_json,omitempty"`
// host_env_json is the marshalled spec.HostEnv (CharlyBin/Home/Version) — the R10 bed-found
// fifth bug in this cluster's move: every lifecycle Op the deleted substrate_lifecycle_grpc.go
// sent used its OWN hostEnvJSON() helper, computed HOST-side (os.Executable() resolves to the
// charly binary only when called IN CORE — a plugin's os.Executable() resolves to the PLUGIN
// binary, wrong for an out-of-process placement even though today's compiled-in placement
// happened not to crash on it, since a bare `spec.HostEnv{}` zero-value was marshalled instead
// of ever actually calling it, plugin-side, in the S3b port). Core (unified_targets.go's
// dispatch, the ONLY place that reliably knows its OWN binary regardless of the substrate's or
// command:fleet's own placement) now computes it ONCE per dispatch call and threads it here;
// candy/plugin-fleet forwards it verbatim to every lifecycle Op instead of computing its own.
HostEnvJSON RawBody `yaml:"host_env_json,omitempty" json:"host_env_json,omitempty"`
}
#DeployTargetDispatchRequest (S3b) is the ONE generic host→command:fleet envelope every UnifiedDeployTarget/LifecycleTarget method dispatches through, discriminated by `op` (the project rulebook's "generic over ad-hoc" — one wire shape, not eleven). Core's thin ResolveTarget proxy (unified_targets.go) constructs this per call from data alone — it never holds a *grpcProvider or any core-private registry object, so the type is free to live entirely on the wire. `word` is the resolved deploy-substrate provider word (e.g. "pod"/"vm"/ "local"/"kubernetes"/"android") the plugin dispatches the ACTUAL substrate leg to, via its own sdk.Executor.InvokeProvider — core never talks to the substrate provider directly once this lands. `has_lifecycle` is the ONE piece of substrate metadata core must resolve itself (the registered-provider's own `lifecycle` flag lives on the core-private *grpcProvider) — it gates whether Start/Stop/Status/Logs/Shell/Attach/Rebuild are even valid for this substrate (mirroring the former ErrNotSupportedOnExternal branches) AND whether the Q1 arbiter bracket applies to Start/Stop — `has_plan` is that DIFFERENT, narrower boolean (K4-exit, FLOOR-SLIM- proper Unit-8): core still computes it (lifecycleStartPlanHooks[word]/lifecycleStopPlanHooks[word] presence, pod_lifecycle_dispatch.go, unmoved) and now THREADS it on the wire instead of bracketing the dispatch call itself — command:fleet's handleLifecycleSimple owns the bracket call, by InvokeProvider("verb","arbiter") peer dispatch (the "arbiter-bracket-*" HostBuild seams are DELETED, K-wave 2 cone R2 bank E; the CHARLY_PREEMPT_LEASE os.Setenv/os.Getenv nested-subprocess-inheritance property lives in candy/plugin-preempt's invokeArbiter + the plugin-fleet bracket, both compiled-in sharing the host process). `node` is the dispatch-merged FleetNode (nil for a ref-based deploy with no charly.yml entry) — required when has_plan is true (the claim fields live on it). `plans_json` carries []InstallPlanView for Add/Update. `opts_json` carries the op-specific opts struct as an opaque envelope (the zero-value-safe pattern every #Pod*Opts request already uses) — kept opaque rather than one field per opts type so a NEW deploy verb never needs a NEW CUE field, only a new decode branch plugin-side.
type DeployTargetLogsOpts ¶
type DeployTargetLogsOpts struct {
Follow bool `yaml:"follow,omitempty" json:"follow,omitempty"`
Tail int64 `yaml:"tail,omitempty" json:"tail,omitempty"`
Sidecar string `yaml:"sidecar,omitempty" json:"sidecar,omitempty"`
}
#DeployTargetLogsOpts is the `charly logs` opts type — formerly the charly-core LogsOpts, now CUE-sourced (the UnifiedDeployTarget contract lives in spec/spec/deploy_target_unified.go).
type DeployTargetRebuildOpts ¶
type DeployTargetRebuildOpts struct {
RebuildImage bool `yaml:"rebuild_image,omitempty" json:"rebuild_image,omitempty"`
AssumeYes bool `yaml:"assume_yes,omitempty" json:"assume_yes,omitempty"`
DryRun bool `yaml:"dry_run,omitempty" json:"dry_run,omitempty"`
Tag string `yaml:"tag,omitempty" json:"tag,omitempty"`
}
#DeployTargetRebuildOpts is the `charly update` rebuild-path opts type — formerly the charly-core RebuildOpts, now CUE-sourced (the UnifiedDeployTarget contract lives in spec/spec/deploy_target_unified.go). `tag` pins the image the rebuild re-deploys to (the `charly update --tag` flag); empty = newest local CalVer resolved via the ai.opencharly.version OCI label.
type DeployTargetStatus ¶
type DeployTargetStatus struct {
State string `yaml:"state,omitempty" json:"state,omitempty"`
Healthy bool `yaml:"healthy,omitempty" json:"healthy,omitempty"`
Details map[string]string `yaml:"details,omitempty" json:"details,omitempty"`
}
#DeployTargetStatus is the live-runtime state for the "status" deploy op — formerly the charly-core StatusInfo, now CUE-sourced because it crosses the plugin boundary; the UnifiedDeployTarget contract (spec/spec/deploy_target_unified.go) uses this type directly.
type DeployTraits ¶
type DeployTraits struct {
// venue: how commands physically execute in this substrate's venue:
//
// container — podman/docker exec into the container by name (pod).
// ssh — an ssh hop into the guest (vm).
// shell — the substrate's own root executor runs on the host (local; kubernetes host-side).
// parent — reached via the parent's venue, no own executor (android).
// none — external-in-place (zero value / group).
Venue string `yaml:"venue,omitempty" json:"venue,omitempty"`
// image_backed: the substrate runs a baked OCI image (pod).
ImageBacked bool `yaml:"image_backed,omitempty" json:"image_backed,omitempty"`
// image_context: the substrate composes over an image build context (pod overlay, kubernetes manifests).
ImageContext bool `yaml:"image_context,omitempty" json:"image_context,omitempty"`
// machine_venue: the substrate is a full machine with a system init (host/vm/local) — its
// services render as systemd units, not a container init.
MachineVenue bool `yaml:"machine_venue,omitempty" json:"machine_venue,omitempty"`
// exclusive_venue: the substrate holds an exclusive host-resource lease boundary (vm).
ExclusiveVenue bool `yaml:"exclusive_venue,omitempty" json:"exclusive_venue,omitempty"`
// leaf_only: the substrate is a deploy-chain LEAF — it cannot be descended into (kubernetes).
LeafOnly bool `yaml:"leaf_only,omitempty" json:"leaf_only,omitempty"`
// bracketed_lifecycle: the substrate's Start/Stop accept direct-mode CLI opts (env/port/
// volume/bind/no-auto-detect on Start, unmount on Stop) AND need the Q1 resource-arbiter
// claim bracketed around the dispatch (pod). A substrate that manages its own venue
// lifecycle + resource claim (vm, via `charly vm start`/`stop`) leaves this false.
BracketedLifecycle bool `yaml:"bracketed_lifecycle,omitempty" json:"bracketed_lifecycle,omitempty"`
// bed_target: the substrate is a valid `kind:check` bed target — pod/vm/local/android run a
// disposable check bed; kubernetes (leaf_only) does not. validateCheckBeds reads this instead of
// switching on the substrate word (the boundary-law incomplete-seam gate).
BedTarget bool `yaml:"bed_target,omitempty" json:"bed_target,omitempty"`
// supports_ephemeral: the substrate's Add/Del path wires the ephemeral register/teardown seam
// (TTL timer + charly.yml persistence) — vm only today; pod/kubernetes reject `ephemeral: true` until
// their Add/Del wire the same seam. validateEphemeral reads this instead of a substrate switch.
SupportsEphemeral bool `yaml:"supports_ephemeral,omitempty" json:"supports_ephemeral,omitempty"`
// supports_from_snapshot: the substrate has a backing chain `from_snapshot:` restores from — vm
// only (pod/kubernetes have no backing chains). validateEphemeral reads this instead of a word switch.
SupportsFromSnapshot bool `yaml:"supports_from_snapshot,omitempty" json:"supports_from_snapshot,omitempty"`
}
#DeployTraits is a SUBSTRATE kind's DECLARED deploy behaviour (P9): a substrate plugin advertises it per word over Describe (ProvidedCapability.deploy_traits), and kit.StampDescent stamps it onto every node's #DescentDescriptor. It is the SINGLE plugin-declared source for "how does this substrate behave in the deploy chain", so the kernel consults the traits off node.Descent BY TRAIT — never by switching on the substrate kind word (the boundary law). Canonical table: pod=container+image_backed+image_context; vm=ssh+machine_venue+exclusive_venue; local=shell+machine_venue; kubernetes=shell+image_context+leaf_only; android=parent; zero value = external-in-place. Not authored — charly-written state stamped at load.
type DeployTraitsCarrier ¶
type DeployTraitsCarrier interface {
DeclaredDeployTraits() *DeployTraits
}
DeployTraitsCarrier is implemented by a provider (grpcProvider out-of-proc, inprocProvider compiled-in) that carries a SUBSTRATE class:kind capability's DECLARED DeployTraits (P9). Non-nil → deployTraitsFor returns them so kit.StampDescent stamps node.Descent BY TRAIT; nil (or not implemented) → the external-in-place default. This is the SINGLE plugin-declared source for a substrate's deploy behaviour — the consult sites read the stamped traits off node.Descent, never switching on the substrate kind word (the kernel/plugin boundary law).
type DeployVenue ¶
type DeployVenue struct {
DeployName string `yaml:"deploy_name,omitempty" json:"deploy_name"`
Env map[string]string `yaml:"env,omitempty" json:"env,omitempty"`
Substrate RawBody `yaml:"substrate,omitempty" json:"substrate,omitempty"`
}
#DeployVenue is the venue descriptor the host puts in op.Env for an external deploy Invoke: the deploy's name plus the merged deploy-node env (KEY=VALUE lines flattened to a map).
type DeployVolume ¶
type DeployVolume struct {
Name string `yaml:"name,omitempty" json:"name"`
Type string `yaml:"type,omitempty" json:"type,omitempty"`
Host string `yaml:"host,omitempty" json:"host,omitempty"`
Path string `yaml:"path,omitempty" json:"path,omitempty"`
DataSeeded bool `yaml:"data_seeded,omitempty" json:"data_seeded,omitempty"`
DataSource string `yaml:"data_source,omitempty" json:"data_source,omitempty"`
}
type Deploy_RecordWrap ¶ added in v0.2026245.1613
type Deploy_RecordWrap struct {
Terminal bool `yaml:"terminal,omitempty" json:"terminal,omitempty"`
Desktop bool `yaml:"desktop,omitempty" json:"desktop,omitempty"`
Fps int64 `yaml:"fps,omitempty" json:"fps,omitempty"`
Record_name string `yaml:"record_name,omitempty" json:"record_name,omitempty"`
Settle_ms int64 `yaml:"settle_ms,omitempty" json:"settle_ms,omitempty"`
Record_env map[string]string `yaml:"record_env,omitempty" json:"record_env,omitempty"`
Artifact string `yaml:"artifact,omitempty" json:"artifact,omitempty"`
Artifact_min_bytes int64 `yaml:"artifact_min_bytes,omitempty" json:"artifact_min_bytes,omitempty"`
}
RecordWrap is the whole-run recording wrap (deploy record: field).
type DeploymentStatus ¶
type DeploymentStatus struct {
Kind SubstrateKind `yaml:"kind,omitempty" json:"kind"`
Image string `yaml:"image,omitempty" json:"image"`
ImageRef string `yaml:"image_ref,omitempty" json:"image_ref,omitempty"`
Instance string `yaml:"instance,omitempty" json:"instance,omitempty"`
Status string `yaml:"status,omitempty" json:"status"`
Uptime string `yaml:"uptime,omitempty" json:"uptime,omitempty"`
Container string `yaml:"container,omitempty" json:"container"`
Ports []PortMapping `yaml:"ports,omitempty" json:"ports,omitempty"`
Devices []string `yaml:"devices,omitempty" json:"devices,omitempty"`
Tools []ToolStatus `yaml:"tools,omitempty" json:"tools,omitempty"`
Volumes []string `yaml:"volumes,omitempty" json:"volumes,omitempty"`
Network string `yaml:"network,omitempty" json:"network,omitempty"`
Tunnel string `yaml:"tunnel,omitempty" json:"tunnel,omitempty"`
Secrets []string `yaml:"secrets,omitempty" json:"secrets,omitempty"`
RunMode string `yaml:"run_mode,omitempty" json:"run_mode"`
Nested []*DeploymentStatus `yaml:"nested,omitempty" json:"nested,omitempty"`
Source string `yaml:"source,omitempty" json:"source,omitempty"`
}
#DeploymentStatus — the rendered shape for the table + JSON outputs across every deployment substrate (pod / vm / kubernetes / local / android). kind discriminates the substrate; nested carries multi-hop children (RECURSIVE self-reference, populated by the nested overlay); source records provenance (libvirt|ledger|adb|tree|podman).
type DescentDescriptor ¶
type DescentDescriptor struct {
// transport: how the deploy chain descends INTO this node's substrate (DERIVED from the
// declared venue + leaf_only by kit.DescentFromTraits):
//
// none — shares the parent venue; no hop (local, android).
// container-exec — enter the container by name (pod; podman/docker per engine).
// ssh — an ssh hop into the guest (vm).
// reject — unreachable via the deploy chain (kubernetes → use kubectl).
Transport string `yaml:"transport,omitempty" json:"transport"`
// host_rooted: the substrate's own ROOT executor runs directly on the host
// (local), so the check runner uses rootExecutorForDeployNode, not a container chain.
HostRooted bool `yaml:"host_rooted,omitempty" json:"host_rooted,omitempty"`
// venue: how commands physically execute in this substrate's venue:
//
// container — podman/docker exec into the container by name (pod).
// ssh — an ssh hop into the guest (vm).
// shell — the substrate's own root executor runs on the host (local; kubernetes host-side).
// parent — reached via the parent's venue, no own executor (android).
// none — external-in-place (zero value / group).
Venue string `yaml:"venue,omitempty" json:"venue,omitempty"`
// image_backed: the substrate runs a baked OCI image (pod).
ImageBacked bool `yaml:"image_backed,omitempty" json:"image_backed,omitempty"`
// image_context: the substrate composes over an image build context (pod overlay, kubernetes manifests).
ImageContext bool `yaml:"image_context,omitempty" json:"image_context,omitempty"`
// machine_venue: the substrate is a full machine with a system init (host/vm/local) — its
// services render as systemd units, not a container init.
MachineVenue bool `yaml:"machine_venue,omitempty" json:"machine_venue,omitempty"`
// exclusive_venue: the substrate holds an exclusive host-resource lease boundary (vm).
ExclusiveVenue bool `yaml:"exclusive_venue,omitempty" json:"exclusive_venue,omitempty"`
// leaf_only: the substrate is a deploy-chain LEAF — it cannot be descended into (kubernetes).
LeafOnly bool `yaml:"leaf_only,omitempty" json:"leaf_only,omitempty"`
// bracketed_lifecycle: the substrate's Start/Stop accept direct-mode CLI opts (env/port/
// volume/bind/no-auto-detect on Start, unmount on Stop) AND need the Q1 resource-arbiter
// claim bracketed around the dispatch (pod). A substrate that manages its own venue
// lifecycle + resource claim (vm, via `charly vm start`/`stop`) leaves this false.
BracketedLifecycle bool `yaml:"bracketed_lifecycle,omitempty" json:"bracketed_lifecycle,omitempty"`
// bed_target: the substrate is a valid `kind:check` bed target — pod/vm/local/android run a
// disposable check bed; kubernetes (leaf_only) does not. validateCheckBeds reads this instead of
// switching on the substrate word (the boundary-law incomplete-seam gate).
BedTarget bool `yaml:"bed_target,omitempty" json:"bed_target,omitempty"`
// supports_ephemeral: the substrate's Add/Del path wires the ephemeral register/teardown seam
// (TTL timer + charly.yml persistence) — vm only today; pod/kubernetes reject `ephemeral: true` until
// their Add/Del wire the same seam. validateEphemeral reads this instead of a substrate switch.
SupportsEphemeral bool `yaml:"supports_ephemeral,omitempty" json:"supports_ephemeral,omitempty"`
// supports_from_snapshot: the substrate has a backing chain `from_snapshot:` restores from — vm
// only (pod/kubernetes have no backing chains). validateEphemeral reads this instead of a word switch.
SupportsFromSnapshot bool `yaml:"supports_from_snapshot,omitempty" json:"supports_from_snapshot,omitempty"`
}
#DescentDescriptor is the loader-DERIVED venue-hop descriptor (Cutover H + P9). candy/plugin-substrate declares its per-word #DeployTraits over Describe, and kit.StampDescent stamps them here so the deploy chain descends generically by TRANSPORT and every consult site reads the substrate behaviour off the stamped TRAITS — never by switching on the substrate kind word. transport/host_rooted are the DERIVED nesting view (from the traits); the closed transport set is the kernel's nesting-boundary vocabulary.
func DescentFromTraits ¶ added in v0.2026213.1726
func DescentFromTraits(t *DeployTraits) *DescentDescriptor
DescentFromTraits builds a #DescentDescriptor from a substrate's DECLARED #DeployTraits, deriving the closed nesting-transport vocabulary GENERICALLY from the traits — never by switching on a substrate kind word. It copies the declared traits verbatim (the consult sites read them off node.Descent) and computes transport + host_rooted:
leaf_only → reject (kubernetes: a deploy-chain leaf, unreachable via exec) venue == container → container-exec (pod: podman/docker exec by name) venue == ssh → ssh (vm: an ssh hop into the guest) otherwise → none (shell/parent/none share the parent venue)
nil traits (a targetless group / empty target / a word with no declared substrate traits) map to the external-in-place default (container-exec) — behaviour-preserving for the former descentForTarget default arm that covered pod and the empty/group target.
type DesktopCategory ¶ added in v0.2026242.935
type DesktopCategory string
#DesktopCategory is the freedesktop registered MAIN categories, verbatim. Closed on purpose — see the note on `categories`.
type DesktopEntry ¶ added in v0.2026242.935
type DesktopEntry struct {
// entry_name is the file stem and the menu label source.
EntryName string `yaml:"entry_name,omitempty" json:"entry_name"`
// exec and url are mutually exclusive: `url` renders the browser --app=<url> form, which
// is all a "web app" installer does. Enforced by OpValidate rather than CUE so the error
// can say which one to remove.
Exec string `yaml:"exec,omitempty" json:"exec,omitempty"`
URL string `yaml:"url,omitempty" json:"url,omitempty"`
// browser_arg are extra flags for the url form.
BrowserArgs []string `yaml:"browser_arg,omitempty" json:"browser_arg,omitempty"`
Icon DesktopIcon `yaml:"icon,omitempty" json:"icon,omitempty"`
// categories is CLOSED to the freedesktop registered main categories. A typo here does
// not error anywhere — the entry silently vanishes from every menu — which is exactly
// the class of failure a closed enum exists to catch.
Categories []DesktopCategory `yaml:"categories,omitempty" json:"categories,omitempty"`
MimeTypes []string `yaml:"mime_type,omitempty" json:"mime_type,omitempty"`
// startup_wm_class ties the launched window back to this entry. Also read by the session
// renderer's window rules, which is this kind's reason to exist.
StartupWMClass string `yaml:"startup_wm_class,omitempty" json:"startup_wm_class,omitempty"`
Window DesktopWindow `yaml:"window,omitempty" json:"window,omitempty"`
Terminal bool `yaml:"terminal,omitempty" json:"terminal,omitempty"`
Comment string `yaml:"comment,omitempty" json:"comment,omitempty"`
}
#DesktopEntry is a freedesktop .desktop file.
A .desktop file is a trivial INI, and this kind earns its keep on ONE ground: startup_wm_class and window are consumed by the SESSION renderer through the render context, so a single entity feeds both the applications menu and the compositor's window rules. Without that it would be `write:` duplication and should be dropped.
type DesktopIcon ¶ added in v0.2026242.935
type DesktopIcon struct {
Name string `yaml:"name,omitempty" json:"name,omitempty"`
Source string `yaml:"source,omitempty" json:"source,omitempty"`
}
#DesktopIcon is name XOR source: a stock icon name, or a candy-relative file that lowers to `copy:` (an icon base64'd through a plan is neither readable nor small).
type DesktopWindow ¶ added in v0.2026242.935
type DesktopWindow struct {
Placement string `yaml:"placement,omitempty" json:"placement,omitempty"`
Workspace string `yaml:"workspace,omitempty" json:"workspace,omitempty"`
Size string `yaml:"size,omitempty" json:"size,omitempty"`
}
#DesktopWindow is the placement the session renderer turns into a window rule.
type DetectedDevices ¶
type DetectedDevices struct {
GPU bool `yaml:"gpu,omitempty" json:"gpu"`
AMDGPU bool `yaml:"amd_gpu,omitempty" json:"amd_gpu"`
AMDGFXVersion string `yaml:"amd_gfx_version,omitempty" json:"amd_gfx_version"`
RenderNode string `yaml:"render_node,omitempty" json:"render_node"`
Devices []string `yaml:"devices,omitempty" json:"devices"`
}
#DetectedDevices holds the results of host device auto-detection.
type Diagnostic ¶
type Diagnostic struct {
Severity string `yaml:"severity,omitempty" json:"severity,omitempty"`
Message string `yaml:"message,omitempty" json:"message"`
Path string `yaml:"path,omitempty" json:"path,omitempty"`
}
#Diagnostic is one finding from a plugin kind's deep OpValidate check (F7/C8).
type Diagnostics ¶
type Diagnostics struct {
Items []Diagnostic `yaml:"items,omitempty" json:"items,omitempty"`
}
#Diagnostics is the OpValidate reply. HasErrors() is a pure Go METHOD — CUE cannot express it — and stays hand-written in spec/deploy_methods.go.
func (Diagnostics) HasErrors ¶
func (d Diagnostics) HasErrors() bool
HasErrors reports whether any item is error-severity (empty severity counts as error).
type DiscoverConfig ¶ added in v0.2026210.808
type DiscoverConfig []ScanSpec
DiscoverConfig is a FLAT list of generic scan specs. Each spec scans a path for directories containing its manifest; every discovered manifest is parsed as a multi-document stream and routed by SHAPE (the kind-key it carries), so one discover root can surface candies, boxes, deploys — any kind. There is no kind dimension and no hardcoded path/filename: discovery is fully configured in charly.yml.
type DiscoveredManifest ¶
type DiscoveredManifest struct {
Dir string `yaml:"dir,omitempty" json:"dir,omitempty"`
Manifest string `yaml:"manifest,omitempty" json:"manifest,omitempty"`
RootDir string `yaml:"rootDir,omitempty" json:"rootDir,omitempty"`
Docs []ParsedProject `yaml:"docs,omitempty" json:"docs,omitempty"`
}
#DiscoveredManifest — one discovered entity directory's manifest (a `discover:` walk hit), applied by the host AFTER the docs (explicit-entry-wins). `dir`/`rootDir`/`manifest` let the host register a lazy candy `From:` reference (a LAYER candy) or materialize the node (any other kind); `docs` are the manifest's parsed documents.
type DiskLayout ¶ added in v0.2026242.753
type DiskLayout struct {
// esp_mount_point is where the EFI System Partition is mounted, relative to the guest
// root. Defaults to "/boot/efi".
//
// This is NOT cosmetic. Omarchy mounts its ESP at "/boot", which is what
// limine-entry-tool and ESP_PATH assume; a loader written to the other path leaves an
// unbootable disk and the build reports success either way.
EspMountPoint string `yaml:"esp_mount_point,omitempty" json:"esp_mount_point,omitempty"`
// subvolume, when non-empty, makes the root filesystem a btrfs subvolume layout
// instead of a bare filesystem. Requires the VM source's rootfs to be "btrfs".
//
// Exactly one entry must mount at "/" — it becomes the root that the others nest
// under, so without it there is nothing to mount them into. Enforced by OpValidate
// rather than by CUE, because "exactly one element of this list has field X = Y" is
// not expressible in a closed struct.
Subvolume []Subvolume `yaml:"subvolume,omitempty" json:"subvolume,omitempty"`
}
#DiskLayout describes how a bootstrap VM's disk is partitioned and mounted, for the distros whose on-disk shape is part of their identity rather than a per-VM choice.
Both fields are optional and both default to what charly did before this def existed, so a distro that omits the block builds exactly the disk it built before: a bare root filesystem with the ESP at /boot/efi.
type DisplayManager ¶ added in v0.2026242.935
type DisplayManager struct {
Manager string `yaml:"manager,omitempty" json:"manager"`
// session is the #SessionDesktop.id to start. Cross-checked at load.
Session string `yaml:"session,omitempty" json:"session"`
Autologin AutoLogin `yaml:"autologin,omitempty" json:"autologin,omitempty"`
Numlock string `yaml:"numlock,omitempty" json:"numlock,omitempty"`
// theme names a greeter theme package or directory. Free-form: every greeter spells
// this differently and none of them validate it either.
Theme string `yaml:"theme,omitempty" json:"theme,omitempty"`
// config is the greeter's own files, rendered the same way a theme's are.
Config []ThemeRender `yaml:"config,omitempty" json:"config,omitempty"`
// unit is the systemd unit to enable. Named explicitly rather than derived from
// `manager`, because a distro may ship it under a different name and guessing would be
// the "renderer guesses the distro" failure this codebase already recorded.
Unit string `yaml:"unit,omitempty" json:"unit,omitempty"`
}
#DisplayManager is the greeter: which one, which session it starts, and whether it logs a user in without asking.
The load-time cross-check is the point: `session` must match a #SessionDesktop.id that some session entity in the same project declares. An autologin pointing at a session file nothing installed produces a black screen and an empty journal, which is the single worst failure this surface has.
type Distro ¶
type Distro struct {
Inherits string `yaml:"inherits,omitempty" json:"inherits,omitempty"`
InheritPackages bool `yaml:"inherit_packages,omitempty" json:"inherit_packages,omitempty"`
Version string `yaml:"version,omitempty" json:"version,omitempty"`
Bootstrap Bootstrap `yaml:"bootstrap,omitempty" json:"bootstrap,omitempty"`
Workarounds []string `yaml:"workaround,omitempty" json:"workaround,omitempty"`
Format map[string]*Format `yaml:"format,omitempty" json:"format,omitempty"`
BaseUser *BaseUser `yaml:"base_user,omitempty" json:"base_user,omitempty"`
Pacstrap *Pacstrap `yaml:"pacstrap,omitempty" json:"pacstrap,omitempty"`
Debootstrap *Debootstrap `yaml:"debootstrap,omitempty" json:"debootstrap,omitempty"`
AlpineBootstrap *AlpineBootstrap `yaml:"alpine_bootstrap,omitempty" json:"alpine_bootstrap,omitempty"`
Bootloader *Bootloader `yaml:"bootloader,omitempty" json:"bootloader,omitempty"`
DiskLayout *DiskLayout `yaml:"disk_layout,omitempty" json:"disk_layout,omitempty"`
Dnf *Dnf `yaml:"dnf,omitempty" json:"dnf,omitempty"`
Installer *DistroInstaller `yaml:"installer,omitempty" json:"installer,omitempty"`
}
func (*Distro) LocalPkgFormat ¶
LocalPkgFormat picks the format whose local_pkg block drives the charly package INSTALL (the download leg): the caller's primary format, then the distro's own PrimaryFormat, then any localpkg-capable format (deterministic). Shares the selection core with the *ResolvedDistro twin (spec/distro_methods.go).
func (*Distro) PrimaryFormat ¶
PrimaryFormat returns the distro's primary (non-secondary) package format. Shares the selection core with the *ResolvedDistro twin (spec/distro_methods.go).
type DistroBuilderCandidate ¶ added in v0.2026213.1726
type DistroBuilderCandidate struct {
Name string `yaml:"name,omitempty" json:"name"`
Distro []string `yaml:"distro,omitempty" json:"distro"`
Builder BuilderMap `yaml:"builder,omitempty" json:"builder"`
}
#DistroBuilderCandidate is one named entity's Distro tags + Builder map — the generic shape PickDistroBuilder consumes (R3: the shared abstraction behind the "distro-keyed builder default" lookup). Pure data (string/[]string/#BuilderMap) with zero buildkit-specific typing, so it is CUE-sourced here alongside #BuilderMap per the same rule — moved out of buildkit (the builder-map value-primitive cluster: EffectiveBuilderForBox/ResolveEffectiveBuilder/ distroBuilderMap/PickDistroBuilder + this candidate type relocated to spec/spec as pure-value CONTRACT computation, plan Rule 2 + step 3). The lookup FUNCTIONS themselves are BEHAVIOR (hand-written Go, not CUE-generated) and live in spec/spec/builder_resolve.go.
type DistroConfig ¶ added in v0.2026210.2052
type DistroConfig struct {
Distro map[string]*ResolvedDistro `yaml:"distro,omitempty" json:"distro"`
}
#DistroConfig is the `distro:` build-vocabulary container — the whole distro map keyed by distro name (charly/charly.yml's `distro:` section). #55 step 3-III: relocated from sdk/buildkit so every charly/plugin file that uses ONLY this value type drops its sdk/buildkit import for spec (the import-purity lever). Its vocabulary-resolution methods (ResolveDistro / ResolveInherits / AllFormatNames / FindFormat / ValidFormat / ExpandPackageInheritance) are pure Go METHODS on the type's own map — CUE cannot express them — and stay hand-written in spec/distro_config_methods.go (mirrors ResolvedDistro's PrimaryFormat in spec/distro_methods.go: a method, not a type).
func ProjectDistroConfig ¶ added in v0.2026213.1726
func ProjectDistroConfig(uf *UnifiedFile, resolveDistro func(json.RawMessage) (*ResolvedDistro, error)) *DistroConfig
ProjectDistroConfig reconstructs the *DistroConfig (distro: section) from uf, resolving each opaque `distro` body via the caller-supplied resolveDistro callback (the distro plugin's OpResolve leg). Nil when no distros are configured.
func WrapDistroDef ¶ added in v0.2026210.2052
func WrapDistroDef(def *ResolvedDistro) *DistroConfig
WrapDistroDef presents one already-resolved ResolvedDistro as a DistroConfig so the format-keyed FindFormat resolver returns that def's Format. Returns nil for a nil def.
func (*DistroConfig) AllFormatNames ¶ added in v0.2026210.2052
func (dc *DistroConfig) AllFormatNames() []string
AllFormatNames returns a sorted, deduplicated list of all format names across all distros.
func (*DistroConfig) ExpandPackageInheritance ¶ added in v0.2026210.2052
func (dc *DistroConfig) ExpandPackageInheritance(tags []string) []string
ExpandPackageInheritance appends, AFTER the authored distro tags, every `inherits:` ancestor of a tag whose distro def opts into package inheritance via `inherit_packages: true` — most-specific authored tags kept first, ancestors appended as the least-specific levels.
func (*DistroConfig) FindFormat ¶ added in v0.2026210.2052
func (dc *DistroConfig) FindFormat(name string) *Format
FindFormat returns the Format for a format name (rpm/deb/pac/aur), resolving distro `inherits:` chains. The first distro that defines the format wins. Returns nil when no distro defines it.
func (*DistroConfig) ResolveDistro ¶ added in v0.2026210.2052
func (dc *DistroConfig) ResolveDistro(distroTags []string) *ResolvedDistro
ResolveDistro finds the distro definition matching the image's distro tags. Walks tags in order, strips :version suffix to match base distro name. Follows inherits: chains with cycle detection, inheriting formats from parent.
func (*DistroConfig) ResolveInherits ¶ added in v0.2026210.2052
func (dc *DistroConfig) ResolveInherits(def *ResolvedDistro, maxDepth int) *ResolvedDistro
func (*DistroConfig) ValidFormat ¶ added in v0.2026210.2052
func (dc *DistroConfig) ValidFormat(name string) bool
ValidFormat returns true if any distro defines this format name.
type DistroInstaller ¶ added in v0.2026241.2320
type DistroInstaller struct {
// volume_id is the filesystem LABEL the installer looks for. archinstall and
// cloud-init NoCloud use "cidata"; Anaconda kickstart uses "OEMDRV". Case is
// preserved verbatim by the ISO writer, so write it exactly as the installer
// matches it.
VolumeID string `yaml:"volume_id,omitempty" json:"volume_id"`
// fs is how the answers volume is packed. iso9660 is correct for every installer
// verified so far; vfat exists for one that matches its label case-sensitively in
// a way ISO 9660 cannot represent.
FS string `yaml:"fs,omitempty" json:"fs,omitempty"`
Files []DistroInstallerFile `yaml:"file,omitempty" json:"file"`
// boot_arg is appended to the INSTALLER's kernel cmdline when the installer needs
// to be pointed at its answers explicitly ("inst.ks=hd:LABEL=OEMDRV:/ks.cfg").
// Empty for an installer that auto-discovers the labelled volume.
BootArg string `yaml:"boot_arg,omitempty" json:"boot_arg,omitempty"`
// done is how the build path learns the install finished. poweroff: the installer
// powers the guest off and the build waits for the process to exit. marker: the
// installer writes marker_path into the target root, polled over the guest agent.
// NEVER a sleep.
Done string `yaml:"done,omitempty" json:"done,omitempty"`
MarkerPath string `yaml:"marker_path,omitempty" json:"marker_path,omitempty"`
}
#DistroInstaller — the UNATTENDED-INSTALL renderer vocabulary for a `source.kind: iso` VM. It is to that arm exactly what #Init.service_schema is to `service:`: THIS DISTRO owns the answer-file FORMAT, while the vm entity's `source.installer:` owns the DATA (#VmInstaller). Nothing may infer an installer format from a URL, an ISO name or a base_user — the same rule distro_vocab.cue's header states for every other distro trait, and for the same reason.
Five real formats live behind this one shape: archinstall JSON (Omarchy, Arch), kickstart (Fedora/RHEL), preseed (Debian), Subiquity autoinstall (Ubuntu) and AutoYaST (SUSE). Each is keyed by distro and by nothing else.
type DistroInstallerFile ¶ added in v0.2026241.2320
type DistroInstallerFile struct {
// path is relative to the volume root.
Path string `yaml:"path,omitempty" json:"path"`
// content is a Go text/template rendered against #InstallerSeedContext.
Content string `yaml:"content,omitempty" json:"content"`
Mode string `yaml:"mode,omitempty" json:"mode,omitempty"`
// when is a Go-template guard: the file is emitted only when it renders non-empty
// and not "false". This is what lets ONE vocabulary express a whole optional
// matrix — an ssh key file only when keys were given, a "defer provisioning"
// sentinel INSTEAD of credentials, an encryption marker only when encrypting —
// with no Go branch anywhere.
When string `yaml:"when,omitempty" json:"when,omitempty"`
}
#DistroInstallerFile is ONE file placed on the answers volume.
type DistroPackages ¶
type DistroPackages struct {
Package []PackageItem `yaml:"package,omitempty" json:"package,omitempty"`
Copr []string `yaml:"copr,omitempty" json:"copr,omitempty"`
Repo []RepoBlock `yaml:"repo,omitempty" json:"repo,omitempty"`
Exclude []string `yaml:"exclude,omitempty" json:"exclude,omitempty"`
Options []string `yaml:"option,omitempty" json:"option,omitempty"`
Module []string `yaml:"module,omitempty" json:"module,omitempty"`
AUR *AUR `yaml:"aur,omitempty" json:"aur,omitempty"`
}
type DistroResolveInput ¶
type DistroResolveInput struct {
Distro RawBody `yaml:"distro,omitempty" json:"distro"`
}
#DistroResolveInput carries one opaque distro body to project.
type DistroResolveReply ¶
type DistroResolveReply struct {
Resolved *ResolvedDistro `yaml:"resolved,omitempty" json:"resolved,omitempty"`
}
#DistroResolveReply wraps the resolved distro.
type DoMode ¶
type DoMode string
DoMode is the act/assert/instruct axis. act = perform a side-effect; assert = run the matchers (read-only); instruct = hand free-form text to the agent grader.
func StepDoMode ¶
StepDoMode maps the step keyword to the act/assert/instruct dispatch enum.
type DocParser ¶
type DocParser interface {
ParseDoc(doc *yaml.Node, t Threaded) (directives map[string]*yaml.Node, pp ParsedProject, err error)
}
DocParser is the swappable per-document PARSE seam: the loader plugin candy implements it (candy/plugin-loader, delegating to loaderkit.ParseDoc), and the host resolves the registered loader provider to it and calls it for every config document — so an alternative loader plugin serves a different config front-end by implementing this. Typed (no wire envelope) since it runs on every document load. `directives` is the reserved-directive mapping (import/discover/version/ repo/defaults/provides); `pp` is the decomposed entity nodes.
type DocShape ¶
type DocShape int
DocShape classifies one parsed YAML document's top level.
const ( // DocShapeEmpty — a scalar-null / empty mapping document (nothing to load). DocShapeEmpty DocShape = iota // DocShapeNode — the unified name-first node-form: reserved document // directives (version/import/discover/defaults/repo/provides) plus a flat // map of arbitrary-name entity nodes (each `<name>: {<discriminator>: …}`), // and NO top-level kind-map key. The ONE authoring surface; a legacy // kind-keyed / root-shape document is hard-rejected downstream (the host's // #NodeDoc CUE gate), not here. DocShapeNode )
func ClassifyDoc ¶
ClassifyDoc inspects a document's top level and returns its shape: a non-empty mapping is a node-form document (arbitrary entity-name nodes and/or the reserved directives version/import/discover/…); a scalar-null / empty mapping is DocShapeEmpty; a non-mapping top level is an error. Entity + directive validation happens downstream (the loader parse + the #NodeDoc CUE gate).
type EmitOpts ¶
type EmitOpts struct {
DryRun bool
FormatJSON bool // print IR as JSON on stdout instead of table
AllowRepoChanges bool
AllowRootTasks bool
WithServices bool
SkipIncompatible bool
AssumeYes bool // skip sudo preflight, confirmation prompts
Verify bool // run layer tests after install
Pull bool // force re-fetch of remote refs / image pull
BuilderImageOverride string
// DevLocalPkg marks a DISPOSABLE CHECK BED's deploy — the deploy-side twin of `charly box
// build --dev-local-pkg`, set by the bed runner and by nothing else. It changes ONE decision:
// a localpkg candy whose package source cannot be located becomes a hard failure instead of
// the benign "the candy's curl/COPY task installs it instead" skip. A bed exists to prove the
// IN-DEVELOPMENT package builds and installs, so a silent skip there makes the bed assert
// something it never tested — which is exactly what happened when an uninitialized pkg/fedora
// submodule let a bed skip building the rpm and fail only later, at an unrelated live check.
DevLocalPkg bool
// LocalPkgBuild is the dev-local-pkg build context: the in-development binary +
// plugins + CalVer + arch + charly.yml the executor uses to build the
// in-development package via the `charly generate-packages` plugin (the
// `--dev-local-pkg` build path). Populated by the check-bed runner (charly
// core) when DevLocalPkg is set; nil → the executor discovers defaults
// (os.Executable(), runtime.GOARCH, `<binary> version`, the baked plugins dir,
// `<CandyDir>/charly.yml`).
LocalPkgBuild *LocalPkgBuildContext
// ParentExec is the DeployExecutor of the parent deployment in a
// nested tree. Non-nil iff this target is dispatched as a child of
// another — FleetAddCmd's tree walker builds the chain root-first
// and passes the immediate ancestor's executor here. Targets that
// support being nested (host, container, vm) compose their own
// executor over ParentExec via NestedExecutor; leaf-only targets
// (kubernetes) ignore it and error if non-nil.
//
// When nil, the target runs against its natural root venue
// (ShellExecutor for host, a fresh SSHExecutor for vm, etc.)
// — preserving the flat-schema behavior for v2 configs that happen
// to have no `children:`.
ParentExec DeployExecutor
// ParentNode is the FleetNode above this target in the tree.
// Useful for targets that need parent-level context beyond the
// executor (e.g. a vm child wants to know its parent container's
// name to wire network forwarding). nil at the root.
ParentNode *FleetNode
// Path is the dotted-path identifier of this node (e.g.
// "stack.web.db"). Used for logging + ledger keying.
Path string
}
EmitOpts carries cross-cutting toggles passed by command-line flags. Gates are checked per-step by the target; target-specific options (the container target's registry auth, the host target's --assume-yes, --dry-run) are gathered here too.
func EmitOptsFromResolveTargetAdd ¶ added in v0.2026218.138
func EmitOptsFromResolveTargetAdd(req DeployResolveTargetAddRequest, parentExec DeployExecutor) EmitOpts
EmitOptsFromResolveTargetAdd rebuilds the FINAL resolved EmitOpts for the resolve-target-add seam from the plugin-sent gate flags + the re-derived parent executor (K-wave 2 cone R2 bank D thin — the seam's inline construction moved here). ParentExec/Path are the two live/local pieces filled host-side; DryRun never reaches the seam (a dry-run prints plugin-side and returns), so it is absent from the request and left false.
func (EmitOpts) ContextOrDefault ¶
ContextOrDefault returns opts' context if one's attached, or a background context.
type EmitReply ¶
type EmitReply struct {
Fragment string `yaml:"fragment,omitempty" json:"fragment"`
ActScript bool `yaml:"act_script,omitempty" json:"act_script,omitempty"`
}
#EmitReply is what a plugin verb/builder returns from an OpEmit Invoke at build time. By default fragment is a verbatim Containerfile FRAGMENT the generator splices into the emitted Containerfile. When act_script is true the fragment is instead a raw state-provision ACT SHELL (the former ProvisionActor.RenderProvisionScript output): the render wraps it in a RUN via the same EmitCmd path a literal command verb uses, rather than splicing it verbatim. This lets a state-provision verb self-declare its build-emit shape over the uniform Invoke(OpEmit), so the render dispatches every verb through InvokeProvider(OpEmit) with no package-main concrete-type assert.
type EmitTarget ¶ added in v0.2026232.520
type EmitTarget interface {
Name() string
Emit(plans []*InstallPlan, opts EmitOpts) error
}
EmitTarget is the interface OCI + container-deploy + host-deploy emitters satisfy. Taking a slice of plans (rather than a single plan) lets whole-image deploys pass all per-candy plans at once and let the target merge them — useful because OCITarget may want to emit a single Containerfile for the image while the local deploy target may batch steps across candies.
type EncExecInput ¶
type EncExecInput struct {
Method string `yaml:"method,omitempty" json:"method"`
ImageID string `yaml:"image_id,omitempty" json:"image_id"`
BoxName string `yaml:"box_name,omitempty" json:"box_name"`
Passphrase string `yaml:"passphrase,omitempty" json:"passphrase,omitempty"`
OldPass string `yaml:"old_pass,omitempty" json:"old_pass,omitempty"`
NewPass string `yaml:"new_pass,omitempty" json:"new_pass,omitempty"`
Volumes []EncVolumePlan `yaml:"volumes,omitempty" json:"volumes"`
}
#EncExecInput is the self-contained gocryptfs-execution request the host ships to plugin-enc over OpExecute. ImageID is the systemd-ask-password / extpass id ("charly-<box>"); BoxName is the bare box name for remediation messages; Passphrase drives mount/ensure (gocryptfs init/mount via GOCRYPTFS_PASSWORD); OldPass/NewPass drive passwd. Volumes carries the host-resolved per-volume plan.
type EncExecReply ¶
type EncExecReply struct {
Error string `yaml:"error,omitempty" json:"error"`
}
#EncExecReply is the execution verdict: Error == "" means success. The host shim turns a non-empty Error into a Go error.
type EncVolumePlan ¶
type EncVolumePlan struct {
Name string `yaml:"name,omitempty" json:"name"`
CipherDir string `yaml:"cipher_dir,omitempty" json:"cipher_dir"`
PlainDir string `yaml:"plain_dir,omitempty" json:"plain_dir"`
ScopeUnit string `yaml:"scope_unit,omitempty" json:"scope_unit"`
Initialized bool `yaml:"initialized,omitempty" json:"initialized"`
Mounted bool `yaml:"mounted,omitempty" json:"mounted"`
}
#EncVolumePlan is one encrypted volume, fully resolved HOST-SIDE: its charly name (for messages), the on-disk cipher/plain dirs, the systemd scope-unit name, and the host-probed initialized/mounted state. The plugin acts on these flags — it never re-derives a charly path convention nor re-probes state.
type EnvConfig ¶
type EnvConfig struct {
Vars map[string]string `yaml:"vars,omitempty" json:"vars,omitempty"`
PathAppend []string `yaml:"path_append,omitempty" json:"path_append,omitempty"`
}
#EnvConfig — resolved candy env (KEY=value vars + PATH-append entries). Mirrors kit.EnvConfig.
type EnvDependency ¶
type EnvDependency struct {
Name string `yaml:"name,omitempty" json:"name"`
Description string `yaml:"description,omitempty" json:"description"`
Default string `yaml:"default,omitempty" json:"default,omitempty"`
Key string `yaml:"key,omitempty" json:"key,omitempty"`
}
EnvDependency (layers.go) — ONE Go type reused for env_require/env_accept, secret_accept/secret_require, AND mcp_require/mcp_accept. The hand struct is a single SUPERSET {name, description, default, key}; the three former CUE defs (#CandyEnvDep/#CandySecretDep/#CandyMCPDep) were distinct only in their per-context name regex (env-var name vs mcp-server name) and the secret-only `key` override — all of which Go validate.go enforces. gengotypes cannot emit THREE shapes for ONE Go type, and a faithful drop-in for []EnvDependency requires every usage to carry all four fields (mcp_require's former 2-field def is missing default/key), so the three collapse to ONE shared #EnvDependency (R3 — one shared abstraction; per-context strictness stays in Go).
type EnvKV ¶
type EnvKV struct {
Key string `yaml:"key,omitempty" json:"key"`
Value string `yaml:"value,omitempty" json:"value"`
}
#EnvKV is a deterministic env-var ordering helper (template iteration order). Named #EnvKV, NOT #KeyValue — the schemagen vocab generator auto-registers EVERY top-level `#<X>Value` def as a fake kind-value gate (kindValueDefs, `^#?([A-Za-z0-9]+)Value$`), so `#KeyValue` would spuriously register a "key" kind (RDD-caught live: it appeared in the generated KindValueDefs map). The charly-name alias `KeyValue = EnvKV` (spec/charly_names.go) preserves the exported Go type name every real consumer (deploykit.MapToKeyValueSlice, spec.SortedEnvList) already uses.
type EnvProvideEntry ¶ added in v0.2026210.808
type EnvProvideEntry struct {
Name string `yaml:"name,omitempty" json:"name"`
Value string `yaml:"value,omitempty" json:"value"`
Source string `yaml:"source,omitempty" json:"source"`
}
#EnvProvideEntry is a RESOLVED env-provided entry — the raw authored env_provide name→value entry plus the Source deployment it came from. Its GetName/GetSource methods satisfy charly's Named interface structurally (hand-written behavior, not wire shape).
func (EnvProvideEntry) GetName ¶ added in v0.2026210.808
func (e EnvProvideEntry) GetName() string
EnvProvideEntry (schema/candy.cue, generated into cue_types_gen.go) is the CUE-sourced RESOLVED env-provided entry — relocated from deploykit (provides.go) so UnifiedFile.Provides (*ProvidesConfig) lives entirely in spec; deploykit keeps the provides PIPELINE (FilterOwnProvides/ResolveTemplate/…), consuming these spec types. Its GetName/GetSource satisfy charly's Named interface — hand-written behavior.
func (EnvProvideEntry) GetSource ¶ added in v0.2026210.808
func (e EnvProvideEntry) GetSource() string
type EphemeralLifetime ¶
type EphemeralLifetime struct {
TTL string `yaml:"ttl,omitempty" json:"ttl,omitempty"`
KeepOnFailure bool `yaml:"keep_on_failure,omitempty" json:"keep_on_failure,omitempty"`
NamingPattern string `yaml:"naming_pattern,omitempty" json:"naming_pattern,omitempty"`
// contains filtered or unexported fields
}
EphemeralLifetime parameterizes the auto-destruction lifecycle for a deploy.
func (*EphemeralLifetime) EffectiveNamingPattern ¶
func (e *EphemeralLifetime) EffectiveNamingPattern() string
EffectiveNamingPattern returns the configured naming pattern, or the default.
func (*EphemeralLifetime) EffectiveTTL ¶
func (e *EphemeralLifetime) EffectiveTTL() time.Duration
EffectiveTTL returns the configured TTL, or the 1h default.
func (*EphemeralLifetime) IsBoolForm ¶
func (e *EphemeralLifetime) IsBoolForm() bool
IsBoolForm reports whether the field was authored as a bare boolean.
func (*EphemeralLifetime) UnmarshalJSON ¶
func (e *EphemeralLifetime) UnmarshalJSON(data []byte) error
UnmarshalJSON accepts the boolean shorthand and the block form (the loader is JSON-based via cue.Value.Decode). `ephemeral: false` is rejected.
type EphemeralRegisterReply ¶
type EphemeralRegisterReply struct {
}
type EphemeralRegisterRequest ¶
type EphemeralRegisterRequest struct {
Name string `yaml:"name,omitempty" json:"name"`
Node *Deploy `yaml:"node,omitempty" json:"node"`
}
#EphemeralRegisterRequest/#EphemeralRegisterReply — the host→command:fleet OpEphemeralRegister leg (FINAL/K5 unit 6a): ephemeral_lifecycle.go's cross-substrate ephemeral-instance registration (systemd TTL transient timer + parent-detection + charly.yml persistence) moved to candy/plugin-fleet, the substrate-neutral deploy-lifecycle owner (vm/pod/kubernetes all register through it via deploy_add_shared.go's registerEphemeralIfMarked, which STAYS host-side — candidate-floor sibling of fleet_add_cmd.go — and Invokes this as the FIRST action of every Add). Registration failure is best-effort (logged plugin-side, never fatal to the deploy) — the reply is empty; the host discards the returned handle (the prior in-core contract already did, registerEphemeralIfMarked only checked the error).
type EphemeralRuntime ¶
type EphemeralRuntime struct {
ID string `yaml:"id" json:"id"`
ParentVm string `yaml:"parent_vm,omitempty" json:"parent_vm,omitempty"`
ParentSnapshot string `yaml:"parent_snapshot,omitempty" json:"parent_snapshot,omitempty"`
ParentEphemeral string `yaml:"parent_ephemeral,omitempty" json:"parent_ephemeral,omitempty"`
ChildRefcount int `yaml:"child_refcount,omitempty" json:"child_refcount,omitempty"`
TimerUnit string `yaml:"timer_unit,omitempty" json:"timer_unit,omitempty"`
TtlDeadline string `yaml:"ttl_deadline,omitempty" json:"ttl_deadline,omitempty"`
Status string `yaml:"status,omitempty" json:"status,omitempty"`
InstanceName string `yaml:"instance_name,omitempty" json:"instance_name,omitempty"`
// DeployAddress is the CLI-addressable deploy identity (the dotted tree path for a nested
// deploy, e.g. "parent.child" — `charly fleet del <DeployAddress>`), DISTINCT from the
// dc.Fleet MAP KEY this entry is stored under (which is a dot-SANITIZED "vm:<domain-id>"
// form — a literal '.' is illegal in a map key, see
// ValidateDeploymentName (deploy_tree_validate.go); FINAL/K5 unit 6a RCA #2). A nested-ephemeral-child teardown scan
// (candy/plugin-fleet's teardownChildrenRec) has ONLY this struct to recover the real
// address from — the map key it was found under cannot be reversed back to the original
// dotted path (VmDomainIdentity's "." -> "-" replacement is lossy).
DeployAddress string `yaml:"deploy_address,omitempty" json:"deploy_address,omitempty"`
}
EphemeralRuntime is the live runtime state of an active ephemeral instantiation. Source: charly/deploy.go EphemeralRuntime.
type EphemeralTeardownReply ¶
type EphemeralTeardownReply struct {
}
type EphemeralTeardownRequest ¶
type EphemeralTeardownRequest struct {
Name string `yaml:"name,omitempty" json:"name"`
Node *Deploy `yaml:"node,omitempty" json:"node"`
}
#EphemeralTeardownRequest/#EphemeralTeardownReply — the OpEphemeralTeardown leg any substrate's own post-teardown handling can Invoke directly (recursive nested-child teardown, TTL timer cancel, snapshot/parent refcount decrement, charly.yml cleanup lives in candy/plugin-fleet's teardownEphemeral). Today's one caller is candy/plugin-deploy-vm/ lifecycle.go's vmPostTeardown (F6 vm-lifecycle move, coneB-vmlifecycle — formerly a host-side pre-dispatch hook, charly/vm_lifecycle_preresolve.go's vmLifecyclePostTeardown, dispatched in-proc via the now-deleted charly/ephemeral_dispatch.go's TeardownEphemeralLifecycle).
type ExecContext ¶
type ExecContext string
ExecContext is where an op runs. An op's Context list (or its VerbCatalog default) declares legality; the active engine supplies the running context and skips ops whose context set does not include it.
const ( CtxBuild ExecContext = "build" CtxDeploy ExecContext = "deploy" CtxRuntime ExecContext = "runtime" )
type ExternalPluginStep ¶ added in v0.2026210.2308
type ExternalPluginStep struct {
Op *Op // the run: step op — Plugin (verb word) + PluginInput + RunAs
CandyName string // owning candy (provenance + the ledger CandyRecord key)
ResolvedUser string // uid:gid or "root" after resolveUserSpec — drives Scope
Distros []string // the image's distro tag chain (img.Tags) for the build-emit BuildEnv
}
ExternalPluginStep is the install-timeline IR node for a `run: plugin: <verb>` step whose verb is served by an EXTERNAL (out-of-process) plugin — a grpcProvider, not a built-in TypedStepProvider (package/service) or ProvisionActor (the state-provision shell verbs) or the `command` install verb. constructOpStep (via the "construct-step" seam's registry consult) routes it here; the Invoke op is picked per venue, placement-agnostic above the registry:
- the DEPLOY venue (the install runs ON the target, not into an image): BOTH target:local AND target:vm externalized, so an ExternalPluginStep on a deploy is reached by the external local/vm plugin's kit.WalkPlans, which routes it through the host's RunHostStep (the nested ExternalPluginStep case) → executeExternalPluginStep → Invoke(OpExecute) WITH the live DeployExecutor on the go-plugin broker (the E3b reverse channel), so the plugin runs its deploy-context effect (RunSystem/RunUser) on the real venue and RETURNS its teardown ReverseOps which the host records into the ledger (record-and-replay — only recorded ops are reversed at `charly fleet del`). This is the deploy-context counterpart of the build-context OpEmit leg (tasks.go invokeVerbBuildEmit), reusing the SAME spec.DeployReply / ReverseOp wire as the external deploy TARGET (candy/plugin-fleet/deploy_target.go, S3b — was charly core's deploy_target_external.go before the deploy-dispatch cluster moved) — R3.
- EmitOCI (a BUILD venue — the pod-overlay Containerfile): Invoke(OpEmit) via the SHARED invokeVerbBuildEmit, splicing the plugin's Containerfile FRAGMENT verbatim — it cannot deploy-execute at build (no live venue). Identical behaviour to the box-build path (writeCandySteps → emitTasks `case "plugin"`), just relocated. EmitOCI is the ONLY in-proc Emit* this provider still implements.
Reverse() is static-nil: the reversal ops are DYNAMIC — they come from the plugin's OpExecute reply at emit time, recorded by the host's RunHostStep handler into the CandyRecord (the externalDeployTarget record-and-replay pattern).
func (*ExternalPluginStep) Kind ¶ added in v0.2026210.2308
func (s *ExternalPluginStep) Kind() StepKind
func (*ExternalPluginStep) RequiresGate ¶ added in v0.2026210.2308
func (s *ExternalPluginStep) RequiresGate() Gate
RequiresGate is GateNone — an external plugin verb-step is operator-authorized build/deploy-time plugin execution (the candy author opted into it), mirroring the external deploy TARGET which is ungated; the plugin picks its own privilege via RunSystem/RunUser on the reverse channel.
func (*ExternalPluginStep) Reverse ¶ added in v0.2026210.2308
func (s *ExternalPluginStep) Reverse() []ReverseOp
Reverse is static-nil: the teardown ops are recorded DYNAMICALLY from the plugin's OpExecute DeployReply at emit time (EmitLocal/EmitVM append reply.ReverseOps to the CandyRecord), exactly like the external deploy target — never recomputed.
func (*ExternalPluginStep) Scope ¶ added in v0.2026210.2308
func (s *ExternalPluginStep) Scope() Scope
Scope classifies by the step's user context — the SAME rule OpStep uses (OpStepScope): root/empty → system, a named/numeric non-root user → user. The plugin then chooses RunSystem vs RunUser on the reverse channel per its own effect; this scope drives only the ledger reverse-op privilege bookkeeping.
func (*ExternalPluginStep) Venue ¶ added in v0.2026210.2308
func (s *ExternalPluginStep) Venue() Venue
type ExternalStep ¶ added in v0.2026210.2308
type ExternalStep struct {
Word string // the reserved step word; Kind() = "external:" + Word
ScopeV Scope // plugin-declared (StepContract.scope)
VenueV Venue // plugin-declared (StepContract.venue)
GateV Gate // plugin-declared (StepContract.gate) — the step SKIPs if the gate is not enabled
Payload json.RawMessage // opaque per-kind input — the OpExecute params
CandyName string // owning candy (provenance + the ledger CandyRecord key)
ReverseOps []ReverseOp // set DYNAMICALLY from the plugin's OpExecute reply (record-and-replay)
}
ExternalStep is an EXTERNAL, plugin-CONTRIBUTED install-step KIND: a step whose Kind() is "external:<word>", carried OPAQUELY (Payload) and whose Scope/Venue/Gate come from the serving class:step plugin's DECLARED StepContract. Its host EXECUTION funnels through the OpExecute-to-the-serving-plugin path; teardown ops are DYNAMIC (recorded from the OpExecute reply), so Reverse() returns the recorded slice.
func (*ExternalStep) Kind ¶ added in v0.2026210.2308
func (s *ExternalStep) Kind() StepKind
func (*ExternalStep) RequiresGate ¶ added in v0.2026210.2308
func (s *ExternalStep) RequiresGate() Gate
func (*ExternalStep) Reverse ¶ added in v0.2026210.2308
func (s *ExternalStep) Reverse() []ReverseOp
func (*ExternalStep) Scope ¶ added in v0.2026210.2308
func (s *ExternalStep) Scope() Scope
func (*ExternalStep) Venue ¶ added in v0.2026210.2308
func (s *ExternalStep) Venue() Venue
type ExtractStep ¶ added in v0.2026224.1942
type ExtractStep struct {
Source string // OCI image ref to extract from (e.g. "higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/all-in-one:2.2.1")
Path string // path in the source image (a file or a directory)
Dest string // absolute destination path on the venue
CandyName string // owning candy (provenance + the ledger CandyRecord key)
}
ExtractStep materializes a candy's `extract:` entries (FROM <source> AS <candy>-extract-<i> + COPY --from=... in the Containerfile build) onto a MACHINE venue (target:local / target:vm), where the built image filesystem is never used — the install plan executes over the venue's own filesystem. The step runs on the HOST (podman pull → create → cp → rm, charly's own machinery), tars the extracted content, ships it into the venue via PutFile, and extracts it in place — the venue-agnostic analog of the BuilderStep host-engine pattern (RunVenueBuilderStep).
COPY semantics are replicated exactly: when Path is a DIRECTORY its contents are extracted into Dest (Dest is a directory); when Path is a FILE the file lands at Dest (Dest is the file path). Ownership is NOT handled here — the candy's own plan steps chown the extracted paths (the agentteams-higress candy chowns /etc/certs /etc/istio /var/lib/istio /var/log/proxy to uid 1000), mirroring the build where COPY --chown is explicit per entry.
Compiled ONLY for machine venues (hostCtx.MachineVenue): the OCI/pod targets emit the Containerfile extract stages directly and never see this step. Like BuilderStep it is a HOST-ENGINE kind — the walk routes it to RunHostStep, which dispatches it to the class:step plugin's OpExecute with the injected image resolve/ensure closures.
func (*ExtractStep) Kind ¶ added in v0.2026224.1942
func (s *ExtractStep) Kind() StepKind
func (*ExtractStep) RequiresGate ¶ added in v0.2026224.1942
func (s *ExtractStep) RequiresGate() Gate
func (*ExtractStep) Reverse ¶ added in v0.2026224.1942
func (s *ExtractStep) Reverse() []ReverseOp
Reverse returns no ledger ops — the extracted content is the candy's own install surface (a system-level install, like a package), removed (if ever) by the operator, not by deploy teardown. Mirrors LocalPkgInstallStep's empty Reverse: the dest may be a file OR a directory, and the existing rm-file reverse kinds cannot express both (rm -f fails on a dir; the recursive kind runs as the deploy user, who cannot remove system paths).
func (*ExtractStep) Scope ¶ added in v0.2026224.1942
func (s *ExtractStep) Scope() Scope
Scope is system — extract lands in system locations (/usr/local/bin, /app, /opt, /var).
func (*ExtractStep) Venue ¶ added in v0.2026224.1942
func (s *ExtractStep) Venue() Venue
Venue is host-native — the podman extract runs on the HOST (like the aur builder), then the content is shipped to the target and extracted in place.
type FeatureEntity ¶
type FeatureEntity struct {
Kind string `yaml:"kind,omitempty" json:"kind"`
Name string `yaml:"name,omitempty" json:"name"`
Description string `yaml:"description,omitempty" json:"description,omitempty"`
Plan []Step `yaml:"plan,omitempty" json:"plan,omitempty"`
}
#FeatureEntity is one enumerated kind: entity + its RAW plan data (Step is already a plain CUE-sourced wire type, so no separate flattened form is needed on the wire). An entity with neither a description nor a plan is still listed (as "(no description)") but the plugin skips summarizing/validating it, matching the former engine.
type FileStep ¶ added in v0.2026210.2308
type FileStep struct {
Source string // path to source content (under layer dir or .build/_inline/)
Dest string // absolute destination path
Mode os.FileMode // permissions
Owner string // "root" or "uid:gid" or the invoking user's name
CandyName string // for ledger
}
FileStep places a single file. The compiler may emit these for candy-declared file directives that aren't wrapped as tasks (e.g. supervisord fragment assembly). Today's tasks.go handles most file placement via OpStep; FileStep exists for cases where the compiler synthesizes file writes that weren't in the candy manifest (e.g. service unit files, managed-block contents).
func (*FileStep) RequiresGate ¶ added in v0.2026210.2308
type FleetConfig ¶ added in v0.2026219.1057
type FleetConfig struct {
Provides *ProvidesConfig `yaml:"provides,omitempty" json:"provides,omitempty"`
Fleet map[string]FleetNode `yaml:"deploy" json:"deploy"`
// Sidecar carries the project's sidecar-template library as OPAQUE bodies
// (the raw PluginKinds["sidecar"] map). candy/plugin-sidecar's OpResolve merges
// these UNDER each deploy node's own overrides; the kernel reads no fields
// (the sidecar de-type, Cutover D).
Sidecar map[string]json.RawMessage `yaml:"sidecar,omitempty" json:"sidecar,omitempty"`
// Cache is the per-host LOCAL cache status (git metadata cache). The unified
// home for local system state — see spec/loadmodel.go UnifiedFile.Cache.
Cache *CacheConfig `yaml:"cache,omitempty" json:"cache,omitempty"`
// Ledger is the per-host INSTALL LEDGER (deploy/candy/step records).
Ledger *LedgerConfig `yaml:"ledger,omitempty" json:"ledger,omitempty"`
// System is the per-host LOCAL SYSTEM INFO.
System *SystemInfo `yaml:"system,omitempty" json:"system,omitempty"`
}
FleetConfig represents per-machine deployment overrides (~/.config/charly/charly.yml). Only runtime/deployment fields are supported — build-time fields are structurally excluded.
Schema v4: the top-level map key is `deployment:` (singular, flat). The legacy `images:` / `deployments.images.*` nesting is gone — all target kinds (host / vm / pod / kubernetes) live under the single `deployment:` map.
func (*FleetConfig) Lookup ¶ added in v0.2026219.1057
func (dc *FleetConfig) Lookup(deployName, instance string) (FleetNode, bool)
Lookup returns the FleetNode for (deployName, instance), or (zero, false) when the entry is absent. Safe to call on a nil *FleetConfig — lets callers chain `loadDeployConfigForRead(...).Lookup(deployName, instance)` without a separate nil check. deployName is the charly.yml key base the caller is operating on (typically c.Image), NOT the baked image short-name — for a kind:check bed or Pattern-B deploy the two differ. Pass the deploy key, never a value derived from an image label (see MergeDeployOntoMetadata).
type Format ¶
type Format struct {
CacheMount []CacheMount `yaml:"cache_mount,omitempty" json:"cache_mount,omitempty"`
SectionFields map[string]string `yaml:"section_field,omitempty" json:"section_field,omitempty"`
UninstallTemplate string `yaml:"uninstall_template,omitempty" json:"uninstall_template,omitempty"`
Phases *PhaseSet `yaml:"phase,omitempty" json:"phase,omitempty"`
Validate []FormatRule `yaml:"validate,omitempty" json:"validate,omitempty"`
Secondary bool `yaml:"secondary,omitempty" json:"secondary,omitempty"`
LocalPkg *LocalPkg `yaml:"local_pkg,omitempty" json:"local_pkg,omitempty"`
}
type FormatRule ¶
type Gate ¶
type Gate string
Gate is the name of a CLI flag that must be enabled for a given step to run on the host target. Steps without a gate run unconditionally; steps with a gate are skipped (with a warning) unless the user opts in.
The OCI target ignores gates — container builds are already isolated, so gating would only slow down image construction without adding safety.
type GitCache ¶ added in v0.2026241.931
type GitCache struct {
LatestTags map[string]GitCacheEntry `yaml:"latest_tags,omitempty" json:"latest_tags,omitempty"`
DefaultBranches map[string]GitCacheEntry `yaml:"default_branches,omitempty" json:"default_branches,omitempty"`
ResolvedRefs map[string]GitCacheEntry `yaml:"resolved_refs,omitempty" json:"resolved_refs,omitempty"`
Downloads map[string]GitCacheEntry `yaml:"downloads,omitempty" json:"downloads,omitempty"`
}
#GitCache is the git metadata cache: latest tags, default branches, resolved refs, and repo downloads. Each entry records the value and when it was resolved (RFC3339), so the TTL policy can decide freshness.
type GitCacheEntry ¶ added in v0.2026241.931
type GitCacheEntry struct {
Value string `yaml:"value,omitempty" json:"value"`
Resolved string `yaml:"resolved,omitempty" json:"resolved"`
}
#GitCacheEntry is one cached git answer: the value plus the resolution time.
type GoogleAccount ¶
type GpuProbeInput ¶
type GpuProbeInput struct {
Action string `yaml:"action,omitempty" json:"action"`
Group int `yaml:"group,omitempty" json:"group,omitempty"`
}
#GpuProbeInput is the action-multiplexed input to verb:gpu over OpRun. Action selects the host probe. The three data tables (device_patterns/gpu_vendors/pci_class_labels) are now candy/plugin-gpu's OWN embedded data (K5 seam-death — they moved out of charly-core's embedded charly.yml alongside the "hostprobe" HostBuild kind's dissolution; plugin-gpu is the actual detection consumer, so it is the one data source, R3), so this input no longer threads them.
type GpuProbeReply ¶
type GpuProbeReply struct {
Bool bool `yaml:"bool,omitempty" json:"bool,omitempty"`
Str string `yaml:"str,omitempty" json:"str,omitempty"`
Vfio *VFIOReport `yaml:"vfio,omitempty" json:"vfio,omitempty"`
HostDevices *DetectedDevices `yaml:"host_devices,omitempty" json:"host_devices,omitempty"`
MemlockSoft uint64 `yaml:"memlock_soft,omitempty" json:"memlock_soft,omitempty"`
MemlockHard uint64 `yaml:"memlock_hard,omitempty" json:"memlock_hard,omitempty"`
}
#GpuProbeReply is the action-multiplexed reply from verb:gpu. Each action populates only the field(s) it produces.
type GpuSelector ¶
type GpuSelector struct {
Vendor string `yaml:"vendor,omitempty" json:"vendor"`
}
vendor required + non-empty; NOT regex-pinned (normalizePCIVendor accepts "10DE"/"0X10de"/"0x10de" — a strict regex would reject Go-valid input).
type GpuSwitchInput ¶
type GpuSwitchInput struct {
Action string `yaml:"action,omitempty" json:"action"`
Gpu *VFIOGpu `yaml:"gpu,omitempty" json:"gpu,omitempty"`
Mode string `yaml:"mode,omitempty" json:"mode,omitempty"`
Addr string `yaml:"addr,omitempty" json:"addr,omitempty"`
Vendor string `yaml:"vendor,omitempty" json:"vendor,omitempty"`
}
#GpuSwitchInput is the action-multiplexed input the core driver-switch shims ship to verb:gpu over OpRun for the DRIVER-SWITCH actions (cutover C9). It rides the SAME verb:gpu provider as the C11 detection actions (GpuProbeInput); the action vocabularies are disjoint, so the plugin's Invoke dispatches by which envelope decodes — detection actions decode GpuProbeInput, switch actions decode this.
type GpuSwitchReply ¶
type GpuSwitchReply struct {
// "bool" quoted for the same builtin-keyword collision as #GpuProbeReply.
Bool bool `yaml:"bool,omitempty" json:"bool,omitempty"`
Str string `yaml:"str,omitempty" json:"str,omitempty"`
Plan []string `yaml:"plan,omitempty" json:"plan,omitempty"`
Wedged bool `yaml:"wedged,omitempty" json:"wedged,omitempty"`
Error string `yaml:"error,omitempty" json:"error,omitempty"`
}
#GpuSwitchReply is the action-multiplexed reply for the DRIVER-SWITCH actions.
type HelmChart ¶ added in v0.2026227.1655
type HelmChart struct {
Repo string `yaml:"repo,omitempty" json:"repo,omitempty"`
Chart string `yaml:"chart,omitempty" json:"chart"`
Version string `yaml:"version,omitempty" json:"version,omitempty"`
Release string `yaml:"release,omitempty" json:"release"`
Namespace string `yaml:"namespace,omitempty" json:"namespace,omitempty"`
Values_files []string `yaml:"values_files,omitempty" json:"values_files,omitempty"`
}
type HexColor ¶ added in v0.2026242.935
type HexColor string
#HexColor is #rrggbb. Six digits only: a renderer that also accepted #rgb or #rrggbbaa would have to normalise, and every consuming format spells alpha differently.
type HolderAddr ¶
type HolderAddr struct {
Name string `yaml:"name,omitempty" json:"name"`
Target string `yaml:"target,omitempty" json:"target"`
Base string `yaml:"base,omitempty" json:"base"`
Instance string `yaml:"instance,omitempty" json:"instance,omitempty"`
Vm string `yaml:"vm,omitempty" json:"vm,omitempty"`
}
#HolderAddr is the self-contained address of a deployment (holder or claimant) — enough for the host seams to probe/stop/start it WITHOUT re-reading config, so a lease loaded after a crash can act on it. Carries BOTH yaml (the persisted ledger) and json (the Invoke/reverse-channel envelope) tags — the retag step emits both automatically, mirroring the json key.
type HolderDescriptor ¶
type HolderDescriptor struct {
Name string `yaml:"name,omitempty" json:"name"`
Holds []string `yaml:"holds,omitempty" json:"holds"`
Addr HolderAddr `yaml:"addr,omitempty" json:"addr"`
Restore string `yaml:"restore,omitempty" json:"restore"`
}
#HolderDescriptor is one candidate preemptible holder, pre-projected (PreemptionHolds() + HolderAddrFor() + PreemptEffectiveRestore(), sdk/deploykit's FilterPreemptibleHolders) so the plugin's holdersToStop is pure config-free coordination. Filled plugin-side now (K1-unblock wave 1, off the #ResolvedProject envelope) rather than by a bespoke host reply type.
type Hook ¶ added in v0.2026218.1950
type Hook struct {
Name string `yaml:"name,omitempty" json:"name"`
Content string `yaml:"content,omitempty" json:"content"`
Trigger string `yaml:"trigger,omitempty" json:"trigger,omitempty"`
Matcher string `yaml:"matcher,omitempty" json:"matcher,omitempty"`
When string `yaml:"when,omitempty" json:"when,omitempty"`
Mode string `yaml:"mode,omitempty" json:"mode,omitempty"`
}
CUE schema for the `hook` KIND — a first-class HARNESS hook entity (the migration of the superproject's .claude/hooks/* gate scripts into candy config). A `hook:` node is a sibling top-level entity in candy/charly-hooks/charly.yml, carrying the script CONTENT inline.
This is the HARNESS hook (the Claude Code PreToolUse/PrePush git-discipline gates), DISTINCT from the candy lifecycle `hook:` field (#CandyHook {post_enable, pre_remove}): the kind word `hook` lives on the kind-word spine while the lifecycle field lives inside a `candy:` body — different namespaces, no parse conflict (the U0 spike exercises a file carrying both).
trigger/matcher absent ⇒ an AUX file (e.g. gitcmd.py, gate_test.py) emitted to .claude/hooks/ but not wired into settings.json. The generator enforces: matcher required iff trigger present.
type HostContext ¶ added in v0.2026213.1726
type HostContext struct {
// MachineVenue selects compilation mode (P9): false (zero value) → compile for a
// CONTAINER image build (the pod overlay / OCI target); true → compile for a MACHINE
// venue with a system init (a target:local / target:vm deploy — services render as
// systemd units, home is deferred via {{.Home}}). detectHostContext sets it true for a
// host deploy; the OCI/pod-overlay compile passes the zero value. It replaced the former
// string Target ("host"/"vm"/"oci"), whose "vm" arm was dead (a vm deploy compiles with
// the host detectHostContext) — the machine-vs-container distinction IS the trait.
MachineVenue bool
// Distro is the resolved host distro tag, e.g. "fedora:43". Used to
// pick the right format section when compiling for a host target
// whose distro differs from the image's primary distro.
Distro string
// GlibcVersion is the host's glibc major.minor as reported by
// `ldd --version`. Used by the host target's preflight check against
// the selected builder image. Optional; empty means skip the check.
GlibcVersion string
// BuilderImage overrides the default builder-image selection for
// VenueContainerBuilder steps. Populated from --builder-image. ""
// means "use the embedded build vocabulary's default".
BuilderImage string
// BuilderContext carries the host-side build PRE-PASS result: each externalized
// detection-builder's per-candy stage context + teardown ops, keyed by
// BuilderCtxKey(candy, builder). Populated by preresolveBuilderContexts BEFORE
// this pure compile (the deploy command path); read by collectBuilderContext +
// compileBuilderSteps so the compiler NEVER dials a builder plugin (purity). Nil
// when no pre-pass ran (a direct BuildDeployPlan caller / test) or no externalized
// builder is triggered → the affected builder gets base-only context, no teardown.
// `json:"-"`: IN-PROCESS-ONLY, populated plugin-side after the HostContextJSON decode —
// never crosses the wire (see the file doc + spec.BuilderPreresolved).
BuilderContext map[string]BuilderPreresolved `json:"-"`
// ActiveInitName/ActiveInit carry the MachineVenue's preresolved active init system —
// resolved ONCE per whole-deploy compile plugin-side off the resolved-project envelope's
// rp.Init (candy/plugin-fleet/compile.go). compileServiceSteps reads these instead of
// re-deriving the active init per-candy or guessing via a container-oriented auto-detect
// heuristic (which cannot disambiguate a machine venue's init from a plain custom-exec
// service entry — proven live 2026-07-20). Nil/empty for a direct BuildDeployPlan caller /
// test that compiles outside the seam; compileServiceSteps falls back to its own lazy
// per-call lookup then. `json:"-"`: IN-PROCESS-ONLY, populated plugin-side — never crosses.
ActiveInitName string `json:"-"`
ActiveInit *ResolvedInit `json:"-"`
}
host_context.go — the deploy-compile HOST CONTEXT value type, promoted from sdk/deploykit (#55 K4 import-purity). A hand-written spec value carrier, NOT a CUE wire type: it crosses the plugin boundary ONLY as the opaque `host_context: bytes` RawBody payload on #DeployCompileRequest (the sanctioned VmJSON/PodConfigJSON idiom), so CUE never decodes it at the wire. Its WIRE form is the 4 host-computed SCALARS below (MachineVenue/Distro/GlibcVersion/ BuilderImage); the remaining fields (BuilderContext/ActiveInitName/ActiveInit) are `json:"-"` IN-PROCESS-ONLY — populated PLUGIN-SIDE after the HostContextJSON decode (candy/plugin-fleet's preresolveBuilderContexts over the reverse channel + the rp.Init active-init resolve), never crossing. The `cue exp gengotypes` spike is the SDD arbiter: it CANNOT express the `json:"-"` intent (it emits `,omitempty`, which would wrongly serialize the in-process fields into the wire blob) and it embeds the already-hand spec.BuilderPreresolved (opaque map[string]any Context) — so hand-written is SPIKE-JUSTIFIED per the SDD mandate, mirroring its child BuilderPreresolved. sdk/deploykit keeps a `type HostContext = spec.HostContext` forwarder so the deploy-compile mechanism (BuildDeployPlan + the compile helpers) + candy/plugin-fleet compile unchanged.
type HostEnv ¶
type HostEnv struct {
CharlyBin string `yaml:"charly_bin,omitempty" json:"charly_bin,omitempty"`
Home string `yaml:"home,omitempty" json:"home,omitempty"`
// version is the host charly's CalVer (CharlyVersion()) — the
// delivery-decision authority for EnsureCharlyInGuest.
Version string `yaml:"version,omitempty" json:"version,omitempty"`
}
#HostEnv is the generic host identity a lifecycle plugin (running ON the host) needs but cannot derive: the host charly binary path and the host home.
type ImageUserInput ¶
type ImageUserInput struct {
Ref string `yaml:"ref,omitempty" json:"ref"`
UID int `yaml:"uid,omitempty" json:"uid"`
}
#ImageUserInput — inspect a remote image's /etc/passwd for the user with the given uid (the build engine's base-image adopt-user probe).
type ImportEntry ¶ added in v0.2026210.808
type ImportEntry struct {
Namespace string // "" = flat import into the current root namespace
Ref string // local path or `@host/org/repo[/sub/path]:version`
}
ImportEntry is one parsed `import:` list item. A flat entry (Namespace == "") merges the referenced file into the current root namespace; a namespaced entry mounts the referenced project under Namespace.
type ImportList ¶ added in v0.2026210.808
type ImportList []ImportEntry
ImportList is the `import:` field type. Custom YAML decoding accepts a list whose items are either a bare string (flat) or a single-key mapping `alias: ref` (namespaced child import).
func (ImportList) MarshalYAML ¶ added in v0.2026210.808
func (il ImportList) MarshalYAML() (any, error)
MarshalYAML emits each entry compactly: a flat entry as a scalar string, a namespaced entry as a single-key `alias: ref` map — the same shapes UnmarshalYAML accepts (round-trip safe; used by migrators that write configs).
func (*ImportList) UnmarshalYAML ¶ added in v0.2026210.808
func (il *ImportList) UnmarshalYAML(node *yaml.Node) error
UnmarshalYAML decodes the mixed-shape import list.
type Incident ¶
type Incident struct {
ID UUIDv7 `yaml:"id,omitempty" json:"id"`
RunID UUIDv7 `yaml:"run_id,omitempty" json:"run_id,omitempty"`
State string `yaml:"state,omitempty" json:"state"`
Summary string `yaml:"summary,omitempty" json:"summary"`
CreatedAt string `yaml:"created_at,omitempty" json:"created_at"`
EvidenceRefs []string `yaml:"evidence_refs,omitempty" json:"evidence_refs,omitempty"`
}
type Init ¶
type Init struct {
CandyFields []string `yaml:"candy_field,omitempty" json:"candy_field,omitempty"`
CandyFiles []string `yaml:"candy_file,omitempty" json:"candy_file,omitempty"`
DependsCandy string `yaml:"depends_candy,omitempty" json:"depends_candy,omitempty"`
RequiresCapability []string `yaml:"requires_capability,omitempty" json:"requires_capability,omitempty"`
// The one mandatory field: the build model.
Model string `yaml:"model,omitempty" json:"model"`
HeaderFile string `yaml:"header_file,omitempty" json:"header_file,omitempty"`
FragmentDir string `yaml:"fragment_dir,omitempty" json:"fragment_dir,omitempty"`
RelayTemplate string `yaml:"relay_template,omitempty" json:"relay_template,omitempty"`
StageName string `yaml:"stage_name,omitempty" json:"stage_name,omitempty"`
StageHeaderCopy string `yaml:"stage_header_copy,omitempty" json:"stage_header_copy,omitempty"`
StageFragmentCopy string `yaml:"stage_fragment_copy,omitempty" json:"stage_fragment_copy,omitempty"`
AssemblyTemplate string `yaml:"assembly_template,omitempty" json:"assembly_template,omitempty"`
SystemEnableTemplate string `yaml:"system_enable_template,omitempty" json:"system_enable_template,omitempty"`
PostAssemblyTemplate string `yaml:"post_assembly_template,omitempty" json:"post_assembly_template,omitempty"`
Entrypoint []string `yaml:"entrypoint,omitempty" json:"entrypoint,omitempty"`
FallbackEntrypoint []string `yaml:"fallback_entrypoint,omitempty" json:"fallback_entrypoint,omitempty"`
ManagementTool string `yaml:"management_tool,omitempty" json:"management_tool,omitempty"`
ManagementCommands map[string]string `yaml:"management_command,omitempty" json:"management_command,omitempty"`
LabelKey string `yaml:"label_key,omitempty" json:"label_key,omitempty"`
ServiceSchema *InitServiceSchema `yaml:"service_schema,omitempty" json:"service_schema,omitempty"`
}
type InitConfig ¶ added in v0.2026213.1726
type InitConfig struct {
Init map[string]*ResolvedInit `yaml:"init,omitempty" json:"init"`
}
#InitConfig is the `init:` build-vocabulary container — the whole init map keyed by init-system name (charly/charly.yml's `init:` section). #55 Cluster-B: CUE-sourced (completing #72, which CUE-sourced its siblings #DistroConfig / #Builder but left InitConfig hand-written) so every charly/plugin file that uses ONLY this value type reaches it over spec+proto — the import-purity lever. Its detection / resolution methods (DetectCandyInit / ResolveInitSystem / ActiveInit / InitNames) are pure Go METHODS CUE cannot express and stay hand-written in spec/init_config_methods.go (mirrors #DistroConfig's methods in spec/distro_config_methods.go: a method, not a type). This is NOT a wire type — it is never marshaled across the host↔plugin boundary; it mirrors the authored `init:` config surface, so SDD CUE-sources it exactly as #DistroConfig.
func ProjectInitConfig ¶ added in v0.2026213.1726
func ProjectInitConfig(uf *UnifiedFile, resolveInit func(json.RawMessage) (*ResolvedInit, error)) *InitConfig
ProjectInitConfig reconstructs the *InitConfig (init: section) from uf, resolving each opaque `init` body via the caller-supplied resolveInit callback (the init plugin's OpResolve config leg). Nil when no init systems are configured.
func (*InitConfig) ActiveInit ¶ added in v0.2026213.1726
func (ic *InitConfig) ActiveInit(layers map[string]CandyReader, candyOrder []string) map[string]*ResolvedInit
ActiveInit returns all init systems that are active for the given image. An image can have multiple active inits (e.g., supervisord + systemd on bootc-flavored compositions).
func (*InitConfig) DetectCandyInit ¶ added in v0.2026213.1726
func (ic *InitConfig) DetectCandyInit(ly *CandyYAML, candyPath string) []string
DetectCandyInit returns which init system names a candy triggers, based on its candy manifest fields and file patterns.
func (*InitConfig) InitNames ¶ added in v0.2026213.1726
func (ic *InitConfig) InitNames() []string
InitNames returns a sorted list of all init system names.
func (*InitConfig) ResolveInitSystem ¶ added in v0.2026213.1726
func (ic *InitConfig) ResolveInitSystem(layers map[string]CandyReader, candyOrder []string, explicit string) (string, *ResolvedInit)
ResolveInitSystem determines the active init system for an image. Priority: explicit override → auto-detect from candies. Returns ("", nil) if no init system is needed.
Candy capability requirements (RequiresCapabilities) are checked against the aggregated candy caps for the composition; init systems whose requirements aren't met are filtered out. The aggregated caps are also consulted for the bootc-prefer-systemd heuristic via PreserveUser (the canonical signal that this is a bootc-flavored composition).
type InitResolveInput ¶
type InitResolveInput struct {
Init RawBody `yaml:"init,omitempty" json:"init"`
}
#InitResolveInput is candy/plugin-init's OpResolve config-leg input: the opaque init body to resolve into a ResolvedInit.
type InitResolveReply ¶
type InitResolveReply struct {
Resolved *ResolvedInit `yaml:"resolved,omitempty" json:"resolved,omitempty"`
}
#InitResolveReply wraps the resolved init.
type InitResolveRequest ¶
type InitResolveRequest struct {
Render *ServiceRenderInput `yaml:"render,omitempty" json:"render,omitempty"`
Config *InitResolveInput `yaml:"config,omitempty" json:"config,omitempty"`
}
#InitResolveRequest is the discriminated OpResolve input for candy/plugin-init: exactly one of Render (leg 1 — render one service unit) or Config (legs 2–4 — the resolved init envelope) is set.
type InitServiceSchema ¶
type InitServiceSchema struct {
ServiceTemplate string `yaml:"service_template,omitempty" json:"service_template,omitempty"`
UnitPathTemplate string `yaml:"unit_path_template,omitempty" json:"unit_path_template,omitempty"`
DropinTemplate string `yaml:"dropin_template,omitempty" json:"dropin_template,omitempty"`
DropinPathTemplate string `yaml:"dropin_path_template,omitempty" json:"dropin_path_template,omitempty"`
SupportsPackaged bool `yaml:"supports_packaged,omitempty" json:"supports_packaged,omitempty"`
}
type InlineCandy ¶ added in v0.2026210.808
type InlineCandy struct {
From string `yaml:"from,omitempty" json:"from,omitempty"`
CandyYAML `yaml:",inline"`
// Manifest carries the discovery manifest filename for a `From:` directory so the candy
// scan reads the right file. Not YAML-authored; carried through the opaque candy-map fold
// via JSON, hence exported + json-tagged.
Manifest string `yaml:"-" json:"__manifest,omitempty"`
}
InlineCandy is a candy declared inline in the unified file's `candy:` map. Mutually exclusive options: `from:` points at a directory to scan (the loader's CandyScanner seam), OR the inline body defines the candy (same fields as the candy manifest, flattened via yaml:",inline").
func DecodeInlineCandy ¶ added in v0.2026210.808
func DecodeInlineCandy(raw json.RawMessage) (*InlineCandy, bool)
DecodeInlineCandy decodes one opaque layer body into the InlineCandy loader shape.
type InstallContext ¶
type InstallContext struct {
CacheMounts []CacheMount `yaml:"cache_mounts,omitempty" json:"cache_mounts,omitempty"`
Packages []string `yaml:"packages,omitempty" json:"packages,omitempty"`
Repos []map[string]any `yaml:"repos,omitempty" json:"repos,omitempty"`
Options []string `yaml:"options,omitempty" json:"options,omitempty"`
Copr []string `yaml:"copr,omitempty" json:"copr,omitempty"`
Modules []string `yaml:"modules,omitempty" json:"modules,omitempty"`
Exclude []string `yaml:"exclude,omitempty" json:"exclude,omitempty"`
Keys []string `yaml:"keys,omitempty" json:"keys,omitempty"`
StageName string `yaml:"stage_name,omitempty" json:"stage_name,omitempty"`
BuilderRef string `yaml:"builder_ref,omitempty" json:"builder_ref,omitempty"`
User string `yaml:"user,omitempty" json:"user,omitempty"`
UID int `yaml:"uid,omitempty" json:"uid,omitempty"`
GID int `yaml:"gid,omitempty" json:"gid,omitempty"`
Home string `yaml:"home,omitempty" json:"home,omitempty"`
}
#InstallContext — data a distro format's install/prepare/cleanup template renders against: packages + repo/copr/module/exclude/key modifiers, resolved cache mounts, and the builder-stage identity/uid/gid/home.
type InstallOpts ¶
type InstallOpts struct {
WithServices bool `yaml:"with_service,omitempty" json:"with_service,omitempty"`
AllowRepoChanges bool `yaml:"allow_repo_changes,omitempty" json:"allow_repo_changes,omitempty"`
AllowRootTasks bool `yaml:"allow_root_tasks,omitempty" json:"allow_root_tasks,omitempty"`
SkipIncompatible bool `yaml:"skip_incompatible,omitempty" json:"skip_incompatible,omitempty"`
Verify bool `yaml:"verify,omitempty" json:"verify,omitempty"`
BuilderImage string `yaml:"builder_image,omitempty" json:"builder_image,omitempty"`
}
install_opts gates (deploy.go InstallOptsConfig) — shared by local + deploy.
type InstallPlan ¶
type InstallPlan struct {
// Identity — populated by the compiler.
DeployID string // per-deploy unique ID (hash of image + add_candy list)
Box string // deployable box name (or candy name for single-candy deploys)
Version string // candy/box CalVer version
Distro string // resolved host distro tag, e.g. "fedora:43"
Candy string // candy name when this plan is for a single candy; "" for whole-image merges
// The ordered step sequence.
Steps []InstallStep
// Provenance — used by teardown and status.
CandiesIncluded []string // ordered layer names this plan composes (for whole-image merges)
AddCandies []string // layers added on top via charly.yml add_layers: (for provenance)
BuilderImage string // selected builder image for VenueContainerBuilder steps
Meta map[string]string // free-form metadata (builder image, glibc version, …)
}
InstallPlan is the full ordered list of steps for one candy or one whole-image deploy. Compiled by the deploykit compiler and consumed by any EmitTarget implementation.
The compiler produces one InstallPlan per candy (then merges them in topological order for whole-image deploys). A whole-image deploy keeps candy boundaries visible so the ledger can refcount which candies participate.
func PlanFromView ¶ added in v0.2026210.2308
func PlanFromView(v InstallPlanView) (*InstallPlan, error)
func PlansFromViews ¶ added in v0.2026218.138
func PlansFromViews(viewsJSON json.RawMessage) ([]*InstallPlan, error)
PlanFromView re-materializes the rich in-core *InstallPlan from its JSON-roundtrippable InstallPlanView wire form — the REVERSE of WireView, used by the host to reconstruct []*InstallPlan from the command:fleet plugin's OpCompile reply (K4-B). Steps round-trip through the SINGLE stepsFromView converter (install_step_view.go), already proven round-trip-faithful by TestStepView_RoundTrip. The host re-materialized plan is byte-equivalent to the former in-proc compile output (the K4-B parity golden proves it via DeepEqual against the OLD host-compile path). PlansFromViews decodes a marshalled []InstallPlanView (the wire shape a command plugin ships for already-compiled plans) and re-materializes each into an *InstallPlan — the resolve-target-add seam's per-view loop, consolidated here (K-wave 2 cone R2 bank D thin).
func (*InstallPlan) StepsByVenue ¶
func (p *InstallPlan) StepsByVenue() []StepBatch
StepsByVenue partitions the plan's steps by (Scope, Venue) tuple while preserving intra-partition order. Host target emission uses this to batch contiguous same-(scope, venue) runs into one heredoc. Not used by the OCI target (it walks Steps directly). Uses only the InstallStep interface (Scope()/Venue()), so it belongs with the envelope.
type InstallPlanView ¶
type InstallPlanView struct {
DeployID string `yaml:"deploy_id,omitempty" json:"deploy_id,omitempty"`
Box string `yaml:"box,omitempty" json:"box,omitempty"`
Version string `yaml:"version,omitempty" json:"version,omitempty"`
Distro string `yaml:"distro,omitempty" json:"distro,omitempty"`
Candy string `yaml:"candy,omitempty" json:"candy,omitempty"`
CandiesIncluded []string `yaml:"candies_included,omitempty" json:"candies_included,omitempty"`
AddCandies []string `yaml:"add_candies,omitempty" json:"add_candies,omitempty"`
BuilderImage string `yaml:"builder_image,omitempty" json:"builder_image,omitempty"`
Meta map[string]string `yaml:"meta,omitempty" json:"meta,omitempty"`
// steps is the serializable per-step IR — the ordered InstallStep sequence
// the in-core InstallPlan carries, projected onto the wire union below.
Steps []InstallStepView `yaml:"steps,omitempty" json:"steps,omitempty"`
}
#InstallPlanView is the JSON-roundtrippable wire VIEW of an InstallPlan.
func WireView ¶ added in v0.2026210.2308
func WireView(p *InstallPlan) InstallPlanView
WireView projects the rich in-core InstallPlan onto the JSON-roundtrippable InstallPlanView the host marshals into an external deploy/step provider's op.Params. The Steps interface slice round-trips through the SINGLE StepsToView / stepsFromView converter (install_step_view.go) — an external deploy/step plugin walks the same ordered step IR the in-proc EmitTargets walk and EXECUTES it on the venue (R3; proven by the step-IR round-trip test). The remaining fields are identity + provenance.
A free function (not a method on InstallPlan) because it type-switches the concrete step vocabulary via StepsToView.
type InstallStep ¶
type InstallStep interface {
// Kind returns the step's concrete type discriminator.
Kind() StepKind
// Scope classifies where the effect lands on the target filesystem.
Scope() Scope
// Venue classifies where the commands physically execute.
Venue() Venue
// RequiresGate names the opt-in flag that must be enabled, or
// GateNone if the step can run unconditionally. Only consulted by
// host-target emission; the OCI target ignores gates.
RequiresGate() Gate
// Reverse returns the teardown actions this step contributes to the
// ledger. Called at install time (not at teardown) so the ledger
// captures the exact reversal actions tied to the specific artifacts
// created. Empty return value means no reversal is recorded (e.g.
// phases that leave no state).
Reverse() []ReverseOp
}
InstallStep is one polymorphic step in an InstallPlan. Every concrete step kind (SystemPackages, Builder, Op, File, …) implements it; an EmitTarget walks a plan's steps and dispatches on the returned discriminators.
func StepFromView ¶ added in v0.2026210.2308
func StepFromView(v InstallStepView) (InstallStep, error)
StepFromView reconstructs the concrete InstallStep a view projects. An unknown Kind is a hard error (the wire carried a step kind this charly does not know — a genuine version/contract breach, never silently dropped).
type InstallStepView ¶
type InstallStepView struct {
Kind string `yaml:"kind,omitempty" json:"kind"`
// Derived ADVISORY fields (Scope()/Venue()/RequiresGate() results).
Scope Scope `yaml:"scope,omitempty" json:"scope,omitempty"`
Venue int `yaml:"venue,omitempty" json:"venue,omitempty"`
Gate string `yaml:"gate,omitempty" json:"gate,omitempty"`
// payload is the OPAQUE per-kind input for an EXTERNAL (plugin-contributed)
// step kind.
Payload RawBody `yaml:"payload,omitempty" json:"payload,omitempty"`
// reverse_ops is the step's host-computed teardown ops.
ReverseOps []ReverseOp `yaml:"reverse_ops,omitempty" json:"reverse_ops,omitempty"`
// Shared identity / provenance.
CandyName string `yaml:"candy_name,omitempty" json:"candy_name,omitempty"`
CandyDir string `yaml:"candy_dir,omitempty" json:"candy_dir,omitempty"`
// SystemPackagesStep + RepoChangeStep + LocalPkgInstallStep.
Format string `yaml:"format,omitempty" json:"format,omitempty"`
// SystemPackagesStep + BuilderStep three-phase tag (int Phase: prepare/install/cleanup).
Phase int `yaml:"phase,omitempty" json:"phase,omitempty"`
// SystemPackagesStep.
Packages []string `yaml:"packages,omitempty" json:"packages,omitempty"`
Repos []map[string]any `yaml:"repos,omitempty" json:"repos,omitempty"`
Options []string `yaml:"options,omitempty" json:"options,omitempty"`
Copr []string `yaml:"copr,omitempty" json:"copr,omitempty"`
Modules []string `yaml:"modules,omitempty" json:"modules,omitempty"`
Exclude []string `yaml:"exclude,omitempty" json:"exclude,omitempty"`
Keys []string `yaml:"keys,omitempty" json:"keys,omitempty"`
CacheMount []CacheMountView `yaml:"cache_mount,omitempty" json:"cache_mount,omitempty"`
RawInstallContext map[string]any `yaml:"raw_install_context,omitempty" json:"raw_install_context,omitempty"`
// BuilderStep.
Builder string `yaml:"builder,omitempty" json:"builder,omitempty"`
BuilderImage string `yaml:"builder_image,omitempty" json:"builder_image,omitempty"`
Artifacts []ArtifactView `yaml:"artifacts,omitempty" json:"artifacts,omitempty"`
RawStageContext map[string]any `yaml:"raw_stage_context,omitempty" json:"raw_stage_context,omitempty"`
BuilderDef *BuilderDef `yaml:"builder_def,omitempty" json:"builder_def,omitempty"`
// local_pkg is shared by BuilderStep (aur) + LocalPkgInstallStep.
LocalPkg *LocalPkg `yaml:"local_pkg,omitempty" json:"local_pkg,omitempty"`
// OpStep + ExternalPluginStep (Op + the shared user/ctx/distro fields).
Op *Op `yaml:"op,omitempty" json:"op,omitempty"`
CtxPath string `yaml:"ctx_path,omitempty" json:"ctx_path,omitempty"`
ResolvedUser string `yaml:"resolved_user,omitempty" json:"resolved_user,omitempty"`
To string `yaml:"to,omitempty" json:"to,omitempty"`
CandyVars map[string]string `yaml:"candy_vars,omitempty" json:"candy_vars,omitempty"`
Distros []string `yaml:"distros,omitempty" json:"distros,omitempty"`
// FileStep.
Source string `yaml:"source,omitempty" json:"source,omitempty"`
Dest string `yaml:"dest,omitempty" json:"dest,omitempty"`
Mode uint32 `yaml:"mode,omitempty" json:"mode,omitempty"`
Owner string `yaml:"owner,omitempty" json:"owner,omitempty"`
// ServicePackagedStep + ServiceCustomStep.
Unit string `yaml:"unit,omitempty" json:"unit,omitempty"`
Name string `yaml:"name,omitempty" json:"name,omitempty"`
TargetScope Scope `yaml:"target_scope,omitempty" json:"target_scope,omitempty"`
Enable bool `yaml:"enable,omitempty" json:"enable,omitempty"`
OverridesText string `yaml:"overrides_text,omitempty" json:"overrides_text,omitempty"`
OverridesPath string `yaml:"overrides_path,omitempty" json:"overrides_path,omitempty"`
PriorEnabled bool `yaml:"prior_enabled,omitempty" json:"prior_enabled,omitempty"`
UnitText string `yaml:"unit_text,omitempty" json:"unit_text,omitempty"`
UnitPath string `yaml:"unit_path,omitempty" json:"unit_path,omitempty"`
// ShellHookStep.
EnvVars map[string]string `yaml:"env_vars,omitempty" json:"env_vars,omitempty"`
PathAdd []string `yaml:"path_add,omitempty" json:"path_add,omitempty"`
EnvFile string `yaml:"env_file,omitempty" json:"env_file,omitempty"`
// ShellSnippetStep.
Origin string `yaml:"origin,omitempty" json:"origin,omitempty"`
Shell string `yaml:"shell,omitempty" json:"shell,omitempty"`
Snippet string `yaml:"snippet,omitempty" json:"snippet,omitempty"`
PathAppend []string `yaml:"path_append,omitempty" json:"path_append,omitempty"`
Destination string `yaml:"destination,omitempty" json:"destination,omitempty"`
Marker string `yaml:"marker,omitempty" json:"marker,omitempty"`
UseDropin bool `yaml:"use_dropin,omitempty" json:"use_dropin,omitempty"`
Priority int `yaml:"priority,omitempty" json:"priority,omitempty"`
// RepoChangeStep.
File string `yaml:"file,omitempty" json:"file,omitempty"`
Content string `yaml:"content,omitempty" json:"content,omitempty"`
Checksum string `yaml:"checksum,omitempty" json:"checksum,omitempty"`
// ApkInstallStep.
ApkPackages []ApkPackageSpec `yaml:"apk_packages,omitempty" json:"apk_packages,omitempty"`
// LocalPkgInstallStep — the deploy-time executor downloads the published
// package from the distro repo (the download leg) instead of building from
// source, so the IR carries the package reference (name + version) rather
// than the old source-dir anchors (pkgbuild_ref/project_dir).
PackageName string `yaml:"package_name,omitempty" json:"package_name,omitempty"`
Version string `yaml:"version,omitempty" json:"version,omitempty"`
// ExtractStep — a candy's `extract:` entry materialized onto a machine venue.
ExtractSource string `yaml:"extract_source,omitempty" json:"extract_source,omitempty"`
ExtractPath string `yaml:"extract_path,omitempty" json:"extract_path,omitempty"`
ExtractDest string `yaml:"extract_dest,omitempty" json:"extract_dest,omitempty"`
}
#InstallStepView is the JSON-roundtrippable wire form of ONE InstallStep — a SUPERSET struct: each kind populates the subset of fields it carries, all optional so the wire stays compact.
func StepToView ¶ added in v0.2026210.2308
func StepToView(step InstallStep) InstallStepView
StepToView projects one concrete InstallStep onto its serializable wire view.
func StepsToView ¶ added in v0.2026210.2308
func StepsToView(steps []InstallStep) []InstallStepView
StepsToView / stepsFromView convert a whole ordered slice. Order is preserved (the InstallPlan's step ordering is load-bearing — see ResolveHome / StepsByVenue).
type InstallerSeedContext ¶ added in v0.2026241.2320
type InstallerSeedContext struct {
Hostname string `yaml:"hostname,omitempty" json:"hostname,omitempty"`
Timezone string `yaml:"timezone,omitempty" json:"timezone,omitempty"`
Locale string `yaml:"locale,omitempty" json:"locale,omitempty"`
Keyboard string `yaml:"keyboard,omitempty" json:"keyboard,omitempty"`
Username string `yaml:"username,omitempty" json:"username,omitempty"`
FullName string `yaml:"full_name,omitempty" json:"full_name,omitempty"`
Email string `yaml:"email,omitempty" json:"email,omitempty"`
PasswordHash string `yaml:"password_hash,omitempty" json:"password_hash,omitempty"`
Disk string `yaml:"disk,omitempty" json:"disk,omitempty"`
Encrypt bool `yaml:"encrypt,omitempty" json:"encrypt,omitempty"`
// encryption_password is the LUKS passphrase, and it is plaintext by necessity:
// the installer needs it to create the volume. It is only ever set when
// encrypt is true, and it makes the rendered answers volume a secret.
EncryptionPassword string `yaml:"encryption_password,omitempty" json:"encryption_password,omitempty"`
SSHAuthorizedKeys []string `yaml:"ssh_authorized_key,omitempty" json:"ssh_authorized_key,omitempty"`
DeferProvisioning bool `yaml:"defer_provisioning,omitempty" json:"defer_provisioning,omitempty"`
// disk_size_bytes is the TARGET DISK's size in bytes, from the vm entity's
// disk_size. It is here because a real installer answer file states partition
// sizes as absolute numbers, not as "the rest of the disk": archinstall, for one,
// indexes partition['size'] with no default and has no fill-remaining sentinel, so
// a template that cannot see the disk size cannot describe a root partition at all.
//
// BYTES, not a suffixed string, because the arithmetic happens in the template and
// a template is the wrong place to parse "40G".
DiskSizeBytes int64 `yaml:"disk_size_bytes,omitempty" json:"disk_size_bytes,omitempty"`
// answer carries the vm entity's per-distro extras verbatim.
Answers map[string]string `yaml:"answer,omitempty" json:"answer,omitempty"`
}
#InstallerSeedContext is what a #DistroInstallerFile.content template renders against. Host-computed. The PLAINTEXT password never appears here: the caller crypt()s it first, so no plaintext reaches a template, a log, or a temp file.
type Iterate ¶
type Iterate struct {
Agent []string `yaml:"agent,omitempty" json:"agent,omitempty"`
Sandbox string `yaml:"sandbox,omitempty" json:"sandbox"`
PlateauIteration int `yaml:"plateau_iteration,omitempty" json:"plateau_iteration,omitempty"`
Prompt string `yaml:"prompt,omitempty" json:"prompt,omitempty"`
Note *bool `yaml:"note,omitempty" json:"note,omitempty"`
Env StrMap `yaml:"env,omitempty" json:"env,omitempty"`
MCPEndpoint *string `yaml:"mcp_endpoint,omitempty" json:"mcp_endpoint,omitempty"`
}
func (*Iterate) NotesEnabled ¶
NotesEnabled reports whether the iteration NOTES.md memory is on (default on).
type KeyValue ¶
type KeyValue = EnvKV
--- wire sub-types renamed to dodge the schemagen kindValueDefs collision (a top-level `#<X>Value` CUE def auto-registers as a kind-value gate; see schema/init.cue's #EnvKV doc comment for the RDD-caught mechanism) ---
func SortedEnvList ¶ added in v0.2026213.1726
SortedEnvList returns env as a deterministically-ordered []KeyValue (template iteration order — a Go map has none of its own).
type Kubernetes ¶ added in v0.2026223.1018
type Kubernetes struct {
// May be empty (a cluster-policy-only template runs no workload itself).
Box string `yaml:"box,omitempty" json:"box"`
Replica *int `yaml:"replica,omitempty" json:"replica,omitempty"`
Resources *KubernetesResources `yaml:"resources,omitempty" json:"resources,omitempty"`
Hostnames []KubernetesHostname `yaml:"hostnames,omitempty" json:"hostnames,omitempty"`
KubeconfigContext string `yaml:"kubeconfig_context,omitempty" json:"kubeconfig_context,omitempty"`
AdmissionPolicy string `yaml:"admission_policy,omitempty" json:"admission_policy,omitempty"`
DefaultNamespace string `yaml:"default_namespace,omitempty" json:"default_namespace,omitempty"`
Storage KubernetesStorage `yaml:"storage,omitempty" json:"storage,omitempty"`
Ingress KubernetesIngressDefaults `yaml:"ingress,omitempty" json:"ingress,omitempty"`
GatewayAPI KubernetesGatewayAPI `yaml:"gateway_api,omitempty" json:"gateway_api,omitempty"`
Secret KubernetesSecretsBackend `yaml:"secret,omitempty" json:"secret,omitempty"`
ImageDefault KubernetesImagesDefaults `yaml:"image_default,omitempty" json:"image_default,omitempty"`
PodDefault KubernetesPodDefaults `yaml:"pod_default,omitempty" json:"pod_default,omitempty"`
Observability KubernetesObservability `yaml:"observability,omitempty" json:"observability,omitempty"`
NetworkPolicy string `yaml:"network_policy,omitempty" json:"network_policy,omitempty"`
Defaults KubernetesResourceDefaults `yaml:"defaults,omitempty" json:"defaults,omitempty"`
Plan []Step `yaml:"plan,omitempty" json:"plan,omitempty"`
}
type KubernetesDeploy ¶ added in v0.2026223.1018
type KubernetesDeploy struct {
Namespace EntityRef `yaml:"namespace,omitempty" json:"namespace,omitempty"`
Workload string `yaml:"workload,omitempty" json:"workload,omitempty"`
Patches []KubernetesPatch `yaml:"patches,omitempty" json:"patches,omitempty"`
Raw []string `yaml:"raw,omitempty" json:"raw,omitempty"`
// helm_charts — chart releases the kustomize/helm venues install alongside the
// generated workload. The k8sgen emitter translates each entry into a kustomize
// `helmCharts:` overlay entry (chart→name, release→releaseName, values_files→
// valuesFile); the vm/pod/local venues install the same releases via
// `step:helm-release`. Field set confirmed by the Unit 1 schema spike.
Helm_charts []HelmChart `yaml:"helm_charts,omitempty" json:"helm_charts,omitempty"`
}
type KubernetesDeployVenue ¶ added in v0.2026223.1018
type KubernetesDeployVenue struct {
OverlayPath string `yaml:"overlay_path,omitempty" json:"overlay_path"`
TreeRoot string `yaml:"tree_root,omitempty" json:"tree_root,omitempty"`
KubeContext string `yaml:"kube_context,omitempty" json:"kube_context,omitempty"`
DeployName string `yaml:"deploy_name,omitempty" json:"deploy_name,omitempty"`
}
#KubernetesDeployVenue is the preresolved deploy:kubernetes substrate payload the host's kubernetes deploy preresolver produces in DeployVenue.Substrate and the candy/plugin-kube deploy:kubernetes provider decodes.
type KubernetesGatewayAPI ¶ added in v0.2026223.1018
type KubernetesGenFile ¶ added in v0.2026223.1018
type KubernetesGenFile struct {
RelPath string `yaml:"rel_path,omitempty" json:"rel_path"`
Doc RawBody `yaml:"doc,omitempty" json:"doc"`
EgressKind string `yaml:"egress_kind,omitempty" json:"egress_kind"`
}
#KubernetesGenFile is one generated manifest the plugin returns: its RELATIVE path (under OutputDir/DeploymentName, e.g. "base/deployment.yaml"), the manifest as JSON (the host unmarshals it back to a value, egress-validates, and writes it as YAML), and the egress kind that gates it ("k8s_object" or "kustomization").
type KubernetesGenInput ¶ added in v0.2026223.1018
type KubernetesGenInput struct {
DeploymentName string `yaml:"deployment_name,omitempty" json:"deployment_name"`
Instance string `yaml:"instance,omitempty" json:"instance"`
ImageRef string `yaml:"image_ref,omitempty" json:"image_ref"`
Deploy Deploy `yaml:"deploy,omitempty" json:"deploy"`
// cluster is the decoded kind:kubernetes cluster template. After the kubernetes
// substrate-value de-type (Cutover K) the KERNEL no longer sets it — it
// ships the opaque body in ClusterRaw and the plugin decodes ClusterRaw
// into Cluster before generating, so the kernel never types spec.Kubernetes.
Cluster Kubernetes `yaml:"cluster,omitempty" json:"cluster,omitempty"`
ClusterRaw RawBody `yaml:"cluster_raw,omitempty" json:"cluster_raw,omitempty"`
Ports []string `yaml:"ports,omitempty" json:"ports"`
UID int `yaml:"uid,omitempty" json:"uid"`
GID int `yaml:"gid,omitempty" json:"gid"`
OutputDir string `yaml:"output_dir,omitempty" json:"output_dir"`
}
#KubernetesGenInput is the pure-generation input the caller ships to plugin-k8sgen over OpEmit. Deploy is the deployment node (the former FleetNode = spec.Deploy); Cluster is the kind:kubernetes cluster template (the former KubernetesSpec = spec.Kubernetes); Ports / UID / GID are lifted from the image's OCI-label Capabilities host-side so the plugin needs no access to the package-main BoxMetadata type.
type KubernetesGenReply ¶ added in v0.2026223.1018
type KubernetesGenReply struct {
OverlayRelPath string `yaml:"overlay_rel_path,omitempty" json:"overlay_rel_path"`
Files []KubernetesGenFile `yaml:"files,omitempty" json:"files"`
}
#KubernetesGenReply is the pure-generation output: the RELATIVE overlay path the host joins onto OutputDir/DeploymentName to form the `kubectl apply -k` argument, and the collected manifest files (base resources + base/overlay kustomizations).
type KubernetesGenerateKustomizeReply ¶ added in v0.2026223.1018
type KubernetesGenerateKustomizeRequest ¶ added in v0.2026223.1018
type KubernetesGenerateKustomizeRequest struct {
Name string `yaml:"name,omitempty" json:"name"`
ImageRef string `yaml:"image_ref,omitempty" json:"image_ref"`
Node *Deploy `yaml:"node,omitempty" json:"node"`
CapsJSON RawBody `yaml:"caps,omitempty" json:"caps"`
ClusterJSON RawBody `yaml:"cluster,omitempty" json:"cluster"`
OutputDir string `yaml:"output_dir,omitempty" json:"output_dir,omitempty"`
}
#KubernetesGenerateKustomizeRequest/#KubernetesGenerateKustomizeReply — the request/reply shape candy/plugin-kube's materializeKustomize (materialize.go) takes/returns. The former Kustomize-generate HostBuild seam this type pair used to cross (FINAL/K5 unit 6a) is RETIRED (K5-A item 6): the egress-validated Kustomize GENERATION now runs ENTIRELY plugin-side — verb:k8sgen + verb:egress reached peer-to-peer via InvokeProvider, disk I/O done directly by the plugin, no host round trip left — so this pair now travels as a plain Go function signature (materializeKustomize's params/return), not a wire envelope. Both callers (candy/plugin-kube/preresolve.go's deploy:kubernetes preresolve, which self-loads the cluster template + image ref/capabilities itself now too — K-wave W3a A3-phase-2 — and candy/plugin-fleet/deploy_from_box.go's source-less from-box path) construct it directly. Cluster/Capabilities ride opaque (the established RawBody idiom this file uses throughout for hand-written host-side types with no CUE def — e.g. CapsJSON/ClusterJSON in this very def below).
type KubernetesHostname ¶ added in v0.2026223.1018
type KubernetesImagesDefaults ¶ added in v0.2026223.1018
type KubernetesIngressDefaults ¶ added in v0.2026223.1018
type KubernetesIngressDefaults struct {
Enabled bool `yaml:"enabled,omitempty" json:"enabled,omitempty"`
Class string `yaml:"class,omitempty" json:"class,omitempty"`
CertIssuer string `yaml:"cert_issuer,omitempty" json:"cert_issuer,omitempty"`
PathTypeDefault string `yaml:"path_type_default,omitempty" json:"path_type_default,omitempty"`
}
type KubernetesObservability ¶ added in v0.2026223.1018
type KubernetesPatch ¶ added in v0.2026223.1018
type KubernetesPatch struct {
Target struct {
Kind string `yaml:"kind,omitempty" json:"kind,omitempty"`
Name string `yaml:"name,omitempty" json:"name,omitempty"`
Namespace string `yaml:"namespace,omitempty" json:"namespace,omitempty"`
} `yaml:"target,omitempty" json:"target"`
Patch string `yaml:"patch,omitempty" json:"patch"`
}
type KubernetesPodDefaults ¶ added in v0.2026223.1018
type KubernetesPodDefaults struct {
PriorityClass string `yaml:"priority_class,omitempty" json:"priority_class,omitempty"`
// Raw Kubernetes Toleration objects (Go []map[string]any) — genuine
// passthrough, so each element stays OPEN.
Tolerations []map[string]any `yaml:"tolerations,omitempty" json:"tolerations,omitempty"`
NodeSelector map[string]string `yaml:"node_selector,omitempty" json:"node_selector,omitempty"`
}
type KubernetesResolveInput ¶ added in v0.2026223.1018
type KubernetesResolveInput struct {
Kubernetes RawBody `yaml:"kubernetes,omitempty" json:"kubernetes"`
}
#KubernetesResolveInput carries one opaque kubernetes cluster template body to project.
type KubernetesResolveReply ¶ added in v0.2026223.1018
type KubernetesResolveReply struct {
Resolved *ResolvedKubernetes `yaml:"resolved,omitempty" json:"resolved,omitempty"`
}
#KubernetesResolveReply wraps the resolved kubernetes cluster template.
type KubernetesResourceDefaults ¶ added in v0.2026223.1018
type KubernetesResourceValues ¶ added in v0.2026223.1018
type KubernetesResources ¶ added in v0.2026223.1018
type KubernetesResources struct {
Requests KubernetesResourceValues `yaml:"requests,omitempty" json:"requests,omitempty"`
Limits KubernetesResourceValues `yaml:"limits,omitempty" json:"limits,omitempty"`
}
type KubernetesSecretsBackend ¶ added in v0.2026223.1018
type KubernetesSpec ¶ added in v0.2026223.1018
type KubernetesSpec = Kubernetes
--- top-level entity types ---
type KubernetesStorage ¶ added in v0.2026223.1018
type KubernetesStorage struct {
ClassDefault string `yaml:"class_default,omitempty" json:"class_default,omitempty"`
ClassCheap string `yaml:"class_cheap,omitempty" json:"class_cheap,omitempty"`
ClassEncrypted string `yaml:"class_encrypted,omitempty" json:"class_encrypted,omitempty"`
ClassFast string `yaml:"class_fast,omitempty" json:"class_fast,omitempty"`
AccessModeDefault string `yaml:"access_mode_default,omitempty" json:"access_mode_default,omitempty"`
}
type LabelDataEntry ¶
type LabelDataEntry struct {
Volume string `yaml:"volume,omitempty" json:"volume"`
Staging string `yaml:"staging,omitempty" json:"staging"`
Candy string `yaml:"candy,omitempty" json:"candy"`
Dest string `yaml:"dest,omitempty" json:"dest,omitempty"`
}
#LabelDataEntry — a data mapping in ai.opencharly.data (volume/staging/candy required).
type LabelDescriptionSet ¶
type LabelDescriptionSet struct {
Candy []LabeledDescription `yaml:"candy,omitempty" json:"candy,omitempty"`
Box []LabeledDescription `yaml:"box,omitempty" json:"box,omitempty"`
Deploy []LabeledDescription `yaml:"deploy,omitempty" json:"deploy,omitempty"`
}
#LabelDescriptionSet — the three-section (candy/box/deploy) description manifest (ai.opencharly.description). kit.LabelDescriptionSet aliases this.
func (*LabelDescriptionSet) IsEmpty ¶
func (s *LabelDescriptionSet) IsEmpty() bool
IsEmpty reports whether the set carries no labeled descriptions in any layer.
type LabelRouteEntry ¶
type LabelRouteEntry struct {
Host string `yaml:"host,omitempty" json:"host"`
Port int `yaml:"port,omitempty" json:"port"`
}
#LabelRouteEntry — a traefik route in ai.opencharly.route (host+port both required).
type LabelSecretEntry ¶
type LabelSecretEntry struct {
Name string `yaml:"name,omitempty" json:"name"`
Target string `yaml:"target,omitempty" json:"target"`
Env string `yaml:"env,omitempty" json:"env,omitempty"`
}
#LabelSecretEntry — a secret requirement in ai.opencharly.secret (name+target required).
type LabelShellSet ¶
type LabelShellSet struct {
Candy []ShellEntry `yaml:"candy,omitempty" json:"candy,omitempty"`
Box []ShellEntry `yaml:"box,omitempty" json:"box,omitempty"`
Deploy []ShellEntry `yaml:"deploy,omitempty" json:"deploy,omitempty"`
}
#LabelShellSet — the three-section shell-init manifest (ai.opencharly.shell).
type LabelSkillEntry ¶ added in v0.2026218.1950
type LabelSkillEntry struct {
Family string `yaml:"family,omitempty" json:"family"`
Name string `yaml:"name,omitempty" json:"name"`
Owner string `yaml:"owner,omitempty" json:"owner,omitempty"`
Description string `yaml:"description,omitempty" json:"description,omitempty"`
Content string `yaml:"content,omitempty" json:"content"`
References []LabelSkillReference `yaml:"references,omitempty" json:"references,omitempty"`
Triggers []string `yaml:"triggers,omitempty" json:"triggers,omitempty"`
Category string `yaml:"category,omitempty" json:"category,omitempty"`
}
#LabelSkillEntry — one skill baked into ai.opencharly.skill (the migration of the vestigial ai.opencharly.skill doc-pointer URL into the actual definitions — an image now carries its composed candies' skills, readable directly via ExtractMetadata / `charly box labels` / `charly fleet from-box`, with no external fetch). family+name+content required (an entry without content is not a readable skill); the JSON label wire form of the authoring #Skill (CollectSkills projects authoring → wire).
type LabelSkillReference ¶ added in v0.2026218.1950
type LabelVolumeEntry ¶
type LabelVolumeEntry struct {
Name string `yaml:"name,omitempty" json:"name"`
Path string `yaml:"path,omitempty" json:"path"`
}
#LabelVolumeEntry — a volume in the ai.opencharly.volume label (name+path both required).
type LabeledDescription ¶
type LabeledDescription struct {
Origin string `yaml:"origin,omitempty" json:"origin"`
Description string `yaml:"description,omitempty" json:"description,omitempty"`
Plan []Step `yaml:"plan,omitempty" json:"plan,omitempty"`
}
#LabeledDescription — one origin's plan-shaped self-description (MARSHALED into ai.opencharly.description). origin is REQUIRED (json:"origin", no omitempty). kit aliases it.
type LedgerConfig ¶ added in v0.2026241.931
type LedgerConfig struct {
Deploys map[string]DeployRecord `yaml:"deploys,omitempty" json:"deploys,omitempty"`
Candies map[string]CandyRecord `yaml:"candies,omitempty" json:"candies,omitempty"`
}
#LedgerConfig is the top-level `ledger:` block. `deploys` maps deploy-id → #DeployRecord; `candies` maps candy name → #CandyRecord.
type LibvirtAudio ¶
type LibvirtCPU ¶
type LibvirtCPU struct {
// mode is REQUIRED-with-default (renderer default host-passthrough) so the
// custom⇒model if-guard below can reference it (optional fields error when
// absent). #LibvirtCPU only instantiates when `cpu:` is present.
Mode string `yaml:"mode,omitempty" json:"mode"`
Model string `yaml:"model,omitempty" json:"model,omitempty"`
Check string `yaml:"check,omitempty" json:"check,omitempty"`
Migratable string `yaml:"migratable,omitempty" json:"migratable,omitempty"`
Topology *LibvirtCPUTopology `yaml:"topology,omitempty" json:"topology,omitempty"`
Features []LibvirtCPUFeature `yaml:"features,omitempty" json:"features,omitempty"`
Cache *LibvirtCPUCache `yaml:"cache,omitempty" json:"cache,omitempty"`
NUMA []LibvirtNUMACell `yaml:"numa,omitempty" json:"numa,omitempty"`
}
type LibvirtCPUCache ¶
type LibvirtCPUFeature ¶
type LibvirtCPUTopology ¶
type LibvirtCPUTune ¶
type LibvirtCPUTune struct {
Period int `yaml:"period,omitempty" json:"period,omitempty"`
Quota int `yaml:"quota,omitempty" json:"quota,omitempty"`
GlobalPeriod int `yaml:"global_period,omitempty" json:"global_period,omitempty"`
GlobalQuota int `yaml:"global_quota,omitempty" json:"global_quota,omitempty"`
EmulatorPeriod int `yaml:"emulator_period,omitempty" json:"emulator_period,omitempty"`
EmulatorQuota int `yaml:"emulator_quota,omitempty" json:"emulator_quota,omitempty"`
IOThreadPeriod int `yaml:"iothread_period,omitempty" json:"iothread_period,omitempty"`
IOThreadQuota int `yaml:"iothread_quota,omitempty" json:"iothread_quota,omitempty"`
VCPUPin []LibvirtVCPUPin `yaml:"vcpupin,omitempty" json:"vcpupin,omitempty"`
EmulatorPin *LibvirtEmulatorPin `yaml:"emulatorpin,omitempty" json:"emulatorpin,omitempty"`
IOThreadPin []LibvirtIOThreadPin `yaml:"iothreadpin,omitempty" json:"iothreadpin,omitempty"`
}
type LibvirtChannel ¶
type LibvirtClock ¶
type LibvirtClock struct {
Offset string `yaml:"offset,omitempty" json:"offset,omitempty"`
Timezone string `yaml:"timezone,omitempty" json:"timezone,omitempty"`
Adjustment string `yaml:"adjustment,omitempty" json:"adjustment,omitempty"`
Basis string `yaml:"basis,omitempty" json:"basis,omitempty"`
Timers []LibvirtTimer `yaml:"timers,omitempty" json:"timers,omitempty"`
}
type LibvirtConsole ¶
type LibvirtDevices ¶
type LibvirtDevices struct {
Emulator string `yaml:"emulator,omitempty" json:"emulator,omitempty"`
Disks []LibvirtDisk `yaml:"disks,omitempty" json:"disks,omitempty"`
Interfaces []LibvirtInterface `yaml:"interfaces,omitempty" json:"interfaces,omitempty"`
Channels []LibvirtChannel `yaml:"channels,omitempty" json:"channels,omitempty"`
Serial []LibvirtSerial `yaml:"serial,omitempty" json:"serial,omitempty"`
Console []LibvirtConsole `yaml:"console,omitempty" json:"console,omitempty"`
Parallel []LibvirtParallel `yaml:"parallel,omitempty" json:"parallel,omitempty"`
Graphics []LibvirtGraphics `yaml:"graphics,omitempty" json:"graphics,omitempty"`
Video []LibvirtVideo `yaml:"video,omitempty" json:"video,omitempty"`
Audio []LibvirtAudio `yaml:"audio,omitempty" json:"audio,omitempty"`
Sound []LibvirtSound `yaml:"sound,omitempty" json:"sound,omitempty"`
Inputs []LibvirtInput `yaml:"inputs,omitempty" json:"inputs,omitempty"`
USB []LibvirtUSB `yaml:"usb,omitempty" json:"usb,omitempty"`
RedirDev []LibvirtRedirDev `yaml:"redirdev,omitempty" json:"redirdev,omitempty"`
Hostdevs []LibvirtHostdev `yaml:"hostdevs,omitempty" json:"hostdevs,omitempty"`
Filesystems []LibvirtFilesystem `yaml:"filesystems,omitempty" json:"filesystems,omitempty"`
RNG []LibvirtRNG `yaml:"rng,omitempty" json:"rng,omitempty"`
TPM []LibvirtTPM `yaml:"tpm,omitempty" json:"tpm,omitempty"`
Watchdog []LibvirtWatchdog `yaml:"watchdog,omitempty" json:"watchdog,omitempty"`
MemBalloon *LibvirtMemBalloon `yaml:"memballoon,omitempty" json:"memballoon,omitempty"`
Shmem []LibvirtShmem `yaml:"shmem,omitempty" json:"shmem,omitempty"`
IOMMU *LibvirtIOMMU `yaml:"iommu,omitempty" json:"iommu,omitempty"`
Vsock *LibvirtVsock `yaml:"vsock,omitempty" json:"vsock,omitempty"`
Panic []LibvirtPanic `yaml:"panic,omitempty" json:"panic,omitempty"`
Smartcard []LibvirtSmartcard `yaml:"smartcard,omitempty" json:"smartcard,omitempty"`
Hub []LibvirtHub `yaml:"hub,omitempty" json:"hub,omitempty"`
}
type LibvirtDisk ¶
type LibvirtDisk struct {
Type string `yaml:"type,omitempty" json:"type,omitempty"`
Device string `yaml:"device,omitempty" json:"device,omitempty"`
Source map[string]string `yaml:"source,omitempty" json:"source,omitempty"`
Target map[string]string `yaml:"target,omitempty" json:"target,omitempty"`
Driver map[string]string `yaml:"driver,omitempty" json:"driver,omitempty"`
Readonly *bool `yaml:"readonly,omitempty" json:"readonly,omitempty"`
Serial string `yaml:"serial,omitempty" json:"serial,omitempty"`
WWN string `yaml:"wwn,omitempty" json:"wwn,omitempty"`
Boot int `yaml:"boot,omitempty" json:"boot,omitempty"`
}
type LibvirtDomain ¶
type LibvirtDomain struct {
Snippets []string `yaml:"snippets,omitempty" json:"snippets,omitempty"`
XMLPassthrough string `yaml:"xml_passthrough,omitempty" json:"xml_passthrough,omitempty"`
Features *LibvirtFeatures `yaml:"features,omitempty" json:"features,omitempty"`
CPU *LibvirtCPU `yaml:"cpu,omitempty" json:"cpu,omitempty"`
Clock *LibvirtClock `yaml:"clock,omitempty" json:"clock,omitempty"`
MemoryBacking *LibvirtMemoryBacking `yaml:"memory_backing,omitempty" json:"memory_backing,omitempty"`
MemTune *LibvirtMemTune `yaml:"memtune,omitempty" json:"memtune,omitempty"`
NUMATune *LibvirtNUMATune `yaml:"numatune,omitempty" json:"numatune,omitempty"`
CPUTune *LibvirtCPUTune `yaml:"cputune,omitempty" json:"cputune,omitempty"`
IOThreads int `yaml:"iothreads,omitempty" json:"iothreads,omitempty"`
Devices *LibvirtDevices `yaml:"devices,omitempty" json:"devices,omitempty"`
SecLabel *LibvirtSecLabel `yaml:"seclabel,omitempty" json:"seclabel,omitempty"`
LaunchSecurity *LibvirtLaunchSecurity `yaml:"launch_security,omitempty" json:"launch_security,omitempty"`
Resource *LibvirtResource `yaml:"resource,omitempty" json:"resource,omitempty"`
SysInfo *LibvirtSysInfo `yaml:"sysinfo,omitempty" json:"sysinfo,omitempty"`
// qemu_override: QEMU frontend device properties libvirt models no element
// for, rendered as
//
// <qemu:override><qemu:device alias='ua-…'><qemu:frontend><qemu:property …/>
//
// on the libvirt backend and as `-device <dev>,<prop>=<val>` on the qemu one.
//
// This is the ONE escape hatch for the device-property axis, deliberately
// shaped like libvirt's own override element rather than as a field per knob:
// virtio-gpu alone carries drm_native_context, hostmem, max_hostmem and venus,
// and rutabaga adds more. A new knob is a YAML line, never a schema change.
//
// Keyed by the device's USER alias, which the device itself must declare (e.g.
// #LibvirtVideo.alias) — an override naming an alias no device carries is a
// hard render error, not a silent no-op. The `ua-` prefix libvirt demands is
// enforced on the ALIAS field, so it reaches these keys transitively: a key
// that is not `ua-`-prefixed can never equal a valid alias, and the renderer
// then names it as unmatched. (The key is typed `[string]` rather than a
// pattern: `cue exp gengotypes` renders a pattern-constrained key as an EMPTY
// Go struct, which would drop every override on the floor.)
//
// Requires libvirt >= 8.2.0. An override TAINTS the domain (libvirt records
// taint flag `custom-device`); that is libvirt's own policy, not charly's.
QemuOverride map[string]map[string]any `yaml:"qemu_override,omitempty" json:"qemu_override,omitempty"`
}
--------------------------------------------------------------------------- libvirt: LibvirtDomain (libvirt_yaml.go). CLOSED, every sub-type modeled as a #Libvirt<Name> def. Genuine passthroughs stay typed (NOT blanket `{...}`): snippets/xml_passthrough (raw XML) and every map[string]string Go field as `{[string]: string}`. ---------------------------------------------------------------------------
type LibvirtEmulatorPin ¶
type LibvirtEmulatorPin struct {
CPUSet string `yaml:"cpuset,omitempty" json:"cpuset"`
}
type LibvirtFeatures ¶
type LibvirtFeatures struct {
ACPI *bool `yaml:"acpi,omitempty" json:"acpi,omitempty"`
APIC *bool `yaml:"apic,omitempty" json:"apic,omitempty"`
PAE *bool `yaml:"pae,omitempty" json:"pae,omitempty"`
SMM *bool `yaml:"smm,omitempty" json:"smm,omitempty"`
HAP *bool `yaml:"hap,omitempty" json:"hap,omitempty"`
VMPort *bool `yaml:"vmport,omitempty" json:"vmport,omitempty"`
PMU *bool `yaml:"pmu,omitempty" json:"pmu,omitempty"`
HyperV *LibvirtHyperV `yaml:"hyperv,omitempty" json:"hyperv,omitempty"`
KVM *LibvirtKVM `yaml:"kvm,omitempty" json:"kvm,omitempty"`
IBS string `yaml:"ibs,omitempty" json:"ibs,omitempty"`
}
type LibvirtFilesystem ¶
type LibvirtFilesystem struct {
Type string `yaml:"type,omitempty" json:"type,omitempty"`
Driver string `yaml:"driver,omitempty" json:"driver,omitempty"`
AccessMode string `yaml:"accessmode,omitempty" json:"accessmode,omitempty"`
Source string `yaml:"source,omitempty" json:"source"`
Target string `yaml:"target,omitempty" json:"target"`
Readonly *bool `yaml:"readonly,omitempty" json:"readonly,omitempty"`
Binary map[string]string `yaml:"binary,omitempty" json:"binary,omitempty"`
}
type LibvirtGraphics ¶
type LibvirtGraphics struct {
Type string `yaml:"type,omitempty" json:"type"`
Port int `yaml:"port,omitempty" json:"port,omitempty"`
AutoPort string `yaml:"autoport,omitempty" json:"autoport,omitempty"`
Listen LibvirtGraphicsListeners `yaml:"listen,omitempty" json:"listen,omitempty"`
Passwd string `yaml:"passwd,omitempty" json:"passwd,omitempty"`
Keymap string `yaml:"keymap,omitempty" json:"keymap,omitempty"`
// gl: libvirt models <gl> PER GRAPHICS TYPE, not as one shared element, so this
// is a struct rather than the scalar it used to be. A bare `gl: "yes"` could
// only ever reach spice's enable= attribute and had NO way to express
// rendernode= — which is the attribute that points virtio-gpu at a specific
// host DRM node, and therefore the whole reason a GPU-in-VM candy touches <gl>
// at all. `<acceleration rendernode=…>` on #LibvirtVideo is NOT the substitute:
// libvirt documents that one as vhostuser-driver-only.
GL *LibvirtGraphicsGL `yaml:"gl,omitempty" json:"gl,omitempty"`
// address/p2p are dbus-only (<graphics type='dbus' address=… p2p=…/>).
Address string `yaml:"address,omitempty" json:"address,omitempty"`
// The per-type field rules — <gl> exists only on spice/egl-headless/dbus,
// gl.enable only on spice/dbus, address/p2p only on dbus — are NOT expressible
// here. Writing them as `if type == … { gl?: _|_ }` type-checks in CUE but
// makes `cue exp gengotypes` emit `type LibvirtGraphics any`: it evaluates the
// definition with `type` still abstract, so every branch stays unresolved, the
// field kinds reduce to bottom, and the generator degrades the WHOLE struct.
// That compiles, so the damage is silent — every graphics block would decode
// into an untyped map. (The `if firmware == …` rules on #Vm are safe only
// because they TIGHTEN fields to concrete values instead of to bottom.)
//
// They are enforced instead as hard render errors in the libvirt bridge's
// mapGraphics, which is the exact point where the field would otherwise be
// dropped on the floor, and where the message can name the reason.
P2P *bool `yaml:"p2p,omitempty" json:"p2p,omitempty"`
}
type LibvirtGraphicsGL ¶ added in v0.2026240.1952
type LibvirtGraphicsGL struct {
Enable *bool `yaml:"enable,omitempty" json:"enable,omitempty"`
// render_node: absolute path to the host DRM render node (/dev/dri/renderD128).
// Requires libvirt >= 5.8.0 (spice) / >= 5.10.0 (egl-headless).
RenderNode string `yaml:"render_node,omitempty" json:"render_node,omitempty"`
}
type LibvirtGraphicsListen ¶
type LibvirtGraphicsListen struct {
Type string `yaml:"type,omitempty" json:"type,omitempty"`
Address string `yaml:"address,omitempty" json:"address,omitempty"`
Network string `yaml:"network,omitempty" json:"network,omitempty"`
Socket string `yaml:"socket,omitempty" json:"socket,omitempty"`
}
--------------------------------------------------------------------------- LibvirtGraphicsListen / LibvirtGraphicsListeners — #LibvirtListen (libvirt_yaml_listen.go). scalar | map | list of listeners. ---------------------------------------------------------------------------
type LibvirtGraphicsListeners ¶
type LibvirtGraphicsListeners []LibvirtGraphicsListen
LibvirtGraphicsListeners is the YAML-shaped list of listeners for one <graphics> element.
func (LibvirtGraphicsListeners) First ¶
func (ll LibvirtGraphicsListeners) First() LibvirtGraphicsListen
First returns the first listener, or the zero value if empty.
func (*LibvirtGraphicsListeners) UnmarshalJSON ¶
func (ll *LibvirtGraphicsListeners) UnmarshalJSON(data []byte) error
UnmarshalJSON accepts the three interchangeable listen shapes on the JSON read path (the opaque substrate-decode path a de-typed vm body takes): a bare scalar address ("127.0.0.1" → one type:address listener), a single object, or a list of objects. The YAML read path decodes the same three via cue.Value.Decode running THIS method (the Matcher/PortScope self-decoding strategy), so ONE decoder now serves both paths (R3) — replacing the former charly-side expandLibvirtListenersNode YAML expander.
type LibvirtHostdev ¶
type LibvirtHostdev struct {
Type string `yaml:"type" json:"type"`
Mode string `yaml:"mode,omitempty" json:"mode,omitempty"`
Managed string `yaml:"managed,omitempty" json:"managed,omitempty"`
Source map[string]string `yaml:"source" json:"source"`
ROM map[string]string `yaml:"rom,omitempty" json:"rom,omitempty"`
Driver map[string]string `yaml:"driver,omitempty" json:"driver,omitempty"`
}
--------------------------------------------------------------------------- LibvirtHostdev — #LibvirtHostdev (libvirt_yaml.go). The `if type=="pci"` source-redefine degrades the generated def to `any`. ---------------------------------------------------------------------------
type LibvirtHub ¶
type LibvirtHub struct {
Type string `yaml:"type,omitempty" json:"type"`
}
type LibvirtHugepages ¶
type LibvirtHyperV ¶
type LibvirtHyperV struct {
Relaxed string `yaml:"relaxed,omitempty" json:"relaxed,omitempty"`
VAPIC string `yaml:"vapic,omitempty" json:"vapic,omitempty"`
Spinlocks *LibvirtSpinlocks `yaml:"spinlocks,omitempty" json:"spinlocks,omitempty"`
VPIndex string `yaml:"vpindex,omitempty" json:"vpindex,omitempty"`
Runtime string `yaml:"runtime,omitempty" json:"runtime,omitempty"`
Synic string `yaml:"synic,omitempty" json:"synic,omitempty"`
STimer string `yaml:"stimer,omitempty" json:"stimer,omitempty"`
Reset string `yaml:"reset,omitempty" json:"reset,omitempty"`
VendorID *LibvirtVendorID `yaml:"vendor_id,omitempty" json:"vendor_id,omitempty"`
Frequencies string `yaml:"frequencies,omitempty" json:"frequencies,omitempty"`
Reenlightenment string `yaml:"reenlightenment,omitempty" json:"reenlightenment,omitempty"`
TLBFlush string `yaml:"tlbflush,omitempty" json:"tlbflush,omitempty"`
IPI string `yaml:"ipi,omitempty" json:"ipi,omitempty"`
EVMCS string `yaml:"evmcs,omitempty" json:"evmcs,omitempty"`
}
HyperV enlightenment toggles — all "on"/"off"-ish strings; no Go validator, kept plain string to avoid false-rejecting valid libvirt values.
type LibvirtIOMMU ¶
type LibvirtIOThreadPin ¶
type LibvirtInput ¶
type LibvirtInterface ¶
type LibvirtInterface struct {
Type string `yaml:"type,omitempty" json:"type,omitempty"`
Source map[string]string `yaml:"source,omitempty" json:"source,omitempty"`
Model string `yaml:"model,omitempty" json:"model,omitempty"`
MAC string `yaml:"mac,omitempty" json:"mac,omitempty"`
MTU int `yaml:"mtu,omitempty" json:"mtu,omitempty"`
Driver map[string]string `yaml:"driver,omitempty" json:"driver,omitempty"`
Boot int `yaml:"boot,omitempty" json:"boot,omitempty"`
PortForwards []LibvirtPortForward `yaml:"port_forwards,omitempty" json:"port_forwards,omitempty"`
}
type LibvirtKVM ¶
type LibvirtKVM struct {
Hidden string `yaml:"hidden,omitempty" json:"hidden,omitempty"`
HintDedicated string `yaml:"hint_dedicated,omitempty" json:"hint_dedicated,omitempty"`
PollControl string `yaml:"poll_control,omitempty" json:"poll_control,omitempty"`
PVIPI string `yaml:"pv_ipi,omitempty" json:"pv_ipi,omitempty"`
DirtyRingSize int `yaml:"dirty_ring_size,omitempty" json:"dirty_ring_size,omitempty"`
}
type LibvirtLaunchSecurity ¶
type LibvirtLaunchSecurity struct {
Type string `yaml:"type,omitempty" json:"type,omitempty"`
CBitPos int `yaml:"cbitpos,omitempty" json:"cbitpos,omitempty"`
ReducedPhysBits int `yaml:"reduced_phys_bits,omitempty" json:"reduced_phys_bits,omitempty"`
Policy string `yaml:"policy,omitempty" json:"policy,omitempty"`
DhCert string `yaml:"dh_cert,omitempty" json:"dh_cert,omitempty"`
Session string `yaml:"session,omitempty" json:"session,omitempty"`
KernelHashes string `yaml:"kernel_hashes,omitempty" json:"kernel_hashes,omitempty"`
}
type LibvirtListenOne ¶
type LibvirtMemBalloon ¶
type LibvirtMemTune ¶
type LibvirtMemTune struct {
HardLimit string `yaml:"hard_limit,omitempty" json:"hard_limit,omitempty"`
SoftLimit string `yaml:"soft_limit,omitempty" json:"soft_limit,omitempty"`
SwapHardLimit string `yaml:"swap_hard_limit,omitempty" json:"swap_hard_limit,omitempty"`
MinGuarantee string `yaml:"min_guarantee,omitempty" json:"min_guarantee,omitempty"`
}
type LibvirtMemnode ¶
type LibvirtMemoryBacking ¶
type LibvirtMemoryBacking struct {
Hugepages *LibvirtHugepages `yaml:"hugepages,omitempty" json:"hugepages,omitempty"`
Locked *bool `yaml:"locked,omitempty" json:"locked,omitempty"`
Source string `yaml:"source,omitempty" json:"source,omitempty"`
Access string `yaml:"access,omitempty" json:"access,omitempty"`
Allocation string `yaml:"allocation,omitempty" json:"allocation,omitempty"`
Discard *bool `yaml:"discard,omitempty" json:"discard,omitempty"`
}
type LibvirtNUMACell ¶
type LibvirtNUMACell struct {
ID int `yaml:"id,omitempty" json:"id,omitempty"`
CPUs string `yaml:"cpus,omitempty" json:"cpus,omitempty"`
Memory string `yaml:"memory,omitempty" json:"memory,omitempty"`
Unit string `yaml:"unit,omitempty" json:"unit,omitempty"`
MemAccess string `yaml:"memaccess,omitempty" json:"memaccess,omitempty"`
}
type LibvirtNUMAMemory ¶
type LibvirtNUMATune ¶
type LibvirtNUMATune struct {
Memory *LibvirtNUMAMemory `yaml:"memory,omitempty" json:"memory,omitempty"`
MemNodes []LibvirtMemnode `yaml:"memnodes,omitempty" json:"memnodes,omitempty"`
}
type LibvirtPCIHex ¶
type LibvirtPCIHex string
PCI source address component: 0x-hex OR bare decimal (hexUintPtr accepts both).
type LibvirtPanic ¶
type LibvirtParallel ¶
type LibvirtPortForward ¶
type LibvirtRNG ¶
type LibvirtRedirDev ¶
type LibvirtResource ¶
type LibvirtSecLabel ¶
type LibvirtSecLabel struct {
Type string `yaml:"type,omitempty" json:"type,omitempty"`
Model string `yaml:"model,omitempty" json:"model,omitempty"`
Relabel string `yaml:"relabel,omitempty" json:"relabel,omitempty"`
Label string `yaml:"label,omitempty" json:"label,omitempty"`
BaseLabel string `yaml:"baselabel,omitempty" json:"baselabel,omitempty"`
ImageLabel string `yaml:"imagelabel,omitempty" json:"imagelabel,omitempty"`
}
type LibvirtSerial ¶
type LibvirtShmem ¶
type LibvirtShmem struct {
Name string `yaml:"name,omitempty" json:"name"`
Role string `yaml:"role,omitempty" json:"role,omitempty"`
Model map[string]string `yaml:"model,omitempty" json:"model,omitempty"`
Size string `yaml:"size,omitempty" json:"size,omitempty"`
Server map[string]string `yaml:"server,omitempty" json:"server,omitempty"`
}
type LibvirtSmartcard ¶
type LibvirtSound ¶
type LibvirtSound struct {
Model string `yaml:"model,omitempty" json:"model"`
}
type LibvirtSpinlocks ¶
type LibvirtSysInfo ¶
type LibvirtSysInfo struct {
Type string `yaml:"type,omitempty" json:"type,omitempty"`
BIOS map[string]string `yaml:"bios,omitempty" json:"bios,omitempty"`
System map[string]string `yaml:"system,omitempty" json:"system,omitempty"`
BaseBoard []map[string]string `yaml:"baseboard,omitempty" json:"baseboard,omitempty"`
Chassis map[string]string `yaml:"chassis,omitempty" json:"chassis,omitempty"`
Processor []map[string]string `yaml:"processor,omitempty" json:"processor,omitempty"`
OEMStrings []string `yaml:"oem_strings,omitempty" json:"oem_strings,omitempty"`
}
type LibvirtTPM ¶
type LibvirtTimer ¶
type LibvirtTimer struct {
Name string `yaml:"name,omitempty" json:"name"`
Present string `yaml:"present,omitempty" json:"present,omitempty"`
Track string `yaml:"track,omitempty" json:"track,omitempty"`
TickPolicy string `yaml:"tickpolicy,omitempty" json:"tickpolicy,omitempty"`
Frequency int `yaml:"frequency,omitempty" json:"frequency,omitempty"`
Mode string `yaml:"mode,omitempty" json:"mode,omitempty"`
}
type LibvirtURI ¶ added in v0.2026213.1726
type LibvirtURI struct {
// Mode is "session" (currently the only supported value).
Mode string
// Remote is the SSH target when scheme is qemu+ssh://. Zero
// value means local (qemu:///session).
Remote SSHTarget
// Raw is the original URI string for error messages.
Raw string
}
LibvirtURI is the parsed form of an `charly --uri …` value.
func ParseLibvirtURI ¶ added in v0.2026213.1726
func ParseLibvirtURI(s string) (LibvirtURI, error)
ParseLibvirtURI handles the four recognized shapes. Empty / missing URI returns a local-session value; invalid URIs return an error.
func (LibvirtURI) IsLocal ¶ added in v0.2026213.1726
func (u LibvirtURI) IsLocal() bool
IsLocal reports whether the URI targets the local libvirt session. Equivalent to len(u.Remote.Host) == 0.
type LibvirtUSB ¶
type LibvirtVCPUPin ¶
type LibvirtVendorID ¶
type LibvirtVideo ¶
type LibvirtVideo struct {
Model string `yaml:"model,omitempty" json:"model"`
// device: the concrete QEMU device behind the model. `model` alone cannot select
// these: model='virtio' emits plain virtio-vga, which has no GL and therefore no
// blob/native-context support. Requires libvirt >= 12.5.0.
//
// The vocabulary is closed because libvirt's own RNG closes it (domaincommon.rng,
// the type='virtio' group): any other value is rejected at DEFINE time with an
// error that blames <devices>, not the attribute. Rejecting it here names the field.
Device string `yaml:"device,omitempty" json:"device,omitempty"`
Ram int `yaml:"ram,omitempty" json:"ram,omitempty"`
VRAM int `yaml:"vram,omitempty" json:"vram,omitempty"`
VRAM64 int `yaml:"vram64,omitempty" json:"vram64,omitempty"`
VGAMem int `yaml:"vgamem,omitempty" json:"vgamem,omitempty"`
Heads int `yaml:"heads,omitempty" json:"heads,omitempty"`
// blob: virtio-gpu blob resources — the guest maps host memory directly
// instead of copying through the device. Required for a native-context or
// venus guest. Requires libvirt >= 9.2.0 and QEMU >= 6.1, and the domain MUST
// have shared memory backing (memory_backing.source: memfd + access: shared).
Blob *bool `yaml:"blob,omitempty" json:"blob,omitempty"`
EDID *bool `yaml:"edid,omitempty" json:"edid,omitempty"`
Accel3D *bool `yaml:"accel3d,omitempty" json:"accel3d,omitempty"`
Accel2D *bool `yaml:"accel2d,omitempty" json:"accel2d,omitempty"`
// render_node on <acceleration> is documented by libvirt as VHOSTUSER-DRIVER
// ONLY (since 5.8.0). To point an ordinary virtio-gpu at a host node, set
// graphics.gl.render_node instead — that is the attribute libvirt actually
// reads for it.
RenderNode string `yaml:"render_node,omitempty" json:"render_node,omitempty"`
Primary *bool `yaml:"primary,omitempty" json:"primary,omitempty"`
Resolution *LibvirtVideoResolution `yaml:"resolution,omitempty" json:"resolution,omitempty"`
Driver *LibvirtVideoDriver `yaml:"driver,omitempty" json:"driver,omitempty"`
// alias: a libvirt USER alias for this device. Its only purpose is to be
// targeted by libvirt.qemu_override — libvirt refuses an override against its
// own auto-assigned alias (video0), which is why the `ua-` prefix is required
// rather than conventional. Emitted ONLY when declared, so a VM that does not
// use it renders byte-identically to before this field existed.
Alias string `yaml:"alias,omitempty" json:"alias,omitempty"`
}
type LibvirtVideoDriver ¶ added in v0.2026240.1952
type LibvirtVideoDriver struct {
// Both enums are closed by libvirt's RNG (domaincommon.rng, the video <driver>
// element). A name like "qxl" — the plausible guess, since it is a valid video
// MODEL — is not a valid driver name and fails at define time.
Name string `yaml:"name,omitempty" json:"name,omitempty"`
VGAConf string `yaml:"vgaconf,omitempty" json:"vgaconf,omitempty"`
// The virtioOptions toggles. libvirt spells these on/off, where the video model's
// own attributes beside them are yes/no — the renderer keeps the two apart.
IOMMU *bool `yaml:"iommu,omitempty" json:"iommu,omitempty"`
ATS *bool `yaml:"ats,omitempty" json:"ats,omitempty"`
Packed *bool `yaml:"packed,omitempty" json:"packed,omitempty"`
PagePerVQ *bool `yaml:"page_per_vq,omitempty" json:"page_per_vq,omitempty"`
}
type LibvirtVideoResolution ¶ added in v0.2026240.1952
type LibvirtVsock ¶
type LibvirtWatchdog ¶
type LifecycleOpts ¶
type LifecycleOpts struct {
DryRun bool `yaml:"dry_run,omitempty" json:"dry_run,omitempty"`
AllowRepoChanges bool `yaml:"allow_repo_changes,omitempty" json:"allow_repo_changes,omitempty"`
AllowRootTasks bool `yaml:"allow_root_tasks,omitempty" json:"allow_root_tasks,omitempty"`
WithServices bool `yaml:"with_services,omitempty" json:"with_services,omitempty"`
AssumeYes bool `yaml:"assume_yes,omitempty" json:"assume_yes,omitempty"`
Verify bool `yaml:"verify,omitempty" json:"verify,omitempty"`
Pull bool `yaml:"pull,omitempty" json:"pull,omitempty"`
SkipIncompatible bool `yaml:"skip_incompatible,omitempty" json:"skip_incompatible,omitempty"`
BuilderImageOverride string `yaml:"builder_image_override,omitempty" json:"builder_image_override,omitempty"`
}
#LifecycleOpts is the serializable subset of the host's EmitOpts shipped in a lifecycle Op's params. The two LIVE EmitOpts fields (ParentExec, ParentNode) cannot cross the []byte wire — they re-attach host-side via the reverse channel's live host-build inputs, never serialized. LifecycleOptsFromEmit (spec/deploy_methods.go) is the ONE hand-written converter — a pure function, not a type, so it stays hand-written.
func LifecycleOptsFromEmit ¶
func LifecycleOptsFromEmit(o EmitOpts) LifecycleOpts
LifecycleOptsFromEmit projects an EmitOpts (spec/deploy_executor.go — a LIVE behaviour-contract type that cannot itself cross the wire, since EmitOpts.ParentExec carries a live DeployExecutor) onto its wire-safe LifecycleOpts subset — the ONE shared conversion the deploy-dispatch envelope (S3b, #DeployTargetDispatchRequest.opts_json) uses on BOTH sides: charly core (unified_targets.go's Add/Update) builds this BEFORE marshaling into OptsJSON — never marshal a raw EmitOpts directly — and candy/plugin-fleet decodes OptsJSON directly into a LifecycleOpts, so no plugin-side conversion is needed at all.
type LifecyclePrepareInput ¶
type LifecyclePrepareInput struct {
Entity string `yaml:"entity,omitempty" json:"entity"`
VM *ResolvedVm `yaml:"vm,omitempty" json:"vm,omitempty"`
SSHUser string `yaml:"ssh_user,omitempty" json:"ssh_user"`
SSHPort int `yaml:"ssh_port,omitempty" json:"ssh_port"`
Alias string `yaml:"alias,omitempty" json:"alias"`
SSHKeyPath string `yaml:"ssh_key_path,omitempty" json:"ssh_key_path"`
KnownHostsPath string `yaml:"known_hosts_path,omitempty" json:"known_hosts_path"`
StateDir string `yaml:"state_dir,omitempty" json:"state_dir"`
PriorState *VmDeployState `yaml:"prior_state,omitempty" json:"prior_state,omitempty"`
}
#LifecyclePrepareInput is the host-resolved DATA a vm substrate's OpPrepareVenue needs but cannot derive itself.
type LifecycleTarget ¶ added in v0.2026218.138
type LifecycleTarget interface {
UnifiedDeployTarget
// Start brings the target up (charly start / podman start / virsh
// start / systemctl start as appropriate). Idempotent: no-op if
// already running.
Start(ctx context.Context) error
// Stop brings the target down. Idempotent.
Stop(ctx context.Context) error
// Status reports the target's live runtime state.
Status(ctx context.Context) (DeployTargetStatus, error)
// Logs streams or tails the target's logs. See DeployTargetLogsOpts
// for follow/tail semantics.
Logs(ctx context.Context, opts DeployTargetLogsOpts) error
// Shell opens an interactive shell in the target. cmd, if
// non-empty, is run instead of starting a login shell.
Shell(ctx context.Context, cmd []string) error
// Attach runs an INTERACTIVE or live-stdio session on the target's venue,
// wired to the operator's terminal. It is the `charly shell` / `charly
// cmd` leg — distinct from Shell (the `charly service` NON-interactive
// capture leg). tty selects the resolver + PTY policy: tty=true is
// `charly shell` (a `-it` TTY, with an ephemeral-run fallback for a
// stopped pod); tty=false is `charly cmd` (a `-i` inherited-stdin exec
// into the running container). cmd is the command argv (empty ⇒ an
// interactive login shell). The host resolves the venue-local command;
// the owning plugin runs it over the served venue executor via
// RunInteractive (stdio stays host-side). A non-zero exit is propagated
// as a typed exit-code error.
Attach(ctx context.Context, cmd []string, tty bool) error
// Rebuild is destroy + create + start. Gated on the target's
// Disposable flag — each implementation must verify it before
// any destructive action.
Rebuild(ctx context.Context, opts DeployTargetRebuildOpts) error
}
LifecycleTarget extends UnifiedDeployTarget for live-runtime targets (host, vm, pod). Kubernetes does NOT implement this: its cluster lifecycle is kubectl-managed outside charly. Commands that require a live runtime (charly start/stop/status/logs/shell/rebuild) assert the interface and error uniformly on kubernetes targets.
type LoadedDoc ¶
type LoadedDoc struct {
// directives is the RAW reserved-directive mapping serialized as YAML bytes (NOT the RawBody
// JSON the entity bodies use) — so the host replays the ORIGINAL mergeUnifiedDocs decode exactly
// (`yaml.Unmarshal(directives, &sub)`), honoring the custom YAML unmarshalers on import/discover.
Directives []byte `yaml:"directives,omitempty" json:"directives,omitempty"`
Project ParsedProject `yaml:"project,omitempty" json:"project,omitempty"`
SrcDir string `yaml:"srcDir,omitempty" json:"srcDir,omitempty"`
SrcLabel string `yaml:"srcLabel,omitempty" json:"srcLabel,omitempty"`
}
#LoadedDoc — one parsed document of a namespace's flattened file tree (root file OR a flat import), in merge order. `directives` is the RAW reserved-directive mapping bytes (version/repo/defaults/provides/discover) the host yaml-decodes into a sub *UnifiedFile before merging (import is consumed by the walk); `project` is the document's decomposed entity nodes; `srcDir` anchors the document's discover paths + labels diagnostics.
type LoadedProject ¶
type LoadedProject struct {
ID int64 `yaml:"id,omitempty" json:"id,omitempty"`
Docs []LoadedDoc `yaml:"docs,omitempty" json:"docs,omitempty"`
Discovered []DiscoveredManifest `yaml:"discovered,omitempty" json:"discovered,omitempty"`
Namespaces []*NamespaceMount `yaml:"namespaces,omitempty" json:"namespaces,omitempty"`
}
#LoadedProject — the whole WALK result of one project (root OR a mounted namespace): a stable per-walk id (so a namespaced cycle/diamond back-reference resolves to the SAME materialized *UnifiedFile), the ordered documents, the discovered manifests, and the mounted namespace subtrees. The host replays materialize+merge over this to reconstruct the typed *UnifiedFile (identical to the former in-line loadUnifiedInto), then applies the binary-embedded default vocab.
type LoaderExecutor ¶ added in v0.2026213.1726
type LoaderExecutor interface {
// LoaderThreaded returns the CURRENT registry-derived snapshot (recognized kinds / deploy
// substrates / DeployTraits / ExternalDeploySubstrates / …). Called FRESH at each DATA-seam
// invocation — NEVER cached at seam-build time — because the walk's connect-declared-kind pass
// mutates the registry BETWEEN seam construction and the post-walk validators.
LoaderThreaded() Threaded
// RunBootstrapPhase invokes every registered bootstrap-phase plugin on the raw root bytes,
// returning the (possibly transformed) bytes. A leg failure (e.g. a broken reverse-channel
// HostBuild round trip) is a hard error — never a silent no-op fallback to the raw bytes,
// which would let LoadUnified proceed on an un-bootstrapped root with zero visible signal.
RunBootstrapPhase(data []byte) ([]byte, error)
// WalkProject runs the kind-blind import/discover/namespace walk (the registered ProjectWalker,
// reached via the host's WalkSeams) → the generic LoadedProject envelope. The host #NodeDoc CUE
// gate (WalkSeams.GateDoc) runs INSIDE this walk.
WalkProject(dir string, rootData []byte) (LoadedProject, error)
// MaterializeLoadedProject replays the host's per-document/per-namespace MATERIALIZE + root-wins
// MERGE over the walk envelope (registry kind-decode via the registered Materializer).
MaterializeLoadedProject(lp *LoadedProject, merged *UnifiedFile, byID map[int64]*UnifiedFile) error
// ValidateAndroidDevices enforces the kind:android box⊻adb XOR — resolves android templates via
// the provider registry (host-coupled), so a leg, not a pure loaderkit move.
ValidateAndroidDevices(uf *UnifiedFile) error
// ValidatePreemptible validates preemptible / requires_exclusive / requires_shared across the
// deploy map, including the resource-vocabulary cross-check (resolves the resource plugin kind +
// vm/resource entities via the registry) — host-coupled, so a leg.
ValidatePreemptible(uf *UnifiedFile) error
}
LoaderExecutor is the typed host-leg contract for the registry-/host-coupled loader steps the kind-blind LoadUnified orchestration cannot do itself: the bootstrap-phase plugin invocation, the registry-coupled import/discover walk, the materialize kind-decode + merge, and the two registry-resolving validators. Promoted here from sdk/loaderkit (#55 loader-keystone) so charly core can hold the host LoaderExecutor implementation while importing ONLY the dedicated spec module — the interface's method signatures already reference only spec types (Threaded / LoadedProject / UnifiedFile), so promoting it is a relocation, not an invention. Because the methods are TYPED, a compiled-in placement pays no envelope tax; only a true out-of-module plugin marshals (the existing LoadedProject / UnifiedFile envelopes). loaderkit.LoadSeamsFromExecutor consumes this to build its internal LoadSeams.
type LoaderWalkRequest ¶
type LoaderWalkRequest struct {
Dir string `yaml:"dir,omitempty" json:"dir"`
RootData []byte `yaml:"root_data,omitempty" json:"root_data,omitempty"`
}
#LoaderWalkRequest is the "loader-walk" host-builder envelope (K1-LOADER RELOCATION, Unit B/D): a plugin driving loaderkit.LoadUnified plugin-side asks the HOST to run the kind-blind import/discover/namespace walk for a project dir over the ALREADY bootstrap-transformed root bytes. RootData is the raw (bootstrap-phase-transformed) YAML, carried as base64-over-JSON []byte (NOT RawBody — it is YAML, not JSON). The reply is a spec.LoadedProject marshalled directly (no reply envelope needed). Every OTHER loader leg carries an existing type verbatim over the []byte wire: loader-bootstrap ([]byte→[]byte), loader-threaded (∅→spec.Threaded), loader-materialize (spec.LoadedProject→loaderkit.UnifiedFile). The former loader-android-validate / loader-preempt-validate legs dissolved — a plugin-side loader now self-serves those validators over InvokeProvider(kind, OpResolve).
type Local ¶
type Local struct {
// Ordered candy stack applied to the host. Required (empty list permitted —
// a staged name-reservation stub; the loader warns, not errors).
Candy []CandyRef `yaml:"candy,omitempty" json:"candy"`
InstallOpts *InstallOpts `yaml:"install_opts,omitempty" json:"install_opts,omitempty"`
Env map[string]string `yaml:"env,omitempty" json:"env,omitempty"`
Description string `yaml:"description,omitempty" json:"description,omitempty"`
Plan []Step `yaml:"plan,omitempty" json:"plan,omitempty"`
}
type LocalPkg ¶
type LocalPkg struct {
InstallTemplate string `yaml:"install_template,omitempty" json:"install_template"`
Probe string `yaml:"probe,omitempty" json:"probe"`
DownloadTemplate string `yaml:"download_template,omitempty" json:"download_template,omitempty"`
}
#LocalPkg — the local_pkg INSTALL machinery only (the source-build fields pkg_glob/source_sentinel/build_template/dep_builder were removed with the pkg/ source-build cutover: the `charly generate-packages` plugin builds the package now, so the deploy-time + image-build paths only INSTALL the published package via install_template/download_template).
type LocalPkgBuildContext ¶ added in v0.2026225.1800
type LocalPkgBuildContext struct {
// Binary is the in-development charly binary to package (default:
// os.Executable()).
Binary string
// PluginsDir is the dir of in-development plugin binaries + .providers to
// package (default: the baked plugins dir /usr/lib/charly/plugins/).
PluginsDir string
// CalVer is the in-development release CalVer (default: `<Binary> version`).
CalVer string
// Arch is the target GOARCH name (default: runtime.GOARCH).
Arch string
// CandyYAML is the charly candy's charly.yml — the `--candy` input the
// plugin reads the packaging section from (default: `<CandyDir>/charly.yml`).
CandyYAML string
}
LocalPkgBuildContext is the dev-local-pkg build context: the in-development binary + plugins + CalVer + arch + charly.yml the executor uses to build the in-development package via the `charly generate-packages` plugin. Every field is optional — the executor discovers a default when one is empty (see EmitOpts.LocalPkgBuild).
type LocalPkgInstallStep ¶ added in v0.2026210.2308
type LocalPkgInstallStep struct {
// PackageName is the published package name (e.g. "charly") the deploy-time
// executor downloads from the distro repo.
PackageName string
// Version is the release CalVer (e.g. "2026.225.1200") to download.
Version string
// CandyName is the candy that declared the packaging section.
CandyName string
// CandyDir is the candy's source dir — the anchor for the candy's charly.yml
// (the `--candy` input the dev-local-pkg build passes to `charly
// generate-packages`). Set at compile time from the candy's SourceDir.
CandyDir string
// Format is the package-format name whose `local_pkg:` config drives this
// step (e.g. "pac"). "" when the target distro declares no localpkg-capable
// format — the executor then treats the step as a clean no-op.
Format string
// LocalPkg is the format's localpkg contract resolved from the embedded build vocabulary (charly/charly.yml) at
// compile time (DistroDef.LocalPkgFormat). It carries the install/download
// templates and probe command — so the executor renders every package-manager
// command from config instead of hardcoding install/glob literals. The install
// command auto-resolves the package's dependencies from the target's repos
// (pacman -U / dnf install / apt-get install), so there is no dep-closure
// builder. Nil when Format == "".
LocalPkg *LocalPkg
}
--------------------------------------------------------------------------- LocalPkgInstallStep — install the published charly package from the distro repo onto a deploy target (the download leg). ---------------------------------------------------------------------------
LocalPkgInstallStep is the IR form of a candy's `localpkg:` field — a reference to the published native package (name + version) the deploy-time executor DOWNLOADS from the distro's package repo (the `download_template`) and installs via the `install_template`. It is the proper-package counterpart of the charly candy's ad-hoc curl-a-binary `cmd:` task: on an Arch/CachyOS DEPLOY target the published `.pkg.tar.zst` is downloaded from the distro repo and `pacman -U`-installed — so `charly` lands as the tracked `charly` package at /usr/bin/charly rather than an untracked binary at /usr/local/bin/charly.
(The old source-build form — PkgbuildRef/CandyDir/ProjectDir anchors for a bundled PKGBUILD built on the host via makepkg — was removed with the pkg/ source-build cutover: the `charly generate-packages` plugin builds the package now, so the deploy-time path only INSTALLS the published package.)
Like ApkInstallStep, the step is compiled REGARDLESS of target and each EmitTarget decides whether to execute or skip:
- the external local deploy (Arch/CachyOS host) and external vm deploy (Arch/CachyOS guest) EXECUTE it via the RunHostStep host-engine leg (download → transfer → pacman -U on the target).
- On a NON-pac deploy target the executor records a clean skip (a Fedora / Debian host has no pacman; the candy's own `cmd:` task curls the binary there as the documented fallback).
- The IMAGE build (generate.go writeCandySteps) AND the pod-overlay build-emit RENDER the localpkg IMAGE install via RenderLocalPkgImageInstall (localpkg.go, a PURE sdk/deploykit function since W3): a PRODUCTION box DOWNLOADS the published release, a DISPOSABLE check bed BUILDS the in-development package on the host and COPYs it in (both install via the SAME dep-resolving install template). The pod-overlay build-emit routes through candy/plugin-installstep's `step:local-pkg-install` OpEmit, which STILL calls back the host `step-emit` seam (stepEmitLocalPkgInstall) — not because the render needs core, but to thread the shared `buildEngineContext` alongside its genuinely host-coupled siblings (system-packages/builder/op) on the same seam. A distro with no localpkg-capable format (LocalPkg==nil) renders nothing; the candy's own COPY/curl `cmd:` task is the fallback there.
- the android / kubernetes substrates (external) SKIP it (no Arch package surface).
func (*LocalPkgInstallStep) Kind ¶ added in v0.2026210.2308
func (s *LocalPkgInstallStep) Kind() StepKind
func (*LocalPkgInstallStep) RequiresGate ¶ added in v0.2026210.2308
func (s *LocalPkgInstallStep) RequiresGate() Gate
func (*LocalPkgInstallStep) Reverse ¶ added in v0.2026210.2308
func (s *LocalPkgInstallStep) Reverse() []ReverseOp
Reverse returns no ledger ops — the package is the deploy substrate's own pacman-tracked package, removed (if ever) via `pacman -R charly` by the operator, not by deploy teardown. Mirrors ApkInstallStep's empty Reverse.
func (*LocalPkgInstallStep) Scope ¶ added in v0.2026210.2308
func (s *LocalPkgInstallStep) Scope() Scope
Scope is system — installing a pacman package mutates global package state.
func (*LocalPkgInstallStep) Venue ¶ added in v0.2026210.2308
func (s *LocalPkgInstallStep) Venue() Venue
Venue is host-native — the download + install runs host-side (the executor downloads the published package and ships it to the target), then the target installs it via pacman.
type LocalResolveInput ¶
type LocalResolveInput struct {
Local RawBody `yaml:"local,omitempty" json:"local"`
}
#LocalResolveInput / #AndroidResolveInput carry one opaque template body to project.
type LocalResolveReply ¶
type LocalResolveReply struct {
Resolved *ResolvedLocal `yaml:"resolved,omitempty" json:"resolved,omitempty"`
}
#LocalResolveReply / #AndroidResolveReply wrap the resolved template.
type MCPProvideEntry ¶
type MCPProvideEntry struct {
Name string `yaml:"name,omitempty" json:"name"`
URL string `yaml:"url,omitempty" json:"url"`
Transport string `yaml:"transport,omitempty" json:"transport,omitempty"`
Source string `yaml:"source,omitempty" json:"source"`
}
#MCPProvideEntry is a RESOLVED mcp_provide entry — a CandyMCPProvide (the raw baked label form) plus the Source deployment it came from. Shared by charly's DEPLOY-time provides injection AND the out-of-process mcp CHECK verb (candy/plugin-mcp), which both apply PodAwareMCPProvides; it lives in package spec (ONE copy, R3) so it crosses the charly/plugin module boundary. Its GetName/GetSource methods satisfy charly's Named interface structurally (hand-written behavior, not wire shape).
func PodAwareMCPProvides ¶
func PodAwareMCPProvides(entries []MCPProvideEntry, consumerKey, ctrName string) []MCPProvideEntry
PodAwareMCPProvides rewrites same-deployment entries' URLs to localhost (a peer MCP server in the SAME pod is reached over loopback, not its container-DNS name) and prefers a local entry over a cross-deployment one of the same name. consumerKey is the consuming deployment's box name; ctrName is its running container name. Pure — moved verbatim from charly/provides.go.
func (MCPProvideEntry) GetName ¶
func (e MCPProvideEntry) GetName() string
MCPProvideEntry (schema/candy.cue, generated into cue_types_gen.go) is the CUE-sourced RESOLVED mcp_provide entry. Its GetName/GetSource let it satisfy charly's Named interface (provides.go) — hand-written behavior, not wire shape.
func (MCPProvideEntry) GetSource ¶
func (e MCPProvideEntry) GetSource() string
type Marketplace ¶ added in v0.2026218.1950
type Marketplace struct {
Name string `yaml:"name,omitempty" json:"name"`
Version string `yaml:"version,omitempty" json:"version"`
Description string `yaml:"description,omitempty" json:"description,omitempty"`
Families map[string]MarketplaceFamily `yaml:"families,omitempty" json:"families"`
Settings MarketplaceSettings `yaml:"settings,omitempty" json:"settings,omitempty"`
}
CUE schema for the `marketplace` KIND — the single harness/marketplace config entity per repo (the migration of plugins/.claude-plugin/marketplace.json + profiles.json + the harness wiring into candy config). A `marketplace:` node is a top-level entity in a candy-dir file (e.g. candy/charly-marketplace/charly.yml), discovered by the recursive `candy/` walk.
families: one entry per plugin family (the plugins/ directory name). The generator emits plugins/<family>/.claude-plugin/plugin.json + .codex-plugin/plugin.json, plugins/<family>/.mcp.json (when mcp_servers is non-empty), the aggregated plugins/.claude-plugin/marketplace.json and plugins/profiles.json, and the .claude/settings.json plugin-owned keys.
type MarketplaceFamily ¶ added in v0.2026218.1950
type MarketplaceFamily struct {
Category string `yaml:"category,omitempty" json:"category,omitempty"`
Description string `yaml:"description,omitempty" json:"description,omitempty"`
Keywords []string `yaml:"keywords,omitempty" json:"keywords,omitempty"`
Version string `yaml:"version,omitempty" json:"version,omitempty"`
Profiles []string `yaml:"profiles,omitempty" json:"profiles,omitempty"`
McpServers []MarketplaceMCPServer `yaml:"mcp_servers,omitempty" json:"mcp_servers,omitempty"`
}
type MarketplaceMCPServer ¶ added in v0.2026218.1950
type MarketplaceMCPServer struct {
Name string `yaml:"name,omitempty" json:"name"`
Type string `yaml:"type,omitempty" json:"type,omitempty"`
Url string `yaml:"url,omitempty" json:"url,omitempty"`
Command string `yaml:"command,omitempty" json:"command,omitempty"`
Args []string `yaml:"args,omitempty" json:"args,omitempty"`
}
type MarketplaceSettings ¶ added in v0.2026218.1950
type MarketplaceSettings struct {
// enabled_plugins — the .claude/settings.json enabledPlugins charly-* entries (each
// "charly-<family>@<name>"); empty ⇒ enable every declared family.
EnabledPlugins []string `yaml:"enabled_plugins,omitempty" json:"enabled_plugins,omitempty"`
// source_path — extraKnownMarketplaces.<name>.source.path (default ./plugins).
SourcePath string `yaml:"source_path,omitempty" json:"source_path,omitempty"`
// hooks — the hook entity names to wire into settings.json hooks.<trigger>.
Hooks []string `yaml:"hooks,omitempty" json:"hooks,omitempty"`
}
type MatchOpMap ¶
Matcher operators (validMatcherOps, validate_check.go). A matcher is a scalar (implicit equals), or a single-operator map; #MatcherList accepts a single matcher OR a list. (The base no longer carries a contains-default list def — that shape left with the `file` verb's `contains` field and is now reproduced standalone in the file plugin's #FileContains, decoded with the substring default via decodeContainsList; no base #Op field uses it anymore.)
type Matcher ¶
Matcher is a goss-style matcher: a scalar (implicit equality) or an operator map ({equals: X}, {contains: […]}, {matches: R}, {lt: N}, …).
func (Matcher) MarshalJSON ¶
MarshalJSON makes the JSON write path the exact inverse of UnmarshalJSON. A ZERO (absent) matcher — Op=="" — marshals to `null`, which UnmarshalJSON maps back to the zero value; without this, the default struct marshal emitted `{"op":""}`, which UnmarshalJSON's single-key-map branch misread as Op="op" (an asymmetry that corrupted every zero matcher round-tripped through JSON — e.g. the all-zero check-matcher fields of an install Op carried in the per-step IR, or a baked-label Plan step). A NON-zero matcher marshals to the SAME canonical `{"op":...,"value":...}` form the default marshaler produced, so real matchers (and existing baked labels) are byte-unchanged.
func (Matcher) MarshalYAML ¶
MarshalYAML emits the canonical operator-map form so round-tripping is stable.
func (*Matcher) UnmarshalJSON ¶
UnmarshalJSON accepts the canonical `{"op":"...","value":...}` form, an operator-map (`{"equals": X}`), a bare scalar (`"PONG"` → equals), and `null` / empty (→ the zero matcher, the inverse of MarshalJSON's zero case).
type MatcherList ¶
type MatcherList []Matcher
MatcherList lets users write scalar/single-map shorthand where a list of Matchers is expected. `stdout: PONG` and `stdout: [PONG]` decode identically.
func (*MatcherList) UnmarshalJSON ¶
func (ml *MatcherList) UnmarshalJSON(data []byte) error
UnmarshalJSON mirrors the scalar-shorthand behavior for the JSON read path.
type MaterializeProjectSeams ¶ added in v0.2026213.1726
type MaterializeProjectSeams struct {
// MaterializeProject folds ONE document's parsed entity nodes into uf via the registered
// Materializer (registry kind-decode + the per-node not-found policy). uf already carries the
// document's decoded reserved directives; this adds the Box/Candy/Fleet/PluginKinds entities,
// accumulating across the document's node list.
MaterializeProject func(pp *ParsedProject, uf *UnifiedFile) error
// FoldDiscoveredManifests folds every discovered manifest's parsed nodes into uf — a LAYER candy
// registers a lazy `From:` reference (the bootstrap-critical candyIsImage box⊻layer routing stays
// host-side), every other kind materializes via the registered Materializer. Shared host-side with
// charly's ApplyDiscover (R3), so it stays one host leaf.
FoldDiscoveredManifests func(dms []DiscoveredManifest, uf *UnifiedFile) error
// ApplyEmbeddedDefaults merges the binary-embedded build vocabulary + sidecar templates UNDER uf's
// own entries (project-wins). Host-resident: the embedded bytes and the host node-form parser are
// charly's.
ApplyEmbeddedDefaults func(uf *UnifiedFile) error
}
MaterializeProjectSeams gathers the host-coupled leaf legs the kind-blind whole-project MATERIALIZE orchestration (loaderkit.MaterializeLoadedProject) calls out to. Each is registry-/host-/bootstrap- coupled and CANNOT run kind-blind inside loaderkit; the host wrapper (charly's hostMaterializeProjectSeams) is the sole constructor and always populates every field. A nil field panics on use. Relocated to the dedicated spec module (#55 2b C3) so the host supplies it as a spec-typed seam via LoaderExecutor.MaterializeProjectSeams() — loaderkit drives the orchestration internally, and charly core stops importing loaderkit to load its own config. All fields are callbacks over spec types (the same injected-seam shape as WalkSeams / MaterializeSeams).
type MaterializeSeams ¶
type MaterializeSeams struct {
// DecodeEntity resolves pn's discriminator against the provider registry and, if a provider is
// found, folds the decoded entity into acc (mutating whichever field the kind belongs in) — the
// SAME dispatch the former in-core normalizeNodeInto/runPluginKind always performed. found=false
// (no error) means the registry has no provider for pn.Disc — the Materializer applies its OWN
// not-found policy from there, using Threaded + the callbacks below.
DecodeEntity func(pn ParsedNode, acc *MaterializedProject) (found bool, err error)
// BuildFleetEntity folds pn as a deploy-substrate entity into acc.Fleet — the fallback for a
// RECOGNIZED (Threaded.DeploySubstrates) but not-yet-connected external deploy substrate word.
BuildFleetEntity func(pn ParsedNode, acc *MaterializedProject) error
// InKindConnectPass reports whether the loader is inside the re-entrant connect-declared-kind
// pre-pass (a nested load triggered by connecting a plugin) — a still-unconnected declared kind
// is silently deferred (skip, no error) during this pass.
InKindConnectPass func() bool
// DeclaredKindConnectError returns the retained build/connect failure for a declared-but-
// unconnected kind word, or nil if it simply hasn't been reached yet.
DeclaredKindConnectError func(word string) error
}
MaterializeSeams is the set of host-supplied callbacks the per-node kind-decode DISPATCH needs for everything registry-coupled — resolving a node's discriminator word to its live Provider and invoking it stays host-side (boundary law clause M: provider_registry.go + provider_kind_invoke.go are the TRUE mechanism, same bucket as WalkSeams.Boundary/ResolveRef/GateDoc above); the Materializer NEVER touches the provider registry directly.
type MaterializedProject ¶
type MaterializedProject struct {
Box map[string]json.RawMessage
Candy map[string]json.RawMessage
Fleet map[string]FleetNode
PluginKinds map[string]map[string]json.RawMessage
}
MaterializedProject accumulates the kind-decoded ENTITY maps ONE document's or ONE discovered node's fold produces — the SAME fields charly-core's *UnifiedFile carries for this purpose (Box/Candy/Fleet/PluginKinds); the host copies them in before a Materializer call and back out after (cheap map-header copies — maps are reference types, so this is NOT a deep copy).
The 5 standalone-substrate-TEMPLATE kinds (vm/pod/kubernetes/local/android) do NOT get their own dedicated fields here — they fold into PluginKinds[disc][name] like every other templated kind (distro/builder/init/sidecar/resource/agent already do), so foldStandaloneTemplateReply (charly/node_normalize.go) needs NO per-kind-word switch to pick a destination field: the generic write `acc.PluginKinds[disc][name] = replyJSON` IS the fold, for any disc. charly-core's UnifiedFile.VM()/.Pod()/.Kubernetes()/.Local()/.Android() are now DERIVED accessor methods reading PluginKinds, mirroring the established Distros()/Builders()/Inits() pattern — not stored fields — so there is nothing left to copy for them either.
SDD classification (hand-written, non-wire — precedent: ParsedProject/LoadedProject/CandyRefs/ ScannedCandy above, the established sibling family this type extends): same-process PIPELINE STATE crossing ONLY the compiled-in typed Materializer seam below — never marshaled, because the loader plugin is bootstrap-critical and ALWAYS compiled-in (see the package doc above). A live `cue exp gengotypes` spike on this exact shape (all fields already-portable map[string]json.RawMessage / map[string]FleetNode / map[string]map[string]json.RawMessage — zero disjunctions, zero open tails) would generate a faithful plain struct per the CAN/CANNOT quick reference in /charly-internals:go — CUE-sourcing it is NOT precluded by shape. It stays hand-written here instead because it is not a wire type at all: it never crosses a real marshal/unmarshal boundary (the compiled-in placement passes it as a live *MaterializedProject Go pointer, exactly like WalkSeams' live callbacks above) — the SAME reasoning CandyRefs/ ScannedCandy already document for this file, which the SDD "wire types are CUE-sourced without exception" mandate does not reach (it binds host↔plugin / render-context WIRE carriers; this is same-process pipeline state, the class loader_seam.go's own precedent already carves out).
type Materializer ¶
type Materializer interface {
MaterializeNode(pn ParsedNode, t Threaded, seams MaterializeSeams, acc *MaterializedProject) error
}
Materializer is the swappable per-node kind-decode DISPATCH seam (#46 unit 1, K1): the loader plugin candy implements it (candy/plugin-loader, delegating to loaderkit.Materialize), and the host resolves the registered loader provider to it and calls it once per parsed entity node — so an alternative loader plugin applies a different not-found/fallback policy by implementing this. Typed (no wire envelope), mirroring DocParser/ProjectWalker/CandyScanner above.
type MergeReply ¶
type MergeReply struct {
LayersBefore int `yaml:"layers_before,omitempty" json:"layers_before,omitempty"`
LayersAfter int `yaml:"layers_after,omitempty" json:"layers_after,omitempty"`
Skipped bool `yaml:"skipped,omitempty" json:"skipped,omitempty"`
Error string `yaml:"error,omitempty" json:"error,omitempty"`
Notes []string `yaml:"notes,omitempty" json:"notes,omitempty"`
}
#MergeReply — the OCI plugin's merge outcome. layers_before/layers_after report the layer-count reduction; skipped is true when the image was too large or had nothing to merge; error carries a per-merge failure (the reply-error convention — e.g. the known podman-load EEXIST case — distinct from an infra Go error on Invoke); notes carries the human progress lines the host prints.
type MergeRequest ¶
type MergeRequest struct {
ImageRef string `yaml:"image_ref,omitempty" json:"image_ref"`
MaxMB int `yaml:"max_mb,omitempty" json:"max_mb"`
MaxTotalMB int `yaml:"max_total_mb,omitempty" json:"max_total_mb"`
Engine string `yaml:"engine,omitempty" json:"engine"`
DryRun bool `yaml:"dry_run,omitempty" json:"dry_run,omitempty"`
}
#MergeRequest — the host resolves the box (image ref + merge limits + engine) and hands the OCI plugin everything it needs to run the go-containerregistry layer merge, so the plugin never imports charly core config-loading.
type NamespaceMount ¶
type NamespaceMount struct {
Alias string `yaml:"alias,omitempty" json:"alias,omitempty"`
Ref bool `yaml:"ref,omitempty" json:"ref,omitempty"`
RefID int64 `yaml:"refID,omitempty" json:"refID,omitempty"`
Project LoadedProject `yaml:"project,omitempty" json:"project,omitempty"`
}
#NamespaceMount — one namespaced `import:` (alias → an isolated child project). Two shapes:
- a DEFINITION (ref=false): the mount carries the child project inline (project), whose own `id` the host registers so a later reference to it shares the SAME materialized *UnifiedFile;
- a REFERENCE (ref=true): a repo-identity cycle-break or a diamond re-import — the child was already walked, so the mount carries NO inline project, only refID (the target project's id).
The host materialize resolves a reference to the pointer it registered for that id (register- before-recurse), preserving the original loader's pointer identity across the mutual-import cycle (main↔cachyos). Modeled as a LIST entry (not a self-recursive map) so `cue exp gengotypes` generates faithfully — the pointer slice `[]*NamespaceMount` on #LoadedProject breaks recursion.
type OCIEmitStepParams ¶ added in v0.2026211.549
type OCIEmitStepParams struct {
Dir string `yaml:"dir,omitempty" json:"dir,omitempty"`
StepView InstallStepView `yaml:"step_view,omitempty" json:"step_view"`
PlanView InstallPlanView `yaml:"plan_view,omitempty" json:"plan_view"`
}
#OCIEmitStepParams is the plain-Go param struct the pod-overlay candy marshals into a HostBuild("step-emit", #StepEmitRequest{Word:"oci-emit-step", Payload:<#OCIEmitStepParams>}) per install step (promoted from sdk/deploykit — #55 import-purity, Cone V value-vocabulary). Dir is the project dir the host "oci-emit-step" emitter uses to look up the cached overlay buildEngineContext (a live *Generator/DistroDef/BuilderConfig/Box cannot cross the wire); StepView is the step's wire form (StepToView) and PlanView the step's plan wire form — the host reconstructs both (StepFromView/PlanFromView) then calls ociEmitStep, returning the rendered Containerfile fragment. StepView/PlanView are required (always set by the caller); Dir is optional (empty for the non-overlay per-step emit path).
type Op ¶
type Op struct {
// --- verb discriminators (exactly one set; Go Kind() enforces) ---
Mkdir string `yaml:"mkdir,omitempty" json:"mkdir,omitempty"`
Copy string `yaml:"copy,omitempty" json:"copy,omitempty"`
Write string `yaml:"write,omitempty" json:"write,omitempty"`
Link string `yaml:"link,omitempty" json:"link,omitempty"`
Download string `yaml:"download,omitempty" json:"download,omitempty"`
Setcap string `yaml:"setcap,omitempty" json:"setcap,omitempty"`
Build string `yaml:"build,omitempty" json:"build,omitempty"`
// plugin — the generic PLUGIN-VERB discriminator, INTERNAL-ONLY: the
// parse-time desugar rewrites every authored `<word>: <input>` sugar key
// into this plugin/plugin_input pair, and authoring plugin:/plugin_input:
// directly in a step is a hard load error (run: charly migrate). The pair
// stays on #Op because it is the wire/label form (the desugared tree this
// def validates, the ai.opencharly.description JSON) and the Go dispatch
// surface (providerRegistry.ResolveVerb). plugin_input is validated by the
// PLUGIN's own served CUE schema (not base #Op).
Plugin string `yaml:"plugin,omitempty" json:"plugin,omitempty"`
// --- shared modifiers ---
ID string `yaml:"id,omitempty" json:"id,omitempty"`
Description string `yaml:"description,omitempty" json:"description,omitempty"`
Skip bool `yaml:"skip,omitempty" json:"skip,omitempty"`
Timeout Duration `yaml:"timeout,omitempty" json:"timeout,omitempty"`
// command — INTERNAL-ONLY rehydration target (never authored): the `command`
// plugin verb's INSTALL-EMIT copies plugin_input.command here for emitCmd
// (build) / renderOpCommand (deploy). Authored `command:` on a step IS the
// command plugin's sugar key (scalar = the primary shorthand), consumed by
// the parse-time desugar before this def validates; the wl/libvirt argv
// moved into those plugins' own input defs.
Command string `yaml:"command,omitempty" json:"command,omitempty"`
Context []Context `yaml:"context,omitempty" json:"context,omitempty"`
// `pod:` (per-step container venue) is RETIRED — a step's execution venue is
// derived ENTIRELY from its position in the fleet tree (flattenFleetVenues
// → Op.venue, yaml:"-"). Authoring it is a closed-schema rejection (run:
// charly migrate).
DependsOn []string `yaml:"depends_on,omitempty" json:"depends_on,omitempty"`
// plugin_input — generic params for a `plugin:` verb. Opaque to base #Op
// (accepts any shape); the plugin's own spliced CUE schema validates it.
PluginInput map[string]any `yaml:"plugin_input,omitempty" json:"plugin_input,omitempty"`
// --- install/build modifiers ---
RunAs string `yaml:"run_as,omitempty" json:"run_as,omitempty"`
To string `yaml:"to,omitempty" json:"to,omitempty"`
Target string `yaml:"target,omitempty" json:"target,omitempty"`
Caps string `yaml:"caps,omitempty" json:"caps,omitempty"`
Content string `yaml:"content,omitempty" json:"content,omitempty"`
Extract string `yaml:"extract,omitempty" json:"extract,omitempty"`
ExtractInclude []string `yaml:"extract_include,omitempty" json:"extract_include,omitempty"`
// unless_exists — capability GATE for an install step: when the named path already
// exists in the image, the step is a no-op. It exists because a plan step has no
// build-time distro filter (`exclude_distro:` is read by the CHECK runner, never by
// the emitter), so a candy whose artifact is supplied by the distro package on SOME
// distros and by an upstream archive on the rest had no declarative way to express
// "fetch only where the package did not already provide this". Without it the only
// recourse was to abandon the verb and hand-roll `curl`, which silently forfeits what
// the verb provides: the content-addressed /tmp/downloads cache, the atomic .part
// rename, and `cache:` mounts. Detection rather than a distro name, so any distro that
// later gains the package is picked up with no edit.
UnlessExists string `yaml:"unless_exists,omitempty" json:"unless_exists,omitempty"`
StripComponents int `yaml:"strip_components,omitempty" json:"strip_components,omitempty"`
Uninstall []string `yaml:"uninstall,omitempty" json:"uninstall,omitempty"`
Comment string `yaml:"comment,omitempty" json:"comment,omitempty"`
Cache []string `yaml:"cache,omitempty" json:"cache,omitempty"`
Env StrMap `yaml:"env,omitempty" json:"env,omitempty"`
// --- step modifiers ---
Eventually Duration `yaml:"eventually,omitempty" json:"eventually,omitempty"`
RetryInterval Duration `yaml:"retry_interval,omitempty" json:"retry_interval,omitempty"`
// `on:` (cross-member driver dispatch) is RETIRED — a step that drives a
// peer/driver is authored as a step CHILD of that member node; its venue is
// derived from position (flattenFleetVenues → Op.venue). Authoring `on:` is
// a closed-schema rejection (run: charly migrate).
Tag []string `yaml:"tag,omitempty" json:"tag,omitempty"`
// Origin is populated at collection time (candy:<name>/box:<name>/deploy-*),
// NEVER authored (Go yaml:"-"), but it TRAVELS in the ai.opencharly.description
// OCI-label JSON (Go json:"origin") — so the generated spec.Op MUST carry it
// for a faithful drop-in (do NOT @go(-) it). gengotypes emits
// `yaml:"origin,omitempty" json:"origin,omitempty"`, matching the hand tag.
Origin string `yaml:"origin,omitempty" json:"origin,omitempty"`
// venue + intent_do are RUNTIME-DERIVED, never authored: venue is stamped
// from a step's fleet-tree position (flattenFleetVenues), intent_do from
// the enclosing Step's keyword (run→act / check→assert / agent→instruct).
// They are generated onto the Go struct (the check runner persists them on
// the Op-by-value passed to runOne / EffectiveDo), but the #Step authoring
// arms forbid them (`venue?: _|_, intent_do?: _|_`) so authoring either is a
// closed-schema rejection — exactly the contract the retired `pod:`/`on:`
// fields enforced. yaml:"-" (never read from YAML); json omits them when
// empty (the bake-time state, so they never leak into the description label).
Venue string `yaml:"venue,omitempty" json:"venue,omitempty"`
IntentDo string `yaml:"intent_do,omitempty" json:"intent_do,omitempty"`
// --- file/copy/write SHARED modifier ---
// `mode` is the SHARED octal-permission modifier: the copy/write install verbs read
// Op.Mode at deploy (the external local/vm deploy walk via kit.ParseTaskMode),
// so it STAYS in #Op. The file-EXCLUSIVE fields (file/exists/owner/group_of/filetype/
// contains/sha256) LEFT #Op — they are read ONLY by the `file` plugin verb and now live
// in its #FileInput (candy/plugin-file, with the contains-default semantic
// reproduced via the candy's decodeContainsList). The state-provision migrator MOVES `mode` into a
// file step's plugin_input while LEAVING it here for copy/write (the shared-companion
// pattern, like gid between unix_group and user).
Mode string `yaml:"mode,omitempty" json:"mode,omitempty"`
// exclude_distro — a SHARED step-level skip filter read by the generic runOne for
// EVERY verb (skip the step when any image distro tag intersects the list), NOT a
// package-exclusive field, so it STAYS on #Op. The `package`-exclusive fields
// (installed/version/package_map) MOVED into #PackageInput when `package` extracted.
ExcludeDistros []string `yaml:"exclude_distro,omitempty" json:"exclude_distro,omitempty"`
// --- shared assertion matchers (SHARED via matchAll: the command/http probe
// plugins + every live-verb plugin assert exit_status/stdout/stderr off the
// marshalled step Op, so they STAY in #Op; verb-exclusive request/input
// fields live in each plugin's input def) ---
ExitStatus *int `yaml:"exit_status,omitempty" json:"exit_status,omitempty"`
Stdout MatcherList `yaml:"stdout,omitempty" json:"stdout,omitempty"`
Stderr MatcherList `yaml:"stderr,omitempty" json:"stderr,omitempty"`
}
#Op is the unified operation vocabulary (Go: charly/checkspec.go Op) AFTER the parse-time desugar: the builtin install verbs + the genuinely SHARED step modifiers + the internal plugin/plugin_input pair every plugin-verb sugar key desugars into. CLOSED: an unknown key on the desugared step is a typo (a plugin word never reaches this def — the desugar consumed it). Per-verb fields live in each plugin's own input def, served over Describe.
func (*Op) Kind ¶
Kind returns the single verb set on the Op, or an error when zero or multiple are set (the exactly-one-verb rule).
func (*Op) PluginInputStr ¶
PluginInputStr returns the string value of plugin_input[verb] when this Op is a `plugin: <verb>` op carrying that key — "" otherwise. The ONE home (R3) for the host check runner (checkrun.go) AND the k8sgen plugin's probe builder (candy/plugin-k8sgen), both of which read a migrated `plugin: http`/`addr`/`file`/`command` check subject.
func (*Op) StringFields ¶
StringFields returns pointers to every string-valued modifier field, for the in-place ${VAR} expansion driven by ExpandVars (free function in main).
type OpStep ¶ added in v0.2026210.2308
type OpStep struct {
Op *Op
CandyName string
CandyDir string
CtxPath string // absolute layer-dir path replacing "/ctx/" on host
ResolvedUser string // uid:gid or "root" after resolveUserSpec
// To is the home-resolved copy/download destination for the DEPLOY path.
// The compiler tokenizes the task's `to:` (~ / ${HOME} / $HOME → {{.Home}})
// here; InstallPlan.ResolveHome substitutes the destination's real home at
// emit. execTask uses this (not the raw Task.To) for PutFile, so a
// `copy: to: ${HOME}/...` lands in the actual guest/host home instead of a
// literal "${HOME}" directory under sudo (HOME=/root). Empty when the task
// has no `to:`. The OCI build emitter reads Task.To directly (build-time
// ENV expands ${HOME}), so it is unaffected by this field.
To string
// CandyVars are the candy manifest `vars:` map propagated into the task
// script as exports. Build-time gets these via Containerfile ENV
// directives (emitVarsEnv); host/local-deploy time has no equivalent
// mechanism, so the renderer emits `export K=V` lines from this field.
// Without this, candies like `kubernetes` whose download URLs reference
// ${K3D_VERSION} fetched an empty path-component at deploy time and
// curl 404'd.
CandyVars map[string]string
// Distros is the image's distro tag chain (img.Tags), carried so the act-emit
// enabler can pass it to a state-provision plugin verb's RenderProvisionScript
// (e.g. the cross-distro package resolution). Empty for non-plugin ops; the
// unix_group act ignores it, but a future package/service state-provision plugin
// reads it.
Distros []string
}
OpStep wraps a raw Op so the IR compiler doesn't have to transform it further. The OCI target and host target both understand Op shapes (cmd / mkdir / copy / write / link / download / setcap / build) and emit the appropriate directives. CtxPath substitutes for /ctx/ in cmd bodies on the host target (container RUN directives keep /ctx via bind-mount, which doesn't exist on the host).
func (*OpStep) RequiresGate ¶ added in v0.2026210.2308
type OverlayBuildReply ¶
type OverlayBuildReply struct {
OverlayRef string `yaml:"overlay_ref,omitempty" json:"overlay_ref,omitempty"`
BaseImage string `yaml:"base_image,omitempty" json:"base_image,omitempty"`
DeployName string `yaml:"deploy_name,omitempty" json:"deploy_name,omitempty"`
Error string `yaml:"error,omitempty" json:"error,omitempty"`
// resolved_project is the overlay-scoped resolved-project envelope the
// candy constructs a deploykit.Generator from. Nil when there is no
// add_candy overlay to synthesize (the tag-only path).
ResolvedProject *ResolvedProject `yaml:"resolved_project,omitempty" json:"resolved_project,omitempty"`
// plans is the deployment's compiled InstallPlans serialized as
// InstallPlanViews. Empty for the no-overlay path.
Plans []InstallPlanView `yaml:"plans,omitempty" json:"plans,omitempty"`
// calver is the host's current CalVer.
CalVer string `yaml:"calver,omitempty" json:"calver,omitempty"`
// overlay_candy_security carries each overlay candy's own `security:`
// block.
OverlayCandySecurity map[string]*Security `yaml:"overlay_candy_security,omitempty" json:"overlay_candy_security,omitempty"`
// parent_volumes carries the PARENT deploy node's bind-mount volumes for a
// NESTED pod-in-pod overlay build.
ParentVolumes []DeployVolume `yaml:"parent_volumes,omitempty" json:"parent_volumes,omitempty"`
}
#OverlayBuildReply is what the "overlay" host-builder returns.
type OverlayBuildRequest ¶
type OverlayBuildRequest struct {
Dir string `yaml:"dir,omitempty" json:"dir,omitempty"`
DeployName string `yaml:"deploy_name,omitempty" json:"deploy_name,omitempty"`
Image string `yaml:"image,omitempty" json:"image,omitempty"`
Version string `yaml:"version,omitempty" json:"version,omitempty"`
DryRun bool `yaml:"dry_run,omitempty" json:"dry_run,omitempty"`
AssumeYes bool `yaml:"assume_yes,omitempty" json:"assume_yes,omitempty"`
AllowRepoChanges bool `yaml:"allow_repo_changes,omitempty" json:"allow_repo_changes,omitempty"`
AllowRootTasks bool `yaml:"allow_root_tasks,omitempty" json:"allow_root_tasks,omitempty"`
WithServices bool `yaml:"with_services,omitempty" json:"with_services,omitempty"`
}
#OverlayBuildRequest is the BUILD-ENGINE DISPATCH envelope for the pod-overlay build — the F10 "overlay" host-builder.
type PackageItem ¶
type PackageItem struct {
Name string `yaml:"name" json:"name"`
Description string `yaml:"description,omitempty" json:"description,omitempty"`
}
--------------------------------------------------------------------------- PackageItem — #PackageItem. Bare scalar XOR object form; the bare-scalar shorthand is canonicalized to {name} by the loader normalizer. ---------------------------------------------------------------------------
type PackageSection ¶
type PackageSection struct {
FormatName string `yaml:"format_name,omitempty" json:"format_name,omitempty"`
Packages []string `yaml:"packages,omitempty" json:"packages,omitempty"`
Raw map[string]any `yaml:"raw,omitempty" json:"raw,omitempty"`
}
#PackageSection — a generic format-specific package section (rpm/deb/pac/aur). Raw carries the full YAML map for template rendering. Mirrors deploykit.PackageSection (which now aliases this).
type Packaging ¶ added in v0.2026225.1508
type Packaging struct {
Name string `yaml:"name,omitempty" json:"name"`
Description string `yaml:"description,omitempty" json:"description"`
Maintainer string `yaml:"maintainer,omitempty" json:"maintainer"`
Homepage string `yaml:"homepage,omitempty" json:"homepage,omitempty"`
License string `yaml:"license,omitempty" json:"license,omitempty"`
Section string `yaml:"section,omitempty" json:"section,omitempty"`
Priority string `yaml:"priority,omitempty" json:"priority,omitempty"`
// variants — the named plugin-set variants. The format's `default_variant`
// (or the variant named "default" when unset) packages as the plain `charly`
// package; every other named variant packages as `charly-<variant>`.
Variants map[string]*PackagingVariant `yaml:"variants,omitempty" json:"variants,omitempty"`
// formats — per-format (nFPM name) dependency + default-variant metadata.
Formats map[string]*PackagingFormat `yaml:"formats,omitempty" json:"formats,omitempty"`
}
#Packaging — the native-package metadata section (candy `packaging:`): the common fields (name/description/maintainer/…) + a `variants:` map (named plugin-set variants → `charly-<variant>` packages) + a per-format map (`formats.<fmt>`: deps + the format's default variant). CLOSED. The per-format keys use the nFPM format names (deb/rpm/apk/archlinux/ipk/msix).
type PackagingFormat ¶ added in v0.2026225.1508
type PackagingFormat struct {
Depends []string `yaml:"depends,omitempty" json:"depends,omitempty"`
// optdepends — the Arch `optdepend = <pkg>: <desc>` map (nFPM does not emit
// optdepends; sdk/packagekit post-processes the .pkg.tar.zst to inject them).
OptDepends map[string]string `yaml:"optdepends,omitempty" json:"optdepends,omitempty"`
Recommends []string `yaml:"recommends,omitempty" json:"recommends,omitempty"`
Suggests []string `yaml:"suggests,omitempty" json:"suggests,omitempty"`
// default_variant — the variant name packaged as the plain `charly` package
// for this format (defaults to "default" when unset).
DefaultVariant string `yaml:"default_variant,omitempty" json:"default_variant,omitempty"`
// publisher — the msix Publisher display name (msix-specific).
Publisher string `yaml:"publisher,omitempty" json:"publisher,omitempty"`
// properties — the msix Properties (msix-specific).
Properties map[string]string `yaml:"properties,omitempty" json:"properties,omitempty"`
}
#PackagingFormat — per-format (nFPM name) packaging metadata.
type PackagingVariant ¶ added in v0.2026225.1508
type PackagingVariant struct {
Description string `yaml:"description,omitempty" json:"description"`
// plugins — the default-plugin words this variant ships at
// /usr/lib/charly/plugins/ (a subset of the released plugin tarball).
Plugins []string `yaml:"plugins,omitempty" json:"plugins"`
}
#PackagingVariant — one named plugin-set variant.
type Pacstrap ¶
type Pacstrap struct {
BasePackages []string `yaml:"base_package,omitempty" json:"base_package,omitempty"`
ExtraRepos []PacstrapRepo `yaml:"extra_repo,omitempty" json:"extra_repo,omitempty"`
RuntimePacmanConf string `yaml:"runtime_pacman_conf,omitempty" json:"runtime_pacman_conf,omitempty"`
}
type PacstrapRepo ¶
type ParsedNode ¶
type ParsedNode struct {
Name string `yaml:"name,omitempty" json:"name"`
Disc string `yaml:"disc,omitempty" json:"disc"`
Body RawBody `yaml:"body,omitempty" json:"body,omitempty"`
Children []*ParsedNode `yaml:"children,omitempty" json:"children,omitempty"`
}
#ParsedNode — one decomposed reserved-word node: its name, kind discriminator, the opaque entity body (the complete kind value as JSON, materialized per-kind by the host), and any sub-entity member children (deployable kinds only). The recursive Children mirror the deploy tree's member nesting the host folds into uf.Fleet.
type ParsedPortMapping ¶ added in v0.2026213.1726
type ParsedPortMapping struct {
BindAddr string // explicit bind prefix if present (e.g. "127.0.0.1" or "[::1]"); empty otherwise
Host int
Container int
Protocol string // "udp" / "tcp" / "" — extracted from /udp or /tcp suffix
}
ParsedPortMapping describes the four possible shapes podman accepts:
"P" -> {Host: P, Container: P}
"H:C" -> {Host: H, Container: C}
"IP:H:C" -> {Host: H, Container: C, BindAddr: "IP"}
"[v6]:H:C" -> {Host: H, Container: C, BindAddr: "[v6]"}
Any of those forms may carry a /tcp or /udp suffix on the trailing port.
func ParsePortMapping ¶ added in v0.2026213.1726
func ParsePortMapping(mapping string) (ParsedPortMapping, bool)
ParsePortMapping is the canonical port-mapping parser.
Returns ok=false on unparseable input. Callers that want a loud failure (warning logged, port skipped) should branch on ok.
All in-tree port handling routes through this — ParseHostPort, ParseContainerPort, parseHostPorts (tunnel.go), buildPortMapping (tunnel.go), and localizePort (shell.go) — so a single fix here covers every site that would otherwise mis-handle the IP:H:C form.
type ParsedProject ¶
type ParsedProject struct {
Version string `yaml:"version,omitempty" json:"version,omitempty"`
Nodes []ParsedNode `yaml:"nodes,omitempty" json:"nodes,omitempty"`
Imports []string `yaml:"imports,omitempty" json:"imports,omitempty"`
}
#ParsedProject — the whole parse result: the schema version (for the post-parse gate), the decomposed reserved-word nodes, and the resolved import refs (already fetched + merged by the plugin's import walk). Discover results are decomposed into the same nodes list.
type ParsedRef ¶
type ParsedRef struct {
Raw string // original string, e.g. "@github.com/org/repo/candy/name:v1.0.0"
RepoPath string // e.g. "github.com/org/repo"
SubPath string // e.g. "candy/name" (path within repo)
Name string // e.g. "name" (last segment)
Version string // e.g. "v1.0.0"
}
ParsedRef is a parsed remote reference: `@host/org/repo/sub/path:version`. Works for both candy refs and image refs.
func ParseRemoteRef ¶
ParseRemoteRef parses a remote reference into repo path, sub-path, name, and version. e.g. "@github.com/org/repo/candy/name:v1.0.0" -> ParsedRef{RepoPath: "github.com/org/repo", SubPath: "candy/name", Name: "name", Version: "v1.0.0"}
type Phase ¶
type Phase int
Phase identifies which template phase (prepare / install / cleanup) a rendered step belongs to. The phase lets targets treat repo-config mutation (prepare) distinctly from package install — which is exactly the granularity --allow-repo-changes gates. A single format section in the candy manifest typically emits three PhasePrepare/PhaseInstall/PhaseCleanup steps, and the compiler tags each with the appropriate phase.
type PhaseCarrier ¶
type PhaseCarrier interface {
PluginPhase() string
}
PhaseCarrier is implemented by a provider (grpcProvider out-of-proc, inprocProvider compiled-in) that carries its declared lifecycle PHASE (F9). A provider not implementing it (e.g. a builtin non-plugin provider) is treated as PhaseRuntime by the host's phaseOfProvider.
type PhaseSet ¶
type PhaseSet struct {
Prepare *PhaseTemplates `yaml:"prepare,omitempty" json:"prepare,omitempty"`
Install *PhaseTemplates `yaml:"install,omitempty" json:"install,omitempty"`
Cleanup *PhaseTemplates `yaml:"cleanup,omitempty" json:"cleanup,omitempty"`
}
Three-phase template set: prepare → install → cleanup, each with a container (Containerfile) and host (shell) rendering. Template bodies are Go text/template — strings, never parsed here.
type PhaseTemplates ¶
type PhaseTemplates struct {
// install is the ONE venue-agnostic package-install body (repo setup + key
// import + package install, written with `&& \` continuations valid in both
// plain shell and a Dockerfile RUN). FormatPhaseTemplate wraps it per venue:
// the container venue prepends `RUN {{cacheMounts .CacheMounts}} \`, the host
// venue runs it as-is. host/container remain as venue-specific OVERRIDES for
// the prepare/cleanup phases and for any install that genuinely differs by
// venue (R3: one canonical body, venue applied at render, never two copies).
Install string `yaml:"install,omitempty" json:"install,omitempty"`
Container string `yaml:"container,omitempty" json:"container,omitempty"`
Host string `yaml:"host,omitempty" json:"host,omitempty"`
}
type Plugin ¶
type Plugin struct {
// providers: the "<class>:<word>" reserved-word capabilities this plugin
// serves (e.g. "verb:exampleprobe", "kind:my-thing"). Each is registered into
// providerRegistry — built-in (init()) or out-of-tree (gRPC).
Providers []PluginCapability `yaml:"providers,omitempty" json:"providers"`
// source: "builtin" (Go compiled into the charly binary, init()-registered) OR
// a git ref (github.com/org/repo[/sub][@tag]) fetched via the @github resolver +
// built into a provider binary. Default builtin.
Source string `yaml:"source,omitempty" json:"source"`
// primary: verb word → the input field its scalar sugar shorthand targets
// (`file: /x` → plugin_input: {<primary>: "/x"}). Declared in the MANIFEST so
// the byte-gated prescan registers it BEFORE the out-of-process provider
// connects (the parse-time desugar needs it pre-parse); the served
// ProvidedCapability.Primary mirrors it for the compiled-in placement.
Primary map[string]string `yaml:"primary,omitempty" json:"primary,omitempty"`
}
#Plugin — the candy's plugin declaration. Its presence makes the candy a plugin (Go: charly/checkspec.go via the generated Candy.Plugin field, consumed by plugin_loader.go). CLOSED.
type PluginCapability ¶
type PluginCapability string
#PluginCapability — a "<class>:<word>" capability string. class ∈ the closed ProviderClass set; word is lowercase-hyphenated.
type Pod ¶
type Pod struct {
// References a kind:box (bare lowercase-hyphenated name or remote ref).
// Optional: the Go field has no non-empty validator.
Box CandyRef `yaml:"box,omitempty" json:"box,omitempty"`
// Each entry is a SidecarConfig wrapper: a single-key map of named
// sidecar templates (PodSpec.Sidecar []SidecarConfig).
Sidecar []PodSidecar `yaml:"sidecar,omitempty" json:"sidecar,omitempty"`
Secret []DeploySecret `yaml:"secret,omitempty" json:"secret,omitempty"`
EnvDefaults map[string]string `yaml:"env_default,omitempty" json:"env_default,omitempty"`
Plan []Step `yaml:"plan,omitempty" json:"plan,omitempty"`
}
type PodAttachOpts ¶
type PodAttachOpts struct {
Cmd []string `yaml:"cmd,omitempty" json:"cmd,omitempty"`
Tty bool `yaml:"tty,omitempty" json:"tty"`
Shell PodShellOpts `yaml:"shell,omitempty" json:"shell,omitempty"`
CmdOpts PodCmdOpts `yaml:"cmd_opts,omitempty" json:"cmd_opts,omitempty"`
}
#PodAttachOpts carries the F12 Attach op's parameters (K4/F12 inversion): tty selects the shell resolver (interactive `charly shell`) vs the cmd resolver (`charly cmd`'s non-interactive `-i` exec) — mirrors the former host-side resolvePodAttachPlan dispatch, now run plugin-side.
type PodCmdOpts ¶
type PodCmdOpts struct {
Sidecar string `yaml:"sidecar,omitempty" json:"sidecar,omitempty"`
}
#PodCmdOpts carries `charly cmd`'s per-invocation extra (--sidecar), replacing the former host-side resolvePodCmdPlan.
type PodCmdPayload ¶ added in v0.2026217.301
type PodCmdPayload struct {
Command string `yaml:"command,omitempty" json:"command,omitempty"`
Sidecar string `yaml:"sidecar,omitempty" json:"sidecar,omitempty"`
}
#PodCmdPayload — see #PodLifecycleRequest's header; op="cmd". Carries `charly cmd <box> <command>`'s per-invocation fields: the host does ONLY the irreducible dispatchLifecycleTarget("cmd") + LifecycleTarget.Attach step, mirroring op="shell" exactly — the interactive `-i` exec runs over the SAME host-held exec.RunInteractive leg (stdio never crosses the wire). The plugin owns the CLI grammar + the completion notification itself.
type PodConfigBoxEngineReply ¶
type PodConfigBoxEngineReply struct {
Engine string `yaml:"engine,omitempty" json:"engine"`
}
type PodConfigBoxEngineRequest ¶
type PodConfigBoxEngineRequest struct {
Box string `yaml:"box,omitempty" json:"box"`
Instance string `yaml:"instance,omitempty" json:"instance,omitempty"`
GlobalEngine string `yaml:"global_engine,omitempty" json:"global_engine"`
}
#PodConfigBoxEngineRequest / Reply: ResolveBoxEngineForDeploy(box,instance,globalEngine) — reads the per-host deploy config's Engine override. A thin wrapper distinct from the (now-deleted) load-deploy seam since callers here want only the resolved engine string, not the whole FleetConfig.
type PodConfigCleanDeployEntryReply ¶
type PodConfigCleanDeployEntryReply struct {
}
type PodConfigCleanDeployEntryRequest ¶
type PodConfigCleanDeployEntryRequest struct {
Box string `yaml:"box,omitempty" json:"box"`
Instance string `yaml:"instance,omitempty" json:"instance,omitempty"`
}
#PodConfigCleanDeployEntryRequest / Reply: deploykit.CleanDeployEntry(box, instance, marshalDeployNode) — the `charly remove` deploy-entry cleanup (Cutover B unit 2 remove-verb completion). Follows the {box!, instance?} → {} host-owns-load+lock+mutate+save shape — deliberately NOT the plugin-side deploykit.SaveDeployState/SaveFleetConfig write (fleet import/reset + deploy-state persist, #55 K4 — no host seam), which persists an ALREADY-LOADED, already-mutated whole FleetConfig with no internal load/lock/entry-removal logic — a genuinely different, narrower operation CleanDeployEntry's own internal file-lock + entry-removal + provides-cleanup + empty-file-delete logic cannot be reduced to.
type PodConfigContainerTunnelReply ¶
type PodConfigContainerTunnelReply struct {
TunnelJSON RawBody `yaml:"tunnel_json,omitempty" json:"tunnel_json,omitempty"`
}
type PodConfigContainerTunnelRequest ¶
type PodConfigContainerTunnelRequest struct {
Box string `yaml:"box,omitempty" json:"box"`
Instance string `yaml:"instance,omitempty" json:"instance,omitempty"`
}
#PodConfigContainerTunnelRequest / Reply: reads the RUNNING container's baked image ref (containerImage), extracts + merges its metadata, and resolves the tunnel config. Distinct from #PodConfigTunnelResolveRequest (which takes an already-resolved MetaJSON) — this seam resolves the image/metadata itself from a container name. candy/plugin-deploy-pod's start/stop path builds its own tunnel plan locally now (enc_tunnel_resolve.go, wave γ); this seam STAYS registered because candy/plugin-pod's `charly remove` teardown path (remove_tunnel.go) is a separate, still-live caller.
type PodConfigMigrateSecretsRequest ¶
type PodConfigMigrateSecretsRequest struct {
ConfigJSON RawBody `yaml:"config_json,omitempty" json:"config_json"`
MetaJSON RawBody `yaml:"meta_json,omitempty" json:"meta_json"`
Box string `yaml:"box,omitempty" json:"box"`
Instance string `yaml:"instance,omitempty" json:"instance,omitempty"`
}
#PodConfigMigrateSecretsRequest / Reply: MigratePlaintextEnvSecret(dc, meta, box, instance) — the one-time plaintext-env → credential-store migration (file backup + DefaultCredentialStore + saveFleetConfigNodeForm, all FINAL/K5-deferred registry-coupled inventory per the ledger). config_json carries the ALREADY-LOADED dc (the plugin's own loaded overlay — the former #PodConfigLoadDeployRequest is deleted, K-wave 2 cone R3) so the host mutates + re-saves the SAME loaded structure the plugin is mid-flow with, never a stale reload.
type PodConfigRemoveReply ¶
type PodConfigRemoveReply struct {
}
#PodConfigRemoveReply is the OpConfigRemove handler's reply — empty.
type PodConfigRemoveRequest ¶
type PodConfigRemoveRequest struct {
Box string `yaml:"box,omitempty" json:"box"`
Instance string `yaml:"instance,omitempty" json:"instance,omitempty"`
}
#PodConfigRemoveRequest carries `charly config remove`'s flags (the former BoxConfigRemoveCmd's authored fields — distinct from `charly remove`/#PodLifecycleRequest op="remove"+#PodRemovePayload, which tears down the whole deploy; this removes only the quadlet + disables the service). Dispatched to the deploy:pod plugin's OpConfigRemove handler VERBATIM as Params, peer-to-peer from candy/plugin-pod's ConfigRemoveCmd (the former HostBuild("pod-config-remove") forwarder is DELETED, K-wave 2 cone R3).
type PodConfigSaveFleetReply ¶ added in v0.2026219.1057
type PodConfigSaveFleetReply struct {
}
type PodConfigSaveFleetRequest ¶ added in v0.2026219.1057
type PodConfigSaveFleetRequest struct {
ConfigJSON RawBody `yaml:"config_json,omitempty" json:"config_json"`
}
#PodConfigSaveFleetRequest / Reply: saveFleetConfigNodeForm(dc) — persists a (plugin-mutated) *deploykit.FleetConfig back through the SAME loader-coupled seam.
type PodConfigScrubCliEnvRequest ¶
type PodConfigScrubCliEnvRequest struct {
CliEnv []string `yaml:"cli_env,omitempty" json:"cli_env,omitempty"`
MetaJSON RawBody `yaml:"meta_json,omitempty" json:"meta_json"`
}
#PodConfigScrubCliEnvRequest / Reply: scrubSecretCLIEnv(cliEnv, meta) — the credential-store Set() pre-scrub for `-e NAME=VAL` flags declared secret_accepts/secret_requires.
type PodConfigSetupReply ¶
type PodConfigSetupReply struct {
SidecarList SidecarList `yaml:"sidecar_list,omitempty" json:"sidecar_list,omitempty"`
}
#PodConfigSetupReply is the OpConfigSetup handler's reply — empty for the normal setup path, mirroring #PodLifecycleReply's empty-for-every-op-but-cmd shape. The `--list-sidecars` leaf (r.list_sidecars) fills sidecar_list instead: the plugin's own embedded template library (sidecar_embedded.go — the "pod-config-list-sidecars" seam is DELETED, K-wave 2 cone R3), so candy/plugin-pod prints it in the charly CLI's own stdio (the out-of-process plugin's stdout is go-plugin-discarded).
type PodConfigSetupRequest ¶
type PodConfigSetupRequest struct {
Box string `yaml:"box,omitempty" json:"box,omitempty"`
Tag string `yaml:"tag,omitempty" json:"tag,omitempty"`
Build bool `yaml:"build,omitempty" json:"build,omitempty"`
Env []string `yaml:"env,omitempty" json:"env,omitempty"`
Clean bool `yaml:"clean,omitempty" json:"clean,omitempty"`
EnvFile string `yaml:"env_file,omitempty" json:"env_file,omitempty"`
Instance string `yaml:"instance,omitempty" json:"instance,omitempty"`
Port []string `yaml:"port,omitempty" json:"port,omitempty"`
KeepMounted bool `yaml:"keep_mounted,omitempty" json:"keep_mounted,omitempty"`
Password string `yaml:"password,omitempty" json:"password,omitempty"`
RefreshSecret []string `yaml:"refresh_secret,omitempty" json:"refresh_secret,omitempty"`
VolumeFlag []string `yaml:"volume_flag,omitempty" json:"volume_flag,omitempty"`
Bind []string `yaml:"bind,omitempty" json:"bind,omitempty"`
Encrypt []string `yaml:"encrypt,omitempty" json:"encrypt,omitempty"`
MemoryMax string `yaml:"memory_max,omitempty" json:"memory_max,omitempty"`
MemoryHigh string `yaml:"memory_high,omitempty" json:"memory_high,omitempty"`
MemorySwapMax string `yaml:"memory_swap_max,omitempty" json:"memory_swap_max,omitempty"`
Cpus string `yaml:"cpus,omitempty" json:"cpus,omitempty"`
Seed bool `yaml:"seed,omitempty" json:"seed,omitempty"`
ForceSeed bool `yaml:"force_seed,omitempty" json:"force_seed,omitempty"`
DataFrom string `yaml:"data_from,omitempty" json:"data_from,omitempty"`
UpdateAll bool `yaml:"update_all,omitempty" json:"update_all,omitempty"`
SSHKey string `yaml:"ssh_key,omitempty" json:"ssh_key,omitempty"`
Sidecar []string `yaml:"sidecar,omitempty" json:"sidecar,omitempty"`
ListSidecars bool `yaml:"list_sidecars,omitempty" json:"list_sidecars,omitempty"`
NoAutoDetect bool `yaml:"no_autodetect,omitempty" json:"no_autodetect,omitempty"`
// explicit_ref is set programmatically (never authored) by `charly fleet from-box`'s
// source-less deploy path (from_box_pod.go) — the P13-KERNEL direction-flip carries
// it across the wire now that the ORCHESTRATION (formerly reading the kong:"-" Go field
// directly) moved into the plugin.
ExplicitRef string `yaml:"explicit_ref,omitempty" json:"explicit_ref,omitempty"`
// host_env_json is the marshalled #HostEnv (CharlyBin/Home/Version) — the SAME R10
// bed-found bug class #DeployTargetDispatchRequest's own host_env_json field documents
// (unified_targets.go's hostEnvJSON(): os.Executable() resolves correctly to the charly
// binary only when called IN CORE — a plugin's os.Executable() resolves to the PLUGIN
// binary, wrong for an out-of-process placement). Setup's quadlet emission needs the REAL
// charly binary path for the encrypted-mount ExecStartPre line
// (deploykit.QuadletConfig.CharlyBin); the bug was dormant here until a deploy actually had
// an encrypted volume to mount (check-enc-pod's R10 first exercised it once the
// project-declared-volume fallback started resolving one). The HOST computes it (core's
// hostEnvJSON(), R3 — one host-identity helper) and threads it as DATA on the OpRun dispatch
// envelope; candy/plugin-pod forwards it verbatim into this field (the former
// hostBuildPodConfigSetup forwarder is DELETED, K-wave 2 cone R3).
HostEnvJSON RawBody `yaml:"host_env_json,omitempty" json:"host_env_json,omitempty"`
}
#PodConfigSetupRequest carries the `charly config [setup]` command flags (the former BoxConfigSetupCmd's authored fields, PLUS explicit_ref — from_box_pod.go's programmatically-set source-less-deploy field, below). P13-KERNEL direction-flip: the deploy:pod plugin's sdk.OpConfigSetup handler receives it VERBATIM as Params. The former HostBuild("pod-config-setup") forwarder is DELETED (K-wave 2 cone R3) — candy/plugin-pod's ConfigSetupCmd (and plugin-fleet's from_box_pod.go) dispatch the op peer-to-peer via InvokeProvider; the plugin RUNS the former runConfig orchestration (candy/plugin-deploy-pod/config_setup.go).
type PodConfigTunnelResolveReply ¶
type PodConfigTunnelResolveReply struct {
TunnelJSON RawBody `yaml:"tunnel_json,omitempty" json:"tunnel_json,omitempty"`
}
type PodConfigTunnelResolveRequest ¶
type PodConfigTunnelResolveRequest struct {
MetaJSON RawBody `yaml:"meta_json,omitempty" json:"meta_json"`
}
#PodConfigTunnelResolveRequest / Reply: TunnelConfigFromMetadata(meta) — resolves the tunnel config (charly.yml overlay applied) from image labels.
type PodConfigWriteReply ¶
type PodConfigWriteReply struct {
WrittenPaths []string `yaml:"written_paths,omitempty" json:"written_paths,omitempty"`
}
#PodConfigWriteReply returns the paths the plugin actually wrote (deterministic; the host already knows them — used for the byte-parity assertion + teardown provenance).
type PodConfigWriteRequest ¶
type PodConfigWriteRequest struct {
PodConfigJSON RawBody `yaml:"pod_config_json,omitempty" json:"pod_config_json"`
ContainerPath string `yaml:"container_path,omitempty" json:"container_path"`
PodPath string `yaml:"pod_path,omitempty" json:"pod_path,omitempty"`
SidecarPaths map[string]string `yaml:"sidecar_paths,omitempty" json:"sidecar_paths,omitempty"`
TunnelPath string `yaml:"tunnel_path,omitempty" json:"tunnel_path,omitempty"`
CloudflaredCfgPath string `yaml:"cloudflared_cfg_path,omitempty" json:"cloudflared_cfg_path,omitempty"`
}
#PodConfigWriteRequest carries the POD config-WRITE (P11). Under Ruling C the config-WRITE (the quadlet/.pod/sidecar/tunnel file generation) moved to the deploy:pod plugin, while the RESOLVE + host side-effects (secret provisioning, saveDeployState, enc-mount, data-seed, systemctl) stay in the HOST `charly config` command (Q1=(a)). So this is HOST→PLUGIN: for a pod deploy, `charly config` resolves the full QuadletConfig + computes the exact target PATHS (the core filename helpers, unchanged) and PUSHES them to the plugin's config-write Op, which generates the file contents (deploykit.GenerateQuadlet + the pod/sidecar/tunnel generators) and os.WriteFiles them — byte-identical to the former core write phase (same paths, same content, same modes: .container/.pod/sidecar 0600, tunnel .service 0644).
PodConfigJSON is the resolved deploykit.QuadletConfig — a hand-written runtime type with no CUE def, so it travels as an opaque RawBody envelope (the VmJSON pattern; no new CUE wire struct). An optional path field being SET is the host's signal to write that file kind (pod_path/sidecar_paths present ⇒ sidecars configured; tunnel_path present ⇒ cloudflare tunnel) — the host owns the write conditionals, the plugin writes what it is told.
type PodDisposableReply ¶
type PodDisposableReply struct {
Disposable bool `yaml:"disposable,omitempty" json:"disposable,omitempty"`
}
#PodDisposableReply carries the single overlay-disposability bit.
type PodDisposableRequest ¶
type PodDisposableRequest struct {
Name string `yaml:"name,omitempty" json:"name"`
}
#PodDisposableRequest asks the host whether a per-host POD deploy overlay entry is disposable (K5-U2/3). This is the ONE AI-harness check-project fact the resolved-project envelope cannot carry: the harness's iterate sandbox is an OPERATOR-provisioned per-host deploy (`charly fleet add <sandbox> <ref> --disposable`), so its disposability lives in the per-host overlay (LoadFleetConfig → ~/.config/charly/charly.yml), NOT the project charly.yml the resolved-project envelope projects (Mode Purity keeps the overlay out of the build-mode projection). The overlay read needs the core loader a plugin cannot import, and no deploy/status provider serves it, so it rides this THIN retained host seam. The host returns Fleet[Name].IsDisposable() (false when the sandbox has no entry — the harness then skips its fresh-per-run restart). Class-generic action noun "pod-disposable" (F11 — never a substrate word).
type PodExecReply ¶
type PodExecReply struct {
Output string `yaml:"output,omitempty" json:"output,omitempty"`
ExitCode int `yaml:"exit_code,omitempty" json:"exit_code,omitempty"`
}
#PodLifecyclePlan is the pod-lifecycle carrier (the K4 deep-body move). Formerly host-resolved and threaded on OpStart/OpStop; candy/plugin-deploy-pod now SELF-RESOLVES it (resolve.go's resolveStartQuadlet/resolveStopPlan, resolve_direct.go's resolveStartDirect) from the deploy key + the raw CLI opts (#PodStartOpts/#PodStopOpts), reaching the host only for genuinely host-only mechanisms (the deploy-overlay HostBuild seam, verb:credential, verb:enc, verb:tunnel) — this type is now built AND consumed entirely within the plugin process. It EXECUTES it — running the container start/stop over the served host executor and composing enc + tunnel via InvokeProvider(verb:enc/verb:tunnel), so the former podCli("start"/"stop"/…) `charly`-reentries are DELETED (bodies, not shells). The pre-built enc verb input (spec.EncExecInput — a hand-written wire type with no CUE def) rides as an opaque RawBody envelope (empty ⇒ that leg is skipped, the common plain-pod case) with its Method set per-op host-side; tunnel references the CUE-def'd #TunnelConfig directly and the plugin infers start-vs-stop from the op. The ARBITER claim is NOT threaded here — its CHARLY_PREEMPT_LEASE machinery is host-PROCESS state a placement-agnostic plugin cannot own, so the host proxy BRACKETS the plugin op (acquire before OpStart; release after OpStop + on the failure path). #PodExecReply is the reply from the pod plugin's OpShell CAPTURED-exec leg (the K4 `charly service` move — an in-container init-mgmt exec, non-interactive). The plugin RunCaptures the argv over the served executor and returns the combined Output + the exact ExitCode; the host reprints Output (placement-agnostic: an out-of-process plugin's stdout is NOT charly's) and propagates a non-zero ExitCode as *sdk.ExitCodeError so `charly service` preserves the container command's exit code exactly (the passthrough→capture semantics change the ruling requires be exit-code-faithful).
type PodLifecyclePlan ¶
type PodLifecyclePlan struct {
Mode string `yaml:"mode,omitempty" json:"mode"`
SvcName string `yaml:"svc_name,omitempty" json:"svc_name,omitempty"`
ContainerName string `yaml:"container_name,omitempty" json:"container_name"`
RunArgv []string `yaml:"run_argv,omitempty" json:"run_argv,omitempty"`
DirectDeploy bool `yaml:"direct_deploy,omitempty" json:"direct_deploy,omitempty"`
EngineBin string `yaml:"engine_bin,omitempty" json:"engine_bin"`
Unmount bool `yaml:"unmount,omitempty" json:"unmount,omitempty"`
Enc RawBody `yaml:"enc,omitempty" json:"enc,omitempty"`
Tunnel *TunnelConfig `yaml:"tunnel,omitempty" json:"tunnel,omitempty"`
}
type PodLifecycleReply ¶ added in v0.2026217.301
type PodLifecycleReply struct {
ExitCode int `yaml:"exit_code,omitempty" json:"exit_code,omitempty"`
}
#PodLifecycleReply is the "pod-lifecycle" host-builder reply. exit_code is populated only for op="cmd" — the container command's own exit code, so `charly cmd`'s process exit propagates it (the plugin reconstructs an *sdk.ExitCodeError from it) — it cannot ride the HostBuild ERROR return, which stringifies the typed error; it must ride a reply FIELD, exactly as the former __cmd/CliReply.ExitCode path did. Every other op's reply is empty; op-specific progress prints host-side (the compiled-in plugin's HostBuild runs in charly's own process) and failure signals via the error return.
type PodLifecycleRequest ¶ added in v0.2026217.301
type PodLifecycleRequest struct {
Op string `yaml:"op,omitempty" json:"op"`
Box string `yaml:"box,omitempty" json:"box"`
Instance string `yaml:"instance,omitempty" json:"instance,omitempty"`
// node is the per-host deploy overlay entry the command:pod / command:cmd plugin ALREADY
// resolved plugin-side (loaderkit.ResolveLifecycleDeployNodeViaExecutor, the cycle-free
// plugin-side overlay read) and threads as DATA — so the host's dispatchLifecycleTarget
// operates on the passed *spec.Deploy instead of re-reading the per-host config itself (the
// config-READ is a plugin loading capability, not a host M — #55 K4 seam-completion). Seven of
// the eight ops carry it (start/stop/shell/logs/service/cmd + update — which resolves the
// deploy node from the merged tree plugin-side since K-wave 2 cone CONTESTED) and remove
// needs no node at all (it only releases the arbiter claim) — absent for that one.
Node *Deploy `yaml:"node,omitempty" json:"node,omitempty"`
// payload is the op-specific #PodXPayload, JSON-marshalled by the calling command plugin and
// re-decoded host-side once op is known (mirrors the plugin wire protocol's own
// Operation.Params json.RawMessage design — there is no parallel envelope-vs-payload type
// system, R3).
Payload RawBody `yaml:"payload,omitempty" json:"payload,omitempty"`
}
#PodLifecycleRequest is the ONE discriminated request every pod-lifecycle HostBuild op (start/stop/shell/logs/service/cmd/update/remove) sends over the single "pod-lifecycle" HostBuild kind (#55 W3 A10b). Converges the seam on the codebase's own established op-discriminated wire idiom — #ArbiterInvokeInput's flat action-multiplexed shape, charly/provider.go's own Operation.Params json.RawMessage envelope — which the former 8-per-verb #PodXRequest family (one CUE type + one HostBuild kind string per verb, each redeclaring box/instance/node) was the last outlier against. op selects which #PodXPayload type `payload` unmarshals into (host_build_pod_lifecycle_dispatch.go's hostBuildPodLifecycle switch); box/instance/node — common to nearly every op — hoisted OUT of the per-op payloads into this shared envelope (R3).
type PodLiveStdioPlan ¶
type PodLiveStdioPlan struct {
// resolved venue command:
//
// shell → `podman exec -it charly-<box> bash [-c cmd]` OR the ephemeral `podman run --rm -it … bash`
// cmd → `<engine> exec [-e env] charly-<box>[-<sidecar>] sh -c <command>` (no -t; stdin piped)
// logs → `<engine> logs [-f] [-n N] charly-<box>` OR quadlet `journalctl --user -u <svc> [-f] [-n N]`
Script string `yaml:"script,omitempty" json:"script"`
}
#PodLiveStdioPlan is the F12 LIVE-STDIO carrier — ONE carrier for shell + cmd + logs (identical shape, R3; the op + the executor method distinguish them). Formerly host-resolved and threaded on OpAttach/OpLogs op.Params; candy/plugin-deploy-pod now SELF-RESOLVES it (resolve_f12.go's resolveAttachPlan/resolveShellPlan/resolveCmdPlan/resolveLogsPlan) from the deploy key + #PodAttachOpts/#PodLogsOpts, so this type is now built AND consumed entirely within the plugin process — no wire crossing for the plan itself, only for the opts that drive it. The plugin EXECUTES it over the served host executor via exec.RunInteractive (OpAttach — inherited LIVE stdin/stdout/stderr; the child `podman exec -it`/`-i` owns the PTY + resize + Ctrl-C) / exec.RunStream (OpLogs — inherited LIVE stdout/stderr, no stdin). UNARY: the host reverse-server runs IN the charly process, so os.Stdin/os.Stdout = the operator's terminal — stdio NEVER crosses the wire, only the session exit code (the hostBuildCli doctrine). This takes the F12 exit for the shell/cmd/logs-follow rows of the #57 M-core register: the former inline `charly shell`/`cmd` bodies + the podCli("logs") reentry are DELETED (bodies, not shells).
type PodLogsOpts ¶
type PodLogsOpts struct {
Follow bool `yaml:"follow,omitempty" json:"follow,omitempty"`
Tail int `yaml:"tail,omitempty" json:"tail,omitempty"`
Sidecar string `yaml:"sidecar,omitempty" json:"sidecar,omitempty"`
}
#PodLogsOpts carries `charly logs [-f]`'s parameters (K4/F12 inversion), replacing the former host-side resolvePodLogsPlan. Mirrors spec.DeployTargetLogsOpts (Follow/Tail/Sidecar) — the plugin-side wire twin for the pod logs payload, distinct from the dispatch-envelope DeployTargetLogsOpts (K-wave 2 cone R5: the charly-core LogsOpts type is DELETED).
type PodLogsPayload ¶ added in v0.2026217.301
type PodLogsPayload struct {
Follow bool `yaml:"follow,omitempty" json:"follow,omitempty"`
Sidecar string `yaml:"sidecar,omitempty" json:"sidecar,omitempty"`
}
#PodLogsPayload — see #PodLifecycleRequest's header; op="logs". The former LogsCmd's authored fields (F12 — the host resolves the journalctl/`<engine> logs` stream command, the owning plugin streams it live to the operator's stdio).
type PodRemovePayload ¶ added in v0.2026217.301
type PodRemovePayload struct {
Purge bool `yaml:"purge,omitempty" json:"purge,omitempty"`
KeepDeploy bool `yaml:"keep_deploy,omitempty" json:"keep_deploy,omitempty"`
Env []string `yaml:"env,omitempty" json:"env,omitempty"`
}
#PodRemovePayload — see #PodLifecycleRequest's header; op="remove". The former RemoveCmd's authored fields — the host orchestration this ONE op still performs is just the arbiter-release bracket (host_build_pod_lifecycle_dispatch.go's hostBuildPodRemove); the rest of remove's orchestration (quadlet/companion-service teardown, pre_remove hooks, purge, deploy-entry cleanup) runs entirely in candy/plugin-pod now.
type PodResolveInput ¶
type PodResolveInput struct {
Pod RawBody `yaml:"pod,omitempty" json:"pod"`
}
#PodResolveInput carries one opaque pod template body to project.
type PodResolveReply ¶
type PodResolveReply struct {
Resolved *ResolvedPod `yaml:"resolved,omitempty" json:"resolved,omitempty"`
}
#PodResolveReply wraps the resolved pod template.
type PodServicePayload ¶ added in v0.2026217.301
type PodServicePayload struct {
Argv []string `yaml:"argv,omitempty" json:"argv"`
}
#PodServicePayload — see #PodLifecycleRequest's header; op="service". Carries the FULLY plugin-resolved argv for `charly service start/stop/status/restart` (Cutover B unit 2 completion): the plugin now performs resolveServiceInit/validateServiceName/execInitCommand's argv-building itself (all portable — spec.ResolvedInit is already an sdk alias, buildkit.RenderTemplate is sdk-portable) and sends the FINAL `<engine> exec <container> <tool> <op> [svc]` argv; the host does ONLY the irreducible dispatchLifecycleTarget + LifecycleTarget.Shell step.
type PodShellOpts ¶
type PodShellOpts struct {
Tag string `yaml:"tag,omitempty" json:"tag,omitempty"`
EnvFile string `yaml:"env_file,omitempty" json:"env_file,omitempty"`
Env []string `yaml:"env,omitempty" json:"env,omitempty"`
VolumeFlag []string `yaml:"volume_flag,omitempty" json:"volume_flag,omitempty"`
Bind []string `yaml:"bind,omitempty" json:"bind,omitempty"`
NoAutoDetect bool `yaml:"no_auto_detect,omitempty" json:"no_auto_detect,omitempty"`
Interactive bool `yaml:"interactive,omitempty" json:"interactive"`
WrapPTY bool `yaml:"wrap_pty,omitempty" json:"wrap_pty"`
}
#PodShellOpts carries `charly shell`'s per-invocation CLI extras (K4/F12 inversion): the plugin self-resolves the #PodLiveStdioPlan using these + the deploy key, replacing the former host-side resolvePodShellPlan/buildShellArgs/buildExecArgs. interactive/wrap_pty are HOST-RESOLVED booleans (interactive = force_tty || isTerminal(); wrap_pty = force_tty && !isTerminal()) — the plugin is a subprocess whose own stdout is not the operator's terminal, so the tty check MUST happen host-side at the moment of the real CLI invocation and cross the wire as data, never be re-derived plugin-side.
type PodShellPayload ¶ added in v0.2026217.301
type PodShellPayload struct {
Tag string `yaml:"tag,omitempty" json:"tag,omitempty"`
Command string `yaml:"command,omitempty" json:"command,omitempty"`
Build bool `yaml:"build,omitempty" json:"build,omitempty"`
TTY bool `yaml:"tty,omitempty" json:"tty,omitempty"`
Env []string `yaml:"env,omitempty" json:"env,omitempty"`
EnvFile string `yaml:"env_file,omitempty" json:"env_file,omitempty"`
VolumeFlag []string `yaml:"volume_flag,omitempty" json:"volume_flag,omitempty"`
Bind []string `yaml:"bind,omitempty" json:"bind,omitempty"`
NoAutoDetect bool `yaml:"no_autodetect,omitempty" json:"no_autodetect,omitempty"`
}
#PodShellPayload — see #PodLifecycleRequest's header; op="shell". The former ShellCmd's authored fields (F12 — the host resolves the venue command, the owning plugin runs it over the served venue executor via RunInteractive, stdio host-held).
type PodSidecar ¶
#PodSidecar mirrors Go SidecarConfig: a single-key wrapper around a map of named sidecar templates. CLOSED (the struct has exactly one field).
type PodStartOpts ¶
type PodStartOpts struct {
Env []string `yaml:"env,omitempty" json:"env,omitempty"`
EnvFile string `yaml:"env_file,omitempty" json:"env_file,omitempty"`
Port []string `yaml:"port,omitempty" json:"port,omitempty"`
VolumeFlag []string `yaml:"volume_flag,omitempty" json:"volume_flag,omitempty"`
Bind []string `yaml:"bind,omitempty" json:"bind,omitempty"`
NoAutoDetect bool `yaml:"no_auto_detect,omitempty" json:"no_auto_detect,omitempty"`
}
#PodStartOpts carries `charly start`'s direct-mode CLI extras (K4 inversion, quadlet-mode first): the plugin now SELF-RESOLVES the #PodLifecyclePlan (over the deploy-overlay HostBuild seam + the already-portable sdk resolvers) using these opts + the deploy key already on lifecycleParams.Name — replacing the former host-side resolvePodStartPlan. The quadlet path ignores every field (mirrors the pre-inversion contract — CLI extras apply only to direct mode).
type PodStartPayload ¶ added in v0.2026217.301
type PodStartPayload struct {
Tag string `yaml:"tag,omitempty" json:"tag,omitempty"`
Build bool `yaml:"build,omitempty" json:"build,omitempty"`
Env []string `yaml:"env,omitempty" json:"env,omitempty"`
EnvFile string `yaml:"env_file,omitempty" json:"env_file,omitempty"`
Port []string `yaml:"port,omitempty" json:"port,omitempty"`
VolumeFlag []string `yaml:"volume_flag,omitempty" json:"volume_flag,omitempty"`
Bind []string `yaml:"bind,omitempty" json:"bind,omitempty"`
NoAutoDetect bool `yaml:"no_autodetect,omitempty" json:"no_autodetect,omitempty"`
}
#PodStartPayload — see #PodLifecycleRequest's header; op="start". The former StartCmd's authored fields (DEPLOY-wave CLI-struct port): the command:pod plugin owns the CLI GRAMMAR but cannot drive the LifecycleTarget dispatch (ResolveTarget, the plugin loader — core Mechanisms), so `charly start`'s command is THIN — it forwards these flags, and the host runs the existing startViaLifecycle orchestration VERBATIM, exactly as `charly fleet add` stayed core behind HostBuild("resolve-target-add").
type PodStopOpts ¶
type PodStopOpts struct {
Unmount bool `yaml:"unmount,omitempty" json:"unmount,omitempty"`
}
#PodStopOpts carries `charly stop --unmount` (K4 inversion): the plugin self-resolves the STOP #PodLifecyclePlan using this + the deploy key, replacing the former host-side resolvePodStopPlan.
type PodStopPayload ¶ added in v0.2026217.301
type PodStopPayload struct {
Unmount bool `yaml:"unmount,omitempty" json:"unmount,omitempty"`
}
#PodStopPayload — see #PodLifecycleRequest's header; op="stop". The former StopCmd's authored fields.
type PodUpdatePayload ¶ added in v0.2026217.301
type PodUpdatePayload struct {
Tag string `yaml:"tag,omitempty" json:"tag,omitempty"`
Build bool `yaml:"build,omitempty" json:"build,omitempty"`
Seed bool `yaml:"seed,omitempty" json:"seed,omitempty"`
ForceSeed bool `yaml:"force_seed,omitempty" json:"force_seed,omitempty"`
DataFrom string `yaml:"data_from,omitempty" json:"data_from,omitempty"`
}
#PodUpdatePayload — see #PodLifecycleRequest's header; op="update". Since K-wave 2 cone CONTESTED the resolved deploy node rides the #PodLifecycleRequest.node field (the plugin resolves it plugin-side from the merged tree); the former tree_json field is DELETED.
type PortMapping ¶
type PortMapping struct {
HostIP string `yaml:"host_ip,omitempty" json:"host_ip,omitempty"`
HostPort int `yaml:"host_port,omitempty" json:"host_port"`
CtrPort int `yaml:"container_port,omitempty" json:"container_port"`
Proto string `yaml:"protocol,omitempty" json:"protocol"`
}
#PortMapping — one published port's structured runtime mapping (host IP/port -> container port/proto). Surfaces on #DeploymentStatus so renderers + host probes consume it without re-parsing.
type PortScope ¶
type PortScope struct {
All bool
Ports []int
PortMap map[int]string // port → hostname (cloudflare only)
}
PortScope handles three YAML forms for public/private port specification: "all" → All; [443, 8443] → Ports; {18789: "host"} → PortMap.
func (PortScope) MarshalJSON ¶
func (PortScope) MarshalYAML ¶
func (*PortScope) UnmarshalJSON ¶
type PortSpec ¶
PortSpec is the canonical {port, protocol} form of a candy `port:` entry. Source: charly/layers.go PortSpec (no struct tags — yaml.v3 lowercases the field name).
type PostTeardownReply ¶
type PostTeardownReply struct {
RemoveEntries []string `yaml:"remove_entries,omitempty" json:"remove_entries,omitempty"`
}
#PostTeardownReply is the OpPostTeardown reply: the host removes each named charly.yml deploy-entry key AFTER the plugin's teardown.
type PreemptLease ¶
type PreemptLease struct {
Claimant string `yaml:"claimant,omitempty" json:"claimant"`
Claim HolderAddr `yaml:"claim,omitempty" json:"claim"`
Tokens []string `yaml:"tokens,omitempty" json:"tokens"`
Mode string `yaml:"mode,omitempty" json:"mode,omitempty"`
Transient bool `yaml:"transient,omitempty" json:"transient"`
Preempted []PreemptedHolder `yaml:"preempted,omitempty" json:"preempted"`
Created string `yaml:"created,omitempty" json:"created"`
// owner_pid/owner_start identify the OUTERMOST process that created the lease — the liveness
// signal a concurrent charly process's reconcile uses (leaseLive).
OwnerPID int64 `yaml:"owner_pid,omitempty" json:"owner_pid,omitempty"`
OwnerStart string `yaml:"owner_start,omitempty" json:"owner_start,omitempty"`
}
#PreemptLease is one active resource claim — exclusive (a VM with sole use) or shared (a refcounted pod claim; many coexist on one token).
type PreemptLedger ¶
type PreemptLedger struct {
Leases []PreemptLease `yaml:"leases,omitempty" json:"leases"`
}
#PreemptLedger is the on-disk lease set (~/.local/share/charly/preemption/leases.yml).
type PreemptedHolder ¶
type PreemptedHolder struct {
Addr HolderAddr `yaml:"addr,omitempty" json:"addr"`
Holds []string `yaml:"holds,omitempty" json:"holds"`
Restore string `yaml:"restore,omitempty" json:"restore"`
}
#PreemptedHolder records one holder a lease stopped, its declared exclusive tokens, and its restore policy — so ReleaseClaimant/reconcile restart exactly what was stopped.
type PreemptibleConfig ¶
type PreemptibleConfig struct {
Holds []string `yaml:"holds,omitempty" json:"holds,omitempty"`
Stop string `yaml:"stop,omitempty" json:"stop,omitempty"`
Restore string `yaml:"restore,omitempty" json:"restore,omitempty"`
}
PreemptibleConfig is the HOLDER side of the resource-arbitration axis.
type PrepareVenueReply ¶
type PrepareVenueReply struct {
Venue VenueDescriptor `yaml:"venue,omitempty" json:"venue"`
State RawBody `yaml:"state,omitempty" json:"state,omitempty"`
Notes []string `yaml:"notes,omitempty" json:"notes,omitempty"`
}
#PrepareVenueReply is the OpPrepareVenue reply. Venue is re-materialized host-side into a live DeployExecutor (the live executor never crosses the wire); State is an opaque deploy-entry patch the host persists; Notes are human-facing lines the host prints.
type Process ¶
type Process interface {
Stdin() io.WriteCloser
Stdout() io.ReadCloser
Stderr() io.ReadCloser
Wait() error
Close() error
}
Process is one exact-argv, full-duplex child running in a deployment venue. It is deliberately a behaviour contract rather than a wire shape: target routes and all data sent over the process are CUE-generated, while this seam only exposes the OS pipes that carry the generated gRPC protocol.
Close must be idempotent, close stdin, terminate the complete child process group when it has not exited, and reap it. Wait may be called more than once.
type ProcessExecutor ¶
type ProcessExecutor interface {
StartProcess(ctx context.Context, launch ProcessLaunch) (Process, error)
}
ProcessExecutor is an optional capability implemented by deployment executors that can host a bidirectional process. Keeping it separate from DeployExecutor preserves compatibility with capture-only executors and test doubles. Callers must return ErrNotSupported when this capability is absent.
type ProcessLaunch ¶
type ProjectLoader ¶ added in v0.2026213.1726
type ProjectLoader interface {
LoadUnified(dir string, exec LoaderExecutor) (*UnifiedFile, bool, error)
// DecodeEntityViaCUE is the kind-blind per-entity CUE decode mechanism (shorthand normalize +
// CUE-ingest + Decode), relocated to loaderkit (K1 unit 1): normalizes node against t's shape
// (shorthand expansion, scalar→string coercion), then CUE-decodes the result into out. node must
// BE the entity value (a candy body / a single kind entity / an assembled node-form body), not a
// kind-keyed wrapper; does not mutate the input node. Compiled-in only (the loader is
// bootstrap-critical), no wire envelope — every kind/candy/node-form decode in charly core
// routes through this seam instead of importing loaderkit directly.
DecodeEntityViaCUE(node *yaml.Node, t reflect.Type, out any, label string) error
// ValidateEntityClosedCUE unifies a single entity with #<Kind> and validates
// it WITHOUT requiring concreteness — closedness violations (unknown keys) and type/enum/regex
// conflicts, but not missing-required fields (K1 unit 2 relocation).
ValidateEntityClosedCUE(kind, label string, entity cue.Value) error
// ValidateEntityCUE is the CONCRETE twin of the above — closedness PLUS missing-required
// fields and unresolved disjunctions. The schema-tightening corpus (charly's
// cue_tighten_test.go) drives it as the regression guard that the modeled subtrees stay
// strict; it needs the host's registry-derived verb primaries to parse its candy cases, so it
// runs host-side and reaches the loader's compiled schema through this seam.
ValidateEntityCUE(kind, label string, entity cue.Value) error
// CueDocFromYAML ingests one YAML document into a cue.Value (the whole doc), built with the
// loader's own cue.Context so the result can Unify against its compiled schema (K1 unit 2
// relocation; the host-threaded schema handle was dropped in K-wave 2 cone R1).
CueDocFromYAML(path string, data []byte) (cue.Value, error)
// ValidateNodeDocCUE validates a unified node-form document (raw YAML bytes) by unifying EACH
// top-level entity node against #Node — the load-time "validate-before-execute" structural gate
// (K1 unit 2 relocation).
ValidateNodeDocCUE(label string, data []byte) error
// ApplyCueDefaults fills schema-declared defaults into an already-RESOLVED entity by unifying
// its marshaled form with #<Kind> and decoding back (K1 unit 2 relocation).
ApplyCueDefaults(kind string, out any) error
// ResolveMergedDeployTree returns the top-level Fleet (deploy-node) map — the merged project
// charly.yml + per-host operator overlay, ready for dotted-path traversal — the host-side
// merged-tree read the check host seams (deployNodePluginContext + check_venue_resolve) need.
// It is the merged-tree sibling of LoadUnified: LoadUnified returns the PROJECT-only tree
// (loadmodel.go Fleet has no overlay field), so a caller that needs the per-host operator
// overlay merged in routes through THIS seam instead. The merge LOGIC (the loaderkit
// project+overlay projection+merge) stays in the ONE copy in sdk/loaderkit
// (loaderkit.ResolveMergedTreeViaExecutor); the host reaches it through this compiled-in seam
// instead of importing loaderkit directly (#55 coneA Q2(1) — charly core's check_cmd.go sheds
// its loaderkit import). The in-proc executor is threaded on ctx via sdk.ContextWithExecutor
// (the SAME in-proc reverse-channel path ExecutorForInvoke uses for Invoke) so the seam
// signature stays spec-typed — the plugin-side impl retrieves it via sdk.ExecutorFromContext.
// Compiled-in only (the loader is bootstrap-critical), no wire envelope.
ResolveMergedDeployTree(ctx context.Context, dir string) (map[string]FleetNode, error)
// MaterializeLoadedProject replays the whole-project per-document/per-namespace MATERIALIZE +
// root-wins MERGE over a walk envelope, driving loaderkit's kind-blind orchestration with the
// host-supplied per-node seams — so charly core reaches materialize WITHOUT importing loaderkit
// (#55 2b C3). The host's own LoadUnified path AND the loader-materialize HostBuild seam (serving a
// plugin-side loader) both route through here.
MaterializeLoadedProject(lp *LoadedProject, merged *UnifiedFile, byID map[int64]*UnifiedFile, seams MaterializeProjectSeams) error
// MarshalMaterialized marshals a materialized UnifiedFile into the wire envelope the
// loader-materialize HostBuild seam returns to a plugin-side loader (it captures the nested
// plugin-kind maps loaderkit-internally, so the host reaches it through this seam).
MarshalMaterialized(uf *UnifiedFile) ([]byte, error)
// ValidateAndroidDevices enforces the kind:android box⊻adb XOR; the host supplies the
// registry-resolve callback (the validation LOGIC stays in loaderkit).
ValidateAndroidDevices(uf *UnifiedFile, resolveAndroid func(json.RawMessage) (*ResolvedAndroid, error)) error
// ValidatePreemptible validates preemptible / requires_exclusive / requires_shared across the
// deploy map; the host supplies the registry-resolve callbacks (the validation LOGIC stays in
// loaderkit).
ValidatePreemptible(uf *UnifiedFile, resolveResource func(json.RawMessage) (*ResolvedResource, error), resolveVm func(json.RawMessage) (*ResolvedVm, error)) error
// ScanCandyFromLocal runs the candy-scan fetch fix-point (remote-ref collect, fetch, per-entity
// version arbitration, host-completion + finalize) over a local candy set, driving the host-coupled
// legs through the caller-supplied ScanSeams closures — so charly core reaches the scan MECHANISM
// through this compiled-in seam instead of importing loaderkit (#55 C3b-ii). The scan LOGIC stays in
// the ONE copy in sdk/loaderkit (candy/plugin-build reaches it directly, being a plugin).
ScanCandyFromLocal(localScanned map[string]ScannedCandy, initCfg *InitConfig, seams ScanSeams) (map[string]CandyReader, error)
// RunDiscover walks the flat generic discover: scan-spec list, parsing each discovered manifest via
// the host-supplied WalkSeams — the discover half of the loader mechanism, reached via the seam so
// charly core never imports loaderkit for it.
RunDiscover(rootDir string, specs []ScanSpec, seams WalkSeams) ([]DiscoveredManifest, error)
// FinalizeScannedCandies is the scan pipeline's finalize choke point (host-completion + bare-string
// the refs + wrap into the FINAL CandyReader). It is deploykit-coupled and stays in loaderkit; the
// host reaches it through this seam — the dominant shared choke point across charly's scan call sites.
FinalizeScannedCandies(scanned map[string]ScannedCandy, initCfg *InitConfig) map[string]CandyReader
// IsResourceDisc reports whether a discriminator names a deploy-substrate kind (the markers of a
// fleet member / fleet-shaped node) — the CUE-derived #ResourceKind vocab, OR a recognized
// external deploy substrate word (t.DeploySubstrates).
IsResourceDisc(d string, t Threaded) bool
// FleetTargetForDisc maps a node discriminator to the FleetNode Target — DATA-driven via
// t.DeployTraits: a word with no declared deploy traits is TARGETLESS (e.g. group).
FleetTargetForDisc(d string, t Threaded) string
// SetFleetCrossRef sets the deploy's cross-ref from a scalar discriminator value — DATA-driven
// via t.DeployTraits' ImageBacked trait (image-backed → dn.Image; otherwise → dn.From). A
// targetless word (no declared traits) sets neither.
SetFleetCrossRef(dn *FleetNode, disc, ref string, t Threaded)
// IsStandaloneResourceKind reports whether disc names one of the substrate kinds that are BOTH a
// standalone TEMPLATE and a deploy — DATA-driven via t.DeployTraits (same fact
// FleetTargetForDisc/SetFleetCrossRef resolve against).
IsStandaloneResourceKind(disc string, t Threaded) bool
// FoldStandaloneTemplateReply folds a standalone-template kind's echoed reply JSON into acc's
// generic PluginKinds[disc][name] map — the C2-substrate TEMPLATE fold arm, GENERIC by
// construction (no per-kind-word switch).
FoldStandaloneTemplateReply(disc, name string, replyJSON json.RawMessage, acc *MaterializedProject) error
// AssembleEntityBody returns the DOCUMENT-wrapped entity-body mapping to decode: pn's body
// value (an empty mapping when the value is null/absent or a scalar cross-ref).
AssembleEntityBody(pn ParsedNode) (*yaml.Node, error)
// DecodeNodeValue decodes pn's body via the shared CUE entity decoder into out (a *struct) —
// the SAME entity-body assembler + CUE decode every candy/kind/node-form decode goes through.
DecodeNodeValue(pn ParsedNode, out any) error
// EntityBodyJSON returns a node's kind-value mapping as canonical JSON, generically — with NO
// concrete-kind Go type.
EntityBodyJSON(pn ParsedNode) (json.RawMessage, error)
// BuildFleetNode recursively builds a FleetNode from a fleet/resource node.
BuildFleetNode(pn ParsedNode, t Threaded) (*FleetNode, error)
// BuildResourceMemberChildren decodes pn's RESOURCE-MEMBER entity children into a
// name→*FleetNode map via the SAME BuildFleetNode recursion — the SINGLE source of truth for
// authored member-tree decode.
BuildResourceMemberChildren(pn ParsedNode, t Threaded) (map[string]*FleetNode, error)
// BuildFleetNodeInto builds pn into a FleetNode and registers it in acc's Fleet map — the
// fallback for a recognized-but-not-yet-connected external deploy substrate word
// (MaterializeSeams.BuildFleetEntity's implementation).
BuildFleetNodeInto(pn ParsedNode, t Threaded, acc *MaterializedProject) error
// IsDeployShape reports whether a substrate node is a DEPLOY (vs a standalone template).
IsDeployShape(pn ParsedNode) bool
// DecodeStandaloneTemplateJSON canonicalizes pn (a substrate TEMPLATE node) to the JSON the
// host threads to the substrate plugin, GENERICALLY — with NO concrete-kind Go type.
DecodeStandaloneTemplateJSON(pn ParsedNode, t Threaded) (json.RawMessage, error)
// ResourceChildren returns pn's children whose discriminator is itself a resource/fleet kind
// (the CUE-derived #ResourceKind vocab).
ResourceChildren(pn ParsedNode) []ParsedNode
// ValidateCandyManifestCUE validates a candy manifest: the whole-document #NodeDoc structural
// gate, then the parsed+desugared entity-tree walk (ValidateNodeFormSteps).
ValidateCandyManifestCUE(path string, data []byte, t Threaded, parser DocParser) error
// ValidateNodeFormSteps parses a node-form document and validates EVERY entity's (and nested
// sub-entity's) assembled body against its closed per-kind def — the step-typo gate for
// candies, boxes, pods, deploys, and check beds alike.
ValidateNodeFormSteps(path string, data []byte, t Threaded, parser DocParser) error
// EnsureRepoDownloaded downloads repoPath@version if not already cached and returns the cache
// path, auto-migrating it to the latest schema CalVer.
EnsureRepoDownloaded(ctx context.Context, repoPath, version string) (string, error)
// ResolveProjectRepo turns a --repo spec ("owner/repo", "owner/repo@ref", a host-qualified
// path, or the "default" literal) into a local cache path a caller can chdir into. It is the
// SAME clone-and-cache machinery EnsureRepoDownloaded drives, with the spec normalization and
// default-branch resolution in front — pure spec vocabulary over one fetch, which is why it
// belongs beside the fetch rather than in the kernel (K-wave 2 cone R1 relocated it out of
// charly/main_repo.go).
ResolveProjectRepo(ctx context.Context, repoSpec string) (string, error)
// CanonicalRef resolves one `import:` ref — a local path, or
// "@host/org/repo[/sub/path]:version" — relative to baseDir, into its stable dedup key and its
// on-disk path, fetching a remote ref through the same clone-and-cache machinery. It is the
// mechanism behind WalkSeams.ResolveRef; the host wires that seam to this method rather than
// holding the resolution itself (K-wave 2 cone R1 relocated it out of charly/unified.go, the
// same route ResolveProjectRepo took).
CanonicalRef(ctx context.Context, ref, baseDir string) (key, path string, err error)
// CollectRemoteRefsOpts collects all unique remote refs reachable from cfg's build/deploy
// targets + layers' manifest depends/candy fields, grouped by (repoPath, version).
CollectRemoteRefsOpts(ctx context.Context, cfg *Config, layers map[string]CandyReader, opts ResolveOpts) ([]RemoteDownload, error)
}
ProjectLoader is the swappable whole-project LOAD-ENTRY seam (#55 loader-keystone) — the terminal loader endpoint every command reaches to load a project's charly.yml. The loader plugin candy implements it (candy/plugin-loader, delegating to loaderkit.LoadUnified over loaderkit.LoadSeamsFromExecutor), and the host resolves the registered loader provider to it and drives LoadUnified THROUGH it — so charly core imports ONLY the dedicated spec module, never the loaderkit mechanism, to load its own config. The host supplies the registry-/host-coupled legs as a LoaderExecutor; the plugin owns the kind-blind orchestration. Typed (no wire envelope), the LOAD-ENTRY sibling of DocParser/ProjectWalker/CandyScanner/Materializer above — the compiled-in placement (the loader MUST always resolve; it is the config front-end) calls it directly, resolved at init() before the first load so there is no bootstrap cycle.
type ProjectTemplates ¶
type ProjectTemplates struct {
Local map[string]RawBody `yaml:"local,omitempty" json:"local,omitempty"`
Kubernetes map[string]RawBody `yaml:"kubernetes,omitempty" json:"kubernetes,omitempty"`
Pod map[string]RawBody `yaml:"pod,omitempty" json:"pod,omitempty"`
VM map[string]RawBody `yaml:"vm,omitempty" json:"vm,omitempty"`
Android map[string]RawBody `yaml:"android,omitempty" json:"android,omitempty"`
}
#ProjectTemplates — the bare pod:/vm:/local:/kubernetes:/android: template maps carried as OPAQUE payloads (the uf.Pod/VM/Local/Kubernetes/Android raw bytes, verbatim). The host projector stays KIND-BLIND — it copies the raw template bytes with NO concrete-kind decode (a kernel that read spec.Local/#Pod/… would violate the boundary law + trip TestNoConcreteKindInKernel). The CONSUMING PLUGINS (validate localtemplates, check-include pod/vm arms, status kubernetes/adb) decode a RawBody into the concrete spec kind type themselves — a plugin MAY know kinds, the kernel may not.
func (*ProjectTemplates) ByKind ¶ added in v0.2026217.301
func (t *ProjectTemplates) ByKind(kind string) map[string]RawBody
ByKind returns t's namespace-qualified template map for kind (nil for an unknown kind or a nil receiver). This is the ONE place ProjectTemplates' named fields fold back to a kind-keyed lookup — kept here (spec, the shared vocabulary) rather than in the kernel: a kernel caller that needs "the template map for THIS kind" stays genuinely kind-blind by keying on the string it already carries instead of switching on it itself (host_build_deploy_entity_resolve.go, W0).
type ProjectWalker ¶
type ProjectWalker interface {
WalkProject(rootDir string, rootData []byte, rootIdentity string, seams WalkSeams) (LoadedProject, error)
}
ProjectWalker is the swappable WHOLE-PROJECT WALK seam: the loader plugin candy implements it (candy/plugin-loader, delegating to loaderkit.Walk), and the host resolves the registered loader provider to it and calls it once per project load — so an alternative loader plugin serves a different walk mechanism by implementing this. Typed (no wire envelope) since the compiled-in placement passes live Go callbacks (WalkSeams) that cannot cross a JSON envelope. rootData is the (possibly bootstrap-transformed) root document bytes; rootIdentity seeds the namespace cycle-break with the root project's own repo identity.
type ProvidesConfig ¶ added in v0.2026210.808
type ProvidesConfig struct {
Env []EnvProvideEntry `yaml:"env,omitempty" json:"env,omitempty"`
MCP []MCPProvideEntry `yaml:"mcp,omitempty" json:"mcp,omitempty"`
}
#ProvidesConfig holds all resolved provides entries in charly.yml.
type RCARecord ¶
type RCARecord struct {
ID UUIDv7 `yaml:"id,omitempty" json:"id"`
IncidentID UUIDv7 `yaml:"incident_id,omitempty" json:"incident_id"`
State string `yaml:"state,omitempty" json:"state"`
Findings []string `yaml:"findings,omitempty" json:"findings,omitempty"`
RootCause string `yaml:"root_cause,omitempty" json:"root_cause,omitempty"`
CompletedAt string `yaml:"completed_at,omitempty" json:"completed_at,omitempty"`
}
type RawBody ¶
type RawBody = json.RawMessage
RawBody is an opaque JSON body the kernel stores and transports WITHOUT typing — a same-package alias for json.RawMessage so a CUE `@go(type=map[string]RawBody)` / `@go(type=RawBody)` override renders an opaque field/map in the generated types with no cross-package import (gengotypes would mis-import a qualified `json.RawMessage`). Moved here from the former hand-written deploy_wire.go (SDD conversion) — it is a scalar alias, not a wire struct, so it belongs beside the other scalar collapses in this file.
type Readiness ¶
type Readiness struct {
PollIntervalLocal Duration `yaml:"poll_interval_local,omitempty" json:"poll_interval_local,omitempty"`
PollIntervalRemote Duration `yaml:"poll_interval_remote,omitempty" json:"poll_interval_remote,omitempty"`
PollIntervalHeavy Duration `yaml:"poll_interval_heavy,omitempty" json:"poll_interval_heavy,omitempty"`
PerAttempt Duration `yaml:"per_attempt,omitempty" json:"per_attempt,omitempty"`
PerAttemptHeavy Duration `yaml:"per_attempt_heavy,omitempty" json:"per_attempt_heavy,omitempty"`
NoProgress Duration `yaml:"no_progress,omitempty" json:"no_progress,omitempty"`
AbsoluteCap Duration `yaml:"absolute_cap,omitempty" json:"absolute_cap,omitempty"`
StopGrace Duration `yaml:"stop_grace,omitempty" json:"stop_grace,omitempty"`
}
ReadinessConfig (config.go / readiness_config.go) — the `defaults.readiness:` bounds for the unified pollUntil readiness primitive (poll.go). CLOSED. Every field is a Go time.ParseDuration string; absent → the named fallback const in poll.go. Defaults-only (reuses the BoxConfig type), but modeled here per the #Box completeness invariant in this file's header. The Resolve()-time ordering invariants (interval <= no_progress <= absolute_cap; poll_interval_local <= stop_grace <= absolute_cap) are cross-field and stay in Go (validateOrdering).
type RebootStep ¶ added in v0.2026210.2308
type RebootStep struct {
CandyName string
}
RebootStep requests a reboot of the deploy target after this candy's steps. It is emitted (last in the candy) when a candy declares `reboot: true` — the canonical case is a kernel-module candy (e.g. nvidia-open-dkms) whose module only loads on a fresh boot with nouveau blacklisted.
Only a rebootable venue acts on it: the external vm deploy reboots the guest over SSH (the host's RunHostStep RebootStep handler, gated on the rebootable-venue flag the vm substrate sets) and waits for the boot_id to change. OCITarget / PodDeployTarget have no machine to reboot at build time and skip it (the external kubernetes substrate does not consume the IR at all); the external local deploy refuses to reboot the host venue unattended (skip + note). Mirrors the ApkInstallStep "only one target executes" pattern.
func (*RebootStep) Kind ¶ added in v0.2026210.2308
func (s *RebootStep) Kind() StepKind
func (*RebootStep) RequiresGate ¶ added in v0.2026210.2308
func (s *RebootStep) RequiresGate() Gate
func (*RebootStep) Reverse ¶ added in v0.2026210.2308
func (s *RebootStep) Reverse() []ReverseOp
Reverse is empty — a reboot is not a persistent artifact to undo.
func (*RebootStep) Scope ¶ added in v0.2026210.2308
func (s *RebootStep) Scope() Scope
func (*RebootStep) Venue ¶ added in v0.2026210.2308
func (s *RebootStep) Venue() Venue
type RecoveryDecision ¶
type RecoveryDecision struct {
ID UUIDv7 `yaml:"id,omitempty" json:"id"`
IncidentID UUIDv7 `yaml:"incident_id,omitempty" json:"incident_id"`
RCAID UUIDv7 `yaml:"rca_id,omitempty" json:"rca_id,omitempty"`
Action string `yaml:"action,omitempty" json:"action"`
AuthorizedEmergencyAbort bool `yaml:"authorized_emergency_abort,omitempty" json:"authorized_emergency_abort,omitempty"`
Params *RecoveryParams `yaml:"params,omitempty" json:"params,omitempty"`
State string `yaml:"state,omitempty" json:"state"`
DecidedAt string `yaml:"decided_at,omitempty" json:"decided_at"`
AppliedAt string `yaml:"applied_at,omitempty" json:"applied_at,omitempty"`
Error string `yaml:"error,omitempty" json:"error,omitempty"`
}
type RecoveryParams ¶
type RecoveryParams struct {
RunID UUIDv7 `yaml:"run_id,omitempty" json:"run_id,omitempty"`
SessionID UUIDv7 `yaml:"session_id,omitempty" json:"session_id,omitempty"`
Target *TargetSpec `yaml:"target,omitempty" json:"target,omitempty"`
TerminalProfile *TerminalProfile `yaml:"terminal_profile,omitempty" json:"terminal_profile,omitempty"`
Provider string `yaml:"provider,omitempty" json:"provider,omitempty"`
Prompt string `yaml:"prompt,omitempty" json:"prompt,omitempty"`
Runtime string `yaml:"runtime,omitempty" json:"runtime,omitempty"`
Deployment string `yaml:"deployment,omitempty" json:"deployment,omitempty"`
Note string `yaml:"note,omitempty" json:"note,omitempty"`
}
type RefsCollectSeams ¶ added in v0.2026217.301
type RefsCollectSeams struct {
// Downloader is the registered remote-repo fetch backend (P7). The loader plugin reaches it as a
// peer — InvokeProvider(class:"refs", word:"refs", OpResolve) — and wraps that dispatch in this
// interface; a cache-miss download goes through it.
Downloader RefsDownloader
// MigrateCache brings a remote-repo cache's PROJECT files up to the head schema via the
// compiled-in command:migrate plugin — registry-coupled (resolves ClassCommand "migrate" +
// Invoke), so it stays a host-supplied callback.
MigrateCache func(path string) error
// ResolveLocal projects one opaque `kind:local` template body into a *ResolvedLocal via
// candy/plugin-substrate's OpResolve leg — registry-coupled (Invoke), so it stays a
// host-supplied callback.
ResolveLocal func(body json.RawMessage) (*ResolvedLocal, error)
// OverrideEnvValue is the raw CHARLY_REPO_OVERRIDE env value (RDD local-overrides) — the host
// reads os.Getenv once; this mechanism never touches the env var NAME (spec/proc.RepoOverrideEnv,
// #55 W3 B2-full — shared by charly core's plugin_loader.go and candy/plugin-check's
// bed_session.go, both of which read/write it independently).
OverrideEnvValue string
// LatestTag resolves a repo's newest tag for a VERSION-LESS remote ref (the candy de-submodule
// cutover, Phase 4: version-less refs resolve to the immutable latest tag, not the mutable
// default branch). Registry-coupled (spec/refs.GitLatestTag shells out to git ls-remote), so it
// stays a host-supplied callback — and a seam, so the mechanism is unit-testable offline.
LatestTag func(repoURL string) (string, error)
}
RefsCollectSeams is the set of host-supplied callbacks EnsureRepoDownloaded/ CollectRemoteRefsOpts need for everything registry-coupled — the host builds this value and hands it to the ProjectLoader seam call; the mechanism never touches the provider registry directly (boundary law clause M: the resolve+invoke dispatch stays host-side, reached through these callbacks, exactly like WalkSeams/MaterializeSeams above).
K-wave 2 cone R1: the value is no longer built by charly core. candy/plugin-loader assembles it per call from the ctx-threaded executor (refs_seams.go) — core was only resolving three peers and reading one env var on the loader's behalf, which is a CALL, not a mechanism. The struct itself stays: it is still how the kind-blind loaderkit mechanism receives its registry-coupled legs, and keeping it a plain parameter is what lets the mechanism be unit-tested with stubs.
type RefsDownloadInput ¶ added in v0.2026218.138
type RefsDownloadInput struct {
// repo_path is the bare repo coordinate (e.g. "github.com/opencharly/charly").
RepoPath string `yaml:"repo_path,omitempty" json:"repo_path"`
// version is the git tag / branch / sha to fetch. Never empty at this point: an empty authored
// version is resolved to the repo's default branch before the request is built.
Version string `yaml:"version,omitempty" json:"version"`
}
#RefsDownloadInput is the cache-MISS download request the loader plugin ships to the registered refs backend. The caller has already resolved local overrides and checked the cache, exactly as the pre-move host orchestration did — a backend only ever sees a genuine miss.
type RefsDownloadReply ¶ added in v0.2026218.138
type RefsDownloadReply struct {
// dir is the absolute path of the populated cache directory.
Dir string `yaml:"dir,omitempty" json:"dir,omitempty"`
}
#RefsDownloadReply carries the populated local cache tree the backend produced.
type RefsDownloader ¶
type RefsDownloader interface {
// Download fetches repoPath@version into the local repo cache and returns the cache path.
// Called only on a cache MISS (the host checks IsRepoCached first).
Download(repoPath, version string) (string, error)
}
RefsDownloader is the swappable remote-repo FETCH BACKEND seam (P7): the host dispatches every cache-miss download through a RefsDownloader; the DEFAULT (candy/plugin-refs, delegating to kit.DownloadRepo) fetches via git, and an alternative refs plugin can serve a different backend (OCI/S3-hosted candies) by registering a different RefsDownloader. Mirrors DocParser/ProjectWalker/ CandyScanner above: a typed interface a compiled-in plugin implements alongside its provider, so the host calls it in-proc with no wire envelope. Relocated from sdk/kit (FLOOR-SLIM axis-A mechanical batch) — the interface contract itself is kind-blind and registry-decoupled; the default git-fetch backend (kit.DefaultDownloader) stays in kit, wrapping the spec/refs.DownloadRepo git primitive (relocated to the spec/refs fabric slice, re-exported by kit for existing callers).
type RemoteDownload ¶ added in v0.2026213.1726
type RemoteDownload struct {
RepoPath string
Version string
Refs []string // bare refs to import (e.g. "github.com/org/repo/candy/name")
}
RemoteDownload represents a unique (repo, version) pair to download in the remote-layer resolver's candy-scan fix-point, plus the bare refs to import from it. A pure data descriptor over strings, relocated to the dedicated spec module (#55 2b Class A) so charly's remote-resolver files (refs.go) reach it without importing loaderkit; loaderkit aliases it for the scan mechanism that produces it.
type RemoteImageResolveReply ¶
type RemoteImageResolveRequest ¶
type RemoteImageResolveRequest struct {
Ref string `yaml:"ref,omitempty" json:"ref"`
Tag string `yaml:"tag,omitempty" json:"tag,omitempty"`
}
#RemoteImageResolveRequest / #RemoteImageResolveReply — the "remote-image-resolve" HostBuild seam: clone/cache an @github.com/org/repo/box ref's source + return the cached dir + short box name. The host does ONLY the git clone/cache (EnsureRepoDownloaded, K1/B floor); the calling plugin resolves the registry pull ref itself via the K1 loader reverse legs (K1 loader wave — sheds deploykit.ResolveSpecBox from charly core; the former image_ref field is GONE).
type RenderedService ¶
type RenderedService struct {
UnitText string `yaml:"unit_text,omitempty" json:"unit_text,omitempty"`
UnitPath string `yaml:"unit_path,omitempty" json:"unit_path,omitempty"`
DropinText string `yaml:"dropin_text,omitempty" json:"dropin_text,omitempty"`
DropinPath string `yaml:"dropin_path,omitempty" json:"dropin_path,omitempty"`
}
#RenderedService is the renderer output: the unit text, where it lands, and any drop-in for packaged-unit reuse.
type RepoChangeStep ¶ added in v0.2026210.2308
type RepoChangeStep struct {
Format string // "rpm" | "deb" | "pac"
File string // absolute path of the repo file
Content string // rendered repo file body
Checksum string // sha256 of Content, for idempotency check at install
CandyName string
}
RepoChangeStep records a repo config file mutation (e.g. adding rpmfusion-free.repo to /etc/yum.repos.d/). Distinct from SystemPackagesStep's PhasePrepare because the compiler often synthesizes these from `cmd:` tasks that happen to install a -release.rpm — we want them tracked separately so `charly fleet del` can reverse them precisely.
func (*RepoChangeStep) Kind ¶ added in v0.2026210.2308
func (s *RepoChangeStep) Kind() StepKind
func (*RepoChangeStep) RequiresGate ¶ added in v0.2026210.2308
func (s *RepoChangeStep) RequiresGate() Gate
func (*RepoChangeStep) Reverse ¶ added in v0.2026210.2308
func (s *RepoChangeStep) Reverse() []ReverseOp
func (*RepoChangeStep) Scope ¶ added in v0.2026210.2308
func (s *RepoChangeStep) Scope() Scope
func (*RepoChangeStep) Venue ¶ added in v0.2026210.2308
func (s *RepoChangeStep) Venue() Venue
type RepoSpec ¶ added in v0.2026210.2308
type RepoSpec struct {
// Fields captured verbatim from the candy manifest. Different formats use
// different subsets (rpm has url/id/key; deb has suite/components;
// pac has url/keys). Carried as map so the template-rendered host
// and container forms can pick what they need.
Raw map[string]any
}
type ResolveOpts ¶ added in v0.2026213.1726
type ResolveOpts struct {
IncludeDisabled bool // skip the `enabled: false` check
IncludeDisabledNames map[string]bool // when non-empty, scope IncludeDisabled to these names only
// RequestedBoxes are the explicit build targets (`charly box build <name>`). A qualified name
// here (e.g. `charly.arch-builder`) is pulled into the resolved set even when it isn't reachable
// as a base/builder of a root image — so a namespaced image can be an on-demand build target, not
// only a transitive base. Bare names are ignored here (they resolve through the root loop).
RequestedBoxes []string
// ExtraCandyRefs are candy refs to collect IN ADDITION to the image/builder/kind:local-template
// closure — specifically a DEPLOY's `add_candy:` candies. The image-closure walk (collectBox)
// never reaches them, so a bed that add_candy's a host-side PLUGIN candy must pass its add_candy
// refs here, or the plugin never enters the candy scan and loadProjectPlugins can't build/connect
// it. NEVER read by the buildkit resolvers — consumed solely by the candy scan.
ExtraCandyRefs []string
// InitCfg is the project init: vocabulary (W9), threaded through so the candy scan can run the
// cross-candy init-system host-completion pass (PopulateCandyInitSystem) BEFORE wrapping each
// candy into the FINAL CandyReader. A caller that leaves this nil skips the pass (correct only for
// a caller with no init-aware consumer downstream). NEVER read by the buildkit resolvers.
InitCfg *InitConfig
// DistroCfg / BuilderCfg are the project's build vocabulary (distro:/builder:), threaded through
// so a resolve does not re-run the project load on every call (a caller with the triple, or a
// multi-box loop, sets it once and skips the redundant reload; nil is byte-identical fallback).
DistroCfg *DistroConfig
BuilderCfg *BuilderConfig
}
ResolveOpts carries the scan/load options threaded through the candy scan + project resolution.
func BoxResolveOpts ¶ added in v0.2026218.138
func BoxResolveOpts(boxes []string, includeDisabled bool) ResolveOpts
BoxResolveOpts builds the ResolveOpts that scope a generate/build to a set of explicitly-named boxes. It is the SINGLE source of the box-selection rule (R3) for `charly box build` and `charly box generate` alike: an empty slice means "all enabled boxes" (no scoping); a non-empty slice pins those names into the resolved set (RequestedBoxes) and, when includeDisabled is set, relaxes the `enabled: false` gate for exactly those names (IncludeDisabledNames) so the override never widens the working set globally. Callers pass boxes already run through buildkit.NormalizeBoxArgs.
It lived as charly's private `boxResolveOpts` until K-wave 2 cone R1 (A2). candy/plugin-build now builds the same value plugin-side to drive CollectRemoteRefsOpts itself, so the rule moves to the shared fabric module both sides import rather than being duplicated across the boundary — a pure ResolveOpts constructor over sibling spec types, pulling in no mechanism package.
func WithLocalRawRefs ¶ added in v0.2026218.138
func WithLocalRawRefs(opts ResolveOpts, localScanned map[string]ScannedCandy) ResolveOpts
WithLocalRawRefs returns opts with every local candy's RAW (pre-finalize) require:/candy: refs appended to ExtraCandyRefs. CollectRemoteRefsOpts's own "candy manifest require:/candy:" walk reads CandyView.Require/.IncludedCandy — the FINALIZED bare-string wire form (FinalizeCandyRefs strips a "@repo:vTAG" pin down to the bare graph-topology name; correct for its OWN consumers, ExpandCandy/ResolveCandyOrder, which are version-agnostic). Feeding that walk a wrapped view therefore leaves it structurally UNABLE to discover a local candy's pinned remote dep at all (a bare name never looks remote to IsRemoteCandyRef) — the confirmed root cause of a "depends: unknown candy" crash a live box/cachyos generate surfaced (a local candy's require: pins a remote plugin candy). So the raw pre-finalize refs (still carrying the full pin, from ScannedCandy.Refs) are harvested here and fed in as ExtraCandyRefs — the SAME mechanism a deploy's add_candy: already uses to reach a ref no base/builder/require edge would otherwise surface. A local (non-remote) ref is a harmless no-op (IsRemoteCandyRef gates it).
Relocated from charly/layers.go in K-wave 2 cone R1 (A2) for the same reason as BoxResolveOpts: candy/plugin-build's own CollectRemoteRefsOpts call needs the identical augmentation, and a second copy across the module boundary is the R3 duplicate this program removes.
func (ResolveOpts) ShouldIncludeDisabled ¶ added in v0.2026213.1726
func (opts ResolveOpts) ShouldIncludeDisabled(name string) bool
ShouldIncludeDisabled reports whether name's disabled gate should be bypassed under opts. Centralizes the IncludeDisabled + IncludeDisabledNames interaction so call sites stay simple.
type ResolvedAndroid ¶
type ResolvedAndroid struct {
Serial string `yaml:"serial,omitempty" json:"serial,omitempty"`
Device string `yaml:"device,omitempty" json:"device,omitempty"`
ApiLevel int `yaml:"api_level,omitempty" json:"api_level,omitempty"`
GoogleAccount *GoogleAccount `yaml:"google_account,omitempty" json:"google_account,omitempty"`
Plan []Step `yaml:"plan,omitempty" json:"plan,omitempty"`
Box string `yaml:"box,omitempty" json:"box,omitempty"`
Adb *AdbEndpoint `yaml:"adb,omitempty" json:"adb,omitempty"`
Raw RawBody `yaml:"raw,omitempty" json:"raw,omitempty"`
}
#ResolvedAndroid is the resolve-to-envelope form of an `android:` device template.
func (*ResolvedAndroid) EffectiveSerial ¶
func (a *ResolvedAndroid) EffectiveSerial() string
EffectiveSerial returns the device serial, defaulting to the emulator serial.
func (*ResolvedAndroid) IsEndpoint ¶
func (a *ResolvedAndroid) IsEndpoint() bool
IsEndpoint reports whether the resolved device targets a remote adb endpoint.
type ResolvedBox ¶ added in v0.2026210.1533
type ResolvedBox struct {
Name string `yaml:"name,omitempty" json:"name"`
// version is the authored per-entity CalVer (the box config `version:`); optional.
Version string `yaml:"version,omitempty" json:"version,omitempty"`
// effective_version is the content-derived identity emitted as the ai.opencharly.version
// label: the dedicated version if set, else the highest candy version across the full
// chain. Stable across builds when no candy changed.
EffectiveVersion string `yaml:"effective_version,omitempty" json:"effective_version,omitempty"`
Status string `yaml:"status,omitempty" json:"status,omitempty"`
Info string `yaml:"info,omitempty" json:"info,omitempty"`
CheckLevel string `yaml:"check_level,omitempty" json:"check_level,omitempty"`
Base string `yaml:"base,omitempty" json:"base"`
// from mirrors BoxConfig.From after resolution. When non-empty (e.g. "builder:pacstrap"),
// the generator emits FROM scratch + ADD <staged-rootfs.tar.gz> instead of FROM <base>.
From string `yaml:"from,omitempty" json:"from,omitempty"`
BootstrapBuilderImage string `yaml:"bootstrap_builder_image,omitempty" json:"bootstrap_builder_image,omitempty"`
Platforms []string `yaml:"platforms,omitempty" json:"platforms"`
Tag string `yaml:"tag,omitempty" json:"tag"`
Registry string `yaml:"registry,omitempty" json:"registry"`
Pkg string `yaml:"pkg,omitempty" json:"pkg"`
Distro []string `yaml:"distro,omitempty" json:"distro"`
BuildFormats []string `yaml:"build_formats,omitempty" json:"build_formats"`
Tags []string `yaml:"tags,omitempty" json:"tags"`
Candy []string `yaml:"candy,omitempty" json:"candy"`
// User configuration.
User string `yaml:"user,omitempty" json:"user"`
UID int `yaml:"uid,omitempty" json:"uid"`
GID int `yaml:"gid,omitempty" json:"gid"`
Home string `yaml:"home,omitempty" json:"home"`
// user_adopted is true when the resolved user came from the distro's base_user
// declaration rather than being created by the bootstrap.
UserAdopted bool `yaml:"user_adopted,omitempty" json:"user_adopted"`
// Merge configuration — layer merge settings (nil means use CLI defaults).
Merge *BoxMerge `yaml:"merge,omitempty" json:"merge,omitempty"`
// Builder configuration (resolved: image -> base image -> defaults -> {}).
Builder BuilderMap `yaml:"builder,omitempty" json:"builder"`
// Builder capability declaration (image-specific, not inherited).
BuilderCapabilities []string `yaml:"builder_capabilities,omitempty" json:"builder_capabilities"`
Auto bool `yaml:"auto,omitempty" json:"auto"`
// Container network mode (e.g. "host", "none") — declaration of required/recommended
// network mode. Deployment overrides via MergeDeployOntoMetadata.
Network string `yaml:"network,omitempty" json:"network"`
DataImage bool `yaml:"data_image,omitempty" json:"data_image"`
// OCI entrypoint/cmd baked into the image's OCI config (final Containerfile
// ENTRYPOINT/CMD). Empty by default — normal images carry no baked command and
// the deploy-time init injects it; declared only when something spawns the image
// directly from its OCI config (e.g. an embedded controller spawning child
// agents) with no charly deploy in the loop.
Entrypoint []string `yaml:"entrypoint,omitempty" json:"entrypoint,omitempty"`
Cmd []string `yaml:"cmd,omitempty" json:"cmd,omitempty"`
// Derived fields.
IsExternalBase bool `yaml:"is_external_base,omitempty" json:"is_external_base"`
FullTag string `yaml:"full_tag,omitempty" json:"full_tag"`
}
#ResolvedBox — a fully resolved box configuration, the wire-clean subset. EXCLUDED here (both stay on the buildkit.ResolvedBox embedding wrapper, as hand `yaml:"-" json:"-"` fields — the gengotypes CANNOT-table's "json:"-" keep-in-Go, drop-from-wire" exception, spike-verified: no CUE construct emits a suppressed-from-marshal field):
- the three host-render-cache VOCABULARY pointers DistroConfig/DistroDef/BuilderConfig (+ InitSystem/InitDef) — buildkit-owned types spec must never import;
- the four RENDER-cache fields CandyCaps/BakedMetadata/RenderCandyOrder/ActiveInits — `yaml:"-" json:"-"` on ResolvedBox itself (the SEPARATE ResolvedBoxView wire type carries the actual wire copies of this same data, via NewSpecResolvedBox/ProjectResolvedBox).
Fields that carried NO json tag on the former hand struct (always emitted, no omitempty) are REQUIRED (`!`) here; fields that already carried `,omitempty` are OPTIONAL (`?`).
func (*ResolvedBox) SupportsBuild ¶ added in v0.2026210.1533
func (img *ResolvedBox) SupportsBuild(format string) bool
SupportsBuild returns true if this image has the given build format.
func (*ResolvedBox) SupportsTag ¶ added in v0.2026210.1533
func (img *ResolvedBox) SupportsTag(tag string) bool
SupportsTag returns true if this image has the given tag. Tags include format (rpm, deb, pac), distro (fedora, arch), version (fedora:43), and the implicit "all".
type ResolvedBoxView ¶
type ResolvedBoxView struct {
Name string `yaml:"name,omitempty" json:"name"`
Version string `yaml:"version,omitempty" json:"version,omitempty"`
EffectiveVersion string `yaml:"effective_version,omitempty" json:"effective_version,omitempty"`
Status string `yaml:"status,omitempty" json:"status,omitempty"`
Info string `yaml:"info,omitempty" json:"info,omitempty"`
CheckLevel string `yaml:"check_level,omitempty" json:"check_level,omitempty"`
Base string `yaml:"base,omitempty" json:"base,omitempty"`
From string `yaml:"from,omitempty" json:"from,omitempty"`
BootstrapBuilderImage string `yaml:"bootstrap_builder_image,omitempty" json:"bootstrap_builder_image,omitempty"`
Platforms []string `yaml:"platforms,omitempty" json:"platforms,omitempty"`
Tag string `yaml:"tag,omitempty" json:"tag,omitempty"`
Registry string `yaml:"registry,omitempty" json:"registry,omitempty"`
Pkg string `yaml:"pkg,omitempty" json:"pkg,omitempty"`
Distro []string `yaml:"distro,omitempty" json:"distro,omitempty"`
BuildFormats []string `yaml:"build_formats,omitempty" json:"build_formats,omitempty"`
Tags []string `yaml:"tags,omitempty" json:"tags,omitempty"`
Candy []string `yaml:"candy,omitempty" json:"candy,omitempty"`
User string `yaml:"user,omitempty" json:"user,omitempty"`
UID int64 `yaml:"uid,omitempty" json:"uid,omitempty"`
GID int64 `yaml:"gid,omitempty" json:"gid,omitempty"`
Home string `yaml:"home,omitempty" json:"home,omitempty"`
UserAdopted bool `yaml:"user_adopted,omitempty" json:"user_adopted,omitempty"`
Merge *BoxMerge `yaml:"merge,omitempty" json:"merge,omitempty"`
Builder map[string]string `yaml:"builder,omitempty" json:"builder,omitempty"`
BuilderCapabilities []string `yaml:"builder_capabilities,omitempty" json:"builder_capabilities,omitempty"`
Auto bool `yaml:"auto,omitempty" json:"auto,omitempty"`
Network string `yaml:"network,omitempty" json:"network,omitempty"`
DataImage bool `yaml:"data_image,omitempty" json:"data_image,omitempty"`
// OCI entrypoint/cmd baked into the image's OCI config (final Containerfile
// ENTRYPOINT/CMD) — carried on the resolved view so the plugin-build resolve
// leg (NewSpecResolvedBox) can re-emit them without the live *Candy graph.
Entrypoint []string `yaml:"entrypoint,omitempty" json:"entrypoint,omitempty"`
Cmd []string `yaml:"cmd,omitempty" json:"cmd,omitempty"`
IsExternalBase bool `yaml:"is_external_base,omitempty" json:"is_external_base,omitempty"`
FullTag string `yaml:"full_tag,omitempty" json:"full_tag,omitempty"`
// box-AGGREGATES — the cross-candy effective values `charly box inspect --format
// ports|volumes|aliases|engine` prints (the host projector runs CollectBoxPorts /
// CollectBoxVolume / CollectBoxAlias / ResolveBoxEngine into these). engine is the raw
// resolver result ("" → the command body renders "(global default)").
Ports []string `yaml:"ports,omitempty" json:"ports,omitempty"`
Volumes []ResolvedVolumeMount `yaml:"volumes,omitempty" json:"volumes,omitempty"`
Aliases []CandyAlias `yaml:"aliases,omitempty" json:"aliases,omitempty"`
Engine string `yaml:"engine,omitempty" json:"engine,omitempty"`
// box-AUTHORED surfaces the validate ENGINE checks (task #60): the box's OWN authored `plan:`
// (validateOps box-arm — every Op validated for authoring errors) and `alias:` (validateAliases
// box-arm — name char-set + cross-entry dedup). Distinct from the resolved `aliases` AGGREGATE
// above (cross-candy CollectBoxAlias) — these are the box entity's raw authored entries the plugin
// re-validates, so the whole validateOps/validateAliases move to plugin-box (ruling-a: grow the
// envelope rather than keep the R-rule in core).
Plan []Step `yaml:"plan,omitempty" json:"plan,omitempty"`
AuthoredAliases []BoxAlias `yaml:"authored_aliases,omitempty" json:"authored_aliases,omitempty"`
// box-AUTHORED deploy-overlay surfaces ExportAllBox reads (K5-Unit-1, the #67 keystone): the
// box entity's OWN authored env / env_file / security / network / raw description — the fields
// `charly fleet export --all` projects into a FleetConfig so the deploy-state model can be
// built from the RESOLVED-PROJECT ENVELOPE, not the live *Config graph. description is the RAW
// authored string (distinct from info above, which is its descriptionInfo first-line summary);
// env/env_file/security are the box-authored deploy-overlay defaults. network is already carried
// above; these three + description are the additions. Filled by projectBoxAggregates from
// cfg.BoxConfig(name), alongside plan/authored_aliases.
Description string `yaml:"description,omitempty" json:"description,omitempty"`
Env map[string]string `yaml:"env,omitempty" json:"env,omitempty"`
EnvFile string `yaml:"env_file,omitempty" json:"env_file,omitempty"`
Security *Security `yaml:"security,omitempty" json:"security,omitempty"`
// build-render label carriers (#67, the build_resolve RENDER-leg death). The host projector runs
// the label collectors + caps + init resolve + skill probe + status into a fully-baked #BakedLabelSet
// (the EXACT wire data writeLabels emits) so the deploykit render's WriteLabels — relocated out of
// core — FORMATS it byte-for-byte WITHOUT the live *Candy graph. #BakedLabelSet is the BUILD-side
// wire-form carrier (env map, volume LabelVolumeEntry, +init_label_key/oci_labels), distinct from
// #BoxMetadata (the deploy-side hub). caps carries the aggregated candy capability surface the
// render reads beyond the baked labels (the two render-gating booleans; oci_labels also ride the
// baked set for the formatter). Both filled ONLY in the build-render projection; empty for the
// resolved-project / validate path (which never renders), so that path stays lean.
BakedMetadata *BakedLabelSet `yaml:"baked_metadata,omitempty" json:"baked_metadata,omitempty"`
Caps *AggregatedCandyCapsView `yaml:"caps,omitempty" json:"caps,omitempty"`
// build-RENDER init/candy-order caches (#67, the build_resolve RENDER-leg death). The host
// render-prep computes these over the live *Candy/*Config graph (globalOrderForBox,
// AggregateCandyCapabilities, ActiveInit, ResolveInitSystem) and either sets them directly on
// the live ResolvedBox (the parity-test live path) or carries them through this envelope (the
// plugin-build drive path, re-attached by NewSpecResolvedBox) so the deploykit render reads
// RESOLVED caches WITHOUT the live graph. Filled ONLY in the build-render projection; empty for
// the resolved-project / validate path. render_candy_order is the per-box cache-optimal candy
// sequence; init_system/init_def the active init; active_inits the full active-init map
// (EmitInitFragmentStages + EmitInitAssembly read it).
RenderCandyOrder []string `yaml:"render_candy_order,omitempty" json:"render_candy_order,omitempty"`
InitSystem string `yaml:"init_system,omitempty" json:"init_system,omitempty"`
InitDef *ResolvedInit `yaml:"init_def,omitempty" json:"init_def,omitempty"`
ActiveInits map[string]*ResolvedInit `yaml:"active_inits,omitempty" json:"active_inits,omitempty"`
}
#ResolvedBoxView — the resolved box METADATA a consumer reads: EXACTLY the non-json:"-" fields of buildkit.ResolvedBox, in declaration order, so this view is the wire-safe half of what `charly box inspect` already serializes (json.MarshalIndent(*ResolvedBox) — which never reaches the 6 json:"-" compute-cache pointers). Field order mirrors ResolvedBox so a future inspect relocation projects into this view byte-faithfully. Every field optional (a projection envelope); name is the stable key.
type ResolvedDistro ¶
type ResolvedDistro struct {
Inherits string `yaml:"inherits,omitempty" json:"inherits,omitempty"`
InheritPackages bool `yaml:"inherit_packages,omitempty" json:"inherit_packages,omitempty"`
Version string `yaml:"version,omitempty" json:"version,omitempty"`
Bootstrap Bootstrap `yaml:"bootstrap,omitempty" json:"bootstrap,omitempty"`
Workarounds []string `yaml:"workaround,omitempty" json:"workaround,omitempty"`
Format map[string]*Format `yaml:"format,omitempty" json:"format,omitempty"`
BaseUser *BaseUser `yaml:"base_user,omitempty" json:"base_user,omitempty"`
Pacstrap *Pacstrap `yaml:"pacstrap,omitempty" json:"pacstrap,omitempty"`
Debootstrap *Debootstrap `yaml:"debootstrap,omitempty" json:"debootstrap,omitempty"`
AlpineBootstrap *AlpineBootstrap `yaml:"alpine_bootstrap,omitempty" json:"alpine_bootstrap,omitempty"`
Bootloader *Bootloader `yaml:"bootloader,omitempty" json:"bootloader,omitempty"`
DiskLayout *DiskLayout `yaml:"disk_layout,omitempty" json:"disk_layout,omitempty"`
Dnf *Dnf `yaml:"dnf,omitempty" json:"dnf,omitempty"`
Installer *DistroInstaller `yaml:"installer,omitempty" json:"installer,omitempty"`
Raw RawBody `yaml:"raw,omitempty" json:"raw,omitempty"`
}
--- resolve-to-envelope wire type (Cutover M, the long pole; SDD conversion, per the standing operator directive: a hand-written wire struct not yet CUE-sourced is conversion-in-progress, never a sanctioned exception). candy/plugin-distro resolves an authored `distro:` build-vocabulary entity into a ResolvedDistro the kernel's build engine consumes without importing the concrete spec.Distro. Written out explicitly (not embedding #Distro) so every field's required/optional state is independently auditable against the former hand type. The host keeps RenderTemplate + the cache-mount vocab (per the plan); the plugin owns the distro KNOWLEDGE (schema/typed shape/validation). PrimaryFormat()/LocalPkgFormat() are pure Go METHODS — CUE cannot express them — and stay hand-written in spec/distro_methods.go (mirrors Op.Kind() in spec/charly_methods.go: a method, not a type).
func (*ResolvedDistro) LocalPkgFormat ¶
func (d *ResolvedDistro) LocalPkgFormat(primaryFormat string) (string, *LocalPkg)
LocalPkgFormat picks the format whose local_pkg block drives the charly package INSTALL (the download leg): the caller's primary format, then PrimaryFormat, then any localpkg-capable format.
func (*ResolvedDistro) PrimaryFormat ¶
func (d *ResolvedDistro) PrimaryFormat() string
PrimaryFormat returns the distro's primary (non-secondary) build format — the deterministic first non-secondary Format name.
type ResolvedGpuSelector ¶
type ResolvedGpuSelector struct {
Vendor string `yaml:"vendor,omitempty" json:"vendor,omitempty"`
}
type ResolvedInit ¶
type ResolvedInit struct {
CandyFields []string `yaml:"candy_field,omitempty" json:"candy_field,omitempty"`
CandyFiles []string `yaml:"candy_file,omitempty" json:"candy_file,omitempty"`
DependsCandy string `yaml:"depends_candy,omitempty" json:"depends_candy,omitempty"`
RequiresCapability []string `yaml:"requires_capability,omitempty" json:"requires_capability,omitempty"`
Model string `yaml:"model,omitempty" json:"model,omitempty"`
HeaderFile string `yaml:"header_file,omitempty" json:"header_file,omitempty"`
FragmentDir string `yaml:"fragment_dir,omitempty" json:"fragment_dir,omitempty"`
RelayTemplate string `yaml:"relay_template,omitempty" json:"relay_template,omitempty"`
StageName string `yaml:"stage_name,omitempty" json:"stage_name,omitempty"`
StageHeaderCopy string `yaml:"stage_header_copy,omitempty" json:"stage_header_copy,omitempty"`
StageFragmentCopy string `yaml:"stage_fragment_copy,omitempty" json:"stage_fragment_copy,omitempty"`
AssemblyTemplate string `yaml:"assembly_template,omitempty" json:"assembly_template,omitempty"`
SystemEnableTemplate string `yaml:"system_enable_template,omitempty" json:"system_enable_template,omitempty"`
PostAssemblyTemplate string `yaml:"post_assembly_template,omitempty" json:"post_assembly_template,omitempty"`
Entrypoint []string `yaml:"entrypoint,omitempty" json:"entrypoint,omitempty"`
FallbackEntrypoint []string `yaml:"fallback_entrypoint,omitempty" json:"fallback_entrypoint,omitempty"`
ManagementTool string `yaml:"management_tool,omitempty" json:"management_tool,omitempty"`
ManagementCommands map[string]string `yaml:"management_command,omitempty" json:"management_command,omitempty"`
LabelKey string `yaml:"label_key,omitempty" json:"label_key,omitempty"`
ServiceSchema *InitServiceSchema `yaml:"service_schema,omitempty" json:"service_schema,omitempty"`
// Raw is the opaque init body — threaded to the service-render leg (leg 1).
Raw RawBody `yaml:"raw,omitempty" json:"raw,omitempty"`
}
#ResolvedInit is the resolve-to-envelope form of an init system the kernel consumes for stage/assembly emission, capability labels, and the entrypoint contract (legs 2–4 of the init de-type). It carries the init system's build + runtime VALUES + templates the host reads generically — the kernel never imports the concrete spec.Init. Raw is the opaque init body threaded to the plugin's service-render leg (leg 1). candy/plugin-init produces it from spec.Init.
type ResolvedKubernetes ¶ added in v0.2026223.1018
type ResolvedKubernetes struct {
KubeconfigContext string `yaml:"kubeconfig_context,omitempty" json:"kubeconfig_context,omitempty"`
Raw RawBody `yaml:"raw,omitempty" json:"raw,omitempty"`
}
#ResolvedKubernetes is the resolve-to-envelope form of a `kubernetes:` cluster template. The kernel reads only KubeconfigContext (the deploy preresolver); the full cluster model rides opaquely in Raw and is decoded by candy/plugin-k8sgen, never the kernel.
type ResolvedLocal ¶
type ResolvedLocal struct {
Candy []CandyRef `yaml:"candy,omitempty" json:"candy,omitempty"`
InstallOpts *InstallOpts `yaml:"install_opts,omitempty" json:"install_opts,omitempty"`
Env map[string]string `yaml:"env,omitempty" json:"env,omitempty"`
Description string `yaml:"description,omitempty" json:"description,omitempty"`
Plan []Step `yaml:"plan,omitempty" json:"plan,omitempty"`
// raw is the opaque authored body (the kernel passes it back unread where
// a verbatim template is needed).
Raw RawBody `yaml:"raw,omitempty" json:"raw,omitempty"`
}
#ResolvedLocal is the resolve-to-envelope form of a `local:` template — a candy-stack applied to a host. The kernel reads it (candy stack / install opts / plan), never spec.Local.
type ResolvedPod ¶
type ResolvedPod struct {
Box CandyRef `yaml:"box,omitempty" json:"box,omitempty"`
Sidecar []PodSidecar `yaml:"sidecar,omitempty" json:"sidecar,omitempty"`
Secret []DeploySecret `yaml:"secret,omitempty" json:"secret,omitempty"`
EnvDefaults map[string]string `yaml:"env_default,omitempty" json:"env_default,omitempty"`
Plan []Step `yaml:"plan,omitempty" json:"plan,omitempty"`
Raw RawBody `yaml:"raw,omitempty" json:"raw,omitempty"`
}
#ResolvedPod is the resolve-to-envelope form of a `pod:` template — a box + sidecar + plan fleet. The kernel reads it (currently the include-spliced Plan), never spec.Pod.
type ResolvedProject ¶
type ResolvedProject struct {
Version string `yaml:"version,omitempty" json:"version,omitempty"`
Boxes map[string]ResolvedBoxView `yaml:"boxes,omitempty" json:"boxes,omitempty"`
Candies map[string]CandyView `yaml:"candies,omitempty" json:"candies,omitempty"`
// candy_models — the serializable candy BUILD models (validate, the plan-include splicer, K3-D)
// keyed by name, distinct from candies (identity/graph). See candymodel.cue.
CandyModels map[string]CandyModel `yaml:"candy_models,omitempty" json:"candy_models,omitempty"`
Deploy map[string]*Deploy `yaml:"deploy,omitempty" json:"deploy,omitempty"`
// build vocabulary (the validate ENGINE consumer): distro/init PIN the hand wire structs
// (spec.ResolvedDistro/ResolvedInit — the distro/init de-type OpResolve envelopes, no #CUE def);
// #Builder is a clean def. Pointer maps for parity with DistroConfig/BuilderConfig/InitConfig.Init.
Distro map[string]*ResolvedDistro `yaml:"distro,omitempty" json:"distro,omitempty"`
Builder map[string]*Builder `yaml:"builder,omitempty" json:"builder,omitempty"`
Init map[string]*ResolvedInit `yaml:"init,omitempty" json:"init,omitempty"`
// kind templates (validate localtemplates + check-include pod/vm arms + status kubernetes/adb enumeration).
Templates *ProjectTemplates `yaml:"templates,omitempty" json:"templates,omitempty"`
// kind:agent catalog (the harness AI-CLI pick — plugin-check reads it off this envelope; charly feature list-agent).
AgentBodies map[string]RawBody `yaml:"agent_bodies,omitempty" json:"agent_bodies,omitempty"`
// build order + auto-intermediates (charly box list targets).
BuildTargets []BuildTarget `yaml:"build_targets,omitempty" json:"build_targets,omitempty"`
// validate D-data word-sets (task #60, ruling: host projects the registry facts into the envelope
// so the validate ENGINE moves to the plugin WITHOUT the plugin ever dialing the host registry).
// The host fills these in the validate-project path only (empty for the resolved-project path);
// clause-D kind/word-recognition DATA consulted BY WORD, never a per-kind branch.
//
// provider_capabilities — every compiled-in provider as "<class>:<word>" (validatePluginCandy
// checks a `source: builtin` candy's declared providers are actually compiled in).
// act_capable_verbs — the plugin WORDS whose act form has a build/deploy install path (the host
// type-asserts ProvisionActor/TypedStepProvider/BuildEmitter + connected/declared externals +
// command, exactly as core's opActsInBuildDeploy does), so validateCheck's act-form rule keeps
// builtin rejection behaviour without the registry.
ProviderCapabilities []string `yaml:"provider_capabilities,omitempty" json:"provider_capabilities,omitempty"`
ActCapableVerbs []string `yaml:"act_capable_verbs,omitempty" json:"act_capable_verbs,omitempty"`
// box_plans — the include-ready FLATTENED acceptance plan per box (the `include: box:<name>`
// arm of the plan-composition splicer). The host projector runs the SAME base-chain walk
// CollectDescriptions uses (candy-chain bakeable steps + the box-level bakeable plan) and
// flattens the three sections into one ordered []Step, keyed by the QUALIFIED box name
// (namespaced boxes like `fedora.jupyter` included) — the exact result the former in-core
// box arm produced. A plugin cannot recompute it (base-chain + candy-order + bakeable filter
// are host resolve Mechanisms over the runtime Candy), so the resolve engine ships it. Only
// boxes with a non-empty flattened plan appear.
BoxPlans map[string][]Step `yaml:"box_plans,omitempty" json:"box_plans,omitempty"`
// global_order — the popularity-weighted GLOBAL candy order (GlobalCandyOrder) the
// build RENDER reads (deploykit Generator.GlobalOrderForBox → per-box candy sequence for
// cache-optimal layering). A host resolve Mechanism over the runtime Candy graph, so the
// resolve engine ships it (the plugin-side render can't recompute it). Filled in the
// build-render projection (#67); empty for the resolved-project/validate path.
GlobalOrder []string `yaml:"global_order,omitempty" json:"global_order,omitempty"`
// externalized_builders — the registry D-FACT: which detection-builder WORDS
// (pixi/npm/aur/cargo) are externalized (their inline render crosses the OpResolve seam vs
// an in-core vocabulary). The render selects the branch by word (deploykit
// DetectExternalizedBuilders + candy_steps builder arm). clause-D word-recognition DATA
// consulted BY WORD; filled in the build-render projection (#67).
ExternalizedBuilders map[string]bool `yaml:"externalized_builders,omitempty" json:"externalized_builders,omitempty"`
// resources — the resolved `resource:` kind entities (K1-unblock wave 1), keyed by name,
// mirroring `deploy` byte-for-byte: the host decodes each project resource:` entity via the
// SAME kind-blind resource-kind plugin resolve it always used (never a kernel decode of the
// concrete #Resource shape — clause-D/M unaffected), so this is a plain data projection, not a
// new engine. Consumer: the resource arbiter (candy/plugin-preempt), which previously reached
// this ONLY via the bespoke ExecutorService.HostArbiter "resources" seam
// (charly/arbiter_host.go) — the last of its 2 genuinely-LoadUnified-coupled host seams. Reading
// it off THIS envelope instead retires that seam, leaving `resources` (this field) as the single
// source both the arbiter plugin and any other resolved-project consumer share (R3).
Resources map[string]*ResolvedResource `yaml:"resources,omitempty" json:"resources,omitempty"`
// primaries — the plugin-verb PRIMARY-field D-fact: a plugin verb WORD → its declared primary
// input field (`cdp` → `method`, `file` → `file`), the SAME registry-derived kind-recognition
// DATA the LOAD path already snapshots as spec.Threaded.Primaries (loaderThreaded), carried here
// SAVE-side too. The deploy-state WRITER's plan RESUGAR (deploy_nodeform.go's resugarPlan —
// collapsing an internal plugin/plugin_input pair back to the authored `<word>: <input>` sugar)
// needs this map to collapse a single-primary input to its scalar shorthand; carrying it on the
// envelope lets a plugin holding this projection resugar a plan WITHOUT dialing the host provider
// registry (the K4 keystone that unblocks moving the deploy-state marshal plugin-side). clause-D
// word-recognition DATA consulted BY WORD, never a per-kind branch; filled on every resolve path.
Primaries map[string]string `yaml:"primaries,omitempty" json:"primaries,omitempty"`
}
#ResolvedProject — the whole resolved projection: the schema version, the resolved boxes keyed by name, the resolved candy graph keyed by name, and the deploy tree (uf.Fleet verbatim — already map[string]spec.Deploy). The deploy map is @go-pinned to a pointer map so `cue exp gengotypes` generates map[string]*Deploy (recursive tree, faithful). provides/sidecar are additive later members of this same envelope (added by the consumer unit that first needs them).
type ResolvedProjectRequest ¶
type ResolvedProjectRequest struct {
Dir string `yaml:"dir,omitempty" json:"dir,omitempty"`
IncludeDisabled bool `yaml:"include_disabled,omitempty" json:"include_disabled,omitempty"`
// Check commands resolving a distro-submodule project must see the parent
// superproject's in-development candies before bed classification. The host
// applies the same local override used by the later R10 session only for the
// duration of this projection.
LocalSuperproject bool `yaml:"local_superproject,omitempty" json:"local_superproject,omitempty"`
// A caller compiling against an `add_candy:`/`--add-candy` ref (a candy the project's own
// image-closure walk never reaches — the reachability-scoped remote-ref collection is
// deliberately narrower than "every candy anywhere," R1) widens the scan the SAME way
// ScanAllCandyWithConfigOpts' ResolveOpts.ExtraCandyRefs already does for a check bed's own
// add_candy: (deploy_add_shared.go's deployNodePluginContext) — without this, the compile
// plugin's OWN independent resolved-project re-fetch (candy/plugin-fleet's compile.go)
// never discovers a remote add-candy ref the HOST's separate scanCandiesForRef call already
// pulled in via its own synthetic-augmented scan, and BuildDeployPlan fails "candy not in
// resolved-project envelope" (RCA'd K1-alpha regression, check-addcandy-pod/check-stepkind-
// emit-pod).
ExtraCandyRefs []string `yaml:"extra_candy_refs,omitempty" json:"extra_candy_refs,omitempty"`
// requested_boxes — the explicit build/generate targets (`charly box generate <name>`),
// buildkit.NormalizeBoxArgs-normalized (task #17 fix). The reachability-scoped remote-ref
// COLLECTION walk (loaderkit.CollectRemoteRefsOpts) only follows base/builder edges from
// ROOT-owned images by default — a namespace-qualified on-demand target
// (`charly box generate fedora.check-pod`) that is not itself a base/builder of any root
// image is otherwise never visited, so its own remote candy refs are silently never
// collected/fetched, and the later candy-order RESOLVE step then fails "unknown candy" for a
// ref the fetch step skipped. Mirrors spec.ResolveOpts.RequestedBoxes, which already pulls a
// qualified on-demand target into buildkit.ResolveAllBox's RESOLVE set the identical way —
// this closes the matching gap on the COLLECT half.
//
// The `boxes` field is DELETED (K-wave 2 cone R1). It carried the plugin-resolved box set to the
// host's `buildengine-prep` leg, whose only job was seeding the render-seam floor's Generator
// cache. That seam is gone — every render seam peer-dispatches now — so the leg, the cache, and
// this field all lost their single consumer together, along with deploykit's host-side
// ResolveAllSpecBoxes/WrapSpecBoxes/SpecBoxes bridge.
RequestedBoxes []string `yaml:"requested_boxes,omitempty" json:"requested_boxes,omitempty"`
}
#ResolvedProjectRequest — the `resolved-project` HostBuild request: which project dir to resolve (empty = the host's cwd) and whether to include enabled:false boxes. The reply is #ResolvedProject.
type ResolvedResource ¶
type ResolvedResource struct {
Gpu *ResolvedGpuSelector `yaml:"gpu,omitempty" json:"gpu,omitempty"`
}
type ResolvedSidecar ¶
type ResolvedSidecar struct {
Name string `yaml:"name,omitempty" json:"name"`
Image string `yaml:"image,omitempty" json:"image,omitempty"`
Env map[string]string `yaml:"env,omitempty" json:"env,omitempty"`
Secret []ResolvedSidecarSecret `yaml:"secret,omitempty" json:"secret,omitempty"`
Volume []ResolvedSidecarVolume `yaml:"volume,omitempty" json:"volume,omitempty"`
Security *Security `yaml:"security,omitempty" json:"security,omitempty"`
}
#ResolvedSidecar is a fully-resolved co-deployed container the host consumes for quadlet generation — the resolve-to-envelope form of a sidecar (NO Sidecar / SidecarDef fields survive; the plugin already merged + resolved everything).
type ResolvedSidecarSecret ¶
type ResolvedSidecarSecret struct {
Name string `yaml:"name,omitempty" json:"name"`
Env string `yaml:"env,omitempty" json:"env,omitempty"`
HostEnv string `yaml:"host_env,omitempty" json:"host_env,omitempty"`
SecretName string `yaml:"secret_name,omitempty" json:"secret_name,omitempty"`
}
#ResolvedSidecarSecret is a resolved sidecar secret (the sidecar-scoped subset of the host's CollectedSecret): the podman secret name + the container/host env-var names + the original manifest secret name.
type ResolvedSidecarVolume ¶
type ResolvedSidecarVolume struct {
VolumeName string `yaml:"volume_name,omitempty" json:"volume_name"`
ContainerPath string `yaml:"container_path,omitempty" json:"container_path"`
}
#ResolvedSidecarVolume is a resolved sidecar volume: the charly-scoped volume name + its container mount path.
type ResolvedVm ¶
type ResolvedVm struct {
Source VmSource `yaml:"source,omitempty" json:"source"`
DiskSize string `yaml:"disk_size,omitempty" json:"disk_size,omitempty"`
Ram string `yaml:"ram,omitempty" json:"ram,omitempty"`
Cpus int `yaml:"cpu,omitempty" json:"cpu,omitempty"`
Machine string `yaml:"machine,omitempty" json:"machine,omitempty"`
Firmware string `yaml:"firmware,omitempty" json:"firmware"`
Backend string `yaml:"backend,omitempty" json:"backend"`
Autostart bool `yaml:"autostart,omitempty" json:"autostart"`
Network *VmNetwork `yaml:"network,omitempty" json:"network,omitempty"`
SSH *VmSsh `yaml:"ssh,omitempty" json:"ssh,omitempty"`
CloudInit *VmCloudInit `yaml:"cloud_init,omitempty" json:"cloud_init,omitempty"`
Libvirt *LibvirtDomain `yaml:"libvirt,omitempty" json:"libvirt,omitempty"`
Plan []Step `yaml:"plan,omitempty" json:"plan,omitempty"`
Snapshots []VmSnapshot `yaml:"snapshot,omitempty" json:"snapshot,omitempty"`
Raw RawBody `yaml:"raw,omitempty" json:"raw,omitempty"`
}
--- resolve-to-envelope wire type (Cutover L; SDD conversion, per the standing operator directive: a hand-written wire struct not yet CUE-sourced is conversion-in-progress, never a sanctioned exception). ResolvedVm mirrors #Vm's fields — written out explicitly rather than embedding #Vm, since the resolved envelope carries PLAIN post-default scalars, not #Vm's own enum/default machinery (firmware/backend/autostart are plain string/bool here, never the `*"bios"|...` disjunction). candy/plugin-substrate resolves an authored `vm:` template into this envelope; the kernel's vm build/deploy consumers read it without importing the concrete spec.Vm.
type ResolvedVolumeMount ¶
type ResolvedVolumeMount struct {
VolumeName string `yaml:"volume_name,omitempty" json:"volume_name,omitempty"`
ContainerPath string `yaml:"container_path,omitempty" json:"container_path,omitempty"`
}
#ResolvedVolumeMount — one entry of the box-aggregate volume list (CollectBoxVolume): the charly-<box>-<name> volume name + the home-expanded container path. Mirrors deploykit.VolumeMount (a permanent deploykit home, never wire-marshaled there — spec carries the envelope copy).
type Resource ¶
type Resource struct {
Gpu *GpuSelector `yaml:"gpu,omitempty" json:"gpu,omitempty"`
}
func (*Resource) HasSelector ¶
HasSelector reports whether the resource carries a GPU selector.
type ResourceResolveInput ¶
type ResourceResolveInput struct {
Resource RawBody `yaml:"resource,omitempty" json:"resource"`
}
type ResourceResolveReply ¶
type ResourceResolveReply struct {
Resolved *ResolvedResource `yaml:"resolved,omitempty" json:"resolved,omitempty"`
}
type RetentionReply ¶
type RetentionReply struct {
ImageRefs []string `yaml:"image_refs,omitempty" json:"image_refs,omitempty"`
DanglingIDs []string `yaml:"dangling_ids,omitempty" json:"dangling_ids,omitempty"`
StagingDirs []string `yaml:"staging_dirs,omitempty" json:"staging_dirs,omitempty"`
BuildDirs []string `yaml:"build_dirs,omitempty" json:"build_dirs,omitempty"`
CheckPaths []string `yaml:"check_paths,omitempty" json:"check_paths,omitempty"`
DeepIDs []string `yaml:"deep_ids,omitempty" json:"deep_ids,omitempty"`
DeepBytes int64 `yaml:"deep_bytes,omitempty" json:"deep_bytes,omitempty"`
TagGroups []TagInfo `yaml:"tag_groups,omitempty" json:"tag_groups,omitempty"`
KeepImages int `yaml:"keep_images,omitempty" json:"keep_images,omitempty"`
KeepCheckRuns int `yaml:"keep_check_runs,omitempty" json:"keep_check_runs,omitempty"`
Error string `yaml:"error,omitempty" json:"error,omitempty"`
}
#RetentionReply is the verb:retention reply: the removed (or would-remove, under dry_run) image refs, build-candy dirs, and check-run paths, plus the effective retention counts, for the caller to present.
deep_ids/deep_bytes report the store-wide untagged-image purge (deep): the removed (or would-remove) image IDs and the sum of their reported storage Size in bytes. deep_bytes is an UPPER BOUND, not a prediction of actual freed disk: each image's reported Size counts every layer it references, and many of those layers are SHARED with images that remain (retained tags, or other still-referenced dangling images) — RDD-verified live on a real host, where a --deep purge removing 68 untagged images (3,552 -> 3,484) reported ~92.6 GiB via this sum but only ~4.6 GiB of disk was actually freed (132.6 GB -> 128 GB), because most layer bytes stayed shared with the ~3,400 remaining (largely stale-tagged) images. `charly clean --deep --dry-run` presents deep_bytes as "up to" for exactly this reason; pairing --deep with --invalidate (removing stale TAGS too, so their exclusively-held layers also become unreferenced) gets closer to the reported figure.
tag_groups is the `list` reply payload: every locally stored charly-labeled tag, newest-first per box.
error is a human-facing message on a non-recoverable failure.
type RetentionRequest ¶
type RetentionRequest struct {
Dir string `yaml:"dir,omitempty" json:"dir"`
DryRun bool `yaml:"dry_run,omitempty" json:"dry_run,omitempty"`
Images bool `yaml:"images,omitempty" json:"images,omitempty"`
Check bool `yaml:"check,omitempty" json:"check,omitempty"`
Deep bool `yaml:"deep,omitempty" json:"deep,omitempty"`
List bool `yaml:"list,omitempty" json:"list,omitempty"`
BuildPrune bool `yaml:"build_prune,omitempty" json:"build_prune,omitempty"`
Keep int `yaml:"keep,omitempty" json:"keep,omitempty"`
KeepImages int `yaml:"keep_images,omitempty" json:"keep_images,omitempty"`
KeepCheckRuns int `yaml:"keep_check_runs,omitempty" json:"keep_check_runs,omitempty"`
Invalidate string `yaml:"invalidate,omitempty" json:"invalidate,omitempty"`
}
#RetentionRequest is the verb:retention request. dir is the project directory (os.Getwd()) — always present, never empty. keep=0 means "use the resolved defaults"; invalidate (non-empty) runs ONLY the targeted image-tag invalidation. deep runs the store-wide untagged/dangling-image purge category (`charly clean --deep`) — unlike images (which only ever touches charly-labeled tags/dangling ids), deep removes EVERY untagged image in local storage, including unlabeled multi-stage build intermediates the images category can never see. list runs the read-only tag inventory (`charly box list tags`) — no dir needed beyond resolving the engine, nothing is removed. build_prune is the narrow post-`charly box build` step: retention-prune image tags + stale .build/_candy staging dirs ONLY (the historic charly/retention.go pruneAfterBuild scope) — deliberately NOT the fuller images category (which also sweeps dangling images + buildah staging), so a build's own post-step behavior is unchanged by this relocation. keep_images/keep_check_runs are the caller's PRE-RESOLVED defaults.keep_images/ keep_check_runs (0 = disabled) — this engine never reads charly.yml itself; a core caller (already running LoadConfig in-process) resolves these directly, a plugin caller resolves them PLUGIN-SIDE via the shared sdk/loaderkit.ResolveRetentionDefaultsViaExecutor (K-wave 2 cone R6 — the former "retention-defaults" HostBuild seam is DELETED). keep (the CLI --keep override) wins over both when > 0.
type ReverseOp ¶
type ReverseOp struct {
Kind ReverseOpKind `yaml:"kind,omitempty" json:"kind"`
Format string `yaml:"format,omitempty" json:"format,omitempty"`
Targets []string `yaml:"targets,omitempty" json:"targets,omitempty"`
Scope Scope `yaml:"scope,omitempty" json:"scope,omitempty"`
Extra map[string]string `yaml:"extra,omitempty" json:"extra,omitempty"`
// uninstall_cmd is the rendered host-venue package-removal command for a
// ReverseOpPackageRemove op, filled at record time from the format's
// uninstall_template by fillReverseUninstallCmds.
UninstallCmd string `yaml:"uninstall_cmd,omitempty" json:"uninstall_cmd,omitempty"`
}
#ReverseOp is a single teardown action. Serialized into the ledger so uninstall can reverse a deploy without re-reading the candy manifest.
type ReverseOpKind ¶
type ReverseOpKind string
ReverseOpKind discriminates the kinds of teardown actions Reverse() produces. Ledger entries serialize these verbatim so a later `charly fleet del` can walk them without re-compiling the plan.
const ( ReverseOpPackageRemove ReverseOpKind = "package-remove" ReverseOpCargoUninstall ReverseOpKind = "cargo-uninstall" ReverseOpNpmUninstallG ReverseOpKind = "npm-uninstall-g" ReverseOpPixiEnvRemove ReverseOpKind = "pixi-env-remove" ReverseOpRmFileSystem ReverseOpKind = "rm-file-system" ReverseOpRmFileUser ReverseOpKind = "rm-file-user" ReverseOpRmDirRecursive ReverseOpKind = "rm-dir-recursive" ReverseOpServiceDisable ReverseOpKind = "service-disable" ReverseOpServiceRemove ReverseOpKind = "service-remove" ReverseOpRemoveDropin ReverseOpKind = "remove-dropin" ReverseOpRestoreEnabled ReverseOpKind = "restore-enabled" ReverseOpRemoveManaged ReverseOpKind = "remove-managed-block" ReverseOpRemoveEnvdFile ReverseOpKind = "remove-envd-file" ReverseOpRemoveRepoFile ReverseOpKind = "remove-repo-file" ReverseOpCoprDisable ReverseOpKind = "copr-disable" // ReverseOpPluginScript is the GENERIC recordable reverse op an external // (out-of-process) deploy/step/builder plugin returns: a shell script + its // scope, run verbatim at teardown via the ReverseExecutor (system → sudo, // user → no sudo). The script lives in Extra["script"]; Scope picks the // privilege. It preserves the record-and-replay invariant — only RECORDED // ops are replayed, never recomputed — without any new struct shape. ReverseOpPluginScript ReverseOpKind = "plugin-script" )
type RouteConfig ¶
type RouteConfig struct {
Host string `yaml:"host,omitempty" json:"host,omitempty"`
Port string `yaml:"port,omitempty" json:"port,omitempty"`
}
#RouteConfig — a resolved route declaration (host + port-as-string). Mirrors deploykit.RouteConfig. Port is a STRING here (the resolved form), distinct from #CandyRoute.port (authored int).
type SSHTarget ¶ added in v0.2026210.1121
SSHTarget is the parsed form of an ssh-style target string. Examples:
- "host" → {User: $USER, Host: "host", Port: 22}
- "user@host" → {User: "user", Host: "host", Port: 22}
- "user@host:2222" → {User: "user", Host: "host", Port: 2222}
- "qemu+ssh://user@host/…" → parsed via ParseLibvirtSSHURI (not here)
func ParseSSHTarget ¶ added in v0.2026210.1121
ParseSSHTarget accepts "[user@]host[:port]" and fills defaults:
- User: $USER
- Port: 22
type SaveDeployStateInput ¶ added in v0.2026211.549
type SaveDeployStateInput struct {
Ports []string `yaml:"ports,omitempty" json:"ports,omitempty"`
// set_ports gates whether Ports is written to charly.yml at all: `charly config <name>` (no
// --port flags) and `charly update <name>` must not silently overwrite operator port overrides
// with image-label defaults.
SetPorts bool `yaml:"set_ports,omitempty" json:"set_ports,omitempty"`
Env map[string]string `yaml:"env,omitempty" json:"env,omitempty"`
CleanEnv bool `yaml:"clean_env,omitempty" json:"clean_env,omitempty"`
EnvFile string `yaml:"env_file,omitempty" json:"env_file,omitempty"`
Network string `yaml:"network,omitempty" json:"network,omitempty"`
Security *Security `yaml:"security,omitempty" json:"security,omitempty"`
Volume []DeployVolume `yaml:"volume,omitempty" json:"volume,omitempty"`
Sidecar map[string]RawBody `yaml:"sidecar,omitempty" json:"sidecar,omitempty"`
Tunnel *TunnelYAML `yaml:"tunnel,omitempty" json:"tunnel,omitempty"`
// secret_names lists env var names declared as secret_accepts / secret_requires on the image;
// saveDeployState defensively strips any matching KEY=VAL entries from both the input Env and
// the existing persisted entry before writing.
SecretNames []string `yaml:"secret_names,omitempty" json:"secret_names,omitempty"`
// Disposable + Lifecycle classification (see /charly-internals:disposable).
SetDisposable bool `yaml:"set_disposable,omitempty" json:"set_disposable,omitempty"`
Disposable bool `yaml:"disposable,omitempty" json:"disposable,omitempty"`
SetLifecycle bool `yaml:"set_lifecycle,omitempty" json:"set_lifecycle,omitempty"`
Lifecycle string `yaml:"lifecycle,omitempty" json:"lifecycle,omitempty"`
// Box + Target — the schema-required fields per the require-image cutover. Written when non-empty
// AND when the existing entry doesn't already have a value (don't clobber operator-authored refs).
Box string `yaml:"box,omitempty" json:"box,omitempty"`
Target string `yaml:"target,omitempty" json:"target,omitempty"`
// resolved_image is the concrete overlay image ref produced by a pod deploy's add_candy: overlay
// build, persisted by PrepareVenue so config/start deploy EXACTLY that overlay.
ResolvedImage string `yaml:"resolved_image,omitempty" json:"resolved_image,omitempty"`
// vm_state + vm_cross_ref — the vm substrate's persisted runtime state + the kind:vm cross-ref,
// shipped by the externalized vm plugin's PrepareVenue reply as the generic State patch.
VmState *VmDeployState `yaml:"vm_state,omitempty" json:"vm_state,omitempty"`
VmCrossRef string `yaml:"vm_cross_ref,omitempty" json:"vm_cross_ref,omitempty"`
// Resource-arbitration axis: the holder-side Preemptible block + the claimant-side
// requires_exclusive / requires_shared token lists. Persisted so a deploy/bed MEMBER round-trips
// its arbiter role through the per-host overlay. Written when non-empty.
Preemptible *PreemptibleConfig `yaml:"preemptible,omitempty" json:"preemptible,omitempty"`
RequiresExclusive []string `yaml:"requires_exclusive,omitempty" json:"requires_exclusive,omitempty"`
}
#SaveDeployStateInput holds the deployment parameters SaveDeployState persists to charly.yml (promoted from sdk/deploykit — #55 import-purity, Cone V value-vocabulary; the SaveDeployState FUNCTION stays in deploykit). Every field is a spec/stdlib type. The deploy candies (plugin-deploy-pod, plugin-fleet) pass it DIRECTLY to deploykit.SaveDeployState plugin-side (#55 K4 — no host seam carries it across the wire anymore; the fields are ephemeral in-operation, never persisted as-is — the node-form marshal writes only the resulting entry).
type ScanSeams ¶ added in v0.2026213.1726
type ScanSeams struct {
// CollectRemoteRefs runs the reachability-scoped remote-ref walk over the project's boxes +
// this local candy set, returning each distinct (repo, git-tag) to fetch. Host closure:
// CollectRemoteRefsOpts(cfg, FinalizeScannedCandies(localScanned, nil), WithLocalRawRefs(opts, localScanned)).
CollectRemoteRefs func(localScanned map[string]ScannedCandy) ([]RemoteDownload, error)
// EnsureRepo resolves a (repoPath, version) to a local cache directory, fetching + auto-migrating
// on a cache miss (host closure: EnsureRepoDownloaded).
EnsureRepo func(repoPath, version string) (string, error)
// ScanRemote scans the wanted bare refs out of a downloaded repo cache dir (host closure:
// requireCandyScanner().ScanRemoteCandy(cacheDir, repoPath, wantRefs, parseCandyYAML)).
ScanRemote func(cacheDir, repoPath string, wantRefs map[string]bool) (map[string]ScannedCandy, error)
// Warn receives the scan's own advisories — candy-version skew and local-shadow notes —
// instead of them going straight to stderr.
//
// They USED to be `fmt.Fprintf(os.Stderr, ...)` calls inside the arbiter, which made them
// unstructured and therefore uncountable: `charly box validate` could not report how many
// warnings a run produced, because they never reached its diagnostics. A summary that
// cannot see them can only omit the number or state a false one.
//
// Optional. When nil the advisories still go to stderr exactly as before, so every existing
// caller keeps its current behaviour and this stays a purely additive seam.
Warn func(format string, args ...any)
}
scan_seams.go — the host-coupled legs the candy-scan fetch fix-point (loaderkit.ScanCandyFromLocal) reaches. Relocated from sdk/loaderkit/scan_orchestrate.go (#55 C3b-ii) so it can be the parameter type on the spec.ProjectLoader.ScanCandyFromLocal seam method — the interface lives in this dedicated spec module, so its param types must live here too. The caller (charly's scanSeamsFor, candy/plugin-build's scanSeamsLeg) builds these as closures capturing its config/opts + host mechanisms (registry, refs backend); the pure fix-point in loaderkit never inspects a package-main type. loaderkit keeps a `type ScanSeams = spec.ScanSeams` forwarder (mirroring its RemoteDownload alias) so its own signature + candy/plugin-build's call sites stay terse.
type ScanSpec ¶ added in v0.2026210.808
type ScanSpec struct {
Path string `yaml:"path" json:"path"`
Recursive bool `yaml:"recursive" json:"recursive"`
// Manifest is the per-directory manifest filename to look for. Empty
// defaults to UnifiedFileName; configurable per spec in charly.yml.
Manifest string `yaml:"manifest,omitempty" json:"manifest,omitempty"`
}
ScanSpec describes one discovery root. Accepts string shorthand ("candy" → {Path: "candy", Recursive: true}) or the explicit object form ({path: X, recursive: false}). Empty Path is invalid.
func AnchorScanSpecs ¶ added in v0.2026213.1726
AnchorScanSpecs returns a copy of `specs` with every relative Path resolved to an absolute path against `srcDir`. Absolute paths are kept verbatim. Empty srcDir leaves specs unchanged so the root-file merge (called with rootDir == workspace) is a no-op. Path-anchoring MECHANISM (filepath) over spec.ScanSpec, relocated from sdk/kit (#55 C3b) so merge.MergeUnified — which itself moved into this spec module — can call it without a spec→sdk import inversion.
type ScannedCandy ¶
type ScannedCandy struct {
Model CandyModel
View CandyView
Refs CandyRefs
}
ScannedCandy gathers one candy's full scan result — the two resolved envelope views plus the rich pre-qualification refs.
SDD classification: same non-wire, same-process pipeline-state rationale as CandyRefs above (one note covers both) — it is the mutable intermediate the whole scan→fetch→qualify→arbitrate pipeline (charly's ScanAllCandy family) carries in place of the pre-move *Candy, until the FINAL step bare-strings the refs (FinalizeCandyRefs) and wraps (Model, View) into a spec.CandyReader via sdk/deploykit.NewSpecCandyModel.
type Scope ¶
type Scope int
Scope classifies what kind of filesystem mutation a step makes. Steps are grouped by scope (and venue) when the host target batches into sudo vs user heredocs — mixing scopes in one batch would need per-command sudo. It is the integer-valued enum the ledger serializes (omitempty omits ScopeSystem=0), so the wire form is unchanged by living in spec.
const ( // ScopeSystem mutates global host state: /etc, /usr, /var, systemd system // units, package DB. Requires sudo on host; emitted as USER root in the // Containerfile. ScopeSystem Scope = iota // ScopeUser mutates the invoking user's home or user-owned paths: // $HOME/.pixi, $HOME/.cargo, $HOME/.npm-global, $HOME/.local, systemd // user units, etc. No sudo needed on host; emitted as USER ${UID} in the // Containerfile. ScopeUser // ScopeUserProfile writes to the user's shell init surface: // ~/.bashrc / ~/.zshenv / fish conf.d + ~/.config/opencharly/env.d/. // Separate from ScopeUser because the host target has special handling // (managed blocks, shell detection) and the OCI target renders these as // ENV directives + path additions rather than file writes. ScopeUserProfile )
func OpStepScope ¶ added in v0.2026210.2308
func ScopeFromName ¶
ScopeFromName parses a StepContract's authored scope NAME (the Describe-carried "user"/"user-profile" string; anything else, including empty, defaults to ScopeSystem) into a Scope.
type ScoreSummary ¶
type ScoreSummary struct {
Total int `yaml:"total,omitempty" json:"total"`
Pass int `yaml:"pass,omitempty" json:"pass"`
Fail int `yaml:"fail,omitempty" json:"fail"`
Skip int `yaml:"skip,omitempty" json:"skip"`
}
#ScoreSummary — the pass/fail/skip tally block (the former hand-written TestRunSummary). The counts are Go `int` (type=int override — the former hand type; CUE `int` defaults to int64), so every existing ++/compare call site compiles unchanged.
type Security ¶
type Security struct {
Privileged bool `yaml:"privileged,omitempty" json:"privileged,omitempty"`
CgroupNS string `yaml:"cgroupns,omitempty" json:"cgroupns,omitempty"`
CapAdd []string `yaml:"cap_add,omitempty" json:"cap_add,omitempty"`
Devices []string `yaml:"devices,omitempty" json:"devices,omitempty"`
SecurityOpt []string `yaml:"security_opt,omitempty" json:"security_opt,omitempty"`
IpcMode string `yaml:"ipc_mode,omitempty" json:"ipc_mode,omitempty"`
ShmSize Size `yaml:"shm_size,omitempty" json:"shm_size,omitempty"`
GroupAdd []string `yaml:"group_add,omitempty" json:"group_add,omitempty"`
Mounts []string `yaml:"mount,omitempty" json:"mount,omitempty"`
MemoryMax Size `yaml:"memory_max,omitempty" json:"memory_max,omitempty"`
MemoryHigh Size `yaml:"memory_high,omitempty" json:"memory_high,omitempty"`
MemorySwapMax Size `yaml:"memory_swap_max,omitempty" json:"memory_swap_max,omitempty"`
Cpus string `yaml:"cpus,omitempty" json:"cpus,omitempty"`
}
--------------------------------------------------------------------------- Container security (SecurityConfig) — shared by box / candy / deploy / sidecar. ---------------------------------------------------------------------------
type ServiceCustomStep ¶ added in v0.2026210.2308
type ServiceCustomStep struct {
Name string // e.g. "charly-ollama-ollama"
UnitText string // fully rendered unit content
UnitPath string // absolute install path
TargetScope Scope // ScopeSystem or ScopeUser
Enable bool
CandyName string
}
ServiceCustomStep writes and enables a full service definition. The unit text is already rendered by the compiler from the candy's generic `services:` entry via the target init system's service_template.
func (*ServiceCustomStep) Kind ¶ added in v0.2026210.2308
func (s *ServiceCustomStep) Kind() StepKind
func (*ServiceCustomStep) RequiresGate ¶ added in v0.2026210.2308
func (s *ServiceCustomStep) RequiresGate() Gate
func (*ServiceCustomStep) Reverse ¶ added in v0.2026210.2308
func (s *ServiceCustomStep) Reverse() []ReverseOp
func (*ServiceCustomStep) Scope ¶ added in v0.2026210.2308
func (s *ServiceCustomStep) Scope() Scope
func (*ServiceCustomStep) Venue ¶ added in v0.2026210.2308
func (s *ServiceCustomStep) Venue() Venue
type ServicePackagedStep ¶ added in v0.2026210.2308
type ServicePackagedStep struct {
Unit string // e.g. "postgresql.service"
TargetScope Scope // ScopeSystem → /etc/systemd/system/; ScopeUser → ~/.config/systemd/user/
Enable bool
OverridesText string // rendered drop-in (.conf) content; "" if no overrides
OverridesPath string // computed absolute path for the drop-in file
CandyName string // for ledger + drop-in naming
PriorEnabled bool // populated at install time (before enable); used for teardown restore
}
ServicePackagedStep enables a systemd unit that arrived via a distro package (e.g. postgresql.service). Optional drop-in overrides are written alongside the packaged unit without touching it. This step is only emitted when rendering to a systemd-based init system; supervisord targets emit a warning-and-skip.
func (*ServicePackagedStep) Kind ¶ added in v0.2026210.2308
func (s *ServicePackagedStep) Kind() StepKind
func (*ServicePackagedStep) RequiresGate ¶ added in v0.2026210.2308
func (s *ServicePackagedStep) RequiresGate() Gate
func (*ServicePackagedStep) Reverse ¶ added in v0.2026210.2308
func (s *ServicePackagedStep) Reverse() []ReverseOp
func (*ServicePackagedStep) Scope ¶ added in v0.2026210.2308
func (s *ServicePackagedStep) Scope() Scope
func (*ServicePackagedStep) Venue ¶ added in v0.2026210.2308
func (s *ServicePackagedStep) Venue() Venue
type ServiceRenderContext ¶
type ServiceRenderContext struct {
Name string `yaml:"name,omitempty" json:"name,omitempty"`
Candy string `yaml:"candy,omitempty" json:"candy,omitempty"`
Exec string `yaml:"exec,omitempty" json:"exec,omitempty"`
Env map[string]string `yaml:"env,omitempty" json:"env,omitempty"`
EnvList []EnvKV `yaml:"env_list,omitempty" json:"env_list,omitempty"`
Restart string `yaml:"restart,omitempty" json:"restart,omitempty"`
WorkingDirectory string `yaml:"working_directory,omitempty" json:"working_directory,omitempty"`
User string `yaml:"user,omitempty" json:"user,omitempty"`
After []string `yaml:"after,omitempty" json:"after,omitempty"`
Before []string `yaml:"before,omitempty" json:"before,omitempty"`
WantedBy []string `yaml:"wanted_by,omitempty" json:"wanted_by,omitempty"`
Stdout string `yaml:"stdout,omitempty" json:"stdout,omitempty"`
StopTimeout string `yaml:"stop_timeout,omitempty" json:"stop_timeout,omitempty"`
Scope string `yaml:"scope,omitempty" json:"scope,omitempty"`
PackagedUnit string `yaml:"packaged_unit,omitempty" json:"packaged_unit,omitempty"`
Home string `yaml:"home,omitempty" json:"home,omitempty"`
SystemUnitDir string `yaml:"system_unit_dir,omitempty" json:"system_unit_dir,omitempty"`
UserUnitDir string `yaml:"user_unit_dir,omitempty" json:"user_unit_dir,omitempty"`
FragmentDir string `yaml:"fragment_dir,omitempty" json:"fragment_dir,omitempty"`
// Lifecycle directives (supervisord + systemd).
Kind string `yaml:"kind,omitempty" json:"kind,omitempty"`
Events string `yaml:"events,omitempty" json:"events,omitempty"`
AutoStart *bool `yaml:"auto_start,omitempty" json:"auto_start,omitempty"`
StartRetries int `yaml:"start_retries,omitempty" json:"start_retries,omitempty"`
StartSecs int `yaml:"start_secs,omitempty" json:"start_secs,omitempty"`
StopSignal string `yaml:"stop_signal,omitempty" json:"stop_signal,omitempty"`
ExitCodes string `yaml:"exit_codes,omitempty" json:"exit_codes,omitempty"`
Priority int `yaml:"priority,omitempty" json:"priority,omitempty"`
// Start hooks (systemd only — supervisord's service_template references
// neither, exactly as it already ignores wanted_by/before). Carried through
// from the entry's exec_start_pre/exec_start_post so the template emits one
// ExecStartPre=/ExecStartPost= line per element, in authored order.
ExecStartPre []string `yaml:"exec_start_pre,omitempty" json:"exec_start_pre,omitempty"`
ExecStartPost []string `yaml:"exec_start_post,omitempty" json:"exec_start_post,omitempty"`
// Portable lifecycle fields, carried verbatim from the entry. Each is honoured
// by at least two inits; an init whose template does not reference one simply
// ignores it, exactly as supervisord already ignores wanted_by/before.
Type string `yaml:"type,omitempty" json:"type,omitempty"`
Requires []string `yaml:"requires,omitempty" json:"requires,omitempty"`
RestartSec string `yaml:"restart_sec,omitempty" json:"restart_sec,omitempty"`
WatchdogSec string `yaml:"watchdog_sec,omitempty" json:"watchdog_sec,omitempty"`
// unit_options — init-specific directives, keyed init-name -> directive -> value.
// A template reads only its OWN key, so this stays ONE field on the shared
// envelope no matter how many inits exist. See #CandyService.unit_options for
// why this is a map rather than a field per directive.
UnitOptions map[string]map[string]any `yaml:"unit_options,omitempty" json:"unit_options,omitempty"`
// wait_for — the readiness precondition, carried from the entry so each init's
// template can lower it in its own idiom.
WaitFor *ServiceWaitFor `yaml:"wait_for,omitempty" json:"wait_for,omitempty"`
// render_dropin is the host-precomputed drop-in decision (the entry
// carries Overrides). PackagedUnit != "" selects the packaged branch. The
// host derives both from the ServiceEntry so the plugin renders from the
// ctx alone.
RenderDropin bool `yaml:"render_dropin,omitempty" json:"render_dropin,omitempty"`
}
#ServiceRenderContext is the data an init system's service_template renders against — everything the renderer needs, nothing else reachable. The host computes it per service and hands it to candy/plugin-init's OpResolve.
func BuildServiceRenderContext ¶ added in v0.2026213.1726
func BuildServiceRenderContext(entry *ServiceEntry, ctx ServiceRenderContext) ServiceRenderContext
BuildServiceRenderContext fills the entry-derived, home-expanded render context (a pure ServiceEntry projection — no init-system knowledge). The plugin renders its templates against this; the packaged/drop-in branch decisions are precomputed here (PackagedUnit, RenderDropin) so the plugin renders from the ctx alone.
type ServiceRenderInput ¶
type ServiceRenderInput struct {
Init RawBody `yaml:"init,omitempty" json:"init"`
Ctx ServiceRenderContext `yaml:"ctx,omitempty" json:"ctx"`
}
#ServiceRenderInput is candy/plugin-init's OpResolve input for the service-render leg: the OPAQUE init body (the chosen init system's config) + the host-built render context (all entry-derived fields, home-expanded, with the packaged/drop-in branch decisions precomputed). The plugin renders from the ctx alone.
type ServiceRenderReply ¶
type ServiceRenderReply struct {
Rendered *RenderedService `yaml:"rendered,omitempty" json:"rendered,omitempty"`
}
#ServiceRenderReply wraps the rendered service.
type ServiceWaitFor ¶ added in v0.2026240.1719
type ServiceWaitFor struct {
// paths — every path must exist before the service starts. Relative paths are
// resolved by the init against the service's own working directory, exactly as
// exec: is.
//
// Required, and at least one element: a wait_for with no paths waits for
// nothing while reading like a guard, which is worse than no guard at all.
Paths []string `yaml:"paths,omitempty" json:"paths"`
// timeout — how long to wait before failing the start. "30s" or a bare 30,
// matching stop_timeout's shape. Absent means the init's own default.
Timeout string `yaml:"timeout,omitempty" json:"timeout,omitempty"`
}
#ServiceWaitFor is a readiness precondition on a service's start.
PATHS only, deliberately. All nine existing loops wait on a filesystem path (a Wayland socket, an IPC socket, a lock file); none polls a TCP port. A ports: form would need a probe tool the base images do not all carry, so it waits for a real second consumer rather than being designed against a hypothetical one.
type Session ¶ added in v0.2026242.935
type Session struct {
// compositor is the binary this session runs, e.g. "Hyprland", "sway", "labwc".
Compositor string `yaml:"compositor,omitempty" json:"compositor"`
// syntax is a label for diagnostics and for choosing an escaping strategy.
Syntax string `yaml:"syntax,omitempty" json:"syntax,omitempty"`
// config_path_template is where the rendered config lands, as a Go template so a
// compositor that keys off its own name does not need a second field.
ConfigPathTemplate string `yaml:"config_path_template,omitempty" json:"config_path_template"`
// model says whether the constructs concatenate into ONE file (assembly) or each render
// to its own (file_set). Hyprland and sway are assembly; labwc is a file set.
Model string `yaml:"model,omitempty" json:"model,omitempty"`
// The per-construct templates. Each renders once per authored entry. A compositor that
// has no notion of a construct omits the template.
MonitorTemplate string `yaml:"monitor_template,omitempty" json:"monitor_template,omitempty"`
BindTemplate string `yaml:"bind_template,omitempty" json:"bind_template,omitempty"`
InputTemplate string `yaml:"input_template,omitempty" json:"input_template,omitempty"`
ExecTemplate string `yaml:"exec_template,omitempty" json:"exec_template,omitempty"`
EnvTemplate string `yaml:"env_template,omitempty" json:"env_template,omitempty"`
RuleTemplate string `yaml:"rule_template,omitempty" json:"rule_template,omitempty"`
IncludeTemplate string `yaml:"include_template,omitempty" json:"include_template,omitempty"`
// extra_file is for the parts of a session that are not one of the constructs above —
// a bootstrap loader, a portal config.
ExtraFiles []ThemeRender `yaml:"extra_file,omitempty" json:"extra_file,omitempty"`
// session_desktop is the /usr/share/wayland-sessions entry a display manager offers.
// Its `id` is what a displaymanager's `session:` must match, and that cross-check is
// enforced at load: an autologin naming a session file nothing installed is a black
// screen at boot with nothing in any log.
SessionDesktop SessionDesktop `yaml:"session_desktop,omitempty" json:"session_desktop,omitempty"`
// theme_render lets a session pull colours from a theme entity by name, so a compositor
// config can be themed without the theme knowing the compositor exists.
ThemeRender []string `yaml:"theme_render,omitempty" json:"theme_render,omitempty"`
}
#Session is a compositor's RENDERER VOCABULARY — the exact `init:`/`service:` mirror.
The kind says HOW a construct is spelled; the candy's `desktop:` block says WHICH constructs exist. That split is what lets one authored keybinding reach Hyprland's Lua, sway's config syntax and labwc's XML without the author knowing any of them.
A compositor that cannot express a construct simply omits its template, and entries of that kind are dropped with a diagnostic — the same way supervisord ignores `wanted_by`.
type SessionDesktop ¶ added in v0.2026242.935
type SessionDesktop struct {
Id string `yaml:"id,omitempty" json:"id"`
Name string `yaml:"name,omitempty" json:"name"`
Exec string `yaml:"exec,omitempty" json:"exec"`
Type string `yaml:"type,omitempty" json:"type,omitempty"`
}
#SessionDesktop is the wayland-sessions entry.
type Shell ¶
type Shell struct {
Init string `yaml:"init,omitempty" json:"init,omitempty"`
PathAppend []string `yaml:"path_append,omitempty" json:"path_append,omitempty"`
Path string `yaml:"path,omitempty" json:"path,omitempty"`
Priority int `yaml:"priority,omitempty" json:"priority,omitempty"`
Bash *ShellSpec `yaml:"bash,omitempty" json:"bash,omitempty"`
Zsh *ShellSpec `yaml:"zsh,omitempty" json:"zsh,omitempty"`
Fish *ShellSpec `yaml:"fish,omitempty" json:"fish,omitempty"`
Sh *ShellSpec `yaml:"sh,omitempty" json:"sh,omitempty"`
}
--------------------------------------------------------------------------- Shell-rc config (ShellConfig/ShellSpec) — shared by box + candy. CLOSED: the Go UnmarshalYAML rejects any key outside the 4 intrinsic + 4 shell names. ---------------------------------------------------------------------------
type ShellEntry ¶
type ShellEntry struct {
Origin string `yaml:"origin,omitempty" json:"origin"`
ID string `yaml:"id,omitempty" json:"id,omitempty"`
Generic *ShellSpec `yaml:"generic,omitempty" json:"generic,omitempty"`
ByShell map[string]*ShellSpec `yaml:"by_shell,omitempty" json:"by_shell,omitempty"`
Priority int `yaml:"priority,omitempty" json:"priority,omitempty"`
}
#ShellEntry — one origin's shell-init contribution (in ai.opencharly.shell). origin required.
type ShellHookStep ¶ added in v0.2026210.2308
type ShellHookStep struct {
CandyName string
EnvVars map[string]string
PathAdd []string // already {{.Home}}-substituted to absolute paths
EnvFile string // computed path (~/.config/opencharly/env.d/<candy>.env); populated at install
}
ShellHookStep records the env vars and PATH contributions a candy makes to the user's shell environment. On the OCI target these translate to `ENV K=V` directives in the Containerfile. On the host target they become `~/.config/opencharly/env.d/<candy>.env` plus a managed block in the user's shell init that sources the env.d directory.
func (*ShellHookStep) Kind ¶ added in v0.2026210.2308
func (s *ShellHookStep) Kind() StepKind
func (*ShellHookStep) RequiresGate ¶ added in v0.2026210.2308
func (s *ShellHookStep) RequiresGate() Gate
func (*ShellHookStep) Reverse ¶ added in v0.2026210.2308
func (s *ShellHookStep) Reverse() []ReverseOp
func (*ShellHookStep) Scope ¶ added in v0.2026210.2308
func (s *ShellHookStep) Scope() Scope
func (*ShellHookStep) Venue ¶ added in v0.2026210.2308
func (s *ShellHookStep) Venue() Venue
type ShellSnippetStep ¶ added in v0.2026210.2308
type ShellSnippetStep struct {
CandyName string // candy this snippet belongs to (also Marker source)
Origin string // "<candy>" or "box" or "deploy" (for ledger refcount + LabelShell origin)
Shell string // bash | zsh | fish | sh
Snippet string // rendered body, ${SHELL_NAME}-substituted, ready to write
PathAppend []string // already rendered into Snippet by the compiler; tracked here for label round-trip / overlay
Destination string // resolved per-target at compile time; absolute path on the target
Marker string // managed-block marker tag (= CandyName) — consumed by the kit walk's managed-block splice (sdk/kit/profile.go)
UseDropin bool // true: write whole file (fish, container drop-in); false: managed-block append into existing rc file
Priority int // load-order hint, 0 = default
}
ShellSnippetStep records a per-(candy, shell) init snippet emitted from a candy's `shell:` block. The compiler emits one step per (candy, shell) pair after applying the selection rule (per-shell ByShell entry wins over generic, with ${SHELL_NAME} substitution).
Per-target rendering:
- OCITarget: snippet bytes are content-address-staged and COPYed to a system-wide drop-in (/etc/profile.d/charly-<candy>.sh for bash/zsh/sh, /etc/fish/conf.d/charly-<candy>.fish for fish).
- the external local / vm deploy (kit.WalkPlans on the venue): managed-block append to the user's rc file (~/.bashrc, ~/.zshrc, ~/.profile) keyed by `# opencharly:begin <Marker>` fence; for fish, a per-candy drop-in at ~/.config/fish/conf.d/charly-<candy>.fish (no fence needed, file IS the unit). UseDropin discriminates the two paths.
- the external kubernetes substrate: does not consume the InstallPlan IR at all (the host generates a Kustomize tree separately — no shell snippet).
func (*ShellSnippetStep) Kind ¶ added in v0.2026210.2308
func (s *ShellSnippetStep) Kind() StepKind
func (*ShellSnippetStep) RequiresGate ¶ added in v0.2026210.2308
func (s *ShellSnippetStep) RequiresGate() Gate
func (*ShellSnippetStep) Reverse ¶ added in v0.2026210.2308
func (s *ShellSnippetStep) Reverse() []ReverseOp
func (*ShellSnippetStep) Scope ¶ added in v0.2026210.2308
func (s *ShellSnippetStep) Scope() Scope
Scope: SnippetStep is system-wide for build-mode (Container drop-ins live under /etc/profile.d/ — root-owned), and user-profile for host/vm deploy- mode (managed block in ~/.bashrc etc.). The compiler picks the right Scope when constructing the step based on the destination path; the step records the choice so each emitter doesn't have to re-derive it.
func (*ShellSnippetStep) Venue ¶ added in v0.2026210.2308
func (s *ShellSnippetStep) Venue() Venue
type Sidecar ¶
type Sidecar struct {
Description string `yaml:"description,omitempty" json:"description,omitempty"`
Image string `yaml:"image,omitempty" json:"image,omitempty"`
// env / parameter are map[string]string — values MUST be strings (quote
// YAML bools/numbers). parameter "" is the "deploy must supply" sentinel.
Env StrMap `yaml:"env,omitempty" json:"env,omitempty"`
Parameter StrMap `yaml:"parameter,omitempty" json:"parameter,omitempty"`
Secret []SidecarSecret `yaml:"secret,omitempty" json:"secret,omitempty"`
Volume []SidecarVolume `yaml:"volume,omitempty" json:"volume,omitempty"`
Security *Security `yaml:"security,omitempty" json:"security,omitempty"`
}
type SidecarList ¶ added in v0.2026218.138
type SidecarList struct {
Names []string `yaml:"names,omitempty" json:"names,omitempty"`
Descriptions map[string]string `yaml:"descriptions,omitempty" json:"descriptions,omitempty"`
}
#SidecarList is the sidecar-template-library introspection list — the `charly config --list-sidecars` payload (names + one-line descriptions), returned inside #PodConfigSetupReply.
type SidecarResolveInput ¶
type SidecarResolveInput struct {
EmbeddedTemplates map[string]RawBody `yaml:"embedded_templates,omitempty" json:"embedded_templates,omitempty"`
ProjectTemplates map[string]RawBody `yaml:"project_templates,omitempty" json:"project_templates,omitempty"`
DeployOverrides map[string]RawBody `yaml:"deploy_overrides,omitempty" json:"deploy_overrides,omitempty"`
CliEnv []string `yaml:"cli_env,omitempty" json:"cli_env,omitempty"`
Box string `yaml:"box,omitempty" json:"box,omitempty"`
Instance string `yaml:"instance,omitempty" json:"instance,omitempty"`
}
#SidecarResolveInput is the input to candy/plugin-sidecar's OpResolve leg (the host-side sidecar de-type): the three sidecar-def layers to merge (embedded template base, project-root templates, per-deploy overrides — each a name→Sidecar map the host keeps OPAQUE) plus the CLI -e flags to route and the box/instance for name scoping.
type SidecarResolveReply ¶
type SidecarResolveReply struct {
Sidecars []ResolvedSidecar `yaml:"sidecars,omitempty" json:"sidecars,omitempty"`
AppEnv []string `yaml:"app_env,omitempty" json:"app_env,omitempty"`
PersistOverrides map[string]RawBody `yaml:"persist_overrides,omitempty" json:"persist_overrides,omitempty"`
}
#SidecarResolveReply is candy/plugin-sidecar's OpResolve reply: the resolved, generation-ready sidecars the host feeds to quadlet gen, the CLI env flags NOT routed to any sidecar (app-only), and the per-deploy sidecar overrides with any routed env folded in.
type SidecarSecret ¶
type SidecarVolume ¶
type Skill ¶ added in v0.2026218.1950
type Skill struct {
Name string `yaml:"name,omitempty" json:"name"`
Family string `yaml:"family,omitempty" json:"family"`
Owner string `yaml:"owner,omitempty" json:"owner"`
Description string `yaml:"description,omitempty" json:"description"`
Content string `yaml:"content,omitempty" json:"content"`
Type string `yaml:"type,omitempty" json:"type,omitempty"`
Model string `yaml:"model,omitempty" json:"model,omitempty"`
Tools []string `yaml:"tools,omitempty" json:"tools,omitempty"`
References []SkillReference `yaml:"references,omitempty" json:"references,omitempty"`
Triggers []string `yaml:"triggers,omitempty" json:"triggers,omitempty"`
Category string `yaml:"category,omitempty" json:"category,omitempty"`
}
CUE schema for the `skill` KIND — a first-class harness skill entity (the migration of the plugins/ skill corpus into candy configs). A `skill:` node is a sibling top-level entity in a candy's charly.yml (ParseDoc iterates every top-level node, each with its own single kind discriminator), so a candy file stacks its `candy:` node plus the `skill:` nodes it owns.
The FULL skill definition is INLINE: every metadata field plus the markdown content as a block scalar — nothing is fetched from elsewhere. The marketplace generator (candy/plugin-marketplace, command:marketplace) synthesizes plugins/<family>/skills/<name>/SKILL.md + references/*.md from these entities; CollectSkills (sdk/deploykit) projects the composed candies' skills into the `ai.opencharly.skill` OCI label so a built image is self-describing (readable via `charly box labels` / `charly fleet from-box`, no external fetch).
owner is the candy/concept-candy entity name that owns this skill — the build-time association that decides which images carry it (CollectSkills filters by owner ∈ composed candy chain). family is the marketplace family (the plugins/ directory name) the generator groups by.
type: "agent" marks a sub-agent definition (the former plugins/<family>/agents/*.md) — one unified doc kind, same lifecycle; model/tools carry the agent frontmatter fields.
type SkillReference ¶ added in v0.2026218.1950
type StandaloneLoad ¶
type StandaloneLoad struct {
Shape string `yaml:"shape,omitempty" json:"shape"`
Deploy *Deploy `yaml:"deploy,omitempty" json:"deploy,omitempty"`
Template RawBody `yaml:"template,omitempty" json:"template,omitempty"`
Box *Box `yaml:"box,omitempty" json:"box,omitempty"`
Candy *Candy `yaml:"candy,omitempty" json:"candy,omitempty"`
}
#StandaloneLoad carries a structural kind's host-pre-decoded canonical node. Exactly one of Deploy / Template / Box / Candy is set, matching Shape.
type Status ¶
type Status int
status_result.go — the check-engine's pass/fail/skip verdict enum (FLOOR-SLIM Unit 4, moved from sdk/kit as part of the CheckResult wire-envelope split). HAND-WRITTEN — a distinct named int type with the iota consts + String() method, NOT emitted by `task cue:gen` (gengotypes has no construct for an iota-based enum + a Stringer method; CUE owns the wire VALUE SET via #CheckResult.status's plain `int` shape, referenced here via @go(Status,type=Status) — this file supplies the named Go type that reference points at). Mirrors the #SubstrateKind split in status_types.go (a string-backed enum suppressed via @go(-) with its Go type hand-written here-style) — Status is int-backed, so there is no separate disjunction def to suppress; the CUE field is a plain `int`.
type StatusNestedNode ¶
type StatusNestedNode struct {
Key string `yaml:"key,omitempty" json:"key"`
Path string `yaml:"path,omitempty" json:"path"`
Kind SubstrateKind `yaml:"kind,omitempty" json:"kind"`
HasChildren bool `yaml:"has_children,omitempty" json:"has_children"`
MatchKeys []string `yaml:"match_keys,omitempty" json:"match_keys,omitempty"`
LiveStatus string `yaml:"live_status,omitempty" json:"live_status,omitempty"`
Children []*StatusNestedNode `yaml:"children,omitempty" json:"children,omitempty"`
}
#StatusNestedNode — one pre-resolved node of the DECLARED nested tree, now built PLUGIN-SIDE (candy/plugin-status/nested_tree.go, K5) directly from the resolved-project envelope + deploykit.LoadFleetConfig/MergeDeployConfigs/ClassifyTarget/ResolveDeployChain — all sdk-portable, so this is no longer a host-computed wire payload; it stays a CUE def only because #StatusNestedNode is self-recursive and the plugin still needs the generated Go type. key is the declared child key (the Image cell). match_keys index the flat rows; for a ROOT node key itself is the flat match. RECURSIVE self-reference mirrors the deploy tree nesting.
type StatusSubstrateReply ¶
type StatusSubstrateReply struct {
Rows []DeploymentStatus `yaml:"rows,omitempty" json:"rows,omitempty"`
Single DeploymentStatus `yaml:"single,omitempty" json:"single,omitempty"`
}
#StatusSubstrateReply — the host-collection result: the flat rows (all substrates, already probed) and — on the single path — the one detail row. The declared nested roots are no longer carried here (K5: resolved plugin-side, see #StatusNestedNode) — the candy computes its own roots and applies the PURE overlay(rows, roots) then renders.
type StatusSubstrateRequest ¶
type StatusSubstrateRequest struct {
Single bool `yaml:"single,omitempty" json:"single,omitempty"`
IncludeAll bool `yaml:"include_all,omitempty" json:"include_all,omitempty"`
Box string `yaml:"box,omitempty" json:"box,omitempty"`
Instance string `yaml:"instance,omitempty" json:"instance,omitempty"`
}
#StatusSubstrateRequest — the host-collection request the command:status plugin sends over HostBuild("status-substrate"). single=true selects the pod-scoped detail path (box+instance); otherwise the full multi-substrate fan-out (include_all mirrors --all). The declared nested tree (formerly resolved here via `nested`) is now resolved PLUGIN-SIDE (candy/plugin-status's own buildStatusRootsTree, K5) directly off the resolved-project envelope + deploykit.LoadFleetConfig — no host round-trip, so this request carries no nested flag anymore.
type Step ¶
type Step struct {
Run string `yaml:"run,omitempty" json:"run,omitempty"`
Check string `yaml:"check,omitempty" json:"check,omitempty"`
AgentRun string `yaml:"agent-run,omitempty" json:"agent-run,omitempty"`
AgentCheck string `yaml:"agent-check,omitempty" json:"agent-check,omitempty"`
Include string `yaml:"include,omitempty" json:"include,omitempty"`
Op `yaml:",inline" json:",inline"`
}
Step is a single plan step. Exactly one of Run/Check/AgentRun/AgentCheck/Include is non-empty (validated by StepKind()). The embedded Op (generated) is inline-promoted.
func (*Step) KeywordText ¶
KeywordText returns the populated keyword's prose regardless of which discriminator holds it.
func (*Step) Mutates ¶
Mutates reports whether the step changes system state (run / agent-run). `charly check live` (verify-only mode) skips mutating steps.
func (*Step) StepKind ¶
func (s *Step) StepKind() (StepKeyword, error)
StepKind returns the step's intent keyword and an error if zero or multiple keyword discriminators are set.
type StepBatch ¶
type StepBatch struct {
Scope Scope
Venue Venue
Steps []InstallStep
}
StepBatch is a contiguous run of steps sharing the same (Scope, Venue). Emitted together: one sudo heredoc, one user heredoc, or one podman run per batch.
type StepContract ¶
type StepContract struct {
Scope Scope
Venue Venue
Gate Gate
// Emits is the F-STEP-EMIT flag: the step produces a build-context Containerfile FRAGMENT
// (the serving plugin answers Invoke(OpEmit) → EmitReply.Fragment). The pod-overlay
// deploykit.OCITarget consults it via the open external-step arm — Emits=true → bake the
// fragment; Emits=false → skip (a deploy-only external step, like apk on an image build).
// Advisory for the DEPLOY leg (executeExternalStep ignores it); load-bearing for the BUILD
// leg (ociEmitStep).
Emits bool
}
StepContract is a class:step plugin's DECLARED install-step contract (F3), decoded from its Describe capability (pb.StepContract / sdk.StepContract). compileActOp reads it (via StepContractCarrier) to build an externalStep carrying the plugin-declared Scope/Venue/Gate — the contract the host applies via the open default arm with NO compiled-in case.
type StepContractCarrier ¶
type StepContractCarrier interface {
DeclaredStepContract() (StepContract, bool)
}
StepContractCarrier is implemented by a provider (grpcProvider out-of-proc, inprocProvider compiled-in) that carries a class:step capability's declared StepContract. A false return means the provider declares no step contract (every non-step capability).
type StepEmitRequest ¶
type StepEmitRequest struct {
Word string `yaml:"word,omitempty" json:"word"`
Payload RawBody `yaml:"payload,omitempty" json:"payload,omitempty"`
Distros []string `yaml:"distros,omitempty" json:"distros,omitempty"`
}
#StepEmitRequest is the F-STEP-EMIT HostBuild envelope for a HOST-COUPLED external step kind's build-context fragment.
type StepKeyword ¶
type StepKeyword string
StepKeyword is the intent discriminator on a Step.
const ( KwRun StepKeyword = "run" KwCheck StepKeyword = "check" KwAgentRun StepKeyword = "agent-run" KwAgentCheck StepKeyword = "agent-check" KwInclude StepKeyword = "include" )
type StepKind ¶
type StepKind string
StepKind names the concrete type behind an InstallStep. Used for ledger serialization and target-specific dispatch (e.g. HostDeploy knows to invoke the pixi builder differently than cargo).
const ( StepKindSystemPackages StepKind = "SystemPackages" StepKindBuilder StepKind = "Builder" StepKindOp StepKind = "Op" StepKindFile StepKind = "File" StepKindServicePackaged StepKind = "ServicePackaged" StepKindServiceCustom StepKind = "ServiceCustom" StepKindShellHook StepKind = "ShellHook" StepKindShellSnippet StepKind = "ShellSnippet" StepKindRepoChange StepKind = "RepoChange" StepKindApkInstall StepKind = "ApkInstall" StepKindLocalPkgInstall StepKind = "LocalPkgInstall" StepKindReboot StepKind = "Reboot" StepKindExternalPlugin StepKind = "ExternalPlugin" StepKindExtract StepKind = "Extract" )
type StepPass ¶ added in v0.2026213.1726
type StepPass struct {
Stdout string `yaml:"stdout,omitempty" json:"stdout,omitempty"`
Stderr string `yaml:"stderr,omitempty" json:"stderr,omitempty"`
ExitCode int `yaml:"exit_code,omitempty" json:"exit_code,omitempty"`
}
#StepPass is the verbatim stdout/stderr/exit-code of a host-delegated guest sub-invocation (the nested pod-in-VM check-live delegation, runVm's guestNestedCheckCmd path). The plugin writes Stdout/Stderr and returns ExitCode unchanged, so a guest-run check reports byte-identically to a direct one. CUE-sourced (SDD): plain carrier, no json:"-".
type StepRecord ¶ added in v0.2026215.1748
type StepRecord struct {
Kind StepKind `yaml:"kind,omitempty" json:"kind"`
Scope Scope `yaml:"scope,omitempty" json:"scope,omitempty"`
Venue Venue `yaml:"venue,omitempty" json:"venue,omitempty"`
Summary string `yaml:"summary,omitempty" json:"summary,omitempty"`
CompletedAt string `yaml:"completed_at,omitempty" json:"completed_at"`
Extra map[string]string `yaml:"extra,omitempty" json:"extra,omitempty"`
}
#StepRecord is a thin summary of a completed InstallStep that the ledger keeps for audit. Kept intentionally small — the ReverseOps list on CandyRecord is the source of truth for teardown.
type StepResult ¶ added in v0.2026213.1726
type StepResult struct {
Keyword string `yaml:"keyword,omitempty" json:"keyword"`
Text string `yaml:"text,omitempty" json:"text"`
Origin string `yaml:"origin,omitempty" json:"origin,omitempty"`
StepID string `yaml:"step_id,omitempty" json:"step_id"`
Result CheckResult `yaml:"result,omitempty" json:"result"`
}
#StepResult is one plan step's outcome — the step's identity (keyword/text/origin/step_id) plus the CheckResult of running it; the result reporters consume a []StepResult. CUE-sourced (SDD): a plain carrier — every field is a string or the CUE-sourced #CheckResult by value, with NO json:"-" and NO disjunction, so gengotypes generates it faithfully (byte-identical JSON to the former hand-written spec.StepResult). The P12 gengotypes exception (cited at sdk/kit/checkrun_seam.go + schema/seam.cue) covers ONLY kit.CheckResult's engine-internal `DeadlineExceeded bool json:"-"` — a field that exists in memory but is excluded from marshaling — which lives on the kit-internal ENGINE wrapper (struct { spec.CheckResult; DeadlineExceeded bool json:"-" }), NOT on this wire type: StepResult.Result is spec.CheckResult (the CUE-sourced base, no json:"-"), so the wire form carries only spec.CheckResult fields and the kit wrapper adds DeadlineExceeded in memory only, dropped at the StepResult boundary.
type StepScore ¶
type StepScore struct {
ID string `yaml:"id,omitempty" json:"id"`
Origin string `yaml:"origin,omitempty" json:"origin,omitempty"`
Text string `yaml:"text,omitempty" json:"text,omitempty"`
Tag []string `yaml:"tag,omitempty" json:"tag,omitempty"`
Keyword string `yaml:"keyword,omitempty" json:"keyword,omitempty"`
Verb string `yaml:"verb,omitempty" json:"verb,omitempty"`
Status string `yaml:"status,omitempty" json:"status"`
SkippedReason string `yaml:"skipped_reason,omitempty" json:"skipped_reason,omitempty"`
}
#StepScore — the scorer's verdict for one check:/agent-check: step, keyed by step id.
type StrMap ¶
StrMap is the Go shape of #StrMap (`{[string]: #StrVal}`). charly decodes every env / var / parameter / oci_label map into map[string]string — yaml.v3 coerces an unquoted scalar (`PORT: 8080`) to its literal text, so the map stays string→string. Source: the StrMap usage across charly/*.go.
type StrVal ¶
type StrVal any /* CUE disjunction: (bool|string|number) */
An env entry: KEY=VALUE (value may be empty / contain =). A YAML scalar Go decodes into a string (map[string]string / string field): yaml.v3 coerces an unquoted int/bool/float to its literal text, so an idiomatic `PORT: 8080` is a valid string value. #StrMap is the matching map.
type StructuralKindCarrier ¶
type StructuralKindCarrier interface {
IsStructuralKind() bool
}
StructuralKindCarrier is implemented by a provider (grpcProvider out-of-proc, inprocProvider compiled-in) that carries a class:kind capability's STRUCTURAL flag (F5). true → the kind's OpLoad returns a Deploy member tree the host folds into uf.Fleet; false (or not implemented) → the flat F4 path (opaque body → uf.PluginKinds).
type StructuralKindLoadEnv ¶
type StructuralKindLoadEnv struct {
Members map[string]*Deploy `yaml:"members,omitempty" json:"members,omitempty"`
// standalone is the host-pre-decoded CANONICAL node channel (candy/plugin-substrate,
// candy/plugin-candy).
Standalone *StandaloneLoad `yaml:"standalone,omitempty" json:"standalone,omitempty"`
}
#StructuralKindLoadEnv is the OpLoad invocation context (op.Env) the host threads to a STRUCTURAL class:kind plugin (F5 authored-member input-threading).
type SubstrateKind ¶
type SubstrateKind string
SubstrateKind identifies which deployment substrate a DeploymentStatus row came from — the discriminator that lets `charly status` present pod, VM, kubernetes, local, and android deployments side-by-side from one unified table. The command:status plugin (fan-out + render) and every substrate plugin's status-collect Op share this type; #DeploymentStatus.kind references it via @go(Kind,type=SubstrateKind).
HAND-WRITTEN — a distinct named string type with the substrate consts, NOT emitted by `task cue:gen` (gengotypes references it from the generated DeploymentStatus.Kind field but does not define it; a CUE string enum would degrade to a plain string and lose the typed consts the collectors switch on).
const ( SubstratePod SubstrateKind = "pod" SubstrateVM SubstrateKind = "vm" SubstrateKubernetes SubstrateKind = "kubernetes" SubstrateLocal SubstrateKind = "local" SubstrateAndroid SubstrateKind = "android" )
type SubstrateStatusReply ¶
type SubstrateStatusReply struct {
Rows []DeploymentStatus `yaml:"rows,omitempty" json:"rows,omitempty"`
Single DeploymentStatus `yaml:"single,omitempty" json:"single,omitempty"`
}
#SubstrateStatusReply — the per-substrate COLLECTOR result: the LIVE rows the substrate plugin produced (pod: snapshot-derived + live mounts + tool probes; local: install-ledger rows). The host applies the deploy enrichment (tunnel + image-label fallback) to the pod rows after they cross this seam; the single field carries the one pod detail row.
type SubstrateStatusRequest ¶
type SubstrateStatusRequest struct {
IncludeAll bool `yaml:"include_all,omitempty" json:"include_all,omitempty"`
RunMode string `yaml:"run_mode,omitempty" json:"run_mode"`
QuadletDir string `yaml:"quadlet_dir,omitempty" json:"quadlet_dir"`
EngineBin string `yaml:"engine_bin,omitempty" json:"engine_bin"`
Single bool `yaml:"single,omitempty" json:"single,omitempty"`
Box string `yaml:"box,omitempty" json:"box,omitempty"`
Instance string `yaml:"instance,omitempty" json:"instance,omitempty"`
}
#SubstrateStatusRequest — the per-substrate COLLECTOR request the host sends to the substrate plugin's OpStatusCollect (P14a: the cleanly-movable collectors — pod live + local + the probes — relocated into candy/plugin-substrate, served on the kind provider's Invoke by word pod/vm/kubernetes/local/android). The host passes the scalar inputs a sdk-only candy cannot derive: the engine binary name (engine_bin), the run mode, the quadlet dir (pod's quadlet-description enrichment + enabled-but-not-running append), include_all (--all), and — on the single path — box+instance. NO deploy-cone (FleetConfig/UnifiedFile) crosses this seam: the deploy enrichment stays host-side until K5, applied to the live rows this reply returns. vm/kubernetes/android are deferred to K5 (their collectors are deploy-cone-coupled); the plugin returns no rows for those words until then.
type SubstrateTemplateResolveRequest ¶
type SubstrateTemplateResolveRequest struct {
Local *LocalResolveInput `yaml:"local,omitempty" json:"local,omitempty"`
Android *AndroidResolveInput `yaml:"android,omitempty" json:"android,omitempty"`
Pod *PodResolveInput `yaml:"pod,omitempty" json:"pod,omitempty"`
Kubernetes *KubernetesResolveInput `yaml:"kubernetes,omitempty" json:"kubernetes,omitempty"`
Vm *VmResolveInput `yaml:"vm,omitempty" json:"vm,omitempty"`
}
#SubstrateTemplateResolveRequest is the discriminated OpResolve request for the substrate-template de-type: exactly one of Local / Android / Pod is set.
type Subvolume ¶ added in v0.2026242.753
type Subvolume struct {
// name is the subvolume as created, e.g. "@" or "@home".
Name string `yaml:"name,omitempty" json:"name"`
// mount_point is the guest-absolute path it is mounted at, e.g. "/" or "/home".
MountPoint string `yaml:"mount_point,omitempty" json:"mount_point"`
// mount_options are extra comma-joined mount options, e.g. "compress=zstd,noatime".
// `subvol=<name>` is always prepended by the emitter and must not be repeated here.
MountOptions string `yaml:"mount_options,omitempty" json:"mount_options,omitempty"`
}
#Subvolume is one btrfs subvolume in a #DiskLayout.
type SystemInfo ¶ added in v0.2026241.931
type SystemInfo struct {
Hostname string `yaml:"hostname,omitempty" json:"hostname,omitempty"`
// distro_id is the host distro identifier (e.g. "fedora", "arch", "debian").
DistroID string `yaml:"distro_id,omitempty" json:"distro_id,omitempty"`
// distro_version is the host distro version string (e.g. "43", "rolling").
DistroVersion string `yaml:"distro_version,omitempty" json:"distro_version,omitempty"`
// kernel is the running kernel release (uname -r).
Kernel string `yaml:"kernel,omitempty" json:"kernel,omitempty"`
// arch is the host architecture (uname -m).
Arch string `yaml:"arch,omitempty" json:"arch,omitempty"`
// gpu is the primary GPU description (vendor + model), when detectable.
GPU string `yaml:"gpu,omitempty" json:"gpu,omitempty"`
// virtualization is the detected virtualization backend (kvm, qemu, none).
Virtualization string `yaml:"virtualization,omitempty" json:"virtualization,omitempty"`
// podman is the podman version string, when present.
Podman string `yaml:"podman,omitempty" json:"podman,omitempty"`
// updated_at is when this snapshot was last refreshed (RFC3339).
UpdatedAt string `yaml:"updated_at,omitempty" json:"updated_at,omitempty"`
}
#SystemInfo is the top-level `system:` block — the host identity snapshot.
type SystemPackagesStep ¶ added in v0.2026210.2308
type SystemPackagesStep struct {
Format string // "rpm" | "deb" | "pac"
Phase Phase // PhasePrepare | PhaseInstall | PhaseCleanup
Packages []string // package names (for Reverse)
Repos []RepoSpec // repository entries from the candy manifest (drives PhasePrepare)
Options []string // format-specific install flags
Copr []string // RPM-only: COPR repos to enable/disable
Modules []string // RPM-only: DNF modules to enable
Exclude []string // RPM-only: packages to exclude
Keys []string // PAC-only: GPG keys to trust
// CacheMounts and RawInstallContext are passed to template rendering.
// These are populated by the compiler and consumed by the OCI target;
// the host target ignores CacheMounts (no BuildKit outside containers).
CacheMount []CacheMountSpec
RawInstallContext map[string]any
}
SystemPackagesStep installs packages via a distro package manager. One step typically expands to (PhasePrepare + PhaseInstall + PhaseCleanup) stages; the compiler emits one SystemPackagesStep per phase so the host target can gate PhasePrepare on --allow-repo-changes.
func (*SystemPackagesStep) Kind ¶ added in v0.2026210.2308
func (s *SystemPackagesStep) Kind() StepKind
func (*SystemPackagesStep) RequiresGate ¶ added in v0.2026210.2308
func (s *SystemPackagesStep) RequiresGate() Gate
func (*SystemPackagesStep) Reverse ¶ added in v0.2026210.2308
func (s *SystemPackagesStep) Reverse() []ReverseOp
func (*SystemPackagesStep) Scope ¶ added in v0.2026210.2308
func (s *SystemPackagesStep) Scope() Scope
func (*SystemPackagesStep) Venue ¶ added in v0.2026210.2308
func (s *SystemPackagesStep) Venue() Venue
type TagInfo ¶
type TagInfo struct {
Box string `yaml:"box,omitempty" json:"box"`
Ref string `yaml:"ref,omitempty" json:"ref"`
Version string `yaml:"version,omitempty" json:"version"`
InUse bool `yaml:"in_use,omitempty" json:"in_use"`
}
#TagInfo is one locally stored image tag, as presented by `charly box list tags` (verb:retention list=true): version is "-" when the image carries no parseable ai.opencharly.version label.
type TagPkgConfig ¶
type TagPkgConfig struct {
Package []string `yaml:"package,omitempty" json:"package,omitempty"`
Raw map[string]any `yaml:"raw,omitempty" json:"raw,omitempty"`
}
#TagPkgConfig — a distro/version-specific package section (debian:13, ubuntu:24.04, fedora:43…). Raw captures the full YAML so tag sections carry repos:/options:/keys:. Mirrors deploykit.TagPkgConfig.
type TargetHop ¶
type TargetHop struct {
Transport string `yaml:"transport,omitempty" json:"transport"`
Address string `yaml:"address,omitempty" json:"address,omitempty"`
User string `yaml:"user,omitempty" json:"user,omitempty"`
Port int64 `yaml:"port,omitempty" json:"port,omitempty"`
IdentityFile string `yaml:"identity_file,omitempty" json:"identity_file,omitempty"`
Command []string `yaml:"command,omitempty" json:"command,omitempty"`
Env StrMap `yaml:"env,omitempty" json:"env,omitempty"`
// Transport-native argv options expressed as deterministic key/value
// data. SSH renders these as repeated `-o key=value` arguments; no shell
// interpolation is involved.
Options StrMap `yaml:"options,omitempty" json:"options,omitempty"`
}
type TargetSpec ¶
type TargetSpec struct {
// Ordered outer-to-inner route after placement. An empty route is the
// selected process. deployment/instance first place that process in any
// Charly deployment; the remaining route composes identically there.
Hops []TargetHop `yaml:"hops,omitempty" json:"hops,omitempty"`
Deployment string `yaml:"deployment,omitempty" json:"deployment,omitempty"`
Instance string `yaml:"instance,omitempty" json:"instance,omitempty"`
WorkingDir string `yaml:"working_dir,omitempty" json:"working_dir,omitempty"`
}
type TerminalExit ¶
type TerminalFrame ¶
type TerminalFrame struct {
RunID UUIDv7 `yaml:"run_id,omitempty" json:"run_id"`
Sequence int64 `yaml:"sequence,omitempty" json:"sequence"`
Kind string `yaml:"kind,omitempty" json:"kind"`
Stream string `yaml:"stream,omitempty" json:"stream,omitempty"`
Data []byte `yaml:"data,omitempty" json:"data,omitempty"`
Snapshot string `yaml:"snapshot,omitempty" json:"snapshot,omitempty"`
Status string `yaml:"status,omitempty" json:"status,omitempty"`
ExitCode *int `yaml:"exit_code,omitempty" json:"exit_code,omitempty"`
Error string `yaml:"error,omitempty" json:"error,omitempty"`
}
type TerminalInput ¶
type TerminalInput struct {
Kind string `yaml:"kind,omitempty" json:"kind"`
Text string `yaml:"text,omitempty" json:"text,omitempty"`
Key string `yaml:"key,omitempty" json:"key,omitempty"`
Cols int64 `yaml:"cols,omitempty" json:"cols,omitempty"`
Rows int64 `yaml:"rows,omitempty" json:"rows,omitempty"`
Signal string `yaml:"signal,omitempty" json:"signal,omitempty"`
}
type TerminalKey ¶
type TerminalKey struct {
Key string `yaml:"key,omitempty" json:"key"`
}
type TerminalProfile ¶
type TerminalProfile struct {
Name string `yaml:"name,omitempty" json:"name"`
Entrypoint []string `yaml:"entrypoint,omitempty" json:"entrypoint"`
WorkingDir string `yaml:"working_dir,omitempty" json:"working_dir,omitempty"`
Env StrMap `yaml:"env,omitempty" json:"env,omitempty"`
Cols int64 `yaml:"cols,omitempty" json:"cols"`
Rows int64 `yaml:"rows,omitempty" json:"rows"`
Readiness map[string]any `yaml:"readiness,omitempty" json:"readiness,omitempty"`
SemanticAdapter string `yaml:"semantic_adapter,omitempty" json:"semantic_adapter,omitempty"`
Keys []string `yaml:"keys,omitempty" json:"keys,omitempty"`
Signals []string `yaml:"signals,omitempty" json:"signals,omitempty"`
Persistence string `yaml:"persistence,omitempty" json:"persistence,omitempty"`
Transcript string `yaml:"transcript,omitempty" json:"transcript,omitempty"`
}
type TerminalResize ¶
type TerminalSnapshot ¶
type TerminalSnapshot struct {
RunID UUIDv7 `yaml:"run_id,omitempty" json:"run_id"`
Sequence int64 `yaml:"sequence,omitempty" json:"sequence"`
Cols int64 `yaml:"cols,omitempty" json:"cols"`
Rows int64 `yaml:"rows,omitempty" json:"rows"`
Screen string `yaml:"screen,omitempty" json:"screen"`
CursorCol int64 `yaml:"cursor_col,omitempty" json:"cursor_col,omitempty"`
CursorRow int64 `yaml:"cursor_row,omitempty" json:"cursor_row,omitempty"`
}
type Theme ¶ added in v0.2026242.935
type Theme struct {
// name is the theme's identity, e.g. "tokyo-night". Lowercase and hyphenated, matching
// what a theme-switcher command accepts.
Name string `yaml:"name,omitempty" json:"name"`
// variant tells applications which system palette to pair with. Closed: these are the
// only two values the freedesktop colour-scheme preference has.
Variant string `yaml:"variant,omitempty" json:"variant,omitempty"`
// token is the colour palette. accent, foreground and background are REQUIRED because a
// theme that defines none of them cannot render anything legible; everything else is
// optional so a minimal theme stays small.
Token ThemeTokens `yaml:"token,omitempty" json:"token"`
// font, cursor_theme and icon_theme are the non-colour half of a theme. They are named
// separately from token because they are resource NAMES, not colours, and a template
// interpolating them into a font stack must not be able to reach the colour namespace.
Font string `yaml:"font,omitempty" json:"font,omitempty"`
CursorTheme string `yaml:"cursor_theme,omitempty" json:"cursor_theme,omitempty"`
IconTheme string `yaml:"icon_theme,omitempty" json:"icon_theme,omitempty"`
// background lists wallpaper files, candy-relative. First entry is the default. These
// lower to `copy:` rather than `write:` — a JPEG base64'd through a plan is neither
// readable nor small.
Background []string `yaml:"background,omitempty" json:"background,omitempty"`
// render is where the theme becomes files. Each entry names an application and the file
// to write for it, as a Go template over {{.Token.*}}, {{.Font}}, {{.CursorTheme}},
// {{.IconTheme}} and {{.Variant}}.
Render []ThemeRender `yaml:"render,omitempty" json:"render,omitempty"`
}
#Theme is a colour scheme plus the per-application files it renders into.
The token vocabulary is CLOSED. A template referencing {{.Token.accnt}} is a typo that would render an empty colour — a subtly wrong desktop rather than a failure — so OpValidate rejects any template naming a token this entity does not define. That check is the whole reason this is a kind.
Nothing here names a compositor, so the same entity serves Hyprland, sway, labwc and KDE.
type ThemeRender ¶ added in v0.2026242.935
type ThemeRender struct {
// app is a label for diagnostics — "foot", "btop", "hyprland". It is not looked up.
App string `yaml:"app,omitempty" json:"app"`
// path is where the file lands, ${HOME}-relative or absolute.
Path string `yaml:"path,omitempty" json:"path"`
Content string `yaml:"content,omitempty" json:"content"`
Mode string `yaml:"mode,omitempty" json:"mode,omitempty"`
// scope selects whose file it is; user means run_as the image user.
Scope string `yaml:"scope,omitempty" json:"scope,omitempty"`
// distro restricts this file to matching distro TAGS, for the cases where one desktop
// spells a path differently across distros.
//
// [...string], not [...#DistroID], and deliberately: these are the same free-form TAGS
// a candy's `distro:` list carries (box.cue does the same), not the closed guest-distro
// id a VM source is validated against. #DistroID is also `@go(-)` — it generates no Go
// type — so a list of it would emit a reference to a type that does not exist.
Distro []string `yaml:"distro,omitempty" json:"distro,omitempty"`
}
#ThemeRender is one file a theme writes.
type ThemeTokens ¶ added in v0.2026242.935
type ThemeTokens struct {
Accent HexColor `yaml:"accent,omitempty" json:"accent"`
Foreground HexColor `yaml:"foreground,omitempty" json:"foreground"`
Background HexColor `yaml:"background,omitempty" json:"background"`
Cursor HexColor `yaml:"cursor,omitempty" json:"cursor,omitempty"`
SelectionForeground HexColor `yaml:"selection_foreground,omitempty" json:"selection_foreground,omitempty"`
SelectionBackground HexColor `yaml:"selection_background,omitempty" json:"selection_background,omitempty"`
// The 16 ANSI slots a terminal palette needs. Named individually rather than as a list
// so a template says {{.Token.color4}} and a missing one is a load error, not an index
// panic at render time.
Color0 HexColor `yaml:"color0,omitempty" json:"color0,omitempty"`
Color1 HexColor `yaml:"color1,omitempty" json:"color1,omitempty"`
Color2 HexColor `yaml:"color2,omitempty" json:"color2,omitempty"`
Color3 HexColor `yaml:"color3,omitempty" json:"color3,omitempty"`
Color4 HexColor `yaml:"color4,omitempty" json:"color4,omitempty"`
Color5 HexColor `yaml:"color5,omitempty" json:"color5,omitempty"`
Color6 HexColor `yaml:"color6,omitempty" json:"color6,omitempty"`
Color7 HexColor `yaml:"color7,omitempty" json:"color7,omitempty"`
Color8 HexColor `yaml:"color8,omitempty" json:"color8,omitempty"`
Color9 HexColor `yaml:"color9,omitempty" json:"color9,omitempty"`
Color10 HexColor `yaml:"color10,omitempty" json:"color10,omitempty"`
Color11 HexColor `yaml:"color11,omitempty" json:"color11,omitempty"`
Color12 HexColor `yaml:"color12,omitempty" json:"color12,omitempty"`
Color13 HexColor `yaml:"color13,omitempty" json:"color13,omitempty"`
Color14 HexColor `yaml:"color14,omitempty" json:"color14,omitempty"`
Color15 HexColor `yaml:"color15,omitempty" json:"color15,omitempty"`
}
#ThemeTokens is the closed colour vocabulary. Every value is a #HexColor: a token that accepted arbitrary strings would let "blue" through and render a broken config.
type Threaded ¶
type Threaded struct {
Kinds map[string]bool // recognizedKind
DeploySubstrates map[string]bool // recognizedDeploySubstrate
StructuralKinds map[string]bool // externalKindMayNestMembers
Primaries map[string]string // pluginPrimaryFor: verb word → scalar-sugar primary field
// DeployTraits is the substrate word → DECLARED #DeployTraits map (K1-LOADER RELOCATION): the
// descent stamp reads THIS registry-derived DATA snapshot instead of querying the provider
// registry live, so the venue-hop descent stamp (loaderkit.StampFleetDescents) is registry-free
// and can run plugin-side. The host fills it (deployTraitsFor per recognized kind/substrate word)
// exactly as the former live stampFleetDescents did; a word absent from the map resolves to the
// external-in-place default, matching deployTraitsFor's nil-for-unrecognized-word semantics.
DeployTraits map[string]*DeployTraits
// ExternalDeploySubstrates is the EXACT set of words for which the host's registry-live
// isExternalDeploySubstrate returns true (K1-LOADER RELOCATION): the host fills it by EVALUATING
// its own isExternalDeploySubstrate over every recognized substrate word, so a plugin-side
// validator (loaderkit.ValidateCheckBeds) checks membership here instead of RECONSTRUCTING the
// host's resourceKindSet ∧ externalizedDeploySubstrates ∧ recognizedDeploySubstrate decision —
// the byte-exact predicate is threaded, never approximated. A word absent from the set is
// NOT an external deploy substrate (isExternalDeploySubstrate would return false for it).
ExternalDeploySubstrates map[string]bool
}
Threaded is the host-computed, registry-derived DATA the per-document parse consults instead of querying the provider registry (boundary law clause D): which words are recognized kinds / deploy substrates, which external kinds may nest sub-entity members, and each plugin verb's scalar-sugar primary field. The host snapshots it before the parse; the parse never touches the registry.
type ToolStatus ¶
type ToolStatus struct {
Name string `yaml:"name,omitempty" json:"name"`
Status string `yaml:"status,omitempty" json:"status"`
Port int `yaml:"port,omitempty" json:"port,omitempty"`
Detail string `yaml:"detail,omitempty" json:"detail,omitempty"`
}
#ToolStatus — one live tool-probe result row. status "-" means the tool is not configured in this deployment; it is filtered before render.
type TunnelConfig ¶
type TunnelConfig struct {
// provider — "tailscale" or "cloudflare".
Provider string `yaml:"provider,omitempty" json:"provider,omitempty"`
// tunnel_name — cloudflare: tunnel name.
TunnelName string `yaml:"tunnel_name,omitempty" json:"tunnel_name,omitempty"`
// hostname — cloudflare: default hostname (from the image dns field).
Hostname string `yaml:"hostname,omitempty" json:"hostname,omitempty"`
// box_name — for PID file naming / cloudflare tunnel-name default.
BoxName string `yaml:"box_name,omitempty" json:"box_name,omitempty"`
// ports — all tunneled ports with their access scope.
Ports []TunnelPort `yaml:"ports,omitempty" json:"ports,omitempty"`
}
#TunnelConfig — the resolved, ready-to-execute tunnel configuration.
func TunnelConfigFromMetadata ¶ added in v0.2026213.1726
func TunnelConfigFromMetadata(meta *BoxMetadata) *TunnelConfig
TunnelConfigFromMetadata creates a TunnelConfig from image-label metadata. It is the SINGLE tunnel-resolution entry point — the former TunnelYAML-based resolution function (with its unused CandyReader/[]string DI params) was deleted (R5): its only production caller, the deploy-overlay `charly box inspect` tunnel display, resolves the same shape by constructing a BoxMetadata from the overlay's Tunnel + published-port set.
type TunnelPort ¶
type TunnelPort struct {
// port — the tailscale HTTPS listen port (must be a valid serve/funnel port).
Port int `yaml:"port,omitempty" json:"port,omitempty"`
// backend_port — the localhost backend port (0 means same as port).
BackendPort int `yaml:"backend_port,omitempty" json:"backend_port,omitempty"`
// protocol — backend scheme: http | https | https+insecure | tcp |
// tls-terminated-tcp | ssh | rdp | smb (udp is skipped, never tunneled).
Protocol string `yaml:"protocol,omitempty" json:"protocol,omitempty"`
// public — true = internet-accessible (funnel), false = private (serve).
Public bool `yaml:"public,omitempty" json:"public,omitempty"`
// hostname — cloudflare: per-port hostname (from the map form).
Hostname string `yaml:"hostname,omitempty" json:"hostname,omitempty"`
}
#TunnelPort — a single port to tunnel with its protocol and access scope.
func (TunnelPort) Backend ¶
func (tp TunnelPort) Backend() int
Backend returns the localhost backend port for a tunneled port, defaulting to Port when BackendPort is unset (0). Pure structural method on the generated spec.TunnelPort wire type (schema/tunnel.cue) — the ONE home (R3) for the backend-port default the pod quadlet emitter (sdk/deploykit) consumes. Lives here beside the type it acts on (the charly_methods.go convention): touches ONLY the type's own fields + the stdlib.
type TunnelYAML ¶
type TunnelYAML struct {
Provider string `yaml:"provider" json:"provider"`
Tunnel string `yaml:"tunnel,omitempty" json:"tunnel,omitempty"`
Public PortScope `yaml:"public,omitempty" json:"public"`
Private PortScope `yaml:"private,omitempty" json:"private"`
}
TunnelYAML supports both bare string and expanded form (the `tunnel:` field).
type UnifiedDeployTarget ¶ added in v0.2026218.138
type UnifiedDeployTarget interface {
// Name is the deployment's identifier from charly.yml (e.g.
// "arch-vm", "sway-pod"). Unique within a charly.yml.
Name() string
// Kind returns one of "host" | "vm" | "pod" | "kubernetes".
// Drives ledger keying ("<kind>:<name>") and command dispatch.
Kind() string
// Executor returns the DeployExecutor this target will use for
// shell operations. For host → ShellExecutor; for vm →
// SSHExecutor; for pod → a podman-exec wrapper; for kubernetes → a nop
// executor that errors on invocation (kubernetes operates via
// kubectl/Kustomize, not shell ops).
//
// Exposing the executor on the interface lets parent targets in
// a nested tree compose a NestedExecutor over the child.
Executor() DeployExecutor
// Add applies the given plans to the target. Equivalent to
// `charly fleet add <name>`. Idempotent: re-applying the same plan
// is safe. dctx carries the dispatch-merged node + loaded configs;
// the adapter constructs its live embedded target from it (never
// re-reading the node from disk — see DeployContext).
Add(ctx context.Context, dctx *DeployContext, plans []*InstallPlan, opts EmitOpts) error
// Del reverses every candy currently recorded for this target
// and removes the deploy record. Equivalent to `charly fleet del
// <name>`. Only recorded ReverseOps are replayed — never an
// ad-hoc computation from the candy manifest.
Del(ctx context.Context, opts DeployTargetDelOpts) error
// Update re-applies the plan diff between the currently-recorded
// candy set and the plan set derived from fresh charly.yml.
// Equivalent to `charly fleet update <name>` (new command; today's
// `charly update` is image-focused and will be separate).
Update(ctx context.Context, plans []*InstallPlan, opts UpdateOpts) error
}
UnifiedDeployTarget is the unified contract all five deploy methods (local, vm, pod, kubernetes, android) implement uniformly. Each method corresponds to an `charly fleet …` subcommand, so the dispatcher in ResolveTarget (charly/unified_targets.go) can route purely on target.Kind() without per-cmd switches. Every method dispatches through the ONE generic DeployTargetDispatchRequest envelope, discriminated by an `op` field.
type UnifiedFile ¶ added in v0.2026210.808
type UnifiedFile struct {
Version string `yaml:"version,omitempty" json:"version,omitempty"`
// Repo is this project's canonical repo identity (e.g. "github.com/opencharly/charly").
// Optional; only meaningful on the ROOT file.
Repo string `yaml:"repo,omitempty" json:"repo,omitempty"`
// Import is the SINGLE composition statement. A list whose items are either a bare string
// (flat import into THIS root namespace) or a single-key map `alias: ref` (a namespaced
// child import). See ImportList (load_directives.go).
Import ImportList `yaml:"import,omitempty" json:"import,omitempty"`
Discover DiscoverConfig `yaml:"discover,omitempty" json:"discover,omitempty"`
// Defaults carries the `defaults:` block (BoxConfig-shaped inheritance defaults).
Defaults BoxConfig `yaml:"defaults,omitempty" json:"defaults,omitempty"`
// Box is the generic kind-keyed IMAGE map (P6): name → opaque marshaled BoxConfig.
Box BoxMap `yaml:"box,omitempty" json:"box,omitempty"`
// Candy is the generic kind-keyed LAYER map: name → opaque marshaled InlineCandy.
Candy map[string]json.RawMessage `yaml:"candy,omitempty" json:"candy,omitempty"`
// Fleet is the flat name-keyed deploy map (the canonical `deploy:` surface).
Fleet map[string]FleetNode `yaml:"deploy,omitempty" json:"deploy,omitempty"`
Provides *ProvidesConfig `yaml:"provides,omitempty" json:"provides,omitempty"`
// Cache is the per-host LOCAL cache status (git metadata cache). Lives in the
// per-host charly.yml (~/.config/charly/charly.yml) — the single home for local
// system state — NOT in ad-hoc JSON files under ~/.cache/charly/repos. Shape:
// schema/cache.cue #CacheConfig.
Cache *CacheConfig `yaml:"cache,omitempty" json:"cache,omitempty"`
// Ledger is the per-host INSTALL LEDGER (deploy/candy/step records). The
// unified home for what is installed on this host, replacing the per-deploy
// JSON files under ~/.config/opencharly/installed/. Shape: schema/ledger.cue
// #LedgerConfig.
Ledger *LedgerConfig `yaml:"ledger,omitempty" json:"ledger,omitempty"`
// System is the per-host LOCAL SYSTEM INFO (hostname, distro, kernel, arch,
// GPU, …). Shape: schema/system.cue #SystemInfo.
System *SystemInfo `yaml:"system,omitempty" json:"system,omitempty"`
// PluginKinds holds entities of KINDS contributed by plugins (a kind the core has no typed
// map for). NAME-KEYED: kind word → entity NAME → canonical body (opaque JSON). Built-in
// kinds decode into their typed maps above. Host-internal — never serialized.
PluginKinds map[string]map[string]json.RawMessage `yaml:"-" json:"-"`
// Namespaces holds child namespaces mounted by namespaced `import:` entries (alias →
// fully-resolved isolated UnifiedFile). NOT authored directly — populated by the
// materialize pass. Entries are referenced qualified, e.g. `base: cachyos.cachyos`.
Namespaces map[string]*UnifiedFile `yaml:"-"`
// RootDir is this UnifiedFile's OWN base directory — the dir its root document's SrcDir
// names. Set once per materialize, for both the top-level project and each mounted
// namespace. Empty for a project-less / synthetic UnifiedFile.
RootDir string `yaml:"-"`
}
UnifiedFile is the full schema of a single unified-format YAML document. Every field is optional — a file with only `distro:` is valid (typical for the embedded build vocabulary); a file with only `deploy:` is valid; etc.
func (*UnifiedFile) Android ¶ added in v0.2026210.808
func (uf *UnifiedFile) Android() map[string]json.RawMessage
func (*UnifiedFile) BoxConfig ¶ added in v0.2026210.808
func (uf *UnifiedFile) BoxConfig(name string) (BoxConfig, bool)
BoxConfig decodes the authored image config for name; ok=false when absent.
func (*UnifiedFile) BoxNames ¶ added in v0.2026210.808
func (uf *UnifiedFile) BoxNames() []string
BoxNames returns the image names, sorted.
func (*UnifiedFile) CheckBeds ¶ added in v0.2026210.808
func (uf *UnifiedFile) CheckBeds() map[string]FleetNode
CheckBeds returns the disposable R10 beds keyed by name. In the unified node-form model a bed IS a `disposable: true` fleet, so the bed set is derived directly from the disposable fleets in the Fleet map. Members are instruments (brought up alongside a driver), never standalone beds. Single enumeration source for `charly check run <bed>` (and the /verify-beds fan-out).
func (*UnifiedFile) HasBox ¶ added in v0.2026210.808
func (uf *UnifiedFile) HasBox(name string) bool
HasBox reports whether an image named name is present.
func (*UnifiedFile) Kubernetes ¶ added in v0.2026223.1018
func (uf *UnifiedFile) Kubernetes() map[string]json.RawMessage
func (*UnifiedFile) Local ¶ added in v0.2026210.808
func (uf *UnifiedFile) Local() map[string]json.RawMessage
func (*UnifiedFile) Pod ¶ added in v0.2026210.808
func (uf *UnifiedFile) Pod() map[string]json.RawMessage
func (*UnifiedFile) ProjectConfig ¶ added in v0.2026210.808
func (uf *UnifiedFile) ProjectConfig() *Config
ProjectConfig returns the *Config equivalent of uf (the box config view).
func (*UnifiedFile) ProjectTemplates ¶ added in v0.2026210.808
func (uf *UnifiedFile) ProjectTemplates() *ProjectTemplates
ProjectTemplates decodes the uf.Local/Kubernetes/Pod/VM/Android raw template maps (map[string]json.RawMessage) into the resolved kind-template maps validate/check-include/status read. Returns nil when no template kind is present. Recurses into uf.Namespaces, mirroring FillBoxPlans's prefix-accumulation pattern, so a namespace-qualified template ref (`local: <ns>.<tmpl>`, `kind:kubernetes` entity `<ns>.<name>`, …) is visible in the envelope too. Purely ADDITIVE (qualified keys never collide with a bare name, since a bare name can never contain "."), so every existing root-scoped consumer is unaffected.
This is UnifiedFile's own projection (relocated from loaderkit.ProjectTemplates as a METHOD — the free function's name collided with the ProjectTemplates TYPE; it is the sibling of ProjectConfig above).
func (*UnifiedFile) SetBox ¶ added in v0.2026210.808
func (uf *UnifiedFile) SetBox(name string, b BoxConfig)
SetBox stores an authored image config under name (marshaling it opaque).
func (*UnifiedFile) SetCandy ¶ added in v0.2026210.808
func (uf *UnifiedFile) SetCandy(name string, il *InlineCandy)
SetCandy stores a layer under name (marshaling it opaque).
func (*UnifiedFile) VM ¶ added in v0.2026210.808
func (uf *UnifiedFile) VM() map[string]json.RawMessage
VM/Pod/Kubernetes/Local/Android are DERIVED accessors over uf.PluginKinds[disc] — the 5 standalone-substrate-TEMPLATE kinds fold into PluginKinds generically (no per-kind-word switch). Each returns the opaque name→body map for its kind (nil when none configured); the kernel never decodes the bodies itself — consuming PLUGINS decode a body into the concrete kind they need.
type UpdateOpts ¶ added in v0.2026218.138
type UpdateOpts struct {
DryRun bool
AssumeYes bool
RebuildImage bool
AllowRepoChanges bool
AllowRootTasks bool
WithServices bool
}
UpdateOpts parameterizes `charly fleet update`. On the wire Update marshals the SAME spec.LifecycleOpts shape Add does (R3 — one wire shape for the shared apply body); RebuildImage is deliberately NOT threaded there — it belongs to DeployTargetRebuildOpts.
type UserInfo ¶
type UserInfo struct {
Found bool `yaml:"found,omitempty" json:"found"`
Name string `yaml:"name,omitempty" json:"name,omitempty"`
UID int `yaml:"uid,omitempty" json:"uid,omitempty"`
GID int `yaml:"gid,omitempty" json:"gid,omitempty"`
Home string `yaml:"home,omitempty" json:"home,omitempty"`
}
#UserInfo — one /etc/passwd entry (name:uid:gid:...:home). Empty reply (found=false) when no user with the requested uid exists in the image.
type VFIOGpu ¶
type VFIOGpu struct {
Addr string `yaml:"addr,omitempty" json:"addr"`
GroupMembers []VFIOPCIDevice `yaml:"group_members,omitempty" json:"group_members"`
VendorID string `yaml:"vendor_id,omitempty" json:"vendor_id"`
DeviceID string `yaml:"device_id,omitempty" json:"device_id"`
Class string `yaml:"class,omitempty" json:"class"`
ClassLabel string `yaml:"class_label,omitempty" json:"class_label"`
Driver string `yaml:"driver,omitempty" json:"driver"`
IOMMUGroup int `yaml:"iommu_group,omitempty" json:"iommu_group"`
}
#VFIOGpu is a display-class device plus every other function sharing its IOMMU group. Passthrough must move the whole group together, so the renderer emits one <hostdev> per GroupMember.
FLATTENED (never a Go anonymous-embedded field — CUE unification has no concept of Go embedding; it always structurally merges fields into one flat struct). The former hand type embedded VFIOPCIDevice anonymously; Go's encoding/json ALREADY promotes an anonymous embedded struct's fields to the parent JSON object on the wire (Go's own embedding-promotion rule), so this flattened shape is BYTE-IDENTICAL JSON to the former embedded type — the only consumer-visible change is Go SOURCE: a composite literal naming the embedded field (`VFIOGpu{VFIOPCIDevice: d}`) must spread d's fields instead (`VFIOGpu{Addr: d.Addr, ...}`). Fixed at every call site in the same cutover (candy/plugin-gpu, charly test fixtures).
func NewVFIOGpu ¶
func NewVFIOGpu(d VFIOPCIDevice) VFIOGpu
gpu_methods.go — a pure Go CONSTRUCTOR for the CUE-generated, FLATTENED VFIOGpu (sdk/schema/gpu.cue -> spec/cue_types_gen.go). VFIOGpu used to embed VFIOPCIDevice anonymously (Go's own JSON-promotion rule already made the wire shape identical either way); CUE unification has no concept of Go embedding, so the SDD conversion flattened VFIOGpu's fields directly. NewVFIOGpu restores the one-line "build a VFIOGpu from a VFIOPCIDevice" ergonomic the former embedded-field composite literal (`VFIOGpu{VFIOPCIDevice: d}`) provided, so every construction site (charly, candy/plugin-gpu) names ONE spread point instead of repeating the 7-field copy (R3) — set GroupMembers on the returned value afterward, exactly as the former embedded-literal call sites already did.
func SelectGPUByVendor ¶
func SelectGPUByVendor(rep VFIOReport, vendor string) (VFIOGpu, bool)
SelectGPUByVendor returns the first passthrough-capable GPU whose PCI vendor matches (case/prefix-insensitive). ok=false when no GPU matches.
type VFIOPCIDevice ¶
type VFIOPCIDevice struct {
Addr string `yaml:"addr,omitempty" json:"addr"`
VendorID string `yaml:"vendor_id,omitempty" json:"vendor_id"`
DeviceID string `yaml:"device_id,omitempty" json:"device_id"`
Class string `yaml:"class,omitempty" json:"class"`
ClassLabel string `yaml:"class_label,omitempty" json:"class_label"`
Driver string `yaml:"driver,omitempty" json:"driver"`
IOMMUGroup int `yaml:"iommu_group,omitempty" json:"iommu_group"`
}
#VFIOPCIDevice is a single PCI function discovered under sysfs.
type VFIOReport ¶
type VFIOReport struct {
IOMMUEnabled bool `yaml:"iommu_enabled,omitempty" json:"iommu_enabled"`
IOMMUKind string `yaml:"iommu_kind,omitempty" json:"iommu_kind"`
GPUs []VFIOGpu `yaml:"gpus,omitempty" json:"gpus"`
}
#VFIOReport summarizes host readiness for VFIO GPU passthrough.
type ValidateProjectReply ¶
type ValidateProjectReply struct {
Project *ResolvedProject `yaml:"project,omitempty" json:"project,omitempty"`
Diagnostics Diagnostics `yaml:"diagnostics,omitempty" json:"diagnostics,omitempty"`
}
#ValidateProjectReply — the error-TOLERANT #ResolvedProject projection (partial; only boxes and candies that resolved) + the per-box resolve-failure diagnostics. The plugin merges these with its own pure-rule, raw-config, CUE-conformance and resolution-graph findings for the verdict.
type ValidateProjectRequest ¶
type ValidateProjectRequest struct {
Dir string `yaml:"dir,omitempty" json:"dir,omitempty"`
IncludeDisabled bool `yaml:"include_disabled,omitempty" json:"include_disabled,omitempty"`
}
#ValidateProjectRequest — which project dir to validate (empty = the host's cwd) + whether to include enabled:false boxes. Mirrors #ResolvedProjectRequest (the sibling resolved-project seam).
type ValidateWordSetsReply ¶ added in v0.2026218.138
type ValidateWordSetsReply struct {
ProviderCapabilities []string `yaml:"provider_capabilities,omitempty" json:"provider_capabilities,omitempty"`
ActCapableVerbs []string `yaml:"act_capable_verbs,omitempty" json:"act_capable_verbs,omitempty"`
}
#ValidateWordSetsReply — the two registry-derived D-data word sets the validate rules consume as membership sets. provider_capabilities is every compiled-in provider as "<class>:<word>" (the TARGET set a `source: builtin` plugin candy's declared providers must be a member of); act_capable_verbs is the subset of the request's plugin_words whose act form has a build/deploy install path (the check act-form rule).
type ValidateWordSetsRequest ¶ added in v0.2026218.138
type ValidateWordSetsRequest struct {
PluginWords []string `yaml:"plugin_words,omitempty" json:"plugin_words,omitempty"`
ExternalProviders []string `yaml:"external_providers,omitempty" json:"external_providers,omitempty"`
}
#ValidateWordSetsRequest — the plugin-supplied inventory the host answers registry-D over.
plugin_words are the DISTINCT `plugin:` verb words the plugin enumerated from its OWN envelope (every candy model's plan plus every box plan); the host reports which of them ACT in build/deploy. external_providers are the `plugin.providers:` capability strings ("<class>:<word>") of every candy whose `plugin.source:` is a real out-of-tree ref — the host must LEARN those declarations before it can answer, because a declared-but-not-yet-connected external verb/step is act-capable by declaration alone (there is no provider to resolve until the build connects it).
type ValidatingKindCarrier ¶
type ValidatingKindCarrier interface {
IsValidatingKind() bool
}
ValidatingKindCarrier is implemented by a provider (grpcProvider out-of-proc, inprocProvider compiled-in) that carries a class:kind capability's VALIDATES flag (F7/C8). true → the host dispatches OpValidate to the kind at load (a deep plugin-owned check returning Diagnostics, beyond the static CUE input-def gate); false (or not implemented) → only the static gate runs.
type ValidationError ¶ added in v0.2026210.808
type ValidationError struct {
Errors []string
}
ValidationError accumulates project/candy validation error messages (the loader validation accumulator, distinct from the richer Diagnostics — a plain []string collector). Relocated from loaderkit (resolve_opts.go) with UnifiedFile so the type-only loader-result consumers share ONE definition.
func (*ValidationError) Add ¶ added in v0.2026210.808
func (e *ValidationError) Add(format string, args ...any)
Add adds an error to the collection.
func (*ValidationError) Error ¶ added in v0.2026210.808
func (e *ValidationError) Error() string
func (*ValidationError) HasErrors ¶ added in v0.2026210.808
func (e *ValidationError) HasErrors() bool
HasErrors returns true if there are any errors.
type Venue ¶
type Venue int
const ( // VenueHostNative runs commands directly on the host (or as a RUN in // the Containerfile for the container target). The step's shell // commands execute natively; no isolation container. VenueHostNative Venue = iota // VenueContainerBuilder runs the step inside the existing multi-stage // builder image (fedora-builder / arch-builder / ...). On the // host target this means `podman run --user <host-uid> -v <paths> // <builder> bash -c "..."`. On the OCI target this is a FROM + RUN // pair that becomes its own build stage, with copy_artifacts pulling // outputs into the final image. VenueContainerBuilder // VenueSkip records the step with a reason but doesn't execute. Used // for container-runtime-only fields (ports:, volumes:, tunnel:, …) // when compiling for the host target, and for aur: on non-Arch hosts. VenueSkip )
type VenueDescriptor ¶
type VenueDescriptor struct {
Kind string `yaml:"kind,omitempty" json:"kind"`
User string `yaml:"user,omitempty" json:"user,omitempty"`
Host string `yaml:"host,omitempty" json:"host,omitempty"`
Port int `yaml:"port,omitempty" json:"port,omitempty"`
Args []string `yaml:"args,omitempty" json:"args,omitempty"`
ConnectTimeout int `yaml:"connect_timeout,omitempty" json:"connect_timeout,omitempty"`
// engine/container_name are set ONLY for kind "container": the container engine
// ("podman"/"docker", selecting JumpPodmanExec vs JumpDockerExec) and the target container
// name ContainerChain jumps into.
Engine string `yaml:"engine,omitempty" json:"engine,omitempty"`
ContainerName string `yaml:"container_name,omitempty" json:"container_name,omitempty"`
}
#VenueDescriptor is the SELF-CONTAINED, serializable description of a deploy venue's executor that a substrate LIFECYCLE plugin's OpPrepareVenue / OpTeardownExecutor returns (F6). K1-unblock W3 Unit B added the "container" kind (engine/container_name) so a plugin-constructed deploykit.ContainerChain venue — a single-hop *NestedExecutor{Parent:ShellExecutor{}, Jump:{Kind:JumpPodmanExec|JumpDockerExec}}, the MOST COMMON check-runner venue — round-trips through kit.DescriptorFromExecutor/VenueFromDescriptor exactly like "shell"/"ssh" already do. This does NOT generalize to arbitrary N-hop composition (a genuinely multi-hop NestedExecutor still degrades to the zero descriptor, unchanged) — it closes the one enumerable, well-known single-hop shape ContainerChain always produces.
type VerbSpec ¶
type VerbSpec struct {
Contexts []ExecContext
DefaultDo DoMode
Reversible bool
}
VerbSpec is the per-verb metadata in VerbCatalog. Contexts[0] is the canonical default context.
func (VerbSpec) HasContext ¶
func (s VerbSpec) HasContext(ctx ExecContext) bool
HasContext reports whether the verb is legal in ctx.
type VerifyChecksRequest ¶ added in v0.2026213.1726
type VerifyChecksRequest struct {
Plan []Step `yaml:"plan,omitempty" json:"plan,omitempty"`
Mode string `yaml:"mode,omitempty" json:"mode,omitempty"`
Box string `yaml:"box,omitempty" json:"box,omitempty"`
Instance string `yaml:"instance,omitempty" json:"instance,omitempty"`
VerifyOnly bool `yaml:"verify_only,omitempty" json:"verify_only,omitempty"`
Dir string `yaml:"dir,omitempty" json:"dir,omitempty"`
Venue VenueDescriptor `yaml:"venue,omitempty" json:"venue,omitempty"`
}
#VerifyChecksRequest is the command:check OpVerifyChecks envelope (#55 CHECK-ENGINE cone, Unit 2): the host threads a live venue executor — flattened to #VenueDescriptor, since a live executor cannot cross the wire, and re-materialized PLUGIN-SIDE via kit.VenueFromDescriptor (the SAME mechanism candy/plugin-fleet's resolveRootExecutor uses) — over the in-proc reverse channel and asks the COMPILED-IN command:check to DRIVE a deploy-scope check pass PLUGIN-SIDE. This sheds charly core's checkrun.go + planrun_adapter.go sdk/kit imports (the in-proc kit.Runner construction moved plugin-side).
ONE drive shape now: plan → the `target: local` --verify path (candy/plugin-fleet's verify_local.go, #55 W3 B3 — a PEER plugin now, not core): a PLUGIN-ASSEMBLED plan (kind:local template, resolved via node_resolve.go's lookupLocalTemplate — no LoadUnified — + deploy node; the per-host overlay merge happens on THIS side) driven via kit.RunPlan (verify-only/context/ keyword gating). The plugin rebuilds the runtime env (USER/HOME/IMAGE/INSTANCE) + ${HOST:} host-vars + the cross-deployment TargetResolver from {dir, box, instance} — plugin-check ALREADY does this for check-live (verb_resolver.go / members.go), so those never cross the wire.
The former SECOND drive shape (ops/only_ids — the deploy-lifecycle Test path, charly core's unified_targets.go runUnifiedTargetChecks feeding raw deploy-scope Op checks via kit.Runner.Run, no plan gating) is GONE (#55 W3 B3 remainder): its own sole production caller, pluginDeployTarget.Test (UnifiedDeployTarget's Test method), had ZERO real callers anywhere in the tree — `charly check live` reaches candy/plugin-check directly (live_gather.go) and never touches this interface method; the ONE caller was a unit test. Test()/runUnifiedTargetChecks/ TestOpts (charly), verifyChecksRunOps/filterOpsByID (candy/plugin-check), and the dead "test" op in #DeployTargetDispatchRequest's enum are all deleted together. The box-mode context-skip regression coverage (TestLiveVerb_SkipsUnderBoxMode, charly/checkrun_charly_verbs_test.go) moved onto the surviving plan shape — RunOne (sdk/kit/planrun.go) is the SAME per-step primitive both kit.Runner.Run and kit.RunPlan dispatched through, so the context-vs-mode gate (opInContext) is identically exercised either way; no coverage was lost.
The reply is []#StepResult (CUE-sourced in this file) — CONSUMED, not modified. All plain fields (plan/venue are spec envelope types; StepResult.Result is #CheckResult by value) → gengotypes-faithful, no @go(-).
type Vm ¶
type Vm struct {
Source VmSource `yaml:"source,omitempty" json:"source"`
DiskSize string `yaml:"disk_size,omitempty" json:"disk_size,omitempty"`
Ram string `yaml:"ram,omitempty" json:"ram,omitempty"`
Cpus int `yaml:"cpu,omitempty" json:"cpu,omitempty"`
Machine string `yaml:"machine,omitempty" json:"machine,omitempty"`
// firmware is REQUIRED-with-default (not optional): an if-guard can only
// reference a field that always resolves to a concrete value, and an
// OPTIONAL field — even one carrying a default — errors with "cannot
// reference optional field" when absent. Required-with-default materializes
// "bios" on omission (matching the Go empty→bios behavior) AND stays
// referenceable by the uefi-secure cross-rule below.
Firmware string `yaml:"firmware,omitempty" json:"firmware"`
Backend string `yaml:"backend,omitempty" json:"backend"`
Autostart bool `yaml:"autostart,omitempty" json:"autostart"`
Network *VmNetwork `yaml:"network,omitempty" json:"network,omitempty"`
SSH *VmSsh `yaml:"ssh,omitempty" json:"ssh,omitempty"`
CloudInit *VmCloudInit `yaml:"cloud_init,omitempty" json:"cloud_init,omitempty"`
Libvirt *LibvirtDomain `yaml:"libvirt,omitempty" json:"libvirt,omitempty"`
Plan []Step `yaml:"plan,omitempty" json:"plan,omitempty"`
Snapshots []VmSnapshot `yaml:"snapshot,omitempty" json:"snapshot,omitempty"`
}
type VmBoxMetadata ¶ added in v0.2026246.554
type VmBoxMetadata struct {
// distro — the guest distro id. Validated against the #DistroID vocabulary (the
// closed id list the VM sources' `distro:` are checked against) at read time; a
// plain string here so a future id needs no schema bump (the #DistroID def is
// @go(-): it generates no Go type, so the field must be a string on the wire).
Distro string `yaml:"distro,omitempty" json:"distro,omitempty"`
// arch — the guest architecture (x86_64 | aarch64).
Arch string `yaml:"arch,omitempty" json:"arch,omitempty"`
// base_user — the adopted account the box is built around (the cloud_image source
// adoption pattern: cloud-init's default user the image was prepared with).
BaseUser string `yaml:"base_user,omitempty" json:"base_user,omitempty"`
// ssh_user — the ssh login user the deploy host connects as.
SSHUser string `yaml:"ssh_user,omitempty" json:"ssh_user,omitempty"`
// firmware — the guest firmware mode the box boots under.
Firmware string `yaml:"firmware,omitempty" json:"firmware,omitempty"`
// init — the guest init system (systemd | openrc).
Init string `yaml:"init,omitempty" json:"init,omitempty"`
// charly_install — how the charly guest agent gets in: auto | scp | skip.
CharlyInstall string `yaml:"charly_install,omitempty" json:"charly_install,omitempty"`
// version — the box's CalVer (v0.<YYYYDDD>.<HHMM>).
Version string `yaml:"version,omitempty" json:"version,omitempty"`
// source — provenance: which #VmSource kind produced the disk artifact and from
// what. The generic BoxRef resolver (cutover task 4) routes every arm onto its
// existing primitive from this record.
Source VmBoxSource `yaml:"source,omitempty" json:"source,omitempty"`
// description — human-facing description of the box.
Description string `yaml:"description,omitempty" json:"description,omitempty"`
// plan — the baked check plan (the SAME #Step the #LabeledDescription plan
// carries): the acceptance surface `charly check live` walks after a from-box
// vm: deploy — the VM analog of the pod box's baked plan.
Plan []Step `yaml:"plan,omitempty" json:"plan,omitempty"`
}
CUE schema for the VM-box metadata contract — the VM-specific half of the OCI-label metadata a VM box image carries (cutover plan §2.1: "a VM box is an OCI image whose labels carry the VM metadata contract and whose layers carry the disk artifact"). A VM box image carries the STANDARD #BoxMetadata labels (the shared ai.opencharly.* hub) AND, layered ON TOP of them, #VmBoxMetadata — the VM-specific facts pods never carry (guest distro/arch, adopted account, firmware, init system, install strategy, disk provenance). Runtime-relevant the same way #BoxMetadata is: the box emitter bakes it at build time (sdk/deploykit EmitVmBox) and the source-less VM deploy (VmCapabilitiesFromLabels) reads it back. CUE-sourced here so the whole VM-box resolution reads ONE spec-owned type. Package-less; concatenated into spec.
R8 (the byte-freeze gate): #VmBoxMetadata IS whole-struct-marshaled into ONE label (ai.opencharly.vm.box, spec.LabelVmBox) — like #CapabilityService → ai.opencharly.service. Because the label is NEW it has no pre-existing hand-struct anchor to freeze, so its json tags ARE the wire contract (required `!` → no omitempty, optional `?` → omitempty) — the deploykit reader round-trips this struct byte-for-byte through the label.
type VmBoxSource ¶ added in v0.2026246.554
type VmBoxSource struct {
Kind string `yaml:"kind,omitempty" json:"kind"`
// clone: the source entity the box's disk was cloned from.
FromVm string `yaml:"from_vm,omitempty" json:"from_vm,omitempty"`
// clone: the base snapshot id the clone overlay was created from.
FromSnapshot string `yaml:"from_snapshot,omitempty" json:"from_snapshot,omitempty"`
// bootc: the candy image ref the disk was installed from.
Box string `yaml:"box,omitempty" json:"box,omitempty"`
// cloud_image | iso: the artifact url the disk was fetched from.
URL string `yaml:"url,omitempty" json:"url,omitempty"`
}
#VmBoxSource — provenance of a VM box's disk artifact: the source kind that produced it plus the kind-specific origin reference. The kind space mirrors #VmSource's arms (cloud_image | bootc | clone | bootstrap | iso) so every arm a VM was built from leaves a resolvable provenance record; one arm's fields are populated per kind.
type VmBuildReply ¶
type VmBuildReply struct {
SourceKind string `yaml:"source_kind,omitempty" json:"source_kind"`
VmJSON RawBody `yaml:"vm_json,omitempty" json:"vm_json"`
DistroJSON RawBody `yaml:"distro_json,omitempty" json:"distro_json,omitempty"`
BuilderJSON RawBody `yaml:"builder_json,omitempty" json:"builder_json,omitempty"`
Engine string `yaml:"engine,omitempty" json:"engine,omitempty"`
Rootful bool `yaml:"rootful,omitempty" json:"rootful,omitempty"`
BootcImageRef string `yaml:"bootc_image_ref,omitempty" json:"bootc_image_ref,omitempty"`
BuilderImageRef string `yaml:"builder_image_ref,omitempty" json:"builder_image_ref,omitempty"`
OutputDir string `yaml:"output_dir,omitempty" json:"output_dir"`
VmStateDir string `yaml:"vm_state_dir,omitempty" json:"vm_state_dir"`
ExistingState *VmDeployState `yaml:"existing_state,omitempty" json:"existing_state,omitempty"`
Force bool `yaml:"force,omitempty" json:"force,omitempty"`
}
#VmBuildReply is the resolveVmBuild reply (P8b-rest — the former "vm-build" host-builder is DELETED; candy/plugin-vm/vm_build_resolve.go resolves it plugin-side): everything the plugin needs to run the disk-build engine without importing the loader. VmJSON is the resolved+validated kind:vm entity (the #Vm-shaped value resolveVmViaPlugin already produces — opaque bytes of the same #Vm-shaped payload convention) so the plugin decodes it into its own spec.Vm rather than re-parsing uf.VM[entity] itself (which needs LoadUnified, a core Mechanism). DistroJSON/BuilderJSON carry the matched *DistroDef/*BuilderDef (bootstrap source only) — hand-written runtime types with no CUE def, so they ride as opaque RawBody too (the established idiom this file documents at the top). Engine/Rootful are the resolved runtime settings (ResolveRuntime) the engine needs to pick `podman` vs `sudo podman`. BootcImageRef/BuilderImageRef are PRE-RESOLVED (and, for the builder image, pre-built via `charly box build`) — both need the local podman-storage + project-config lookup a plugin cannot do (resolveBootcImageRef / ensureBuilderImageBuilt stay host-side). OutputDir/VmStateDir are the resolved per-entity paths (vmshared.VmDiskDir is ALREADY plugin-importable, but the host still resolves+creates VmStateDir since it also reads the existing ledger state below). ExistingState is the entity's persisted VmDeployState (#VmDeployState already has a CUE def — a typed embed, not opaque) so the plugin reuses the same instance-id / regenerates the seed ISO idempotently.
type VmBuildRequest ¶
type VmBuildRequest struct {
Box string `yaml:"box,omitempty" json:"box"`
Size string `yaml:"size,omitempty" json:"size,omitempty"`
RootSize string `yaml:"root_size,omitempty" json:"root_size,omitempty"`
Tag string `yaml:"tag,omitempty" json:"tag,omitempty"`
Type string `yaml:"type,omitempty" json:"type,omitempty"`
Transport string `yaml:"transport,omitempty" json:"transport,omitempty"`
Console bool `yaml:"console,omitempty" json:"console,omitempty"`
Force bool `yaml:"force,omitempty" json:"force,omitempty"`
}
#VmBuildRequest carries the `charly vm build` command flags (the former VmBuildCmd fields). candy/plugin-vm/vm_build_resolve.go resolves the kind:vm entity + the build vocabulary + the per-source-kind image refs into a #VmBuildReply envelope PLUGIN-SIDE (the former "vm-build" host-builder is DELETED — PREP+RESOLVE moved into the plugin, P8b-rest); the plugin's `charly vm build` command then runs the actual privileged-container / qemu-img / bootc-install / cloud-init disk-build ENGINE itself, exactly as candy/plugin-build's podman DRIVE runs behind HostBuild("buildengine-prep") (P8b) — the same inversion, applied to the VM disk-build engine. force skips the cloud_image content-freshness check, forcing a base-disk rebuild even when unchanged (P8b-rest: `--force` predates command:vm's P10 externalization but was dropped from this seam then — restored here since BuildCloudImage's force parameter is load-bearing).
type VmCharlyInstall ¶
type VmCharlyInstall struct {
// VmCharlyInstall has ONLY `strategy` (the vm-spec skill's url/checksum are
// STALE — the Go struct dropped them). auto: scp host binary post-boot;
// scp: explicit form; skip: user-managed.
Strategy string `yaml:"strategy,omitempty" json:"strategy,omitempty"`
}
type VmChecksum ¶
type VmCloudInit ¶
type VmCloudInit struct {
Hostname string `yaml:"hostname,omitempty" json:"hostname,omitempty"`
Timezone string `yaml:"timezone,omitempty" json:"timezone,omitempty"`
Locale string `yaml:"locale,omitempty" json:"locale,omitempty"`
Users []VmCloudInitUser `yaml:"users,omitempty" json:"users,omitempty"`
Package []string `yaml:"package,omitempty" json:"package,omitempty"`
RunCmd []string `yaml:"runcmd,omitempty" json:"runcmd,omitempty"`
BootCmd []string `yaml:"bootcmd,omitempty" json:"bootcmd,omitempty"`
WriteFiles []VmCloudInitFile `yaml:"write_files,omitempty" json:"write_files,omitempty"`
Network *VmCloudInitNetwork `yaml:"network,omitempty" json:"network,omitempty"`
Mirrors *VmCloudInitMirrors `yaml:"mirrors,omitempty" json:"mirrors,omitempty"`
CharlyInstall *VmCharlyInstall `yaml:"charly_install,omitempty" json:"charly_install,omitempty"`
Extra string `yaml:"extra,omitempty" json:"extra,omitempty"`
}
--------------------------------------------------------------------------- cloud_init: VmCloudInit. CLOSED. Genuine passthroughs: extra (raw cloud-config string) and network.ethernets (network-config v2, map[string]map[string]any → {[string]: {[string]: _}}). ---------------------------------------------------------------------------
type VmCloudInitFile ¶
type VmCloudInitFile struct {
Path string `yaml:"path,omitempty" json:"path"`
Content string `yaml:"content,omitempty" json:"content,omitempty"`
Owner string `yaml:"owner,omitempty" json:"owner,omitempty"`
Perms string `yaml:"perms,omitempty" json:"perms,omitempty"`
Encoding string `yaml:"encoding,omitempty" json:"encoding,omitempty"`
}
type VmCloudInitMirrors ¶
type VmCloudInitNetwork ¶
type VmCloudInitUser ¶
type VmCloudInitUser struct {
Name string `yaml:"name,omitempty" json:"name"`
Sudo bool `yaml:"sudo,omitempty" json:"sudo,omitempty"`
Groups []string `yaml:"groups,omitempty" json:"groups,omitempty"`
Shell string `yaml:"shell,omitempty" json:"shell,omitempty"`
LockPasswd *bool `yaml:"lock_passwd,omitempty" json:"lock_passwd,omitempty"`
}
type VmDeployState ¶
type VmDeployState struct {
InstanceID string `yaml:"instance_id,omitempty" json:"instance_id,omitempty"`
DiskPath string `yaml:"disk_path,omitempty" json:"disk_path,omitempty"`
SeedIso string `yaml:"seed_iso,omitempty" json:"seed_iso,omitempty"`
SSHPort int `yaml:"ssh_port,omitempty" json:"ssh_port,omitempty"`
// PortForwards maps a guest port (string) to its auto-allocated host port for
// each `auto:<guest>` network.port_forwards entry — persisted at vm-create so
// the allocation is reused across create→deploy-add (the sibling of SSHPort).
PortForwards map[string]int `yaml:"port_forwards,omitempty" json:"port_forwards,omitempty"`
SSHUser string `yaml:"ssh_user,omitempty" json:"ssh_user,omitempty"`
Backend string `yaml:"backend,omitempty" json:"backend,omitempty"`
KeyInjectionResolved *VmKeyInjectionResolved `yaml:"key_injection_resolved,omitempty" json:"key_injection_resolved,omitempty"`
CharlyInstallStrategy string `yaml:"charly_install_strategy,omitempty" json:"charly_install_strategy,omitempty"`
CloudInitRenderedDigest string `yaml:"cloud_init_rendered_digest,omitempty" json:"cloud_init_rendered_digest,omitempty"`
Snapshots []VmSnapshotState `yaml:"snapshot,omitempty" json:"snapshot,omitempty"`
Ephemeral *EphemeralRuntime `yaml:"ephemeral,omitempty" json:"ephemeral,omitempty"`
}
VmDeployState is the runtime state the vm lifecycle hook's PrepareVenue writes on first apply. Source: charly/deploy.go VmDeployState.
type VmDisplayEndpoint ¶
type VmDisplayEndpoint struct {
Kind string `yaml:"kind,omitempty" json:"kind,omitempty"`
IsSocket bool `yaml:"is_socket,omitempty" json:"is_socket,omitempty"`
SocketPath string `yaml:"socket_path,omitempty" json:"socket_path,omitempty"`
Host string `yaml:"host,omitempty" json:"host,omitempty"`
Port int `yaml:"port,omitempty" json:"port,omitempty"`
Password string `yaml:"password,omitempty" json:"password,omitempty"`
TunnelNeeded bool `yaml:"tunnel_needed,omitempty" json:"tunnel_needed,omitempty"`
}
#VmDisplayEndpoint describes how to reach one graphics channel (SPICE or VNC) of a running VM. Shared by candy/plugin-vm's own DisplayEndpoint (a running-VM's resolved endpoint, kind="spice"| "vnc") and charly-core's decode of the resolve-spice/resolve-vnc RPC reply — ONE shape, R3.
type VmInstaller ¶ added in v0.2026241.2320
type VmInstaller struct {
Username string `yaml:"username,omitempty" json:"username,omitempty"`
Full_name string `yaml:"full_name,omitempty" json:"full_name,omitempty"`
Email string `yaml:"email,omitempty" json:"email,omitempty"`
// password is PLAINTEXT. The renderer crypt()s it before it reaches any template,
// so the plaintext exists only in memory and never lands on the answers volume.
Password string `yaml:"password,omitempty" json:"password,omitempty"`
Password_hash string `yaml:"password_hash,omitempty" json:"password_hash,omitempty"`
// disk is the target block device the installer wipes. It is the guest's view
// (/dev/vda), not a host path.
Disk string `yaml:"disk,omitempty" json:"disk,omitempty"`
// encrypt requests full-disk encryption. NOTE: an encrypted unattended install is
// not fully unattended — someone still types the LUKS passphrase at first boot —
// and the passphrase is written in PLAINTEXT onto the answers volume, which makes
// that volume a secret. Source it from the secrets backend, never a committed
// literal.
Encrypt bool `yaml:"encrypt,omitempty" json:"encrypt,omitempty"`
Keyboard string `yaml:"keyboard,omitempty" json:"keyboard,omitempty"`
Timezone string `yaml:"timezone,omitempty" json:"timezone,omitempty"`
Locale string `yaml:"locale,omitempty" json:"locale,omitempty"`
Hostname string `yaml:"hostname,omitempty" json:"hostname,omitempty"`
// distro whose installer honours it this is what makes the guest reachable at all
// (a stock Omarchy install ships openssh with the service disabled and the port
// closed), so charly defaults it to the public half of the per-VM generated key.
Ssh_authorized_key []string `yaml:"ssh_authorized_key,omitempty" json:"ssh_authorized_key,omitempty"`
// defer_provisioning installs with NO personal details, leaving the first person
// to boot the machine to create their own user — the imaging-rig mode. Mutually
// exclusive with the credential fields; enforced in OpValidate.
Defer_provisioning bool `yaml:"defer_provisioning,omitempty" json:"defer_provisioning,omitempty"`
// answer carries per-distro extras the common fields do not model (a Tailscale
// auth key, a proxy, a licence). Typed-open by design.
Answer map[string]string `yaml:"answer,omitempty" json:"answer,omitempty"`
}
#VmInstaller — the unattended-install ANSWERS for a `source.kind: iso` VM.
Distro-AGNOSTIC on purpose: every field here is a question EVERY installer asks. Distro-SPECIFIC extras go in `answer:` (typed-open — a distro's installer vocabulary is that distro's business, not vm.cue's), reachable from a #DistroInstaller template as {{index .Answers "key"}}. The FORMAT that renders these into files lives on the distro (#DistroInstaller), exactly as `init:` renders `service:`: archinstall JSON, kickstart, preseed, Subiquity autoinstall and AutoYaST are five real formats, each keyed by distro and by nothing else.
password ⊻ password_hash is enforced by the vm kind's OWN OpValidate, not by CUE — the same reason validateSourceDistro lives there: the host's value gate is closedness-only by design, and a disjunction over two optional strings cannot express "exactly one present".
type VmKeyInjection ¶
type VmKeyInjectionResolved ¶
type VmKeyInjectionResolved struct {
SMBIOS bool `yaml:"smbios" json:"smbios"`
CloudInit bool `yaml:"cloud_init" json:"cloud_init"`
}
VmKeyInjectionResolved is the effective key-injection state. Source: charly/deploy.go VmKeyInjectionResolved.
type VmNetwork ¶
type VmNetwork struct {
Model string `yaml:"model,omitempty" json:"model,omitempty"`
Mode string `yaml:"mode,omitempty" json:"mode"`
Bridge string `yaml:"bridge,omitempty" json:"bridge,omitempty"`
MAC string `yaml:"mac,omitempty" json:"mac,omitempty"`
// Each entry is "<host>:<guest>". The host side may be a fixed port OR the
// literal `auto` sentinel (matching the pod `port: [auto]` word) — `auto`
// auto-allocates a free host port at vm-create (persisted in vm_state,
// reused across the create→deploy-add sequence), the sibling of ssh.port_auto.
PortForwards []string `yaml:"port_forwards,omitempty" json:"port_forwards,omitempty"`
}
type VmPluginEnv ¶
type VmPluginEnv struct {
VmOp string `yaml:"vm_op,omitempty" json:"vm_op"`
VmName string `yaml:"vm_name,omitempty" json:"vm_name,omitempty"`
URI string `yaml:"uri,omitempty" json:"uri,omitempty"`
Force bool `yaml:"force,omitempty" json:"force,omitempty"`
DeleteDisk bool `yaml:"delete_disk,omitempty" json:"delete_disk,omitempty"`
Snap *VmSnapInternalReq `yaml:"snap,omitempty" json:"snap,omitempty"`
}
#VmPluginEnv is the host→plugin env for an internal VM-resolution RPC (domain-state/ list-domains/resolve-spice/resolve-vnc/snapshot-internal). Matches the SUBSET of candy/plugin-vm's own (richer) internal vmEnv decode struct that charly-core's dispatch actually sends — plugin-vm's vmEnv carries an ADDITIONAL `create` field (the `charly vm create` CLI's own in-process construction, never sent by charly-core), which stays a plugin-local concern outside this shared def (containing *VmSpec/VmRuntimeParams, neither CUE-sourced yet). (A former `state_dir` field served ONLY the "qemu-shutdown" op, whose sole sender — charly/vm_qemu_client.go — was deleted in FLOOR-SLIM-proper Unit-8B alongside the arbiter's direct-QEMU stop path; the field was removed in the same cutover, confirmed zero remaining setters via `git grep 'VmPluginEnv{'` across charly/candy/sdk.)
type VmResolveInput ¶
type VmResolveInput struct {
Vm RawBody `yaml:"vm,omitempty" json:"vm"`
}
#VmResolveInput carries one opaque vm template body to project (Cutover L).
type VmResolveReply ¶
type VmResolveReply struct {
Resolved *ResolvedVm `yaml:"resolved,omitempty" json:"resolved,omitempty"`
}
#VmResolveReply wraps the resolved vm value envelope (#ResolvedVm lives in vm.cue).
type VmResolveResult ¶
type VmResolveResult struct {
Endpoint VmDisplayEndpoint `yaml:"endpoint,omitempty" json:"endpoint,omitempty"`
Error string `yaml:"error,omitempty" json:"error,omitempty"`
TunnelTarget string `yaml:"tunnel_target,omitempty" json:"tunnel_target,omitempty"`
}
#VmResolveResult decodes a resolve-spice/resolve-vnc reply.
type VmSnapInternalReq ¶
type VmSnapInternalReq struct {
SnapOp string `yaml:"snap_op,omitempty" json:"snap_op"`
VmName string `yaml:"vm_name,omitempty" json:"vm_name"`
Opts *VmSnapshotCreateOpts `yaml:"opts,omitempty" json:"opts,omitempty"`
Entry *VmSnapshotEntry `yaml:"entry,omitempty" json:"entry,omitempty"`
OutPath string `yaml:"out_path,omitempty" json:"out_path,omitempty"`
}
#VmSnapInternalReq is the snapshot-internal op payload (create/delete/revert/promote/ create-external/delete-external/revert-external), threaded via #VmPluginEnv.snap.
type VmSnapshot ¶
type VmSnapshot struct {
Name string `yaml:"name,omitempty" json:"name"`
Description string `yaml:"description,omitempty" json:"description,omitempty"`
Mode string `yaml:"mode,omitempty" json:"mode,omitempty"`
Quiesce bool `yaml:"quiesce,omitempty" json:"quiesce,omitempty"`
From string `yaml:"from,omitempty" json:"from,omitempty"`
}
type VmSnapshotCreateOpts ¶
type VmSnapshotCreateOpts struct {
VmName string `yaml:"vm_name,omitempty" json:"vm_name"`
SnapName string `yaml:"snap_name,omitempty" json:"snap_name"`
Mode string `yaml:"mode,omitempty" json:"mode,omitempty"`
Description string `yaml:"description,omitempty" json:"description,omitempty"`
Quiesce bool `yaml:"quiesce,omitempty" json:"quiesce,omitempty"`
LibvirtBackend string `yaml:"libvirt_backend,omitempty" json:"libvirt_backend,omitempty"`
}
#VmSnapshotCreateOpts parameterizes the creation of a snapshot — the host-resolved payload for the snapshot-internal "create"/"create-external" ops.
type VmSnapshotEntry ¶
type VmSnapshotEntry struct {
Name string `yaml:"name,omitempty" json:"name"`
Mode string `yaml:"mode,omitempty" json:"mode"`
LibvirtName string `yaml:"libvirt_name,omitempty" json:"libvirt_name,omitempty"`
DiskPath string `yaml:"disk_path,omitempty" json:"disk_path,omitempty"`
Description string `yaml:"description,omitempty" json:"description,omitempty"`
Created string `yaml:"created,omitempty" json:"created,omitempty"`
Parent string `yaml:"parent,omitempty" json:"parent,omitempty"`
Refcount int `yaml:"refcount,omitempty" json:"refcount"`
Quiesced bool `yaml:"quiesced,omitempty" json:"quiesced,omitempty"`
}
#VmSnapshotEntry is one snapshot record (mirrors sdk/vmshared's SnapshotEntry, the on-disk registry shape) — carried on delete/revert/promote ops so the plugin's go-libvirt call has the full record without re-reading the host-side registry itself.
type VmSnapshotPolicy ¶ added in v0.2026244.920
type VmSnapshotPolicy struct {
OnFinalize string `yaml:"on_finalize,omitempty" json:"on_finalize,omitempty"`
ResetBeforeCheck string `yaml:"reset_before_check,omitempty" json:"reset_before_check,omitempty"`
Mode string `yaml:"mode,omitempty" json:"mode,omitempty"`
Consistent bool `yaml:"consistent,omitempty" json:"consistent,omitempty"`
KeepVenue bool `yaml:"keep_venue,omitempty" json:"keep_venue,omitempty"`
}
#VmSnapshotPolicy — the check-bed snapshot-anchoring policy (§5.3.1). Extends the existing from_snapshot: (boot from a backing chain) with capture-and-reset semantics for check runs: capture at install finalize (on_finalize), revert before every check run (reset_before_check), optionally quiesced (consistent), internal or external mode, and keep_venue for batch loops.
type VmSnapshotState ¶
type VmSnapshotState struct {
Name string `yaml:"name" json:"name"`
Mode string `yaml:"mode" json:"mode"`
LibvirtName string `yaml:"libvirt_name,omitempty" json:"libvirt_name,omitempty"`
DiskPath string `yaml:"disk_path,omitempty" json:"disk_path,omitempty"`
Description string `yaml:"description,omitempty" json:"description,omitempty"`
Created string `yaml:"created,omitempty" json:"created,omitempty"`
Parent string `yaml:"parent,omitempty" json:"parent,omitempty"`
Refcount int `yaml:"refcount" json:"refcount"`
}
VmSnapshotState mirrors one snapshot in the charly.yml vm_state record. Source: charly/deploy.go VmSnapshotState.
type VmSource ¶
type VmSource struct {
Kind string `yaml:"kind" json:"kind"`
URL string `yaml:"url,omitempty" json:"url,omitempty"`
Checksum VmChecksum `yaml:"checksum,omitempty" json:"checksum,omitempty"`
Cache string `yaml:"cache,omitempty" json:"cache,omitempty"`
BaseUser string `yaml:"base_user,omitempty" json:"base_user,omitempty"`
Box string `yaml:"box,omitempty" json:"box,omitempty"`
Transport string `yaml:"transport,omitempty" json:"transport,omitempty"`
Rootfs string `yaml:"rootfs,omitempty" json:"rootfs,omitempty"`
RootSize string `yaml:"root_size,omitempty" json:"root_size,omitempty"`
KernelArgs string `yaml:"kernel_args,omitempty" json:"kernel_args,omitempty"`
FromVm string `yaml:"from_vm,omitempty" json:"from_vm,omitempty"`
FromSnapshot string `yaml:"from_snapshot,omitempty" json:"from_snapshot,omitempty"`
CloudInitClean bool `yaml:"cloud_init_clean,omitempty" json:"cloud_init_clean,omitempty"`
LibvirtName string `yaml:"libvirt_name,omitempty" json:"libvirt_name,omitempty"`
DiskPath string `yaml:"disk_path,omitempty" json:"disk_path,omitempty"`
DiskFormat string `yaml:"disk_format,omitempty" json:"disk_format,omitempty"`
AdoptedAt string `yaml:"adopted_at,omitempty" json:"adopted_at,omitempty"`
LastSyncedAt string `yaml:"last_synced_at,omitempty" json:"last_synced_at,omitempty"`
Builder string `yaml:"builder,omitempty" json:"builder,omitempty"`
BuilderImage string `yaml:"builder_image,omitempty" json:"builder_image,omitempty"`
Distro string `yaml:"distro,omitempty" json:"distro,omitempty"`
Package []string `yaml:"package,omitempty" json:"package,omitempty"`
BootstrapArch string `yaml:"bootstrap_arch,omitempty" json:"bootstrap_arch,omitempty"`
BootstrapVariant string `yaml:"bootstrap_variant,omitempty" json:"bootstrap_variant,omitempty"`
// iso arm. Installer carries the unattended-install ANSWERS; the distro owns the
// FORMAT that renders them (Distro.Installer / #DistroInstaller).
Installer *VmInstaller `yaml:"installer,omitempty" json:"installer,omitempty"`
}
VmSource is the discriminated-union source for a VM disk image (Kind selects the active branch: cloud_image / bootc / clone / imported / bootstrap).
type VmSsh ¶ added in v0.2026232.520
type VmSsh struct {
User string `yaml:"user,omitempty" json:"user,omitempty"`
Port int `yaml:"port,omitempty" json:"port,omitempty"`
PortAuto bool `yaml:"port_auto,omitempty" json:"port_auto,omitempty"`
KeySource string `yaml:"key_source,omitempty" json:"key_source,omitempty"`
KeyInjection *VmKeyInjection `yaml:"key_injection,omitempty" json:"key_injection,omitempty"`
}
VmSsh is the guest SSH access config (port XOR port_auto is a Go cross-field rule). KeyInjection references the generated VmKeyInjection.
type VmVariant ¶ added in v0.2026244.920
type VmVariant struct {
Cpus int `yaml:"cpus,omitempty" json:"cpus,omitempty"`
Memory VmSize `yaml:"memory,omitempty" json:"memory,omitempty"`
Video string `yaml:"video,omitempty" json:"video,omitempty"`
Gpu *struct {
Hostdev *string `yaml:"hostdev,omitempty" json:"hostdev,omitempty"`
Vendor *string `yaml:"vendor,omitempty" json:"vendor,omitempty"`
} `yaml:"gpu,omitempty" json:"gpu,omitempty"`
Display string `yaml:"display,omitempty" json:"display,omitempty"`
Devices []string `yaml:"devices,omitempty" json:"devices,omitempty"`
Attachments []string `yaml:"attachments,omitempty" json:"attachments,omitempty"`
}
#VmVariant — a named VM-config override that boots the SAME golden disk with a different shape. Only VM-shape fields are legal (cpus/memory/video/gpu/ display/devices/attachments); any change to source: or disk identity is rejected at validate time, because the disk comes exclusively from the shared snapshot chain.
type VolumeMount ¶
type VolumeMount struct {
VolumeName string `yaml:"volume_name,omitempty" json:"volume_name,omitempty"`
ContainerPath string `yaml:"container_path,omitempty" json:"container_path,omitempty"`
}
#VolumeMount — a resolved named-volume mount (charly-<deploy>-<name> → container path). In-memory only (never marshaled) → tags wire-irrelevant. deploykit.VolumeMount aliases this.
type WalkSeams ¶
type WalkSeams struct {
// Parser is the per-document parse (the host passes the registered DocParser).
Parser DocParser
// Boundary runs at each PROJECT boundary (the root file AND each namespace root) BEFORE that
// boundary's documents parse: the host does the parse pre-scan + connect-declared-kind-plugins
// side effects (registry mutation). data = the boundary file bytes.
Boundary func(dir string, data []byte) error
// Threaded returns the current registry-derived kind-recognition snapshot. Called fresh per
// document parse (the host's loaderThreaded()).
Threaded func() Threaded
// ResolveRef resolves an import ref (local path OR remote "@host/org/repo[/sub]:ver") to a
// stable cache KEY + a concrete on-disk PATH. The host owns remote fetch + cache + auto-migration.
ResolveRef func(ref, baseDir string) (key, path string, err error)
// GateDoc runs the host #NodeDoc CUE validate-before-execute gate on one raw document's bytes.
GateDoc func(label string, raw []byte) error
// RepoIdentity returns the canonical repo identity of an import ref (for the cycle-break), or ""
// (the host's nsRepoIdentity). Empty → version-keyed fallback.
RepoIdentity func(ref, baseDir string) string
}
WalkSeams is the set of host-supplied callbacks the whole-project WALK needs for everything registry-coupled or host-coupled — the host builds this value and hands it to the registered ProjectWalker; the walk mechanism (sdk/loaderkit.Walk) calls each seam and never does the coupled work itself (boundary law clause D: the walk consults host-threaded DATA/mechanisms, never the provider registry directly).
Source Files
¶
- arbiter_consts.go
- box_name.go
- build_resolved_box.go
- builder_config_methods.go
- builder_preresolved.go
- builder_resolve.go
- buildwire_render.go
- cache_mount_render.go
- calver.go
- candy_capabilities.go
- candy_reader.go
- candy_ref_entry.go
- candy_vocab.go
- charly_methods.go
- charly_names.go
- checkcontext.go
- checklevel.go
- cloud_init_runtime_params.go
- config.go
- container_name.go
- cue_types_gen.go
- deploy_consts.go
- deploy_executor.go
- deploy_key.go
- deploy_methods.go
- deploy_target_unified.go
- deploy_tree_validate.go
- deployconfig.go
- descent.go
- distro_config_methods.go
- distro_methods.go
- enc_consts.go
- env_path.go
- externalized_builders.go
- fleet_config.go
- fleet_keys.go
- gpu_consts.go
- gpu_methods.go
- gpu_select.go
- hand_state_types.go
- host_context.go
- image_errors.go
- init_config_methods.go
- injection_seams.go
- install_plan.go
- install_plan_view.go
- install_step.go
- install_step_view.go
- install_step_vocab.go
- ir_enums.go
- label_consts.go
- labelset_methods.go
- layout_paths.go
- ledger_records.go
- libvirt_uri.go
- load_directives.go
- loader_seam.go
- loadmodel.go
- materialize_project_seams.go
- mcp_provides.go
- merge_plugin_kinds.go
- namespace_ref.go
- node_helpers.go
- op_input.go
- phase_template.go
- port_mapping.go
- preview.go
- provider_carriers.go
- quadlet_names.go
- quadlet_paths.go
- ref_parse.go
- remote_download.go
- repo_identity.go
- resolve_opts.go
- resolved_box.go
- scalar_aliases.go
- scan_seams.go
- service_render_context.go
- sidecar_naming.go
- ssh_target.go
- status_result.go
- status_types.go
- substrate_template_methods.go
- token_set.go
- tunnel_methods.go
- tunnel_resolve.go
- union_types.go
- verb_context.go
- version_gen.go
- vm_box_labels.go
- vm_domain.go
- vocab_gen.go
- vocab_project.go
- wrap_container.go
- yaml_version_line.go