kit

package
v0.2026218.157 Latest Latest
Warning

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

Go to latest
Published: Aug 6, 2026 License: MIT Imports: 37 Imported by: 0

Documentation

Overview

Package kit is the importable contract a HOST-COUPLED plugin candy implements to run against charly's live check engine — the seam that lets a check verb whose logic needs the running deployment (exec-in-container, host TCP dial, host-vantage HTTP) live in its own candy module instead of charly's module.

A host-coupled verb candy implements CheckVerbProvider; charly runs it in EITHER placement, invisibly above the registry: IN-PROCESS (compiled-in — charly passes the live *Runner as a CheckContext) OR OUT-OF-PROCESS (the CheckContext legs are served back to the candy over the host's reverse channel — ExecutorService for Exec + CheckContextService for HTTPDo/AddBackground, F2 — and the scalar legs ride the env_json snapshot). RunVerb is identical in both. This package imports only the stdlib + charly/spec (the generated param/Op types), so a candy module can import it without pulling charly's package main.

Index

Constants

View Source
const (
	// StepKindServicePackaged — the `service` verb (enable a packaged unit; load-bearing reversals).
	StepKindServicePackaged = checkstep.StepKindServicePackaged
	// StepKindSystemPackages — the `package` verb (install system packages).
	StepKindSystemPackages = checkstep.StepKindSystemPackages
)
View Source
const (
	// CheckLevelNone — skip acceptance entirely.
	CheckLevelNone = spec.CheckLevelNone
	// CheckLevelBuild — build-context ops only (charly check box).
	CheckLevelBuild = spec.CheckLevelBuild
	// CheckLevelNoAgent — build + deploy + runtime act/assert, NO do: instruct (default).
	CheckLevelNoAgent = spec.CheckLevelNoAgent
	// CheckLevelAgent — also run do: instruct steps through the agent grader.
	CheckLevelAgent = spec.CheckLevelAgent
)
View Source
const (
	ScopeSystem      = spec.ScopeSystem
	ScopeUser        = spec.ScopeUser
	ScopeUserProfile = spec.ScopeUserProfile

	ReverseOpPackageRemove  = spec.ReverseOpPackageRemove
	ReverseOpCargoUninstall = spec.ReverseOpCargoUninstall
	ReverseOpNpmUninstallG  = spec.ReverseOpNpmUninstallG
	ReverseOpPixiEnvRemove  = spec.ReverseOpPixiEnvRemove
	ReverseOpRmFileSystem   = spec.ReverseOpRmFileSystem
	ReverseOpRmFileUser     = spec.ReverseOpRmFileUser
	ReverseOpRmDirRecursive = spec.ReverseOpRmDirRecursive
	ReverseOpServiceDisable = spec.ReverseOpServiceDisable
	ReverseOpServiceRemove  = spec.ReverseOpServiceRemove
	ReverseOpRemoveDropin   = spec.ReverseOpRemoveDropin
	ReverseOpRestoreEnabled = spec.ReverseOpRestoreEnabled
	ReverseOpRemoveManaged  = spec.ReverseOpRemoveManaged
	ReverseOpRemoveEnvdFile = spec.ReverseOpRemoveEnvdFile
	ReverseOpRemoveRepoFile = spec.ReverseOpRemoveRepoFile
	ReverseOpCoprDisable    = spec.ReverseOpCoprDisable
	ReverseOpPluginScript   = spec.ReverseOpPluginScript
)
View Source
const (
	VenueLocal              = exec.VenueLocal
	SignalKillErrMarker     = exec.SignalKillErrMarker
	PodmanInfraExitCode     = exec.PodmanInfraExitCode
	ContainerInfraErrMarker = exec.ContainerInfraErrMarker

	JumpPodmanExec   = exec.JumpPodmanExec
	JumpDockerExec   = exec.JumpDockerExec
	JumpSSH          = exec.JumpSSH
	JumpVirshConsole = exec.JumpVirshConsole

	CharlyInstallAuto = exec.CharlyInstallAuto
	CharlyInstallScp  = exec.CharlyInstallScp
	CharlyInstallSkip = exec.CharlyInstallSkip
)
View Source
const (
	// ModeLive — `charly check live`, against a running container/VM (in-container probes).
	ModeLive = spec.CheckModeLive
	// ModeBox — `charly check box`, against a disposable build container.
	ModeBox = spec.CheckModeBox
)
View Source
const (
	StatusPass = spec.StatusPass
	StatusFail = spec.StatusFail
	StatusSkip = spec.StatusSkip
)
View Source
const (
	// DocShapeEmpty — a scalar-null / empty mapping document (nothing to load).
	DocShapeEmpty = spec.DocShapeEmpty
	// DocShapeNode — the unified name-first node-form. See spec.DocShapeNode.
	DocShapeNode = spec.DocShapeNode
)
View Source
const (
	DefaultBoxDir   = spec.DefaultBoxDir   // discovered box/<name>/ directory
	DefaultCandyDir = spec.DefaultCandyDir // discovered candy/<name>/ directory
)

Layout path constants shared by core and kit. The canonical values moved to spec (spec.DefaultBoxDir / spec.DefaultCandyDir, #55 import-purity cone-render) — 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 (R3, one source).

View Source
const (
	KwRun        spec.StepKeyword = "run"
	KwCheck      spec.StepKeyword = "check"
	KwAgentRun   spec.StepKeyword = "agent-run"
	KwAgentCheck spec.StepKeyword = "agent-check"
	KwInclude    spec.StepKeyword = "include"
)
View Source
const CharlyNetworkName = "charly"

CharlyNetworkName is the shared bridge network used by all charly containers.

View Source
const DefaultCheckLevel = spec.DefaultCheckLevel

DefaultCheckLevel is the rung applied when a box declares no check_level.

View Source
const DeployConfigEnv = spec.DeployConfigEnv

deployconfig.go — re-export of the per-host deploy-overlay path resolver, RELOCATED to spec/spec/deployconfig.go (#55 value extraction). A pure host-path resolver over the deploy-config E-envelope, so it homes in spec; kit re-exports it here so existing kit.DeployConfigEnv / kit.DefaultDeployConfigPath call sites (plugins + sdk) are untouched. New consumers should reference spec.DeployConfigEnv / spec.DefaultDeployConfigPath.

View Source
const GraderDefaultTimeout = 5 * time.Minute

GraderDefaultTimeout bounds a single grader invocation when neither the `charly check feature run --timeout` flag nor the AI entry's own `timeout:` is set. Unlike the plateau-bounded harness loop, a grader call MUST be wall-clock-bounded so one stuck prose step can't hang an acceptance run.

View Source
const HostVar = "HOST"

HostVar is the cross-member address variable name — ${HOST:<member>} (+ optional :port). An unresolved one means the member is UNREACHABLE (a real failure, never a skip).

View Source
const LedgerSchemaVersion = spec.LedgerSchemaVersion

LedgerSchemaVersion is the install-ledger record format version, DECOUPLED from the project schema HEAD so a non-ledger cutover never invalidates a migrated ledger. Read by core's ledger path (ReadDeployRecord/ReadCandyRecord, which hard-reject a record lacking this stamp). Core aliases via `const … = kit.LedgerSchemaVersion`. SPIKE (value-type relocation, #55 cluster 4): relocated to spec.LedgerSchemaVersion.

Variables

View Source
var (
	ResolveApkPath      = checkhost.ResolveApkPath
	ResolveCommittedApk = checkhost.ResolveCommittedApk
)

apk_path.go — ResolveApkPath + ResolveCommittedApk RELOCATED to the spec fabric slice github.com/opencharly/spec/checkhost (#55 CHECK-ENGINE cone Option A — the committed-APK candy-source anchoring the adb:/appium: check verbs + candy/plugin-adb's deploy:android collector share; a pure filesystem walk-up, homed in the check-host fabric slice so charly core reaches it importing zero kit). kit re-exports so existing kit.ResolveApkPath / kit.ResolveCommittedApk call sites (charly + candy/plugin-adb) are untouched.

View Source
var (
	ParseCalVer         = spec.ParseCalVer
	MustCalVer          = spec.MustCalVer
	LatestSchemaVersion = spec.LatestSchemaCalVer
	SchemaFloor         = spec.SchemaFloorCalVer
)
View Source
var (
	ContainerName         = spec.ContainerName
	ContainerNameInstance = spec.ContainerNameInstance
)
View Source
var (
	StampDescent      = spec.StampDescent
	DescentFromTraits = spec.DescentFromTraits
)

descent.go — re-export of the descent stamper, RELOCATED to spec/spec/descent.go (#55 fabric-primitive/value extraction). StampDescent/DescentFromTraits are pure #Deploy-tree value transforms over the E-envelope, so they home in spec; kit re-exports them here so every existing kit.StampDescent / kit.DescentFromTraits call site (plugins + sdk) is untouched. New consumers should reference spec.StampDescent / spec.DescentFromTraits.

View Source
var (
	EngineBinary = container.EngineBinary
	GPURunArgs   = container.GPURunArgs
	DetectEngine = container.DetectEngine
)

engine.go — re-export of the container-engine helpers, RELOCATED to the spec/container fabric slice (#55 fabric-primitive extraction). EngineBinary/GPURunArgs/DetectEngine are host engine-resolution primitives (DetectEngine shells `LookPath`), homed in spec/container which carries os/exec in its own slice (Rule 2). kit re-exports them here so kit's own callers (container_image.go/container_probe.go/runtime_config.go's ResolveRuntime) and every existing kit.EngineBinary / kit.GPURunArgs / kit.DetectEngine call site are untouched.

View Source
var (
	LoadProcessDotenv = hostenv.LoadProcessDotenv
	ParseEnvFile      = hostenv.ParseEnvFile
	ParseEnvBytes     = hostenv.ParseEnvBytes
	LoadWorkspaceEnv  = hostenv.LoadWorkspaceEnv
	ResolveEnvVars    = hostenv.ResolveEnvVars
	EnrichNoProxy     = hostenv.EnrichNoProxy
	DotenvLoaded      = hostenv.DotenvLoaded
)
View Source
var (
	RunCaptureCmd       = exec.RunCaptureCmd
	NestedContainerName = exec.NestedContainerName

	WaitForSSH         = exec.WaitForSSH
	WaitForCloudInit   = exec.WaitForCloudInit
	WaitForPackageLock = exec.WaitForPackageLock

	VenueFromDescriptor          = exec.VenueFromDescriptor
	ContainerChainFromDescriptor = exec.ContainerChainFromDescriptor
	DescriptorFromExecutor       = exec.DescriptorFromExecutor

	ResolveCharlyInstallStrategy = exec.ResolveCharlyInstallStrategy
	HostCharlyIsNewer            = exec.HostCharlyIsNewer
	EnsureCharlyInDeployVenue    = exec.EnsureCharlyInDeployVenue
	EnsureCharlyInGuest          = exec.EnsureCharlyInGuest

	BuilderRun          = exec.BuilderRun
	BuildBuilderRunArgs = exec.BuildBuilderRunArgs
	UserScopeBindMounts = exec.UserScopeBindMounts
	UserScopeEnv        = exec.UserScopeEnv

	ClassifyContainerInfraFailure = exec.ClassifyContainerInfraFailure
	IsContainerInfraResult        = exec.IsContainerInfraResult
	ContainerInfraError           = exec.ContainerInfraError
)
View Source
var (
	ErrLockBusy              = lock.ErrLockBusy
	AcquireFileLock          = lock.AcquireFileLock
	ImageBuildLockPath       = lock.ImageBuildLockPath
	AcquireImageBuildLock    = lock.AcquireImageBuildLock
	AcquireLocalPkgBuildLock = lock.AcquireLocalPkgBuildLock
)

filelock.go — re-export of the advisory-flock primitive, RELOCATED to the spec/lock fabric slice (#55 fabric-primitive extraction). A file lock is a host primitive a plugin cannot hold, so it homes in spec/lock (carrying syscall in its own slice, Rule 2); kit re-exports the symbols here so the compiled-in candy/plugin-preempt and every existing kit.AcquireFileLock / kit.ErrLockBusy / … call site are untouched. New consumers should import spec/lock directly.

View Source
var (
	StripPortSuffix   = spec.StripPortSuffix
	ParsePortMapping  = spec.ParsePortMapping
	FormatPortMapping = spec.FormatPortMapping
)
View Source
var (
	ServiceName                    = spec.ServiceName
	ServiceNameInstance            = spec.ServiceNameInstance
	QuadletFilename                = spec.QuadletFilename
	QuadletFilenameInstance        = spec.QuadletFilenameInstance
	PodQuadletFilename             = spec.PodQuadletFilename
	PodQuadletFilenameInstance     = spec.PodQuadletFilenameInstance
	SidecarQuadletFilename         = spec.SidecarQuadletFilename
	SidecarQuadletFilenameInstance = spec.SidecarQuadletFilenameInstance

	QuadletDir            = spec.QuadletDir
	SystemdUserDir        = spec.SystemdUserDir
	QuadletExists         = spec.QuadletExists
	QuadletExistsInstance = spec.QuadletExistsInstance
)
View Source
var (
	RepoGitURL       = refs.RepoGitURL       // sdk/loaderkit
	GitDefaultBranch = refs.GitDefaultBranch // sdk/loaderkit
	CompareSemver    = refs.CompareSemver    // sdk/loaderkit + candy/plugin-box
	DownloadRepo     = refs.DownloadRepo     // kit refs_downloader.go DefaultDownloader
)

refs_git.go — the kit tail of the remote-repo GIT primitives (P7). The pure git-exec + string primitives + cache-path helpers RELOCATED to the spec/refs fabric slice (#55 fabric-primitive extraction); charly core reaches them via spec/refs directly. This file keeps ONLY:

  • DiscoverRemoteCandy, which needs the kit-local layout constant DefaultCandyDir; and
  • thin re-export shims for the primitives with a remaining sdk/candy caller (sdk/loaderkit + candy/plugin-refs's DefaultDownloader), so those keep compiling unchanged. The shims are tracked residue that die when loaderkit + plugin-refs migrate onto spec/refs in their cones.
View Source
var (
	StripURLScheme   = spec.StripURLScheme
	IsRemoteImageRef = spec.IsRemoteImageRef
	ParseRemoteRef   = spec.ParseRemoteRef
	ResolveBoxName   = spec.ResolveBoxName
)
View Source
var (
	LoadRuntimeConfig           = hostenv.LoadRuntimeConfig
	SaveRuntimeConfig           = hostenv.SaveRuntimeConfig
	ResolveRuntime              = hostenv.ResolveRuntime
	ResolveValue                = hostenv.ResolveValue
	ValidateEngine              = hostenv.ValidateEngine
	ValidateRunMode             = hostenv.ValidateRunMode
	DetectRunMode               = hostenv.DetectRunMode
	SystemdUserAvailable        = hostenv.SystemdUserAvailable
	ValidateBindAddress         = hostenv.ValidateBindAddress
	ResolveEncryptedStoragePath = hostenv.ResolveEncryptedStoragePath
	ResolveVolumesPath          = hostenv.ResolveVolumesPath
	ExpandHostHome              = hostenv.ExpandHostHome
	ResolveAutoEnable           = hostenv.ResolveAutoEnable
)
View Source
var (
	PodName                      = spec.PodName
	PodNameInstance              = spec.PodNameInstance
	SidecarContainerName         = spec.SidecarContainerName
	SidecarContainerNameInstance = spec.SidecarContainerNameInstance
)
View Source
var AcquireVmDomainLock = lock.AcquireVmDomainLock

AcquireVmDomainLock takes a BLOCKING, host-global advisory lock serializing every check bed that occupies the given libvirt domain. Re-exported from lock.AcquireVmDomainLock.

View Source
var AnchorScanSpecs = spec.AnchorScanSpecs

AnchorScanSpecs forwards to spec.AnchorScanSpecs — the ONE path-anchoring mechanism, now resident in the spec module. Kept as a var alias so kit's own walk.go caller stays terse and there is exactly one implementation.

View Source
var BedVmDomains = lock.BedVmDomains

BedVmDomains returns the sorted, deduped libvirt domain names (charly-<from>) a bed's VM(s) occupy. Re-exported from lock.BedVmDomains (the body lives there).

View Source
var CheckLevelReaches = spec.CheckLevelReaches

CheckLevelReaches reports whether a box resolved to `have` runs at least as deep as `want`.

View Source
var ClassifyDoc = spec.ClassifyDoc

ClassifyDoc inspects a document's top level and returns its shape. See spec.ClassifyDoc.

View Source
var ClassifyStepFailures = report.ClassifyStepFailures

ClassifyStepFailures splits FAIL results into genuine check failures vs. container-setup INFRA failures. Re-exported from report.ClassifyStepFailures.

View Source
var CompareCalVer = spec.CompareCalVer

calver_compare.go — re-export of the lenient dotted-string CalVer comparator, RELOCATED to spec/spec/calver_parse.go (#55 value extraction). kit re-exports it so every existing kit.CompareCalVer call site (charly image-tag logic + sdk/deploykit render engine) is unchanged.

View Source
var ContainerExists = defaultContainerExists

ContainerExists reports whether a container exists (RUNNING OR STOPPED) — a bare `container inspect` succeeds for any existing container, so its exit status is the signal. R3 consolidation (K-wave 2 cone R2 bank C): the shared home for the token-identical copies that used to travel per module (candy/plugin-pod's `container exists` form + the new candy/plugin-bundle del-resolve probe); charly core keeps its OWN copy (import purity — charly/ imports only spec, and spec/container.EngineBinary is the allowed equivalent). Package-level var for testability (tests inject a stub, same pattern as ContainerRunning).

View Source
var ContainerImage = container.ContainerImage

ContainerImage returns the image ref for a running container, best-effort ("" on error). Re-exported from container.ContainerImage.

View Source
var ContainerImageRef = container.ContainerImageRef

ContainerImageRef returns the image ref backing a running container (.Config.Image via `<engine> inspect`). Re-exported from container.ContainerImageRef (the body lives there).

View Source
var ContainerRunning = defaultContainerRunning

ContainerRunning reports whether a container is running. Package-level var for testability (tests inject a stub, same pattern as EnsureCharlyNetwork/InspectLabels).

View Source
var CurrentCharlyExecutable = os.Executable

CurrentCharlyExecutable is the executable that owns the current check/deploy-verify run. Keeping it a package var (not a direct os.Executable() call) lets a caller's test prove the resolver contract uses the ACTIVE binary rather than a stale PATH selection.

View Source
var DefaultDeployConfigPath = spec.DefaultDeployConfigPath
View Source
var DirExists = spec.DirExists

DirExists reports whether path exists and is a directory. See spec.DirExists.

View Source
var DiscoverSkipDir = spec.DiscoverSkipDir

DiscoverSkipDir reports whether a directory name is a VCS or build-artifact dir that never contains a discoverable charly.yml manifest. See spec.DiscoverSkipDir.

View Source
var DoHTTPRequest = spec.DoHTTPRequest

DoHTTPRequest issues req from the HOST's network namespace. Re-exported from spec.DoHTTPRequest (the body lives there).

View Source
var EndpointForVenue = checkhost.EndpointForVenue
View Source
var EnsureCharlyNetwork = defaultEnsureCharlyNetwork

EnsureCharlyNetwork creates the "charly" network if it does not exist. It is a package-level var for testability.

View Source
var EnvMapToPairs = spec.EnvMapToPairs

EnvMapToPairs converts the deploy schema's env map into sorted KEY=VALUE pairs (the OCI-label wire + env-resolution chain form). RELOCATED to the spec/spec fabric slice (#55 coneB build-render cone, Class A — github.com/opencharly/spec/spec/env_pairs_coneb.go); re-exported here so every existing kit.EnvMapToPairs call site (sdk/deploykit's deploy_file.go + read_labels.go, kit/box_metadata.go) is unchanged. New consumers reference spec.EnvMapToPairs directly.

View Source
var ErrImageNotLocal = spec.ErrImageNotLocal

ErrImageNotLocal is the canonical image-not-in-local-storage sentinel (spec/spec/image_errors.go).

View Source
var ExpandPath = spec.ExpandPath

ExpandPath (a pure ~/${HOME}/$HOME path expander) now lives in spec (spec.ExpandPath, #55 import-purity cone-render); kit re-exports it via alias so every existing kit.ExpandPath call site (sdk) is untouched (R3, one source).

View Source
var ExtractCalVerTag = container.ExtractCalVerTag

ExtractCalVerTag returns the CalVer portion of a ref's tag, or "" if the tag is not a recognisable CalVer (`YYYY.DDD.HHMM`).

View Source
var ExtractMetadata = container.ExtractMetadata

ExtractMetadata reads OCI labels from a local image and returns parsed spec.BoxMetadata. Returns nil if the image has no ai.opencharly labels. Returns spec.ErrImageNotLocal wrapped with the image ref if the image is not in local storage.

View Source
var FileExists = spec.FileExists

FileExists reports whether path exists and is a regular (non-dir) file. See spec.FileExists.

View Source
var FindEntityDirs = spec.FindEntityDirs

FindEntityDirs walks a scan root and returns every directory that contains the given canonical filename. See spec.FindEntityDirs.

View Source
var FirstYAMLVersionLine = spec.FirstYAMLVersionLine

FirstYAMLVersionLine extracts the value of the first top-level `version:` line. RE-EXPORT shim: the body was RELOCATED to spec/spec (yaml_version_line.go, #55 coneG import-purity — charly core's refs.go inlines spec.FirstYAMLVersionLine, dropping its sdk/kit import). This var keeps the existing kit.FirstYAMLVersionLine plugin call sites (candy/plugin-migrate/engine.go) unchanged; new charly-core consumers reference spec.* directly. Pure string parsing (strings.SplitSeq + CutPrefix + TrimSpace), no yaml.v3.

View Source
var FormatHTTPHeaders = spec.FormatHTTPHeaders

FormatHTTPHeaders renders an http.Header into a "Key: value\n" blob. Re-exported from spec.FormatHTTPHeaders (the body lives there).

View Source
var FormatStepResultsJSON = report.FormatStepResultsJSON

FormatStepResultsJSON emits a structured JSON document. Re-exported from report.FormatStepResultsJSON.

View Source
var FormatStepResultsJUnit = report.FormatStepResultsJUnit

FormatStepResultsJUnit emits JUnit XML for CI dashboards. Re-exported from report.FormatStepResultsJUnit.

View Source
var FormatStepResultsTAP = report.FormatStepResultsTAP

FormatStepResultsTAP emits TAP v13. Re-exported from report.FormatStepResultsTAP.

View Source
var FormatStepResultsText = report.FormatStepResultsText

FormatStepResultsText emits a human-readable per-step report to w. Re-exported from report.FormatStepResultsText.

View Source
var HTTPClientFor = spec.HTTPClientFor

HTTPClientFor builds a per-request *http.Client honoring the HTTPRequest policy, derived from the engine's base client. Re-exported from spec.HTTPClientFor (the body lives there).

View Source
var InputStr = spec.InputStr

InputStr reads a string field from the step's desugared plugin input map — homed in the spec contract module (spec.InputStr, op_input.go, #55 CHECK-ENGINE cone) so charly core reads a step's per-verb input while importing only spec; re-exported here so the kit.InputStr / kit.Pos* call sites compile unchanged.

View Source
var InspectContainer = defaultInspectContainer

InspectContainer is swappable for tests. Real calls shell out to `<engine> inspect <name>` (which returns a one-element JSON array).

View Source
var InspectImageLabels = container.InspectImageLabels

InspectImageLabels reads a local image's OCI labels via engine inspect. RELOCATED to spec/container (coneA); re-exported here so every existing kit.InspectImageLabels call site is untouched.

View Source
var InspectLabels = container.InspectLabels

InspectLabels reads OCI labels from a local image via engine inspect. Package-level var for testability — re-exported from spec/container (the canonical home). Override container.InspectLabels to stub in tests.

View Source
var InstallDirAtomic = lock.InstallDirAtomic

InstallDirAtomic atomically installs the freshly-populated tmp directory as final.

View Source
var IsHostNetworked = container.IsHostNetworked

container_probe.go — the pure container-runtime host probes (K4: relocated from the deleted charly/container.go and charly/shell.go — genuinely pure `<engine>` shell-outs with no project-loader dependency). Consumed directly by candy/plugin-deploy-pod/plugin-pod/plugin-adb and by charly core's remaining caller (commands.go, the check harness), which import kit directly (K3 ZERO-ALIASES — no alias file); android_deploy_cmd.go and volume_cp_tags_cmd.go, former core callers, are both since deleted.

IsHostNetworked RELOCATED to the spec fabric slice github.com/opencharly/spec/container (#55 CHECK-ENGINE cone Option A — a podman-inspect probe, the slice's charter), re-exported below so kit.IsHostNetworked call sites are untouched.

View Source
var IsValidCheckLevel = spec.IsValidCheckLevel

IsValidCheckLevel reports whether level is one of the four canonical rungs.

View Source
var ListLocalImages = container.ListLocalImages

ListLocalImages returns all images in the engine's local storage.

View Source
var LocalImageExists = container.LocalImageExists

LocalImageExists checks whether an image reference exists in the given engine's local store. Package-level var for testability (same pattern as DetectGPU in gpu.go). RELOCATED to the spec/container fabric slice (#55 coneB build-render cone, Class A — co-located with the ResolveLocalImageRef family that reads it); re-exported here so every existing direct kit.LocalImageExists call site (candy/plugin-build, candy/plugin-box, candy/plugin-deploy-pod, candy/plugin-kube, charly core's host_build_pod_config_seams + ensure_image) is unchanged. Override container.LocalImageExists (the var container.ResolveLocalImageRef reads) to stub the resolution path in tests; this kit re-export var is a value-copy that no longer affects the relocated body.

View Source
var LooksLikeFullRef = container.LooksLikeFullRef

LooksLikeFullRef returns true if the image ref contains a registry segment (a "/" before any ":") — e.g. "ghcr.io/org/name:tag" — so it can be pulled without charly.yml resolution.

View Source
var MappingRoot = spec.MappingRoot

MappingRoot unwraps a YAML document node to its top-level mapping node (or nil). Relocated to sdk/spec (FLOOR-SLIM axis-A mechanical batch, zero logic change); aliased here so every existing kit.MappingRoot call site (charly core's former mappingRoot alias + the out-of-module candy/plugin-migrate engine) keeps compiling unchanged (R3).

View Source
var ParseLocalImagesJSON = container.ParseLocalImagesJSON

ParseLocalImagesJSON parses `{podman,docker} images --format json` output into ONE LocalImageInfo per distinct image ID.

View Source
var ParsePublishedPort = container.ParsePublishedPort

ParsePublishedPort RELOCATED to the spec fabric slice github.com/opencharly/spec/container (#55 CHECK-ENGINE cone Option A — a pure `podman port` output parser, the slice's charter), re-exported here so kit.ParsePublishedPort call sites are untouched.

View Source
var Plural = report.Plural

Plural returns "" for n==1 and "s" otherwise — the trivial English pluralization used by the result reporters and the eventually-retry summaries. Re-exported from report.Plural.

View Source
var ReportStepResults = report.ReportStepResults

ReportStepResults writes results in the requested format. Re-exported from report.ReportStepResults.

View Source
var ReportStepResultsCount = report.ReportStepResultsCount

ReportStepResultsCount renders results per format via ReportStepResults and returns how many results ended in a FAIL verdict. Re-exported from report.ReportStepResultsCount.

View Source
var ResolveCheckLevel = spec.ResolveCheckLevel

ResolveCheckLevel normalizes an authored check_level to a canonical rung, applying the default for the empty value.

View Source
var ResolveLocalImageRef = container.ResolveLocalImageRef

ResolveLocalImageRef resolves a user-supplied image reference against the engine's local storage — never reads charly.yml.

View Source
var ResolveNewestLocalCalVer = container.ResolveNewestLocalCalVer

ResolveNewestLocalCalVer is the canonical "find the newest local image for this short name" helper.

View Source
var ResolvePackageName = checkstep.ResolvePackageName

ResolvePackageName picks the correct package name for the running image's distro. Re-exported from checkstep.ResolvePackageName (the body lives there); the single cross-distro name resolver shared by the `package` candy's check + act AND the host's step materializer (R3).

View Source
var ResolveShellImageRef = container.ResolveShellImageRef

ResolveShellImageRef builds the full image reference from registry, name, and tag.

View Source
var RuntimeOnlyVarPrefixes = []string{
	"HOST_PORT",
	"VOLUME_PATH",
	"VOLUME_CONTAINER_PATH",
	"CONTAINER_IP",
	"CONTAINER_NAME",
	"INSTANCE",
	"ENV_",

	"STEP_ID",

	"VM_HOSTDEV_COUNT",

	"DEPLOY_NAME",

	"HOST",
}

RuntimeOnlyVarPrefixes lists variable name prefixes that are only resolvable against a running container. scope:"build" checks must not reference these.

View Source
var ScalarNode = spec.ScalarNode

ScalarNode builds a string scalar YAML node. Relocated to sdk/spec (FLOOR-SLIM axis-A mechanical batch, zero logic change) so charly core can call it without importing kit; aliased here so every existing kit.ScalarNode call site (candy/plugin-migrate + others) keeps compiling unchanged.

View Source
var SudoLocalImageExists = defaultSudoLocalImageExists

SudoLocalImageExists checks whether an image reference exists in the rootful (sudo podman) local store. Mirrors LocalImageExists but always queries the root user's storage namespace, regardless of the caller's BuildEngine. The rootless and rootful podman storage roots are isolated, so an image built by the user's `podman build` is invisible to `sudo podman` until transferred.

Package-level var for testability (same pattern as LocalImageExists).

View Source
var TestVarRefPattern = regexp.MustCompile(`\$\{([A-Z_][A-Z0-9_]*)(?::([^}]+))?\}`)

TestVarRefPattern matches a ${NAME} or ${NAME:arg} reference (uppercase-underscore names).

View Source
var TransferImage = container.TransferImage

TransferImage pipes an image from one engine to another via save | load. RELOCATED to the spec/container fabric slice (#55 coneC — charly/ off sdk/kit, co-located with the EngineBinary + LocalImageExists family this transfer path complements); re-exported here so every existing direct kit.TransferImage call site (candy/plugin-build, candy/plugin-box, candy/plugin-deploy-pod, …) is unchanged. A package-level var (not a func) so tests can override it the same way as LocalImageExists.

View Source
var TrimPreview = spec.TrimPreview

TrimPreview truncates s to a 200-char preview — homed in the fabric slice github.com/opencharly/spec/spec (the same fabric-primitive class as ShellQuote), re-exported here so kit.TrimPreview callers compile unchanged (R3, single source).

View Source
var UnixToTCPBridge = checkhost.UnixToTCPBridge

vnc_bridge.go — UnixToTCPBridge RELOCATED to the spec fabric slice github.com/opencharly/spec/checkhost (#55 CHECK-ENGINE cone Option A — pure host-side networking, a check host-vantage primitive). kit re-exports so every existing kit.UnixToTCPBridge call site (charly's ssh.go / the VM-VNC endpoint resolution) is untouched.

View Source
var VmSshAlias = spec.VmSshAlias

VmSshAlias returns the canonical alias for a VM deployment name ("charly-" namespaced). The pure naming leaf now lives in package spec (a peer of VmDomainIdentity — the always-floor- legal wire/vocabulary home, #55 K4); kit re-exports it so the host-coupled ssh-config-fragment writers here (renderStanza/VmSshStanza/SshFragmentPath, which STAY in kit — they write ~/.config/charly/ssh_config) reference `kit.VmSshAlias` unchanged.

View Source
var WrapContainerCommand = spec.WrapContainerCommand

WrapContainerCommand guards an in-container command-check script against stdin-consuming subcommands. RELOCATED to the spec contract module (spec.WrapContainerCommand, wrap_container.go, #55 CHECK-ENGINE cone Option A — a pure stdlib string guard charly core's check-op dispatch reaches importing zero kit); re-exported here so every existing kit.WrapContainerCommand call site (charly core + the candies + sdk) is untouched. New consumers should call spec.WrapContainerCommand directly.

Functions

func AddBox added in v0.2026193.1241

func AddBox(dir, name, base string, layers []string) error

AddBox writes a new box to its discovered per-box file box/<name>/charly.yml as a node-form IMAGE — `<name>: {candy: {base: …}}`. The base argument is the value of the image's `base:` field (an external URL or the name of another box). If layers is non-nil it populates the image's `candy:` composition list. Errors if box/<name>/charly.yml exists.

func AddCandyDeployment added in v0.2026190.848

func AddCandyDeployment(paths *LedgerPaths, candyName, deployID string, update func(*CandyRecord)) error

AddCandyDeployment adds deployID to candy.DeployedBy and writes the record. Used at install time.

func AddCandyDeploymentVia added in v0.2026190.848

func AddCandyDeploymentVia(exec spec.DeployExecutor, paths *LedgerPaths, candyName, deployID string, update func(*CandyRecord)) error

AddCandyDeploymentVia is the executor-routed variant of AddCandyDeployment. When exec is nil or a local executor, it falls back to operator-side file I/O (today's behaviour). When exec is a non-local DeployExecutor (SSHExecutor / NestedExecutor), the ledger file I/O goes through exec.GetFile + exec.RunSystem so the ledger lands on the substrate's filesystem under the substrate's ~/.config/opencharly/installed/ — matching the install's actual venue (arch-vm.arch-host writes in the arch VM guest; sway-pod with nested pods writes in the parent pod; etc.).

func AppendUnique added in v0.2026198.345

func AppendUnique(dst []string, items ...string) []string

AppendUnique appends items to dst, skipping any already present in dst.

func ApplyPortOverrides added in v0.2026190.848

func ApplyPortOverrides(ports []string, overrides []string) ([]string, error)

ApplyPortOverrides modifies port mappings based on --port flags. Each override is "newHost:containerPort". It replaces the host port for the matching container port in the ports list. Preserves protocol suffixes like /udp.

func AtomicWriteFile added in v0.2026192.1709

func AtomicWriteFile(path string, data []byte, perm os.FileMode) error

AtomicWriteFile writes data to path atomically: a temp file in the SAME dir (same filesystem, so rename is atomic) is written, chmod'd, then renamed over path. A concurrent reader sees either the old complete file or the new complete file, never a partial write; concurrent writers of identical content converge (last rename wins, bytes identical). Relocated from charly core (P8) so the build render engine (sdk/deploykit) and charly's staging primitives share it.

func BareVolumeName added in v0.2026197.1320

func BareVolumeName(volumeName, boxName, instance string) string

BareVolumeName strips the "charly-<box>[-<instance>]-" prefix from a resolved volume name.

P12a: relocated from sdk/deploykit/quadlet.go (a pure string helper with no deploykit-specific coupling) so this file's mergeRuntimeVars can call it without a kit→deploykit import (deploykit already imports kit — the reverse direction would cycle). deploykit's own caller now calls kit.BareVolumeName.

func BuildActivityDir added in v0.2026205.1618

func BuildActivityDir() (string, error)

BuildActivityDir is the user-scope directory of live build-activity locks — one flocked nonce file per in-flight `charly box build` engine run.

func BuildArchExports

func BuildArchExports() string

BuildArchExports emits the BUILD_ARCH=$(uname -m) + ARCH=<buildkit-triplet> shell preamble so a cmd:/download: body can template ${ARCH}/${BUILD_ARCH} at deploy-time the same way the container build gets them from BuildKit's TARGETARCH.

func BuilderCollectContext

func BuilderCollectContext(word string, in spec.BuilderCollectInput) map[string]any

BuilderCollectContext returns the builder-specific stage-context keys for `word` given the host-supplied candy descriptor. An unknown word returns nil (a custom candy builder with no plugin keeps base-only context — the host never invokes a plugin for it).

func BuilderResolve

func BuilderResolve(word string, in spec.BuilderResolveInput) (spec.BuilderResolveReply, error)

BuilderResolve renders `word`'s build-time multi-stage from the host-supplied context, returning the pieces the host splices into the Containerfile: Stage (pre-main-FROM), CopyArtifacts + CopyBinary (post-main-FROM), or InlineFragment (in-candy, inline builders). An unknown word is a LOUD error (never a silent empty stage). This is the ONE render both the box-build plugin OpResolve and the in-proc pod-overlay build-emit call (R3).

func BuilderReverse

func BuilderReverse(word string, in spec.BuilderReverseInput) []spec.ReverseOp

BuilderReverse returns the teardown ops for `word` given its resolved stage context (the BuilderCollectContext output the host stored on the BuilderStep). An unknown word, or a context missing the keys a builder needs, returns nil (no teardown — the same best-effort the in-proc builders had).

func CloseHostCleanups added in v0.2026197.1537

func CloseHostCleanups(cleanups []func())

CloseHostCleanups tears down any ssh -L forwards opened while resolving ${HOST:<member>} address variables. Safe to call on a nil/empty slice.

func CollectAnyStrings added in v0.2026191.2033

func CollectAnyStrings(v any) []string

CollectAnyStrings returns every string within a plugin_input value (scalar string / nested map / list), depth-first. The READ-ONLY analogue of ExpandAnyVars: it lets the ${HOST:…} cross-member scan (collectHostRefs) reach a plugin verb's authored fields, which live in the opaque PluginInput map rather than StringFields.

func CollectHostRefs added in v0.2026197.1537

func CollectHostRefs(checks []spec.Op) []string

CollectHostRefs returns the distinct ${HOST:<member>} variable keys referenced across every string field of every check (keys in the "NAME:arg" form used by ExpandTestVars).

func ContainerPortsFromMappings added in v0.2026190.848

func ContainerPortsFromMappings(mappings []string) []int

ContainerPortsFromMappings extracts the container-side port number from each mapping. "auto" sentinels are skipped (they have no container port to extract — they ARE the request to allocate one). Unparseable entries are silently dropped (the loud-skip warning lives in CheckPortAvailability).

func DecodeInput

func DecodeInput(in map[string]any, out any)

DecodeInput decodes an Op's plugin_input (map[string]any) into a candy's CUE-generated typed params struct via a JSON round-trip. A nil/empty input leaves out at its zero value; the host has already validated the input against the served schema.

func DeleteCandyRecord added in v0.2026190.848

func DeleteCandyRecord(paths *LedgerPaths, layer string) error

DeleteCandyRecord removes candy/<candy>.json.

func DeleteDeployRecord added in v0.2026190.848

func DeleteDeployRecord(paths *LedgerPaths, id string) error

DeleteDeployRecord removes deploys/<deploy-id>.json; silently ignores not-found (teardown is idempotent).

func DirectDeployMarkerDir added in v0.2026198.345

func DirectDeployMarkerDir() (string, error)

DirectDeployMarkerDir returns ~/.config/charly/direct/, the registry directory for direct-mode deploys (the equivalent of ~/.config/containers/systemd/ for quadlet deploys).

func DirectDeployMarkerPath added in v0.2026198.345

func DirectDeployMarkerPath(box, instance string) (string, error)

DirectDeployMarkerPath returns the marker JSON path for a deploy.

func DiscoverRemoteCandy added in v0.2026192.1026

func DiscoverRemoteCandy(repoDir string) ([]string, error)

DiscoverRemoteCandy returns the list of candy names in a remote repo directory

func EffectiveStepID added in v0.2026191.2205

func EffectiveStepID(s *spec.Step, origin string, stepIdx int) string

EffectiveStepID returns the step's author id when set, else a derived id.

func EffectiveTags added in v0.2026191.1413

func EffectiveTags(stepTags []string) []string

EffectiveTags normalizes and de-dups a step's tags, preserving first-seen order. (Per-step tags only — there is no group-level tag inheritance.)

func EnsureSshConfigInclude added in v0.2026186.739

func EnsureSshConfigInclude(home string) error

EnsureSshConfigInclude inserts the managed `Include ~/.config/charly/ssh_config` directive at the TOP of the user's ~/.ssh/config (creating it if needed) — the Include MUST be outside any Host block (ssh_config(5) lexical scoping), so it is PREPENDED. Idempotent.

func EnvPairsToMap added in v0.2026190.848

func EnvPairsToMap(pairs []string) map[string]string

EnvPairsToMap converts KEY=VALUE pairs (the CLI -e / label wire form) into the map form the deploy schema stores since the env-shape unification.

func EnvdDir

func EnvdDir(home string) string

EnvdDir returns the directory where per-candy env files live under a home.

func EnvdFilePath

func EnvdFilePath(home, candyName string) string

EnvdFilePath returns the env file path for a given candy under a home.

func ExpandAnyVars added in v0.2026191.2033

func ExpandAnyVars(v any, env map[string]string) (any, []string)

ExpandAnyVars expands ${VAR} references in every string within a plugin_input value (scalar string / nested map / list), mutating maps and slices in place, and returns the (possibly rewritten) value plus the unresolved var names. Non-string scalars pass through untouched.

func ExpandOpVars added in v0.2026191.2033

func ExpandOpVars(c *spec.Op, env map[string]string) []string

ExpandOpVars rewrites every ${...} reference on an Op in place using the supplied environment map. Returns the combined (sorted) list of unresolved refs encountered across all string fields.

func ExpandTestVars added in v0.2026191.2033

func ExpandTestVars(s string, env map[string]string) (string, []string)

ExpandTestVars substitutes ${NAME} and ${NAME:arg} references using the supplied environment map.

Keys in env for plain refs use just the name: env["HOME"] = "/home/user". Keys for parameterized refs combine name and argument with a colon: env["HOST_PORT:6379"] = "16379", env["VOLUME_PATH:workspace"] = "/var/lib/…".

Returns the substituted string and a list of unresolved refs (in encounter order, deduplicated). The caller decides whether unresolved refs are an error (build-time validation) or a skip reason (runtime).

func FillReverseUninstallCmds added in v0.2026190.848

func FillReverseUninstallCmds(ops []ReverseOp, renderUninstall func(format string, packages []string) string)

FillReverseUninstallCmds renders the host-venue uninstall command for every ReverseOpPackageRemove op in the slice from the format's uninstall_template (the embedded build vocabulary, charly/charly.yml), in place. Called at install/record time by the local deploy target and the external vm deploy (R3 — one shared filler) when the DistroConfig is in hand, so the persisted ledger op carries the exact removal command the teardown will run. Ops whose format declares no uninstall_template, or whose format isn't in the config, are left with an empty UninstallCmd (teardown then errors loudly rather than silently running a wrong command). The renderUninstall seam maps (format, packages) → the format's uninstall command, or "" when the format has no uninstall template. It keeps this function free of the buildkit DistroConfig/RenderTemplate dependency (which would cycle kit→buildkit→kit once reverse_ops lives in kit) — the caller (candy/plugin-bundle/deploy_target.go, S3b — was charly core's deploy_target_external.go before the deploy-dispatch cluster moved) closes over DistroCfg + RenderTemplate.

func FilterHostVars added in v0.2026191.2205

func FilterHostVars(missing []string) []string

FilterHostVars returns the ${HOST:…} cross-member references among the unresolved keys. An unresolved ${HOST:…} means the member is unreachable — a real failure, never a SKIP (a skip on an unreachable dependency is a fake pass). Other unresolved vars (a deploy-only var under build scope, an unmounted volume) stay a legitimate skip.

func FindMappingValue

func FindMappingValue(m *yaml.Node, key string) *yaml.Node

FindMappingValue returns the value node for key in a YAML mapping node, or nil. (Like MapValue, but requires the key node to be a scalar — the form the migration transforms + the core loader's legacy-shape detection both use.)

func FindPortOwner added in v0.2026190.848

func FindPortOwner(port int, engine string) (owner string, ownerType string)

FindPortOwner checks running containers to identify what is using a port.

func FirstUnmetDepStep added in v0.2026201.1934

func FirstUnmetDepStep(s spec.Step, verdictByID map[string]string) string

FirstUnmetDepStep returns the first dep id in s.DependsOn whose verdict is anything other than "pass" (or that is unknown / not yet run). Returns "" if every dep passed (or the step has no deps).

func FormatPortConflicts added in v0.2026190.848

func FormatPortConflicts(conflicts []PortConflict, image string) string

FormatPortConflicts produces a user-friendly error message with remediation suggestions.

func HumanBytes added in v0.2026202.1944

func HumanBytes(n int64) string

HumanBytes renders a byte count as a compact human-readable string.

func IsAutoPort added in v0.2026190.848

func IsAutoPort(mapping string) bool

IsAutoPort reports whether a port-list entry is the literal "auto" sentinel. Authors write `port: [auto]` (or `port: [auto, "8443:443"]` to mix auto-allocation with explicit pins) in charly.yml.

func IsDirectDeploy added in v0.2026198.345

func IsDirectDeploy(box, instance string) bool

IsDirectDeploy reports whether the named deploy was created in direct mode (i.e. has a marker file). Used by lifecycle commands.

func IsGitSubmoduleDir

func IsGitSubmoduleDir(p, root string) bool

IsGitSubmoduleDir reports whether p (≠ root) contains a .git entry (a nested submodule/repo boundary).

func IsRuntimeOnlyVar added in v0.2026191.2033

func IsRuntimeOnlyVar(key string) bool

IsRuntimeOnlyVar reports whether the given variable key (as returned by TestVarRefs) refers to a runtime-only value. The check matches on name prefix because parameterized vars share a common prefix with their arg.

func KeywordOf added in v0.2026191.2205

func KeywordOf(s *spec.Step) spec.StepKeyword

KeywordOf returns the populated step keyword, or "" when none is set.

func ListVmSshAliases added in v0.2026186.739

func ListVmSshAliases(home string) ([]string, error)

ListVmSshAliases returns the alias names in the managed fragment, sorted.

func ManagedBlockBody

func ManagedBlockBody(shell ShellKind, home string) string

ManagedBlockBody returns the shell-specific loop that sources the env.d directory under a home. POSIX-sh for bash/zsh, fish syntax for fish.

func ManagedBody added in v0.2026186.739

func ManagedBody(text string) string

ManagedBody returns just the contents between the begin/end fence markers (the global, untagged managed block) in text. Returns "" when the markers are absent.

func MapValue

func MapValue(m *yaml.Node, key string) *yaml.Node

MapValue returns the value node for key in a YAML mapping node, or nil.

func MappingChild added in v0.2026186.1019

func MappingChild(m *yaml.Node, key string) *yaml.Node

MappingChild looks up a key in a mapping node. Returns the value node or nil if missing. yaml mapping nodes store [key, value, key, value, …].

func MarkersForTag

func MarkersForTag(marker string) (begin, end string)

MarkersForTag returns the begin/end fence pair for a marker tag. Empty tag → the global-block fence; non-empty → a per-candy fence so multiple candies coexist.

func MigrateCandidateYAMLFiles

func MigrateCandidateYAMLFiles(dir string, treeSubdirs []string) []string

MigrateCandidateYAMLFiles is the ONE candidate-file scanner the multi-document doc-migration steps share AND the core loader's legacy-vocab rejection scan uses: every `.yml`/`.yaml` under each of treeSubdirs (walked recursively, skipping nested git submodules + any `testdata` dir) plus the root-level YAML siblings in dir. Sorted, deduplicated.

func NodeShapedValue

func NodeShapedValue(val *yaml.Node) bool

NodeShapedValue reports whether a mapping node carries a reserved kind word as a key (i.e. it is a name-first node-form value).

func NormalizeTag added in v0.2026191.1413

func NormalizeTag(t string) string

NormalizeTag strips a single leading '@' so `@smoke` and `smoke` are identical — authors commonly write `@smoke` from Gherkin habit, but the sigil is optional in the YAML surface.

func OpUnifyCandidateFiles

func OpUnifyCandidateFiles(dir string) []string

OpUnifyCandidateFiles is the candidate-file set the op/plan-unify migrators AND the core loader's legacy-test-vocab rejection scan walk (candy/ + box/ trees + root siblings).

func ParseContainerPort added in v0.2026190.848

func ParseContainerPort(mapping string) (int, error)

ParseContainerPort extracts the container port from a mapping. Accepts every form ParsePortMapping does, including the IP:H:C bind-address form.

func ParseHostPort added in v0.2026190.848

func ParseHostPort(mapping string) (int, error)

ParseHostPort extracts the host port from a mapping. Accepts every form ParsePortMapping does, including the IP:H:C bind-address form.

func ParseTaskMode

func ParseTaskMode(mode string, def uint32) uint32

ParseTaskMode parses a candy task mode string ("0644","0o755") into a uint32 file mode, falling back to def when empty/unparseable.

func PosArtifact

func PosArtifact(c *spec.Op) []string

func PosCommandFields

func PosCommandFields(c *spec.Op) []string

PosCommandFields splits the input `command` into argv slots, prefixed with `--` so kong does not treat embedded -flags as its own (libvirt:guest/exec). For shell metachars use `command: "sh -c '<full>'"`.

func PosKeyNameSplit

func PosKeyNameSplit(c *spec.Op) []string

PosKeyNameSplit splits the input `key` on whitespace (libvirt send-key: "ctrl alt F2" → 3 slots).

func PosLibvirtQmp

func PosLibvirtQmp(c *spec.Op) []string

PosLibvirtQmp emits a QMP method name + optional JSON args (input text = method, input = JSON).

func PosTarget

func PosTarget(c *spec.Op) []string

func PosText

func PosText(c *spec.Op) []string

func RemoveEnvdFile added in v0.2026206.837

func RemoveEnvdFile(home, candyName string) error

RemoveEnvdFile deletes an env.d entry. Silently succeeds when absent.

func RemoveImagesByReference added in v0.2026186.831

func RemoveImagesByReference(engineBin, reference string)

RemoveImagesByReference best-effort removes every local image whose repository BASENAME exactly equals `reference` (e.g. "<deploy>-overlay") via `<engineBin> images … | rmi`. Silent on error (image cleanup is best-effort). engineBin is the resolved engine binary (the host resolves "podman"/"docker"/"auto" and passes the concrete binary — kit does no detection).

The `--filter reference=<reference>` glob is NOT trusted alone: podman lists EVERY repo:tag of any matched image ID, so a base image or a cross-deploy image that shares content (same image ID as the overlay) leaks into the output. rmi'ing those blindly would destroy the base + unrelated deploys' images. So the emitted repo is re-checked in Go and only the EXACT `<reference>` repo is removed.

func RemoveManagedBlockAt added in v0.2026190.848

func RemoveManagedBlockAt(path, marker string) error

RemoveManagedBlockAt strips the managed block (tagged with marker) from the file at path, in place. A file left empty is removed; a missing file is a no-op. The file-level counterpart to StripManagedBlock (the pure string op).

func RemoveSshConfigInclude added in v0.2026186.739

func RemoveSshConfigInclude(home string) error

RemoveSshConfigInclude removes the managed Include line from ~/.ssh/config. Idempotent.

func RemoveVmSshStanza added in v0.2026186.739

func RemoveVmSshStanza(home string, alias string) (remaining int, err error)

RemoveVmSshStanza drops the named alias and returns the remaining count (0 → the caller also removes the Include). Idempotent.

func RenderDownloadScript

func RenderDownloadScript(op *spec.Op, candyVars map[string]string) string

RenderDownloadScript emits a shell snippet that fetches op.Download to a temp file, optionally extracts it into op.To, then cleans up — honoring the same flags the container build path respects (extract format, strip_components, include, mode, env). candyVars are exported alongside op.Env so a vars: key referenced in the URL resolves at deploy time.

func RenderEnvdBody

func RenderEnvdBody(candyName string, envVars map[string]string, pathAdd []string) string

RenderEnvdBody produces the deterministic, shell-agnostic POSIX-sh fragment for a candy's env vars + PATH additions. Sorted keys guarantee stable output.

func RenderManagedBlockStrip added in v0.2026190.848

func RenderManagedBlockStrip(path, marker string) string

RenderManagedBlockStrip is the remote analogue of RemoveManagedBlockAt: a POSIX-sh snippet that drops the marker's begin/end fence pair (and its body) from the file IN PLACE on the venue. The fences come from MarkersForTag so they match exactly what the forward walk wrote, and awk index() matches them LITERALLY (no regex). The final `cat > "$f"` rewrites in place to preserve the rc file's perms/owner.

func RenderOpCommand

func RenderOpCommand(op *spec.Op, ctxPath string, candyVars map[string]string) (string, bool)

RenderOpCommand turns an op into a shell command suitable for sudo/user execution. It handles every plugin-renderable verb EXCEPT copy (staged via the executor's PutFile) and the act-`plugin:` verb (whose ProvisionActor shell needs the in-proc registry — package main / RunHostStep render those). Returns (cmd, handled): handled=false means the op is a copy or an act-`plugin:` verb the caller must route elsewhere; handled=true with an empty cmd never occurs (every handled verb yields a body).

func ReplaceOrAppendManagedBlock

func ReplaceOrAppendManagedBlock(existing, body, marker string) string

ReplaceOrAppendManagedBlock replaces the begin/end fence pair's body (tagged with marker, empty for the global block) in existing, appending a fresh block at EOF when absent.

func ReplaceOrPrependManagedBlock added in v0.2026186.739

func ReplaceOrPrependManagedBlock(existing, body, marker string) string

ReplaceOrPrependManagedBlock replaces the marker-tagged managed block in place, or PREPENDS it (with a blank-line separator before the rest of the file) when absent. Used for the ssh-config Include line, which MUST be at the TOP of ~/.ssh/config (outside any Host block) — an appended Include would be gated on the last preceding Host stanza's match (ssh_config(5) lexical scoping).

func ResolveDeployPorts added in v0.2026190.848

func ResolveDeployPorts(containerPorts []int, pins, prior []string, occupied map[int]bool) ([]string, error)

ResolveDeployPorts maps each image-declared container port to a host:container publish mapping — the AUTO-PORT-MAPPING default. For every container port:

  • an explicit deploy pin (host:container, matched by container port) wins;
  • else a still-valid prior allocation (from a previous `charly config` — keeps a deploy's host ports STABLE across `charly update`) is reused;
  • else a fresh free 127.0.0.1 host port is allocated.

`occupied` seeds host ports already taken by SIBLING deployments so concurrent beds never collide; every chosen host port is recorded back into it. Pins for container ports the image does not expose are honored too (an operator publishing an extra port). A stray `auto` token in `pins` is ignored (treated as "no pin" → allocate), so a not-yet-migrated `port: [auto]` still works. Returned mappings carry no bind address; localizePort prepends BindAddress (127.0.0.1 by default) at quadlet/run time so every published port is loopback.

func ResolveEntrypointFromMeta added in v0.2026198.345

func ResolveEntrypointFromMeta(meta *spec.BoxMetadata) []string

ResolveEntrypointFromMeta determines the entrypoint from image metadata (runtime mode). Label-first: the build-resolved init contract is baked into the ai.opencharly.init_def label (meta.InitDef), so any init system declared in the embedded `init:` vocabulary — including custom ones — now reaches runtime. wellKnownInitDefs is consulted only for pre-init_def-label images (built before the label existed; their labels cannot be re-baked).

func ResolveInitDefFromMeta added in v0.2026198.345

func ResolveInitDefFromMeta(meta *spec.BoxMetadata) (*spec.ResolvedInit, error)

ResolveInitDefFromMeta returns the init contract for management-command rendering. Label-first: the build-resolved def is baked into the ai.opencharly.init_def label, so any vocabulary-declared init system — including custom ones — resolves at runtime. Falls back to wellKnownInitDefs only for pre-init_def-label images (built before the label existed).

func ResolveNetwork added in v0.2026198.345

func ResolveNetwork(configured, engine string) (string, error)

ResolveNetwork returns the network to use for a container. If configured is non-empty (explicit override like "host"), it is returned as-is. Otherwise, the shared "charly" network is ensured and returned.

func ResolveVmSshPort added in v0.2026201.1934

func ResolveVmSshPort(vm *spec.ResolvedVm, vmName string, persistedPort int) (int, error)

ResolveVmSshPort resolves the guest SSH host port from the resolved spec:

  • ssh.port_auto: true → reuse persistedPort when the caller already resolved one (idempotent across rebuilds), else allocate a free host port.
  • ssh.port: N → that fixed port.
  • neither → 2222.

The persisted-state READ is the caller's concern (it differs by placement: charly core reads its project config directly; an out-of-process plugin reads over a host seam) — this is the PURE resolution/allocation decision only.

func RunAgentOnce added in v0.2026197.1320

func RunAgentOnce(ctx context.Context, ai *spec.AgentExecSpec, prompt string, timeout time.Duration) (string, string, error)

RunAgentOnce launches the configured AI CLI exactly once with the given prompt and returns its stdout/stderr. It is the bounded, single-shot sibling of the harness loop's iteration launcher (candy/plugin-check/harness_loop.go) and of LocalCaptureVersion (candy/plugin-check/agent.go) — same host-exec shape, no iteration directories, no plateau state. ${PROMPT} in the AI's command argv (and a PromptVia: file temp file) is substituted with the prompt text.

func RunReverseOps added in v0.2026190.848

func RunReverseOps(ops []ReverseOp, exec ReverseExecutor)

RunReverseOps executes ops in REVERSE order (last-installed, first- removed). Idempotent where possible: a missing file is treated as "already removed" rather than an error.

func SameStringSlice added in v0.2026190.848

func SameStringSlice(a, b []string) bool

SameStringSlice reports whether two string slices are element-wise equal (order-sensitive) — used to skip a redundant resolved-port re-save.

func SanitizeDeployName added in v0.2026197.1320

func SanitizeDeployName(s string) string

func SaveYAMLNodeFile added in v0.2026193.1241

func SaveYAMLNodeFile(path string, root *yaml.Node) error

SaveYAMLNodeFile marshals a node tree back to an arbitrary file path, preserving comments + key order (the yaml.v3 Node round-trip). Shared by the scaffold/authoring engine (kit.AddBox) and charly core's box add-candy/rm-candy verbs — ONE marshal-to-file home (R3).

func ScaffoldCandy added in v0.2026193.1241

func ScaffoldCandy(dir, name, calver string) error

ScaffoldCandy creates a new candy directory at dir/<DefaultCandyDir>/<name> with a placeholder manifest in the compact name-first node form. ADE mandates a description + at least one deterministic check: step, so the scaffold ships a minimal passing pair the author replaces. calver stamps the candy's mandatory version:. Errors if the candy already exists. The caller prints the created path.

func ScaffoldProject added in v0.2026193.1241

func ScaffoldProject(dir string) error

ScaffoldProject creates an empty charly project at dir. Idempotency: errors out if dir already contains an charly.yml so we never silently clobber an existing project. The dir itself may exist. The seed's schema version is stamped to the current HEAD (LatestSchemaVersion).

func SetByDotPath added in v0.2026186.1019

func SetByDotPath(path, dotpath, valueYAML string) error

SetByDotPath edits the file at path, navigating into the YAML structure via dotpath (dot-separated keys) and replacing the leaf value with valueYAML (parsed as YAML so callers can pass scalars, lists, or maps). Comments and key order are preserved.

func ShDoubleQuote

func ShDoubleQuote(v string) string

ShDoubleQuote wraps a string in double quotes for a shell context where variable expansion MUST still happen (e.g. download URLs that template ${BUILD_ARCH}). Escapes the metachars that break out of a double-quoted string, but deliberately does NOT escape `$` so authored ${FOO} / $FOO still expand.

func ShDoubleQuotePath

func ShDoubleQuotePath(v string) string

ShDoubleQuotePath escapes a PATH-list value for use INSIDE double quotes, leaving `$` unescaped so `$PATH` expands at sourcing time.

func ShQuoteArg

func ShQuoteArg(v string) string

ShQuoteArg single-quotes an argument for POSIX shell embedding.

func ShQuoteEnv

func ShQuoteEnv(v string) string

ShQuoteEnv single-quotes a value for POSIX sh (env.d export values). Inside single quotes nothing needs escaping except the single quote itself.

func ShellInitFilePath

func ShellInitFilePath(shell ShellKind, home string) string

ShellInitFilePath returns the init file the managed block lands in for each shell under a home.

func SidecarConfigDir added in v0.2026198.358

func SidecarConfigDir() (string, error)

SidecarConfigDir returns the per-user directory where sidecar companion config files live (e.g. charly-foo-tailscale-serve.json), used by the `charly config remove` sidecar-config sweep.

func SortStrings

func SortStrings(s []string)

SortStrings sorts s in place (ascending). A small insertion-free bubble sort, kept identical to the original package-main helper.

func SplitHostKey added in v0.2026197.1537

func SplitHostKey(key string) (name, arg string, ok bool)

SplitHostKey splits a "HOST:web" / "HOST:web:8080" key into the variable name and the remaining argument(s) (everything after the FIRST colon).

func SshConfigPath added in v0.2026186.739

func SshConfigPath(home string) string

SshConfigPath returns ~/.ssh/config for the user's home dir.

func SshFragmentPath added in v0.2026186.739

func SshFragmentPath(home string) string

SshFragmentPath returns ~/.config/charly/ssh_config for the user's home dir.

func StepDoMode added in v0.2026191.2205

func StepDoMode(s *spec.Step) spec.DoMode

StepDoMode maps the step keyword to the act/assert/instruct dispatch enum.

func StepID added in v0.2026191.2205

func StepID(origin string, stepIdx int) string

StepID returns the stable identifier used for plan-overlay merge lookups, depends_on references, and ${STEP_ID} substitution — a deterministic id derived from origin + position.

func StripManagedBlock

func StripManagedBlock(existing, marker string) string

StripManagedBlock removes the begin/end fence pair (tagged with marker) and its body.

func TaskShellPreamble

func TaskShellPreamble(candyVars, opEnv map[string]string) string

TaskShellPreamble returns the BUILD_ARCH/ARCH exports plus any candy vars + op env (sorted for deterministic output) so cmd: bodies can reference ${ARCH} / ${MY_CANDY_VAR} at deploy-time the same way they do at build-time.

func TestVarRefs added in v0.2026191.2033

func TestVarRefs(s string) []string

TestVarRefs returns the set of ${NAME[:arg]} references in s, as their fully-qualified keys (matching the env-map format used by ExpandTestVars). Used by the validator to catch typos at config time.

func TransferToRootful added in v0.2026190.848

func TransferToRootful(imageRef string) error

TransferToRootful pipes an image from rootless podman storage into rootful (sudo podman) storage via `podman save | sudo podman load`. Idempotent — returns nil immediately when the image already exists in rootful storage.

Used by RunPrivileged when engine.rootful=sudo because rootless and rootful podman maintain separate container-storage trees (~/.local/share/containers vs /var/lib/containers). Without this transfer, sudo podman run against a locally-built image falls back to a registry pull (which 403s for build-only images that were never pushed).

Surfaced 2026-05 by the cachyos / cachyos-pacstrap-builder pair — the first time the bootstrap-builder framework was exercised end-to-end on a host with rootless build + sudo run.

func ValidatePlanSteps added in v0.2026197.1320

func ValidatePlanSteps(desc string, plan []spec.Step, eid string) []string

planvalidate.go — ValidatePlanSteps, the SHARED static plan-block validator (P12a: relocated from charly/plan_validate.go). It lives here — not candy/plugin-check — because it is invoked by BOTH `charly box validate` (charly/validate.go) AND the externalized `charly feature` command's "feature" HostBuild seam (charly/host_build_feature.go): CORE calls it directly at both sites, so it must be reachable without importing a plugin candy. One copy, R3.

  • description non-empty
  • every step has exactly one keyword (StepKind())
  • run/check steps carry exactly one Op verb; agent-* steps carry none

Returns a list of human-readable error strings (empty if OK).

func ValidateTagExpr added in v0.2026201.1934

func ValidateTagExpr(tag string) error

ValidateTagExpr syntax-checks an optional --tag expression (rejecting a malformed one) without keeping the parsed *TagExpr — moved from charly/check_feature_run.go (CHECK-wave), a pure wrapper with zero core-state coupling. It does NOT apply the parsed expression as a step filter: kit.RunPlan (the walk both hostFeatureBox, still core, and candy/plugin-check's pluginCheckRunFeatureLive drive) takes no tag-filter parameter and walks every step unconditionally — a confirmed, RCA'd, non-blocking gap (per-tag filtering was never wired past this parse), routed to the next check-correctness thematic batch.

func WalkPlans

func WalkPlans(ctx context.Context, exec DeployExecutor, plans []spec.InstallPlanView, opts WalkOpts) ([]spec.ReverseOp, error)

WalkPlans executes every plan's steps on the venue and returns the combined teardown ops (plugin-renderable kinds echo the host-computed view.ReverseOps; host-engine kinds return theirs from RunHostStep). The caller folds them into its DeployReply.

func WriteCandyRecord added in v0.2026190.848

func WriteCandyRecord(paths *LedgerPaths, rec *CandyRecord) error

WriteCandyRecord serializes rec to candy/<candy>.json.

func WriteDeployRecord added in v0.2026190.848

func WriteDeployRecord(paths *LedgerPaths, rec *DeployRecord) error

func WriteDeployRecordVia added in v0.2026190.848

func WriteDeployRecordVia(exec spec.DeployExecutor, paths *LedgerPaths, rec *DeployRecord) error

WriteDeployRecordVia is the executor-routed variant of WriteDeployRecord. Same semantics as AddCandyDeploymentVia but for deploy records (deploys/<id>.json).

func WriteVmSshStanza added in v0.2026186.739

func WriteVmSshStanza(home string, s VmSshStanza) error

WriteVmSshStanza adds (or replaces) a Host stanza in the managed fragment. Idempotent.

Types

type AgentForwardMounts added in v0.2026198.345

type AgentForwardMounts struct {
	Volumes []string // host:container[:options] — only for container CREATION
	Env     []string // KEY=VALUE — for both creation and exec
}

AgentForwardMounts holds the resolved bind mounts and env vars needed to forward SSH and GPG agent sockets from the host into a container.

func ResolveAgentForwarding added in v0.2026198.345

func ResolveAgentForwarding(rt *ResolvedRuntime, deploy *spec.BundleNode, containerHome string) AgentForwardMounts

ResolveAgentForwarding detects available agent sockets on the host and returns the bind mounts and environment variables needed to forward them into a container. deploy may be nil (no per-image overrides). containerHome is the home directory inside the container (e.g., "/root" or "/home/user") — determines where GPG expects its agent socket.

Graceful degradation: logs warnings to stderr for missing sockets but never returns errors — missing agents are silently skipped.

type AgentGrader added in v0.2026197.1320

type AgentGrader struct {
	Agent    *spec.AgentExecSpec // the resolved kind:agent exec spec (how to launch the CLI)
	Target   string              // the deployment name the agent probes (e.g. "check-pod")
	Instance string              // optional deploy instance
	Timeout  string              // optional Go-duration override (from --timeout)
}

AgentGrader is the production StepGrader: it drives the configured `kind: agent` CLI against a live deployment.

func (*AgentGrader) Grade added in v0.2026197.1320

Grade builds the grader prompt, runs the AI once, and parses its verdict.

type BuilderRunOpts added in v0.2026190.848

type BuilderRunOpts = spec.BuilderRunOpts

type CalVer

type CalVer = spec.ParsedCalVer

calver.go — re-export of the parsed CalVer schema-version type + the HEAD schema version / migration floor + comparator, RELOCATED to spec/spec/calver_parse.go (#55 value extraction). The parsed type is a pure value/transform over the version E-envelope, so it homes in spec; it is named spec.ParsedCalVer there (spec already binds CalVer=string, the CUE wire scalar). kit re-exports it as `type CalVer = spec.ParsedCalVer` + var forwarders so every existing kit.CalVer / kit.ParseCalVer / kit.LatestSchemaVersion / kit.SchemaFloor call site (charly core's migrate/version gate + plugin-box/clean/migrate) is unchanged. New consumers reference spec.*.

type CandyRecord added in v0.2026190.848

type CandyRecord = spec.CandyRecord

SPIKE (value-type relocation, #55 cluster 4): DeployRecord/CandyRecord/ StepRecord relocated to spec (spec/spec/ledger_records.go) — every field already resolved to a spec.* type and none carried methods, so they moved verbatim. Zero-churn aliases.

func ReadCandyRecord added in v0.2026190.848

func ReadCandyRecord(paths *LedgerPaths, layer string) (*CandyRecord, error)

ReadCandyRecord loads candy/<candy>.json; returns nil, nil if absent.

func RemoveCandyDeployment added in v0.2026190.848

func RemoveCandyDeployment(paths *LedgerPaths, candyName, deployID string) (*CandyRecord, bool, error)

RemoveCandyDeployment decrements a candy's deployed_by set. Returns (recordAfter, shouldFullyRemove, error). When shouldFullyRemove is true, the caller should perform the actual file/package/service teardown and then delete the candy ledger entry.

type CharlyInstallStrategy added in v0.2026186.739

type CharlyInstallStrategy = exec.CharlyInstallStrategy

type CheckContext

type CheckContext = spec.CheckContext

CheckContext is the live check-engine surface a host-coupled verb's RunVerb consumes — homed in the spec contract module (spec.CheckContext, checkcontext.go) so charly core's reverse-channel dispatch references it while importing only spec; charly's *Runner implements it, and this alias keeps candy RunVerb signatures compiling unchanged.

type CheckEndpoint added in v0.2026209.2002

type CheckEndpoint = checkhost.CheckEndpoint

check_endpoint.go — EndpointForVenue + CheckEndpoint RELOCATED to the spec fabric slice github.com/opencharly/spec/checkhost (#55 CHECK-ENGINE cone Option A — the check-verb host-vantage resolution family: net/ssh host primitives belong in a spec fabric slice, so charly core's check dispatch reaches them importing zero kit). The kind-blind resolver + its containerPublishedAddr / sshForwardEndpoint bodies live there; kit re-exports the exported surface so every existing kit.EndpointForVenue / kit.CheckEndpoint call site (the candies + sdk) is untouched. New consumers reference spec/checkhost directly.

type CheckResult added in v0.2026191.1732

type CheckResult struct {
	spec.CheckResult

	// DeadlineExceeded marks a result whose probe was killed by hitting its OWN
	// per-attempt deadline (probeNeverHang), NOT an external infra interruption. The
	// group-kill in runCaptureCmd surfaces the deadline SIGKILL as a signal-kill, so
	// without this flag the killed-probe retry (probeWasKilled) would futilely re-run a
	// probe that will only re-hang and re-hit the same deadline. An authoritative
	// "too slow" failure — never retried. NEVER crosses the wire (see above).
	DeadlineExceeded bool `json:"-"`
}

CheckResult is the engine's record of running a single check: the verb's verdict (Status/Message/CapturedValue — the same three fields a verb returns as a Result) plus the engine bookkeeping a Result does not carry (which Op ran, its Verb, and the timing / retry accounting). The dispatch boundary builds a CheckResult from a verb's Result by stamping Op/Verb/Elapsed.

Attempts and TotalElapsed are populated only when the check had an `eventually:` modifier (retry loop): Attempts=1 + TotalElapsed==Elapsed for a check that ran exactly once. Reporters surface these when Attempts>1 so slow startup paths are visible ("PASS in 5 attempts over 12.3s").

FLOOR-SLIM Unit 4 — the wire-envelope split: every field above is now spec.CheckResult (CUE-sourced, sdk/schema/checkresult.cue), EMBEDDED here. The ONE exception is DeadlineExceeded (below) — the spike-proven (P12) exception the wire mandate's own documented exception path authorizes: `json:"-"` (keep-in-Go, drop-from-wire) has no gengotypes construct. charly core's registry-coupled floor files (provider.go, provider_verb.go, verb_builtins.go, unified_targets.go, provider_checkenv.go) reference spec.CheckResult DIRECTLY (they never touch DeadlineExceeded) — zero new sdk/kit import. Field-promotion means every existing `.Status`/`.Op`/`.Verb`/`.Message`/`.Elapsed`/ `.Attempts`/`.TotalElapsed`/`.CapturedValue` selector expression compiles UNCHANGED; only a composite LITERAL naming one of those fields must nest it under the embedded field name (`CheckResult{CheckResult: spec.CheckResult{Status: ...}, ...}`) — Go does not promote embedded-struct field names into a literal key position.

The wire keys are deliberately snake_case now (op/verb/status/message/elapsed/…), a documented breaking change to `--format json`/TAP output — see sdk/schema/checkresult.cue and CHANGELOG for the old→new key mapping.

func RunOne added in v0.2026191.2205

func RunOne(ctx context.Context, pc PlanContext, c *spec.Op) CheckResult

RunOne handles all the per-check housekeeping (verb resolution, skip handling, variable expansion, venue swap, do-mode routing) and dispatches to a verb handler through the VerbResolver seam. The `eventually:` retry wraps the verb dispatch when requested.

func RunWithEventually added in v0.2026191.1837

func RunWithEventually(ctx context.Context, check *spec.Op, handler func() CheckResult) CheckResult

RunWithEventually wraps a per-check verb handler in a retry loop when the check declares `eventually: <duration>`. The handler is called with no arguments — it closes over the check, runner, and context.

Semantics:

  • eventually: outer retry cap (parsed as time.Duration). Defaults the returned CheckResult to Attempts=1 when unset (handler runs exactly once, unchanged).
  • retry_interval: sleep between retries. Defaults to 1s. Must be ≤ eventually or the loop would sleep past the deadline on the first miss.
  • PASS semantics: the FIRST attempt that returns StatusPass wins; its stdout/captures/message are what propagate.
  • FAIL semantics: the LAST attempt before deadline is returned, ensuring authors see the most recent failure detail rather than a stale first-attempt error.
  • SKIP semantics: treated the same as FAIL for retry purposes — skips aren't actionable to retry against.

Variable expansion: the handler re-runs from the same expanded check each attempt. The caller must expand variables BEFORE calling RunWithEventually — re-expansion per attempt would re-evaluate ${CAPTURED:name} mid-run, which is unwanted (captures record on PASS only, and pre-pass attempts wouldn't see their own future capture).

Context: RunWithEventually honours ctx.Deadline / ctx.Done — a canceled context short-circuits the loop with the last attempt's result.

type CheckRunReply added in v0.2026194.1605

type CheckRunReply = spec.CheckRunReply

CheckRunReply is the host-resolved result of a check-run. Aliased to spec.CheckRunReply (the body lives there).

type CheckVarResolver added in v0.2026197.1320

type CheckVarResolver struct {
	// Env is the final flat map: plain refs use the name as key, parameterized
	// refs use "name:arg" as key (e.g., "HOST_PORT:6379"). Matches the format
	// expected by ExpandTestVars.
	Env map[string]string
	// HasRuntime is true when the resolver was populated from a running
	// container. When false, runtime-only vars return unresolved (skip path).
	HasRuntime bool
}

CheckVarResolver holds the materials for variable expansion under `charly check live` (running container present) and `charly check box` (build-time, no container). Builders for each scope are ResolveCheckVarsRuntime and ResolveCheckVarsBuild.

func NewRuntimeCheckVarResolver added in v0.2026206.837

func NewRuntimeCheckVarResolver(env map[string]string) *CheckVarResolver

NewRuntimeCheckVarResolver constructs a runtime check-var resolver (HasRuntime true) from an env map, stamping CHARLY_BIN via StampCharlyBin.

func ResolveCheckVarsBuild added in v0.2026197.1320

func ResolveCheckVarsBuild(meta *spec.BoxMetadata) *CheckVarResolver

ResolveCheckVarsBuild builds a variable map for build-time tests (no running container). Only BoxMetadata-derived vars are populated.

func ResolveCheckVarsRuntime added in v0.2026197.1320

func ResolveCheckVarsRuntime(meta *spec.BoxMetadata, deploy *vmshared.BundleNode, engine, deployName, containerName, instance string) (*CheckVarResolver, error)

ResolveCheckVarsRuntime builds a variable map for `charly check live` against a running container. It combines image metadata (build-time knowledge), deploy overlay (per-host knowledge), and podman inspect (effective runtime state). Any of the inputs may be nil.

On inspect failure the function still returns a resolver with the build-time portion populated; HasRuntime is false and runtime-only vars will be unresolved downstream.

func StampCharlyBin added in v0.2026206.837

func StampCharlyBin(res *CheckVarResolver) *CheckVarResolver

StampCharlyBin records the active charly executable path into a runtime check-var resolver's Env as CHARLY_BIN, so host-side plan re-entry (a step referencing ${CHARLY_BIN}) drives the active binary instead of a stale PATH selection. CHARLY_BIN is deliberately never synthesized from PATH: an unavailable executable leaves the variable unresolved instead of silently selecting an unrelated installed charly. nil-safe; idempotent.

type CheckVerbProvider

type CheckVerbProvider = spec.CheckVerbProvider

CheckVerbProvider is the typed in-process contract a host-coupled check-verb candy implements — homed in the spec contract module (spec.CheckVerbProvider, checkcontext.go) so charly core's registry references it while importing only spec; aliased here so every candy implementing it (RunVerb signature) compiles unchanged.

type ContainerInspection added in v0.2026197.1320

type ContainerInspection struct {
	Name            string            `json:"Name"`
	Config          InspectConfig     `json:"Config"`
	HostConfig      InspectHostConfig `json:"HostConfig"`
	NetworkSettings InspectNetwork    `json:"NetworkSettings"`
	Mounts          []InspectMount    `json:"Mounts"`
}

ContainerInspection is the subset of `podman inspect` JSON that the test runner needs to resolve runtime ${…} variables. Field names mirror the podman/docker inspect schema — we read the JSON the engine produces, we do not construct it.

func (*ContainerInspection) IsHostNetworked added in v0.2026197.1320

func (c *ContainerInspection) IsHostNetworked() bool

IsHostNetworked returns true when the container uses --network=host, in which case every container port is trivially the same host port.

type DefaultDownloader added in v0.2026192.1026

type DefaultDownloader struct{}

DefaultDownloader is the built-in git fetch backend — it delegates to DownloadRepo (git clone into the cache). The host uses it until a refs plugin registers a different RefsDownloader. Stays in kit (real git-clone I/O, not a pure seam contract).

func (DefaultDownloader) Download added in v0.2026192.1026

func (DefaultDownloader) Download(repoPath, version string) (string, error)

Download implements RefsDownloader via the git DownloadRepo primitive.

type DeployExecutor

type DeployExecutor interface {
	// Venue returns the host executor's stable venue identifier.
	Venue(ctx context.Context) (string, error)
	// RunSystem runs a root (sudo) script on the venue; optsJSON is a marshalled EmitOpts (nil ok).
	RunSystem(ctx context.Context, script string, optsJSON []byte) error
	// RunUser runs an unprivileged script on the venue.
	RunUser(ctx context.Context, script string, optsJSON []byte) error
	// PutFile places content at a path on the venue (ownerRoot → root:root). Binary-safe.
	PutFile(ctx context.Context, remotePath string, content []byte, mode uint32, ownerRoot bool) error
	// GetFile reads a venue file back to the host (asRoot reads via sudo).
	GetFile(ctx context.Context, path string, asRoot bool) ([]byte, error)
	// RunCapture runs a command on the venue, returning stdout/stderr/exit separately.
	RunCapture(ctx context.Context, script string) (stdout, stderr string, exit int, err error)
	// RunHostStep drives a HOST-ENGINE step on the host engine + applies onto the venue,
	// returning the step's recorded reverse ops.
	RunHostStep(ctx context.Context, step spec.InstallStepView, optsJSON []byte) ([]spec.ReverseOp, error)
}

DeployExecutor is the reverse-channel surface WalkPlans drives. charly's plugin SDK *Executor satisfies it structurally (identical method set), so a plugin passes its sdk.Executor straight through — kit need not import the SDK (no cycle).

type DeployRecord added in v0.2026190.848

type DeployRecord = spec.DeployRecord

SPIKE (value-type relocation, #55 cluster 4): DeployRecord/CandyRecord/ StepRecord relocated to spec (spec/spec/ledger_records.go) — every field already resolved to a spec.* type and none carried methods, so they moved verbatim. Zero-churn aliases.

func ReadDeployRecord added in v0.2026190.848

func ReadDeployRecord(paths *LedgerPaths, id string) (*DeployRecord, error)

ReadDeployRecord loads deploys/<deploy-id>.json; returns nil, nil if the file doesn't exist.

type DocShape added in v0.2026194.1400

type DocShape = spec.DocShape

DocShape classifies one parsed YAML document's top level.

type EmitOpts added in v0.2026190.848

type EmitOpts = spec.EmitOpts

type EngineConfig added in v0.2026190.848

type EngineConfig = hostenv.EngineConfig

runtime_config.go — re-export of the user-level runtime configuration subsystem, RELOCATED to spec/hostenv/runtime_config.go (#55 host-env fabric extraction). Resolving engine / run-mode / storage paths from env > config-file > defaults is a HOST-ENVIRONMENT primitive, homed in spec/hostenv. kit re-exports the types + funcs so the ~23 kit.ResolveRuntime / kit.RuntimeConfig / kit.ResolvedRuntime / … call sites (charly core + plugins) are untouched.

NOTE: the two test-injection SEAM vars — RuntimeConfigPath and SystemdUserRuntimeDir — are DELIBERATELY NOT re-exported. A live var that callers REASSIGN cannot be `var X = hostenv.X` aliased (that copies the value, so a reassignment through kit would never reach hostenv's own reader). Every reader/writer references hostenv.RuntimeConfigPath / hostenv.SystemdUserRuntimeDir directly (charly host_build_hostprobe.go, plugin-settings, and the test-injection sites).

type EnvConfig added in v0.2026190.848

type EnvConfig = spec.EnvConfig

EnvConfig — resolved candy env (KEY=value vars + PATH-append entries). CUE-SOURCED in spec now (sdk/schema/candymodel.cue, the S-CM enabler) so #CandyModel can compose it; this ALIASES onto spec (SDD). The helper functions below operate on it unchanged.

func ExpandEnvConfig added in v0.2026190.848

func ExpandEnvConfig(cfg *EnvConfig, home string) *EnvConfig

ExpandEnvConfig expands all ~ and $HOME references in an EnvConfig

func MergeEnvConfigs added in v0.2026190.848

func MergeEnvConfigs(configs []*EnvConfig) *EnvConfig

MergeEnvConfigs merges multiple env configs, later configs override earlier

type Executor

type Executor = spec.CheckExecutor

Executor is the subset of charly's DeployExecutor a check verb needs (spec.CheckExecutor, checkcontext.go): run one command/script on the venue and capture stdout/stderr/exit separately. charly's DeployExecutor satisfies it structurally, so *Runner.Exec is passed straight through. Aliased here so candy call sites compile unchanged.

type FetchedImage added in v0.2026190.848

type FetchedImage struct {
	Path   string
	SHA256 string
}

FetchedImage is the result of FetchQcow2: the absolute path to the cached file plus the resolved sha256 (useful for logging / audit).

func FetchQcow2 added in v0.2026190.848

func FetchQcow2(src VmSource) (FetchedImage, error)

FetchQcow2 downloads (or reuses a cached copy of) a qcow2 URL, verifying the sha256 checksum. Resumable: partial downloads with a matching Content-Length are continued via Range: bytes=.

If the checksum value is empty, the fetcher attempts to auto-resolve a sidecar file at <url>.SHA256 / .sha256 / .sha256sum (Arch convention is .SHA256). The first one that returns HTTP 200 wins.

type GraderRequest added in v0.2026191.2205

type GraderRequest struct {
	Description string
	Keyword     string
	Text        string
	// ReadOnly is true for agent-check: (assessment only), false for agent-run: (may mutate).
	ReadOnly bool
}

GraderRequest is the agent grader's input for one agent-run:/agent-check: step.

type GraphicsEndpoint added in v0.2026187.2153

type GraphicsEndpoint = spec.CheckGraphicsEndpoint

GraphicsEndpoint is the resolved, dialable VM graphics endpoint a vnc/spice verb gets from CheckContext.ResolveGraphicsEndpoint — homed in the spec contract module (spec.CheckGraphicsEndpoint, checkcontext.go), aliased here for unchanged candy call sites.

type HTTPRequest

type HTTPRequest = spec.CheckHTTPRequest

HTTPRequest is the host-vantage HTTP request a check verb hands cc.HTTPDo — homed in the spec contract module (spec.CheckHTTPRequest, checkcontext.go), aliased here unchanged.

type HTTPResponse

type HTTPResponse = spec.CheckHTTPResponse

HTTPResponse is the result of cc.HTTPDo — homed in the spec contract module (spec.CheckHTTPResponse, checkcontext.go), aliased here unchanged.

type InspectConfig added in v0.2026197.1320

type InspectConfig struct {
	Hostname string   `json:"Hostname"`
	Env      []string `json:"Env"`
}

InspectConfig carries the fields inside the "Config" object that we need: the hostname (container's internal name) and the effective env.

type InspectHostConfig added in v0.2026197.1320

type InspectHostConfig struct {
	NetworkMode string `json:"NetworkMode"`
}

InspectHostConfig carries the fields inside the "HostConfig" object that we need — currently just NetworkMode so host-networked containers can be detected (their NetworkSettings.Ports is empty, but container ports are bound 1:1 to host ports).

type InspectMount added in v0.2026197.1320

type InspectMount struct {
	Type        string `json:"Type"`
	Name        string `json:"Name,omitempty"`
	Source      string `json:"Source"`
	Destination string `json:"Destination"`
}

InspectMount describes a single mount attached to the container. For named volumes Name is set and Source is the volume's _data path; for bind mounts Name is empty and Source is the host directory.

type InspectNetwork added in v0.2026197.1320

type InspectNetwork struct {
	IPAddress string                        `json:"IPAddress"`
	Networks  map[string]InspectNetworkBind `json:"Networks"`
	// Ports keys are like "6379/tcp"; values are nil when unexposed or
	// a slice of bindings when published.
	Ports map[string][]InspectPortBind `json:"Ports"`
}

InspectNetwork carries IP address and port-binding data. Both top-level IPAddress and per-network IPAddress are captured — podman rootless with the default network uses the per-network form; docker bridge uses the top-level form.

type InspectNetworkBind added in v0.2026197.1320

type InspectNetworkBind struct {
	IPAddress string `json:"IPAddress"`
}

InspectNetworkBind is the per-network record under NetworkSettings.Networks.

type InspectPortBind added in v0.2026197.1320

type InspectPortBind struct {
	HostIp   string `json:"HostIp"`
	HostPort string `json:"HostPort"`
}

InspectPortBind is the host-side record of a port publication.

type JumpKind added in v0.2026190.848

type JumpKind = exec.JumpKind

type LabelDescriptionSet added in v0.2026191.2205

type LabelDescriptionSet = spec.LabelDescriptionSet

func MergeDeployDescriptions added in v0.2026197.1537

func MergeDeployDescriptions(baked *LabelDescriptionSet, localPlan []spec.Step, originName string) *LabelDescriptionSet

MergeDeployDescriptions overlays a deployment node's local `plan:` steps onto a label-baked LabelDescriptionSet's Deploy section. A baked deploy step with the same step id is replaced by the local one; otherwise the local step is appended. This is the per-host override surface for acceptance steps (charly.yml deploy entries). If localPlan is empty, returns baked unchanged.

type LabeledDescription added in v0.2026191.2205

type LabeledDescription = spec.LabeledDescription

LabeledDescription + LabelDescriptionSet are CUE-sourced in spec (boxmetadata.cue, P2B, #60) and ALIASED here — spec.BoxMetadata.Description contains a *LabelDescriptionSet and spec sits below kit, so containment forces them down to spec (like VolumeMount → spec). Consumers keep using kit.LabelDescriptionSet / kit.LabeledDescription unchanged; the IsEmpty method lives in spec/labelset_methods.go beside the type.

type LedgerLock added in v0.2026190.848

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

LedgerLock is an acquired advisory lock on the ledger directory. Call Release() when done. Panic-safe via defer.

func AcquireLedgerLock added in v0.2026190.848

func AcquireLedgerLock(paths *LedgerPaths) (*LedgerLock, error)

AcquireLedgerLock takes a blocking exclusive flock on the ledger lock file via the shared AcquireFileLock primitive (filelock.go). Blocks until the lock is available.

func (*LedgerLock) Release added in v0.2026190.848

func (l *LedgerLock) Release() error

Release releases the flock and closes the file.

type LedgerPaths added in v0.2026190.848

type LedgerPaths struct {
	Root     string // ~/.config/opencharly/installed
	Deploys  string // <Root>/deploys/
	Candies  string // <Root>/layers/
	LockFile string // <Root>/.lock
}

LedgerPaths describes where ledger files live on disk. Extracted so tests can redirect to a temp dir.

func DefaultLedgerPaths added in v0.2026190.848

func DefaultLedgerPaths() (*LedgerPaths, error)

DefaultLedgerPaths returns the canonical paths anchored at the invoking user's home directory.

func (*LedgerPaths) Ensure added in v0.2026190.848

func (p *LedgerPaths) Ensure() error

Ensure creates the ledger directory tree if missing.

type LocalImageInfo added in v0.2026197.1320

type LocalImageInfo = container.LocalImageInfo

LocalImageInfo describes an image present in the engine's local storage.

type MethodSpec

type MethodSpec struct {
	// Path is the `charly check <verb> <method...>` subcommand path.
	Path []string
	// Required names the plugin-input fields that must be set for this method.
	Required []string
	// PosArgs builds the positional args inserted after the image name, before -i.
	PosArgs func(c *spec.Op) []string
	// Artifact marks a state-dependent capture method (screenshot) whose produced
	// file is validated.
	Artifact bool
	// SkipBox = true means the verb targets a cluster/other non-image target, so the
	// usual image/deploy-name positional is NOT inserted.
	SkipBox bool
}

MethodSpec is one method's nested-CLI dispatch spec: the `charly check <verb> <method...>` subcommand path, the required #Op modifiers, the positional-arg builder, and the artifact / skip-box flags. A plugin's method allowlist is a map[string]MethodSpec; fields are exported so a candy module can author it.

type NestedExecutor added in v0.2026190.848

type NestedExecutor = exec.NestedExecutor

type NestedJump added in v0.2026190.848

type NestedJump = exec.NestedJump

type ParsedPortMapping added in v0.2026190.848

type ParsedPortMapping = spec.ParsedPortMapping

The pure podman port-mapping PARSE/FORMAT value-vocabulary — the ParsedPortMapping value type + ParsePortMapping / FormatPortMapping / StripPortSuffix — now lives in spec (#55 value-type consolidation; stdlib-only, no mechanism dependency, in-process only, so it is a plain spec Go value, not a wire type). These forwarders keep kit's own host-coupled port helpers (ParseHostPort / CheckPortAvailability / ApplyPortOverrides / AllocateAutoPorts / …) + the deploy candies compiling against kit.* unchanged.

func AllocateAutoPorts added in v0.2026190.848

func AllocateAutoPorts(containerPorts []int, occupied map[int]bool) ([]ParsedPortMapping, error)

AllocateAutoPorts probes free TCP host ports — one per container port, in declaration order. The `occupied` set names host ports already in use by other deployments (so two `port: [auto]` deploys on the same host don't collide). Returned mappings have BindAddr="" (default host bind) and Protocol="tcp". Each successful allocation is recorded back into `occupied` so subsequent calls in the same BundleConfig pass see the reservation.

Free-port discovery uses the same net.Listen("tcp","127.0.0.1:0") + immediate-close pattern already used by ssh_tunnel.go:78 and vnc_helpers.go's unixToTcpBridge — the OS picks an ephemeral port, we close, and the caller binds in the (small) window before the OS reassigns.

type ParsedRef added in v0.2026198.345

type ParsedRef = spec.ParsedRef

remote_ref.go — re-export of the remote-ref parsing helpers, RELOCATED to spec (#55 value extraction). The ref-parsing VOCAB (ParsedRef/IsRemoteImageRef/ParseRemoteRef/SplitRepoAndSubPath) already lived in spec/spec/ref_parse.go; kit's former self-contained copy was a duplicate (R3), now collapsed. StripURLScheme/ResolveBoxName rejoined it in spec/spec/box_name.go. kit re-exports them here so every existing kit.ParseRemoteRef / kit.ResolveBoxName / … call site (plugins + sdk) is untouched. New consumers should reference spec.* directly.

type PlanContext added in v0.2026191.2205

type PlanContext interface {
	// Distros is the image's distro tag list, for the exclude_distros: skip.
	Distros() []string
	// Mode selects RunModeLive vs RunModeBox routing.
	Mode() RunMode
	// VerifyOnly restricts a plan walk to idempotent verify steps (check:/agent-check:),
	// skipping mutating steps (run:/agent-run:) — the check live / check box mode.
	VerifyOnly() bool
	// SkipDeterministicRun skips deterministic run: install-timeline steps while still
	// running check:/agent-check: and the agent-graded agent-run: — the feature-run mode.
	SkipDeterministicRun() bool
	// ContextSkipReason returns a non-empty skip message when the op's effective execution
	// context is not active in the current mode (box→build, live→runtime); "" means the op
	// runs. Wraps the core VerbCatalog grammar so ExecContext never crosses this seam.
	ContextSkipReason(op *spec.Op) string
	// EffectiveDo resolves the op's do-mode (the keyword-stamped intentDo, else the verb's
	// VerbCatalog default, else DoAssert). Wraps the core grammar.
	EffectiveDo(op *spec.Op) spec.DoMode
	// EffectiveEnv builds the variable-expansion env for the current step (the resolver base
	// overlaid with cross-deployment ${HOST:…} addresses + the scenario captures).
	EffectiveEnv() map[string]string
	// ProbeNeverHang is the per-probe-attempt never-hang ceiling for op.
	ProbeNeverHang(op *spec.Op) time.Duration
	// SwapVenue retargets the host executor/resolver to op's per-step venue for the duration
	// of one dispatch, returning a restore func (nil when no swap) and a non-empty failReason
	// when the venue could not be resolved (the op is reported FAIL). The core impl mutates
	// the *Runner it wraps, so EffectiveEnv + the verb dispatch see the swapped venue.
	SwapVenue(op *spec.Op) (restore func(), failReason string)
	// Scenario is the per-run capture/var context; SetScenario installs a fresh one for a
	// RunPlan walk (restored by the caller).
	Scenario() *ScenarioContext
	SetScenario(sc *ScenarioContext)
	// Verbs is the verb-dispatch seam; Grader is the agent-step grader (nil when unbound).
	Verbs() VerbResolver
	Grader() StepGrader
}

PlanContext is the host-driver surface the plan walk consumes. The core *Runner implements it; a plugin running a plan in-proc supplies its own impl. Every method is kind-blind — no concrete kind, no provider word crosses this interface (spec.ExecContext is a plain vocabulary type; the grammar that consults it stays core behind ContextSkipReason / EffectiveDo).

type PlanGrammar added in v0.2026194.1605

type PlanGrammar interface {
	// EffectiveDo resolves op's do-mode (the keyword-stamped intentDo wins, else the verb's
	// VerbCatalog default, else DoAssert).
	EffectiveDo(op *spec.Op) spec.DoMode
	// InContext reports whether op is legal in the run's active context: runtime=true → the
	// live (runtime) context, runtime=false → the box (build) context.
	InContext(op *spec.Op, runtime bool) bool
	// ContextsLabel is op's effective-contexts list pre-formatted for the context-skip message.
	ContextsLabel(op *spec.Op) string
}

PlanGrammar is the do-mode + execution-context grammar seam. charly core's VerbCatalog impl stays core (ExecContext lives in deploykit, which imports kit, so the concrete context enum never crosses this seam: the context predicate is a bool and the skip-message contexts a pre-formatted string).

type PollCond added in v0.2026186.739

type PollCond = exec.PollCond

type PollFunc added in v0.2026186.739

type PollFunc = exec.PollFunc

type PortConflict added in v0.2026190.848

type PortConflict struct {
	HostPort  int
	ContPort  int
	Owner     string // container name, or "unknown"
	OwnerType string // "charly-container", "container", "host-process"
}

PortConflict describes a host port that is already in use.

func CheckPortAvailability added in v0.2026190.848

func CheckPortAvailability(ports []string, bindAddr string, engine string) []PortConflict

CheckPortAvailability tests whether each host port can be bound. Returns a list of conflicts for ports that are already in use. Detects /udp suffix and uses UDP bind check accordingly.

type ProvisionActor

type ProvisionActor = checkstep.ProvisionActor

ProvisionActor is the OPTIONAL second role of a host-coupled verb candy: the do:act renderer for a state-provision verb. Aliased to checkstep.ProvisionActor.

type RefsDownloader added in v0.2026192.1026

type RefsDownloader = spec.RefsDownloader

refs_downloader.go — the swappable remote-repo FETCH BACKEND seam (P7). The interface itself relocated to sdk/spec (FLOOR-SLIM axis-A mechanical batch, alongside DocParser/ProjectWalker/ CandyScanner) so charly core's plugin_inproc.go can type-assert against it without importing kit; aliased here so every existing kit.RefsDownloader reference (candy/plugin-refs, charly/refs_threaded.go) keeps compiling unchanged. Only the DOWNLOAD is pluggable — the backend that turns a (repoPath, version) into a populated local cache tree. The fetch ORCHESTRATION (local-override resolution, cache-hit short-circuit, and the post-fetch schema auto-migration) used to be a P7-era "the host keeps it" boundary; K1 unit 4 relocated it to sdk/loaderkit.EnsureRepoDownloaded (reached through the ProjectLoader seam), since the v2 end-state ("core does not parse config, resolve, build, deploy, or check") supersedes that earlier boundary the same way K1 unit 1 already superseded materialize.go's own former "stays core, clause M" self-classification. The host still supplies the registry-touching legs (the resolved RefsDownloader, the migrate-command dispatch) as spec.RefsCollectSeams callbacks.

type ResolvedRuntime added in v0.2026190.848

type ResolvedRuntime = hostenv.ResolvedRuntime

runtime_config.go — re-export of the user-level runtime configuration subsystem, RELOCATED to spec/hostenv/runtime_config.go (#55 host-env fabric extraction). Resolving engine / run-mode / storage paths from env > config-file > defaults is a HOST-ENVIRONMENT primitive, homed in spec/hostenv. kit re-exports the types + funcs so the ~23 kit.ResolveRuntime / kit.RuntimeConfig / kit.ResolvedRuntime / … call sites (charly core + plugins) are untouched.

NOTE: the two test-injection SEAM vars — RuntimeConfigPath and SystemdUserRuntimeDir — are DELIBERATELY NOT re-exported. A live var that callers REASSIGN cannot be `var X = hostenv.X` aliased (that copies the value, so a reassignment through kit would never reach hostenv's own reader). Every reader/writer references hostenv.RuntimeConfigPath / hostenv.SystemdUserRuntimeDir directly (charly host_build_hostprobe.go, plugin-settings, and the test-injection sites).

type Result

type Result = spec.CheckVerbResult

Result is a host-coupled verb's verdict — homed in the spec contract module (spec.CheckVerbResult, checkcontext.go), aliased here so the Pass/Fail/Skip constructors and every candy call site compile unchanged. charly converts it to its internal CheckResult (stamping the Op/Verb/timing) at the dispatch boundary.

func Fail

func Fail(msg string) Result

func Failf

func Failf(format string, a ...any) Result

func Pass

func Pass(msg string) Result

Pass / Fail / Skip are the verdict constructors a verb returns; the *f variants take a printf format (mirror charly's passf/failf/skipf).

func Passf

func Passf(format string, a ...any) Result

func Skip

func Skip(msg string) Result

func Skipf

func Skipf(format string, a ...any) Result

type ReverseExecutor added in v0.2026190.848

type ReverseExecutor interface {
	ReverseDryRun() bool
	ReverseKeepRepoChanges() bool
	ReverseKeepServices() bool
	ReverseRunner() ReverseRunner
}

ReverseExecutor is the interface ReverseOp handlers expect. Allows us to pass either BundleDelCmd (for real teardown) or a test mock.

ReverseRunner returns the shell-runner used to execute reversal commands. When non-nil, handlers dispatch through it (so VM teardown runs commands over SSH instead of locally); when nil, handlers fall back to local `exec.Command` — the long-standing host-teardown path.

type ReverseOp added in v0.2026190.848

type ReverseOp = spec.ReverseOp

type ReverseOpKind added in v0.2026190.848

type ReverseOpKind = spec.ReverseOpKind

type ReverseRunner added in v0.2026190.848

type ReverseRunner interface {
	// RunSystem runs a bash script as root (wraps with sudo on host
	// runners; uses `ssh sudo bash -s` on VM runners).
	RunSystem(script string) error
	// RunUser runs a bash script as the deploy user (no sudo).
	RunUser(script string) error
}

ReverseRunner executes reversal shell scripts at the requested privilege level. Implemented by both the local-exec host runner and the SSH-based VM runner (SSHReverseRunner, below).

type RunMode

type RunMode = spec.CheckRunMode

RunMode mirrors charly's RunMode: the mode a check runs under. The type (+ its consts + String()) is homed in the spec contract module (spec.CheckRunMode, checkcontext.go) with the rest of the check-verb contract cluster, so charly core references the contract while importing only spec; aliased here (with the const re-exports) so candy call sites compile UNCHANGED.

type Runner added in v0.2026194.1605

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

Runner wires the execution context for one pass of checks. Constructor-shaped: build it with NewRunner and reach its state through the methods below.

func NewRunner added in v0.2026194.1605

func NewRunner(cfg RunnerConfig) *Runner

NewRunner constructs a Runner from cfg, applying defaults for a zero HTTPClient (10s), DialTimeout (3s), and ProbeTimeout (the fallback ceiling; the host normally supplies its readiness-derived value).

func (*Runner) Box added in v0.2026194.1605

func (r *Runner) Box() string

Box / Instance are the deployment's image + instance names (empty under ModeBox).

func (*Runner) CandyDirs added in v0.2026194.1605

func (r *Runner) CandyDirs() map[string]string

func (*Runner) CandyScanErr added in v0.2026194.1605

func (r *Runner) CandyScanErr() error

func (*Runner) ContextSkipReason added in v0.2026194.1605

func (r *Runner) ContextSkipReason(op *spec.Op) string

ContextSkipReason returns a non-empty skip message when op's effective execution context is not active in the run's mode (box→build, live→runtime); "" means the op runs. The mode-composition and the message formatting are kit-side; only the VerbCatalog context predicate crosses the grammar seam (a nil grammar — a bare-Op test — never skips on context).

func (*Runner) DialTimeout added in v0.2026194.1605

func (r *Runner) DialTimeout() time.Duration

DialTimeout is the per-dial ceiling for host-side TCP reachability probes. HTTPClient is the engine's base client (the host HTTPDo leg derives per-request clients from it). HasRuntime reports whether the resolved env carries running-container state (false → runtime-only vars resolve to a skip). CandyDirs / CandyScanErr anchor a relative committed-APK path.

func (*Runner) Distros added in v0.2026194.1605

func (r *Runner) Distros() []string

Distros is the image's distro tag list, for the exclude_distros: skip.

func (*Runner) EffectiveDo added in v0.2026194.1605

func (r *Runner) EffectiveDo(op *spec.Op) spec.DoMode

EffectiveDo resolves op's do-mode via the injected grammar (DoAssert when no grammar is wired).

func (*Runner) EffectiveEnv added in v0.2026194.1605

func (r *Runner) EffectiveEnv() map[string]string

EffectiveEnv builds the variable-expansion env for the current step: the resolver base overlaid with cross-deployment ${HOST:…} addresses (per-run, target-independent) then the scenario captures (which win on a key collision). Copy-on-overlay keeps the base map clean across runs.

func (*Runner) Exec added in v0.2026194.1605

func (r *Runner) Exec() Executor

Exec is the venue executor for the current (possibly venue-swapped) target.

func (*Runner) Grader added in v0.2026194.1605

func (r *Runner) Grader() StepGrader

func (*Runner) HTTPClient added in v0.2026194.1605

func (r *Runner) HTTPClient() *http.Client

func (*Runner) HasRuntime added in v0.2026194.1605

func (r *Runner) HasRuntime() bool

func (*Runner) Instance added in v0.2026194.1605

func (r *Runner) Instance() string

func (*Runner) Mode added in v0.2026194.1605

func (r *Runner) Mode() RunMode

Mode selects ModeLive vs ModeBox routing.

func (*Runner) ProbeNeverHang added in v0.2026194.1605

func (r *Runner) ProbeNeverHang(op *spec.Op) time.Duration

ProbeNeverHang is the per-probe-attempt never-hang ceiling for op. It is NOT the probe's semantic timeout (the http client, dial timeout, a verb's own timeout:, and the eventually: retry all operate INSIDE it) — it is the kill-switch for a probe that wedges in its data phase. A longer author-declared timeout: is honored over the floor so a slow probe is never cut short.

func (*Runner) Run added in v0.2026194.1605

func (r *Runner) Run(ctx context.Context, checks []spec.Op) []CheckResult

Run executes the supplied checks sequentially and returns per-check results. It does not short-circuit on failure — the report shows every check's outcome. The per-check walk lives in RunOne; Runner is its driver (it implements PlanContext).

func (*Runner) Scenario added in v0.2026194.1605

func (r *Runner) Scenario() *ScenarioContext

Scenario is the per-run capture/var context; SetScenario installs one for a RunPlan walk.

func (*Runner) SetScenario added in v0.2026194.1605

func (r *Runner) SetScenario(sc *ScenarioContext)

func (*Runner) SkipDeterministicRun added in v0.2026194.1605

func (r *Runner) SkipDeterministicRun() bool

SkipDeterministicRun skips deterministic run: install-timeline steps (feature-run mode).

func (*Runner) SwapVenue added in v0.2026194.1605

func (r *Runner) SwapVenue(op *spec.Op) (func(), string)

SwapVenue retargets the executor + env + image to op's per-step venue for the duration of one dispatch, returning a restore func (nil when no swap) and a non-empty failReason when the venue cannot be resolved. It mutates the Runner in place so EffectiveEnv + the verb dispatch (which read exec/env/box) see the swapped venue — the same self-swap guard the classical inline path used (venue set, differs from the active target, and a TargetResolver is wired).

func (*Runner) Verbs added in v0.2026194.1605

func (r *Runner) Verbs() VerbResolver

Verbs is the verb-dispatch seam; Grader is the agent-step grader (nil when unbound).

func (*Runner) VerifyOnly added in v0.2026194.1605

func (r *Runner) VerifyOnly() bool

VerifyOnly restricts the walk to idempotent verify steps (check:/agent-check:).

func (*Runner) VmName added in v0.2026194.1605

func (r *Runner) VmName() string

VmName is the caller-set VM domain-target (the resolved per-deploy domain identity; empty for non-VM deployments); VmTargetName falls back to Box, the name the host vm/spice legs hand the plugin as the libvirt-domain target.

func (*Runner) VmTargetName added in v0.2026194.1605

func (r *Runner) VmTargetName() string

type RunnerConfig added in v0.2026194.1605

type RunnerConfig struct {
	Exec Executor
	Mode RunMode

	// Env is the resolved variable map (the host CheckVarResolver.Env); HasRuntime mirrors
	// CheckVarResolver.HasRuntime (false → runtime-only vars resolve to an unresolved skip).
	Env        map[string]string
	HasRuntime bool

	Distros  []string
	Box      string
	Instance string
	// VmName is the VM domain-target name the host vm/spice/libvirt verb legs address for a VM
	// deployment: the caller sets it to the already-resolved per-deploy domain identity
	// (charly-<VmName> is the live libvirt domain). Empty for non-VM deployments, where
	// VmTargetName falls back to Box.
	VmName string

	HostVars map[string]string
	// CandyDirs maps candy name → resolved source dir (relative committed-APK anchoring);
	// CandyScanErr is its build error (only an apk-anchoring check consults it). Host-read.
	CandyDirs    map[string]string
	CandyScanErr error

	VerifyOnly           bool
	SkipDeterministicRun bool

	Scenario *ScenarioContext
	Grader   StepGrader

	// Injected host seams (all required for a live run; a bare-Op test may leave Grammar/
	// TargetResolver nil — SwapVenue/ContextSkipReason then no-op, matching the classical path).
	Verbs          VerbResolver
	Grammar        PlanGrammar
	TargetResolver VenueResolver

	// Optional overrides — zero values take the NewRunner defaults.
	DialTimeout  time.Duration
	ProbeTimeout time.Duration
	HTTPClient   *http.Client
}

RunnerConfig carries every field a Runner is built with. The host fills it (folding the former post-construction field-pokes + attachCheckRunnerContext); zero DialTimeout/ProbeTimeout/ HTTPClient fall back to sensible defaults in NewRunner.

type RuntimeConfig added in v0.2026190.848

type RuntimeConfig = hostenv.RuntimeConfig

runtime_config.go — re-export of the user-level runtime configuration subsystem, RELOCATED to spec/hostenv/runtime_config.go (#55 host-env fabric extraction). Resolving engine / run-mode / storage paths from env > config-file > defaults is a HOST-ENVIRONMENT primitive, homed in spec/hostenv. kit re-exports the types + funcs so the ~23 kit.ResolveRuntime / kit.RuntimeConfig / kit.ResolvedRuntime / … call sites (charly core + plugins) are untouched.

NOTE: the two test-injection SEAM vars — RuntimeConfigPath and SystemdUserRuntimeDir — are DELIBERATELY NOT re-exported. A live var that callers REASSIGN cannot be `var X = hostenv.X` aliased (that copies the value, so a reassignment through kit would never reach hostenv's own reader). Every reader/writer references hostenv.RuntimeConfigPath / hostenv.SystemdUserRuntimeDir directly (charly host_build_hostprobe.go, plugin-settings, and the test-injection sites).

type RuntimeVmConfig added in v0.2026190.848

type RuntimeVmConfig = hostenv.RuntimeVmConfig

runtime_config.go — re-export of the user-level runtime configuration subsystem, RELOCATED to spec/hostenv/runtime_config.go (#55 host-env fabric extraction). Resolving engine / run-mode / storage paths from env > config-file > defaults is a HOST-ENVIRONMENT primitive, homed in spec/hostenv. kit re-exports the types + funcs so the ~23 kit.ResolveRuntime / kit.RuntimeConfig / kit.ResolvedRuntime / … call sites (charly core + plugins) are untouched.

NOTE: the two test-injection SEAM vars — RuntimeConfigPath and SystemdUserRuntimeDir — are DELIBERATELY NOT re-exported. A live var that callers REASSIGN cannot be `var X = hostenv.X` aliased (that copies the value, so a reassignment through kit would never reach hostenv's own reader). Every reader/writer references hostenv.RuntimeConfigPath / hostenv.SystemdUserRuntimeDir directly (charly host_build_hostprobe.go, plugin-settings, and the test-injection sites).

type SSHArgs added in v0.2026186.739

type SSHArgs = exec.SSHArgs

type SSHExecutor added in v0.2026190.848

type SSHExecutor = exec.SSHExecutor

type SSHReverseRunner added in v0.2026201.1934

type SSHReverseRunner struct {
	Exec *SSHExecutor
}

SSHReverseRunner adapts an *SSHExecutor to ReverseRunner (P13-KERNEL), so a teardown replays recorded ReverseOps over SSH — inside a VM guest, or on a `local: {host: user@machine}` remote — instead of on the operator host.

func (*SSHReverseRunner) RunSystem added in v0.2026201.1934

func (r *SSHReverseRunner) RunSystem(script string) error

func (*SSHReverseRunner) RunUser added in v0.2026201.1934

func (r *SSHReverseRunner) RunUser(script string) error

type ScenarioContext added in v0.2026191.1837

type ScenarioContext struct {
	// CurrentStepID is rewritten for each step as the plan run executes, so ${STEP_ID}
	// references resolve to the currently-running step's identifier. Reporters surface this
	// on failures.
	CurrentStepID string

	// Backgrounds tracks PIDs of host-side processes spawned by `command:` verbs with
	// `background: true`. Reaped at plan teardown via SIGTERM (best-effort; non-fatal on
	// failure).
	Backgrounds []int

	// Results accumulates CheckResults from steps that have completed, indexed by step ID.
	// Used by the `summarize:` verb to walk prior steps' Elapsed durations and compute
	// distribution metrics.
	Results map[string]CheckResult
	// contains filtered or unexported fields
}

ScenarioContext carries per-plan-run mutable state across the execution of that run's steps — principally the capture store populated by checks with `capture: <name>`. Instantiated fresh per plan run (and per count expansion) so cross-run state never leaks.

The struct also threads the current step identifier through variable expansion (${STEP_ID}) so artifact paths and narrative text can embed stable references without the runner having to know about them.

A ScenarioContext is OWNED by one plan run's execution pass. When that pass completes, the context is discarded; the next run gets a fresh one. `capture:` values never survive the run that produced them.

func NewScenarioContext added in v0.2026191.1837

func NewScenarioContext() *ScenarioContext

NewScenarioContext returns an empty plan-run context.

func (*ScenarioContext) AddBackground added in v0.2026191.1837

func (s *ScenarioContext) AddBackground(pid int)

AddBackground records a PID for later teardown reaping. Thread-safe.

func (*ScenarioContext) ApplyToEnv added in v0.2026191.1837

func (s *ScenarioContext) ApplyToEnv(env map[string]string)

ApplyToEnv merges plan-run-scope variables into an env map for variable expansion. Called by the runner immediately before `Check.ExpandVars(env)` so the existing `${NAME[:arg]}` grammar picks up captures and the step id without knowing about the ScenarioContext type.

Keys populated:

  • STEP_ID → ctx.CurrentStepID

ApplyToEnv overlays — it never overwrites existing keys so a host-level `${STEP_ID}` override (if ever introduced for testing) continues to win. The runner builds env by copying its resolver's base env first and then calling ApplyToEnv on the copy.

func (*ScenarioContext) SnapshotBackgrounds added in v0.2026191.1837

func (s *ScenarioContext) SnapshotBackgrounds() []int

SnapshotBackgrounds returns a copy of the current backgrounds slice. Used by the teardown reaper.

type Scope added in v0.2026190.848

type Scope = spec.Scope

type ServicePackagedDesc

type ServicePackagedDesc = checkstep.ServicePackagedDesc

ServicePackagedDesc is the candy-decodable construction input for a service-packaged step. Aliased to checkstep.ServicePackagedDesc.

type ShellExecutor added in v0.2026190.848

type ShellExecutor = exec.ShellExecutor

type ShellKind

type ShellKind string

ShellKind classifies the venue's login shell.

const (
	ShellBash ShellKind = "bash"
	ShellZsh  ShellKind = "zsh"
	ShellFish ShellKind = "fish"
)

func DetectShellFromPath

func DetectShellFromPath(shellPath string) ShellKind

DetectShellFromPath maps a $SHELL path (or shell base name) to a ShellKind. Unknown / empty shells default to bash — the POSIX-safest choice.

type Status

type Status = spec.Status

Status is a check verdict. It is the ONE pass/fail/skip enum for the check engine and every plugin candy — charly's CheckStatus is a type alias of it. FLOOR-SLIM Unit 4: Status itself (+ the iota consts + the String() method) moved to spec.Status (sdk/spec/status_result.go) as part of the CheckResult wire-envelope split — gengotypes has no construct for an iota enum + Stringer, so CUE owns the wire VALUE SET (a plain int) and Go owns the formatting behavior there. Kept as a type ALIAS here (not a repointed reference) so kit.Result + every out-of-process plugin candy's kit.Pass/Fail/Skip/StatusPass call sites compile UNCHANGED — this is a public SDK surface with ~15 external consumers, out of scope for an internal core-floor refactor.

type StepDescriptor

type StepDescriptor = checkstep.StepDescriptor

StepDescriptor is the candy-decodable construction input for a TYPED install-plan step. Aliased to checkstep.StepDescriptor.

type StepGrader added in v0.2026191.2205

type StepGrader interface {
	Grade(ctx context.Context, req GraderRequest) CheckResult
}

StepGrader judges an agent step (agent-run:/agent-check:). The host impl spawns the configured kind:agent CLI to probe the live target and return a pass/fail verdict; nil when no grader is bound (agent steps then advisory-skip, or fail under strict).

type StepKind added in v0.2026190.848

type StepKind = spec.StepKind

type StepKindName

type StepKindName = checkstep.StepKindName

StepKindName names the TYPED install-plan step a step-providing verb lowers into. Aliased to checkstep.StepKindName (the body lives there).

type StepPass added in v0.2026194.1605

type StepPass = spec.StepPass

StepPass is the verbatim stdout/stderr/exit-code of a host-delegated guest sub-invocation. Aliased to spec.StepPass (the body lives there).

type StepProvider

type StepProvider = checkstep.StepProvider

StepProvider is the OPTIONAL third role of a host-coupled verb candy: a verb whose build/deploy ACT lowers into a TYPED install-plan step. Aliased to checkstep.StepProvider.

type StepRecord added in v0.2026190.848

type StepRecord = spec.StepRecord

SPIKE (value-type relocation, #55 cluster 4): DeployRecord/CandyRecord/ StepRecord relocated to spec (spec/spec/ledger_records.go) — every field already resolved to a spec.* type and none carried methods, so they moved verbatim. Zero-churn aliases.

type StepResult added in v0.2026191.1732

type StepResult = spec.StepResult

StepResult is one plan step's outcome — the step's identity (keyword/text/origin/id) plus the CheckResult of running it. The result reporters consume a []StepResult. RELOCATED to the spec contract module (spec.StepResult, CUE-sourced in spec/schema/checkresult.cue, #55 CHECK-ENGINE cone Option A) so charly core's check-run seam + deploy-verify path reference it importing only spec; aliased here (kit.StepResult = spec.StepResult) so every candy call site compiles UNCHANGED. The wire shape is byte-identical: kit.CheckResult embeds spec.CheckResult + the engine-internal DeadlineExceeded (json:"-"), so on the wire only spec.CheckResult fields ever crossed — spec.StepResult.Result is spec.CheckResult directly, the SAME wire bytes. Construction sites that wrap a kit.CheckResult extract the embedded spec.CheckResult (.CheckResult).

func RunPlan added in v0.2026191.2205

func RunPlan(ctx context.Context, pc PlanContext, set *LabelDescriptionSet, strict bool) []StepResult

RunPlan executes the flat plan in a LabelDescriptionSet (already collected + include-expanded + overlay-merged) against the plan context, returning per-step results.

The context's mode selects which steps execute: VerifyOnly (check live / box) runs check:/agent-check: only; provision-and-verify (default) runs every step in order. include: steps never reach here (expanded at collect time); a residual one is a no-op skip. Agent steps route to the grader; run/check stamp the keyword-derived do-mode and dispatch through RunOne.

type SystemPackagesDesc

type SystemPackagesDesc = checkstep.SystemPackagesDesc

SystemPackagesDesc is the candy-decodable construction input for a system-packages step. Aliased to checkstep.SystemPackagesDesc.

type TagExpr added in v0.2026191.1413

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

TagExpr is an opaque compiled tag expression. Nil means "match everything" (no filter), so callers can write `if expr.Match(tags)` without a nil check.

func CombineTagFilters added in v0.2026191.1413

func CombineTagFilters(include, exclude *TagExpr) *TagExpr

CombineTagFilters composes an include-filter and an exclude-filter into one effective expression. Either side may be nil:

  • include nil, exclude nil → always matches
  • include X, exclude nil → matches when X is true
  • include nil, exclude Y → matches when Y is false
  • include X, exclude Y → matches when X is true AND Y is false

func ParseTagExpr added in v0.2026191.1413

func ParseTagExpr(src string) (*TagExpr, error)

ParseTagExpr compiles a tag expression. Empty / whitespace input produces a nil TagExpr that matches everything. A syntax error is returned rather than silently matching or silently failing.

func (*TagExpr) Match added in v0.2026191.1413

func (t *TagExpr) Match(tags []string) bool

Match reports whether the tag set satisfies the expression. A nil TagExpr matches everything.

func (*TagExpr) String added in v0.2026191.1413

func (t *TagExpr) String() string

String returns the raw source the expression was compiled from.

type Venue added in v0.2026190.848

type Venue = spec.Venue

type VenueResolver added in v0.2026194.1605

type VenueResolver func(venue string) (exec Executor, env map[string]string, hasRuntime bool, err error)

VenueResolver retargets a per-step venue (the `on:` modifier / venue swap) to a host executor, its resolved variable env, and whether that env carries runtime state. venue→executor is the host check_venue atom and the env is host var resolution — both stay core; Runner drives the swap through this seam. An unknown venue returns a non-nil err.

type VerbResolver added in v0.2026191.2205

type VerbResolver interface {
	// RunVerb resolves op's verb word and runs it, returning (result, true). (_, false)
	// means no such verb is registered — the walk reports the op as an unknown-verb skip.
	RunVerb(ctx context.Context, op *spec.Op) (spec.CheckResult, bool)
	// RunProvisionAct runs a do:act state-provision verb's act (create/configure) and
	// returns (result, true). (_, false) means the verb has no act path — the walk falls
	// through to the assert dispatch (the handler IS the act for action verbs).
	RunProvisionAct(ctx context.Context, op *spec.Op, verb string) (spec.CheckResult, bool)
}

VerbResolver is the verb-dispatch seam — the ONE thing the walk needs from the core provider registry. The core impl resolves the op's verb word and runs it (in-proc CheckVerbProvider fast path or out-of-process Invoke envelope), threading the live host CheckContext it owns; the walk never sees a provider type or the registry. VerbResolver's methods return spec.CheckResult (not the richer kit.CheckResult) — FLOOR-SLIM Unit 4: the host-side implementer (charly's hostVerbResolver) dispatches through the core-only provider registry down to package-main's own CheckVerbProvider family, which never sets DeadlineExceeded (an engine-internal retry signal kit's OWN dispatch loop, below, stamps AFTER the verb call returns) — so the interface boundary needs only the wire-portable base.

type VmSource added in v0.2026190.848

type VmSource = spec.VmSource

type VmSshStanza added in v0.2026186.739

type VmSshStanza struct {
	Alias          string // ssh-config Host alias (e.g. "charly-arch-vm"); unique within the fragment
	Hostname       string // IP/DNS ssh connects to ("127.0.0.1" for user-mode networking)
	Port           int    // host-side port forwarded to the guest's :22
	User           string // guest account ssh logs in as
	IdentityFile   string // absolute private-key path
	KnownHostsFile string // absolute per-VM known_hosts path
}

VmSshStanza captures the fields to render one ssh-config Host stanza for a VM.

type WalkOpts

type WalkOpts struct {
	// Shell overrides the detected venue login shell for the managed-block finalizer.
	Shell ShellKind
	// Home overrides the detected venue home. When empty WalkPlans probes `$HOME`.
	Home string
}

WalkOpts tunes the walk. All fields optional — WalkPlans probes the venue for the shell + home it needs (the env.d managed-block finalizer) when they are not supplied.

Jump to

Keyboard shortcuts

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