loaderkit

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: 31 Imported by: 0

Documentation

Overview

directives.go — the loaderkit-local WALK-only directives: MaxIncludeDepth, namespaceAliasRe, and validateNamespaceAlias. The kind-blind document directives (ImportList/ImportEntry, DiscoverConfig/ScanSpec, anchorScanSpecs, docShape/classifyDoc) relocated to sdk/kit (loader_directives.go + loader_classify.go) so charly core AND sdk/loaderkit share ONE copy (R3) — see spec.ImportList / spec.DiscoverConfig / kit.AnchorScanSpecs / kit.ClassifyDoc.

discover.go — the K1 port of charly/unified.go's discover walk (ApplyDiscover / applyDiscoveredManifest): scans each `discover:` spec for directories carrying its manifest (via the shared kit.FindEntityDirs primitive) and PARSES every document each discovered manifest contains into a spec.DiscoveredManifest. Faithful port, MINUS the host-only candy-image lazy-ref registration + the per-node materialize FOLD (candy/plugin-loader's own Materializer seam, K1 unit 1 — the not-found policy lives there too now; only the ACTUAL registry resolve + provider dispatch stays host-side, boundary law clause M) — this file only WALKS + PARSES, exactly like the rest of loaderkit.

load_unified.go — the K1 keystone (task #24 unit 2) port of charly's TOP-LEVEL LoadUnified ORCHESTRATION out of charly core. This is the entry point every command (build/deploy/check/validate) calls to load a project's charly.yml — the sequence of steps (bootstrap phase, early + post-merge schema gate, kind-blind walk, registry materialize, venue flatten, member fold, descent stamp, the validation chain) now lives here, kind-blind, exactly as charly/unified.go's former inline body did. Every step that touches the provider registry, the build-vocabulary plugins, or a standing K5-final-decision core file (bundle_members.go's foldMembers/validateMembers) is a SEAM CALLBACK the host supplies via LoadSeams — the same injected-seam pattern spec.WalkSeams/ spec.MaterializeSeams already established (#46); LoadUnified itself never touches the registry. charly-core's own LoadUnified(dir) becomes a thin wrapper that builds a LoadSeams from its existing host-coupled functions and delegates here.

materialize.go — the K1 unit-1 port of charly's per-node kind-decode DISPATCH POLICY (charly/node_normalize.go's former normalizeNodeInto) out of charly core. Materialize decides what to do with ONE parsed entity node: if the provider registry resolves its discriminator, the host-supplied DecodeEntity seam callback has already folded it (clause M stays host-side, unchanged — provider_registry.go / provider_kind_invoke.go are the TRUE mechanism, this package never touches the registry); otherwise Materialize applies the NOT-FOUND policy purely from the host-threaded spec.Threaded snapshot (clause D) plus two small state-check callbacks — a recognized-but-unconnected external deploy substrate routes to the bundle builder, a declared- but-unconnected kind is deferred (mid re-entrant connect pass) or warned-and-skipped, and a truly unrecognized discriminator is a hard load error. This is the exact fallback branch normalizeNodeInto's "not found" arm ran, faithfully ported (the former in-proc KindProvider fast path is NOT ported — it was dead code: spec.KindWords is permanently empty, so no real provider has ever satisfied ClassKind + KindProvider; the DecodeEntity seam callback always dispatches via the JSON-envelope path now, exactly as it already did in production).

materialize_project.go — the K1 task #48 port of charly's per-document/per-namespace MATERIALIZE + root-wins MERGE ORCHESTRATION (charly/materialize.go's former materializeLoadedProject) out of charly core. This is the kind-blind walk+merge MECHANISM: which document, which discovered node, root-wins merge, namespace fold + byID pointer-identity — none of it touches the provider registry. The three genuinely host-/registry-/bootstrap-coupled leaf legs (the per-document registry kind-decode, the bootstrap-candy-routed discovered-manifest fold, the binary-embedded default vocabulary) are reached through MaterializeProjectSeams, the same injected-seam pattern LoadSeams / spec.WalkSeams / spec.MaterializeSeams already established (#46/#47) — so this orchestration runs plugin-side (in loaderkit) exactly as LoadUnified does, calling back for only the coupled leaves. charly's hostMaterializeProjectSeams is the sole host constructor.

Package loaderkit is the importable form of charly's unified-config PARSE — the parse half of LoadUnified relocated out of charly core (P6). It decomposes one node-form YAML document into the generic, sdk-expressible spec.ParsedProject the host MATERIALIZES into the typed *spec.UnifiedFile. Shared by the loader plugin candy (candy/plugin-loader, its OpLoad) and, during the transition, by charly core — the SAME parse, one copy (R3), the way sdk/kit is the one copy of the check walk.

The parse consults ONLY spec vocabulary (CUE-sourced) + yaml + host-threaded kind-recognition DATA (Threaded) — never the provider registry directly. The registry is core fabric; the host snapshots which words it recognizes (kinds / deploy substrates / structural / scalar-sugar primaries) into Threaded before the parse, and the re-entrant connect-then-reload re-parses with an updated snapshot. That keeps the parse a kind-blind mechanism (boundary law clause D).

walk.go — the K1 port of charly's unified-config loader ORCHESTRATION: the kind-blind WALK+PARSE half of LoadUnified (charly/unified.go) relocated out of charly core. Walk drives the import queue + discover + namespaced-import mounts and PARSES every document (via the existing loaderkit.ParseDoc, P6) into a generic spec.LoadedProject envelope — it does NO materialize (registry kind-decode) and NO merge (root-wins field folding) into a typed *spec.UnifiedFile; the host replays materialize+merge over the returned spec.LoadedProject to reconstruct that, exactly as it did inline before.

Everything registry-coupled or host-coupled (the parse pre-scan + connect-declared-kind-plugins side effects, the #NodeDoc CUE validate-before-execute gate, remote-ref resolution + the project-repo cache, and the repo-identity cycle-break) is a SEAM CALLBACK the host supplies via spec.WalkSeams (#46 — the seam contract lives in sdk/spec, not here, so charly core references it without importing this package) — Walk itself calls the seam and never does the coupled work directly (boundary law clause D: Walk consults host-threaded DATA, never the provider registry).

Faithful-port mapping (see charly/unified.go + charly/ns_identity.go):

LoadUnified (the walk portion only)      → Walk
loadUnifiedInto                          → (*walker).walkFile
mergeUnifiedDocs (the parse portion)     → (*walker).parseDocs (materialize/mergeUnified excluded)
loadNamespaceCached                      → (*walker).walkNamespace
the per-project "load + discover" body   → (*walker).walkProject
ApplyDiscover/…                          → discover.go

The kind-blind document DIRECTIVES (spec.ImportList/spec.DiscoverConfig/kit.ClassifyDoc/ kit.FindEntityDirs/…) live in sdk/kit (loader_directives.go/loader_classify.go/ loader_discover.go), shared with charly core (R3) — this package keeps only its own walk-scoped namespace-alias validation (directives.go).

Index

Constants

View Source
const MaxIncludeDepth = 8

MaxIncludeDepth caps recursive include resolution. A cycle or excessive depth raises a clear error with the offending file path.

Variables

View Source
var (
	SortedDeployKeys        = spec.SortedDeployKeys
	SortedMemberKeys        = spec.SortedMemberKeys
	VenueIsAgentProvisioned = spec.VenueIsAgentProvisioned
)

SortedDeployKeys / SortedMemberKeys / VenueIsAgentProvisioned are DEFINED in the dedicated spec module (spec/spec/bundle_keys.go, #55 2b Class A) — pure bundle-map helpers with no kind-specific logic. These forwarders keep loaderkit's own callers (FoldMembers / FlattenBundleVenues here) + charly's DEPLOY-half owner-walk terse (R3, one shared abstraction).

View Source
var (
	NormalizeRepoSpec     = spec.NormalizeRepoSpec
	RepoIdentity          = spec.RepoIdentity
	RootRepoIdentity      = spec.RootRepoIdentity
	GitRemoteIdentity     = spec.GitRemoteIdentity
	NormalizeRepoIdentity = spec.NormalizeRepoIdentity
	NormalizeGitRemoteURL = spec.NormalizeGitRemoteURL
)

repo_identity.go — the repo-identity helpers (NormalizeRepoSpec / RepoIdentity / RootRepoIdentity + the git-URL normalizers) are DEFINED in the dedicated spec module (spec/spec/repo_identity.go, #55 2b Class A) so charly core reaches them without importing loaderkit. These package-level forwarders keep loaderkit's own callers + the candy consumers (candy/plugin-loader's WalkSeams defaulting, candy/plugin-build's superproject resolve) terse — same pattern as the ResolveOpts / LoaderExecutor relocations.

View Source
var (
	ProjectDistroConfig  = spec.ProjectDistroConfig
	ProjectBuilderConfig = spec.ProjectBuilderConfig
	ProjectInitConfig    = spec.ProjectInitConfig
)

vocab.go — the SHARED build-vocabulary projections (ProjectDistroConfig / ProjectBuilderConfig / ProjectInitConfig) are DEFINED in the dedicated spec module (spec/spec/vocab_project.go, #55 2b Class A): they are pure over spec types + spec's own plugin-kind decoders, so spec is their single home. These forwarders keep loaderkit's callers — charly core (format_config.go) + candy/plugin-build (the build-engine RESOLVE plugin-side) — terse, never duplicating the projection (R3).

Functions

func ApplyCueDefaults added in v0.2026217.313

func ApplyCueDefaults(kind string, out any) error

ApplyCueDefaults fills schema-declared defaults into an already-RESOLVED entity by unifying its marshaled form with #<Kind> and decoding back. It is the unify-AFTER-merge counterpart to the loader's decode (which deliberately does NOT unify, so merge/inheritance see unset-as-zero): run this only at the point an entity is finalized for use, never at load.

Only REQUIRED-with-default schema fields materialize — an optional-with-default field (`field?: *x`) stays absent on unify and does not reach the struct, so a value the caller never set for such a field is unaffected. A field already carrying a value is preserved (unify keeps the concrete value; the default only fills the gap). The canonical use is `firmware: *"bios"` in schema/vm.cue, which is required-with-default precisely so it materializes.

Because it round-trips through the CLOSED #<Kind> schema, the entity must already validate against it (it does — the loader validated it). The round-trip is lossless for every modeled field; see cue_defaults_test.go (charly).

func AssembleEntityBody added in v0.2026217.313

func AssembleEntityBody(pn spec.ParsedNode) (*yaml.Node, error)

AssembleEntityBody returns the DOCUMENT-wrapped entity-body mapping to decode: pn's body value (an empty mapping when the value is null/absent or a scalar cross-ref like `vm: pg-vm`, which the constructor consumes separately via discValue).

func BuildBundleNode added in v0.2026217.313

func BuildBundleNode(pn spec.ParsedNode, t spec.Threaded) (*spec.BundleNode, error)

BuildBundleNode recursively builds a BundleNode from a bundle/resource node. The discriminator value carries the deploy config; inline STEP children (checks) fold into the bundle's plan via DecodeNodeValue (the assembler); ENTITY children are RESOURCE members (deploy-into / alongside).

func BuildBundleNodeInto added in v0.2026217.313

func BuildBundleNodeInto(pn spec.ParsedNode, t spec.Threaded, acc *spec.MaterializedProject) error

BuildBundleNodeInto builds pn into a BundleNode and registers it in the Deploy (bundle) map. acc is the K1-unit-1 spec.MaterializedProject accumulator — this function only ever touches the Bundle field.

func BuildResourceMemberChildren added in v0.2026217.313

func BuildResourceMemberChildren(pn spec.ParsedNode, t spec.Threaded) (map[string]*spec.BundleNode, error)

BuildResourceMemberChildren decodes pn's RESOURCE-MEMBER entity children into a name→*BundleNode map via the SAME BuildBundleNode recursion — the SINGLE source of truth for authored member-tree decode (R3). Every pn.Children entry is an entity child by construction (the parse-time desugar already separates step/data children into the plan/body fields before a spec.ParsedNode ever reaches here — see charly/node_parse.go), so no discClass filter is needed. A non-resource entity child is a hard error (deploy/resource children must be pod/vm/k8s/local/android/group).

func BundleTargetForDisc added in v0.2026217.313

func BundleTargetForDisc(d string, t spec.Threaded) string

BundleTargetForDisc maps a node discriminator to the BundleNode Target — DATA-driven via t.DeployTraits (P9's plugin-declared #DeployTraits, the D-clause fact every substrate word resolves against), never a kind-word switch: a word with no declared deploy traits is TARGETLESS (`group` — the only such word today; a plugin-declared external deploy substrate DOES carry traits, the Venue="none" external-in-place default).

func CanonicalRef added in v0.2026218.157

func CanonicalRef(ref, baseDir string, seams spec.RefsCollectSeams) (key, path string, err error)

CanonicalRef resolves ref (relative to baseDir) to its dedup key and its on-disk path. A remote ref is downloaded into the shared repo cache first (and auto-migrated) via EnsureRepoDownloaded; an unpinned remote ref resolves to the repo's default branch. A local ref resolves to its absolute path, which is both key and path.

func CollectRemoteRefs added in v0.2026217.313

func CollectRemoteRefs(cfg *spec.Config, layers map[string]spec.CandyReader, seams spec.RefsCollectSeams) ([]spec.RemoteDownload, error)

CollectRemoteRefs is the default-opts wrapper (enabled images only) around CollectRemoteRefsOpts. The overwhelming majority of call sites want enabled-only collection, so they keep this three-arg form.

func CollectRemoteRefsOpts added in v0.2026217.313

func CollectRemoteRefsOpts(cfg *spec.Config, layers map[string]spec.CandyReader, opts spec.ResolveOpts, seams spec.RefsCollectSeams) ([]spec.RemoteDownload, error)

CollectRemoteRefsOpts collects all unique remote refs from charly.yml candy lists and candy manifest depends/candy fields. Different candies from the same repo can use different versions. Only the same bare ref at conflicting versions is an error. Returns a list of spec.RemoteDownload grouped by (repoPath, version).

opts gates the disabled-image walk: a disabled image's candy refs are collected when opts.ShouldIncludeDisabled(name) is true (i.e. a `--include-disabled <name>` build). This keeps the remote-ref FETCH set in lockstep with the RESOLVE set walked by ResolveAllBox / GlobalCandyOrder — the same shouldIncludeDisabled predicate gates both. Without it, a disabled named image lands in the build working set but its remote candies are never fetched/registered, surfacing as "unknown layer" while computing global candy order.

func CompleteCandyRunOps added in v0.2026209.2002

func CompleteCandyRunOps(m *spec.CandyModel, v *spec.CandyView)

CompleteCandyRunOps finishes the ONE host-completed predicate scanFromParsed's own doc comment flags as not scan-computable standalone: RunOps needs the op-context classifier (registry-adjacent D-data, reached via spec.OpInContext — task #39), so a single candy's scan can't derive it — this runs the SAME live-compute the pre-move *Candy.runOps() did (a `run:` step passes unless it is PURELY runtime-context), then OR-completes HasInstallFiles/HasContent with it (+ the already-known InitSystems term, when PopulateCandyInitSystem has run for this candy) — the associative-OR completion the scan-time partial computation deliberately deferred. MUST run on the mutable pre-wrap (Model, View) pair, before FinalizeCandyRefs+NewSpecCandyModel (a spec.CandyReader is read-only after that).

func CueDocFromYAML added in v0.2026217.313

func CueDocFromYAML(path string, data []byte) (cue.Value, error)

CueDocFromYAML ingests one YAML document into a cue.Value (the whole doc), built with the loader's own cue.Context so the result can Unify against the compiled schema's definitions.

func DecodeEntityViaCUE added in v0.2026217.313

func DecodeEntityViaCUE(node *yaml.Node, t reflect.Type, out any, label string) error

DecodeEntityViaCUE normalizes a single entity node against its Go type, then CUE-ingests + Decodes it into out (a pointer). Does not mutate the input node. The node must BE the entity value (the candy body / a single kind entity / an assembled node-form body), not a kind-keyed wrapper. Used by the kind-keyed / candy / inline / node-form decode paths.

func DecodeNodeValue added in v0.2026217.313

func DecodeNodeValue(pn spec.ParsedNode, out any) error

DecodeNodeValue decodes pn's body via the shared CUE entity decoder (decode_entity.go) into out (a *struct) — the SAME entity-body assembler + CUE decode every candy/kind/node-form decode goes through.

func DecodeStandaloneTemplateJSON added in v0.2026217.313

func DecodeStandaloneTemplateJSON(pn spec.ParsedNode, t spec.Threaded) (json.RawMessage, error)

DecodeStandaloneTemplateJSON canonicalizes pn (a substrate TEMPLATE node — no cross-ref, no resource members) to the JSON the host threads to candy/plugin-substrate (op.Env), GENERICALLY via EntityBodyJSON — with NO concrete-kind Go type.

func EnsureRepoDownloaded added in v0.2026217.313

func EnsureRepoDownloaded(repoPath, version string, seams spec.RefsCollectSeams) (string, error)

EnsureRepoDownloaded downloads the repo if not already cached. Returns the cache path. The cache is auto-migrated to the latest schema CalVer via seams.MigrateCache on EVERY access — cache HIT and fresh clone alike. Re-migrating a cache hit is required (and safe, the chain being idempotent): a cache populated by an OLDER binary — or relocated from a prior cache directory across a schema bump (an older-schema cache) — so the current binary would otherwise fail to find charly.yml. An already-current cache is a no-op.

func EntityBodyJSON added in v0.2026217.313

func EntityBodyJSON(pn spec.ParsedNode) (json.RawMessage, error)

EntityBodyJSON returns a node's kind-value mapping as canonical JSON, generically — with NO concrete-kind Go type. It is the single body→wire mechanism for both the op.Params plugin-kind path and the substrate TEMPLATE thread, so the kernel never types a spec.<Kind> merely to canonicalize a value. Reuses parse.go's entityBodyJSON (R3: the SAME yaml→map→JSON transform, already the parse's own body-serialization step — this is that same transform applied to a RECONSTRUCTED discValue rather than the freshly-parsed one).

func FinalizeScannedCandies added in v0.2026209.2002

func FinalizeScannedCandies(scanned map[string]spec.ScannedCandy, initCfg *buildkit.InitConfig) map[string]spec.CandyReader

FinalizeScannedCandies is the SOLE choke point that produces a spec.CandyReader: every construction path (ScanCandy, legacyScanCandiesDirScanned via scanLocalCandies, (*loaderkit.spec.UnifiedFile).projectCandiesScanned via scanLocalCandies, and ScanAllCandyWithConfigOpts over its combined local+remote set) funnels through here, so no path can ever wrap a candy with a term (InitSystems, RunOps) still missing — there is no OTHER way to obtain a spec.CandyReader. Order: InitSystems (initCfg-gated; a nil initCfg is a documented no-op) THEN RunOps + the HasInstallFiles/HasContent OR-fold (unconditional) THEN FinalizeCandyRefs (bare-string the refs) THEN wrap — since a CandyReader is read-only from the wrap onward. Does NOT mutate its input map: PopulateCandyInitSystem mutates `scanned` in place when initCfg is non-nil, but every OTHER step below operates on a range-loop COPY, so calling this twice against the SAME map with different initCfg values (the throwaway nil-initCfg call ScanAllCandyWithConfigOpts makes for CollectRemoteRefsOpts's edge-walk, then the real opts.InitCfg call at the end) is safe.

func FlattenBundleVenues added in v0.2026209.2002

func FlattenBundleVenues(uf *spec.UnifiedFile) error

FlattenBundleVenues stamps venue + hoists plan steps for every top-level bundle in uf. Idempotent on an already-flattened tree (members/children have empty Plan after the first pass, so re-running hoists nothing). Must run before FoldMembers (which promotes members to top-level, mutating the map) and before validateCheckBeds/validateIterateBed (which count root Plan checks).

func FoldMembers added in v0.2026209.2002

func FoldMembers(uf *spec.UnifiedFile) error

FoldMembers copies every deploy node's `peer:` entries into the Bundle map as top-level addressable entries (MemberOf set, disposability inherited), so every deploy verb resolves a member by name through the same path as any deploy. Runs BEFORE validateDeploymentTree (so folded members get the same deploy validation); a check bed is itself a `disposable: true` bundle, so a bed's members fold the same way. A member name colliding with any existing deploy/member entry is a hard error.

func FoldStandaloneTemplateReply added in v0.2026217.313

func FoldStandaloneTemplateReply(disc, name string, replyJSON json.RawMessage, acc *spec.MaterializedProject) error

FoldStandaloneTemplateReply folds candy/plugin-substrate's ECHOED template JSON into acc.PluginKinds[disc][name] — the C2-substrate TEMPLATE fold arm (the standalone counterpart of runPluginKind's deploy fold into acc.Bundle). GENERIC by construction: no per-kind-word switch — every standalone-template kind (vm/pod/k8s/local/android) folds into the SAME map[disc][name] shape PluginKinds already uses for every other templated kind (distro/builder/init/sidecar/ resource/agent), so a new standalone-template kind needs no core edit here. disc is validated by the caller (foldSubstrateKind only reaches here for a kind IsStandaloneResourceKind already confirmed), so no error return is needed in practice — kept for seam-signature symmetry with the other MaterializedProject-folding calls.

func GateSchemaVersion added in v0.2026206.1046

func GateSchemaVersion(root, version string) error

GateSchemaVersion enforces the load-time schema-version contract: a config NEWER than this binary supports → "update charly"; an OLDER/absent/non-CalVer version → the `charly migrate` hint. Shared by the early pre-parse gate (root's raw version) and the post-merge gate (merged version) so both speak identically. Pure — kit.ParseCalVer/kit.LatestSchemaVersion carry no registry coupling.

func IsDeployShape added in v0.2026217.313

func IsDeployShape(pn spec.ParsedNode) bool

IsDeployShape reports whether a substrate node is a DEPLOY (vs a standalone template): a scalar discriminator value (`vm: pg-vm` / `pod: img`) is a cross-ref deploy, and a mapping value carrying `from:` or `image:` is a deploy.

func IsResourceDisc added in v0.2026217.313

func IsResourceDisc(d string, t spec.Threaded) bool

IsResourceDisc reports whether a discriminator names a deploy-substrate kind (the markers of a bundle member / bundle-shaped node) — the CUE-derived resourceKindSet (#ResourceKind), OR a recognized external DEPLOY substrate word (t.DeploySubstrates, a registered/pre-scanned out-of-process deploy provider, e.g. `exampledeploy`), so a deploy whose edge is an external target is built as a bundle node.

func IsStandaloneResourceKind added in v0.2026217.313

func IsStandaloneResourceKind(disc string, t spec.Threaded) bool

IsStandaloneResourceKind reports whether disc names one of the 5 substrate kinds (pod/vm/k8s/local/android) — the kinds that are BOTH a standalone TEMPLATE (→ the typed uf.Pod/uf.VM/… map) and a deploy (→ uf.Bundle). DATA-driven via t.DeployTraits — the SAME kind-blind fact BundleTargetForDisc/SetBundleCrossRef resolve against — rather than a hand-kept kind-word switch. group is a structural kind too but resolves false here — it declares no #DeployTraits (no per-substrate template map; it always folds to uf.Bundle).

func LoadBundleConfigViaExecutor added in v0.2026213.1748

func LoadBundleConfigViaExecutor(ctx context.Context, ex *sdk.Executor, dir string) (*deploykit.BundleConfig, error)

LoadBundleConfigViaExecutor loads <dir>/charly.yml through the unified loader PLUGIN-SIDE (over the reverse channel when out-of-process) and projects it to a *deploykit.BundleConfig — the cycle-free, placement-invariant overlay/project read. dir is the config directory (the per-host overlay dir for a deploy read; a project dir for a project read). An ABSENT or EMPTY overlay returns a NON-NIL &deploykit.BundleConfig{} — matching deploykit.LoadBundleConfig's contract (deploy_file.go line 112: a present-but-empty config returns &BundleConfig{} so callers that range/index dc.Deploy without a nil guard keep working after an overlay's last entry is removed); the former LoadBundleConfigViaSeam round-tripped to LoadBundleConfig and inherited the same non-nil-empty wrap..

func LoadHostBundleConfigViaExecutor added in v0.2026213.1748

func LoadHostBundleConfigViaExecutor(ctx context.Context, ex *sdk.Executor) (*deploykit.BundleConfig, error)

LoadHostBundleConfigViaExecutor is the drop-in replacement for the deleted deploykit.LoadBundleConfigViaSeam(ctx, ex, caller): it reads the PER-HOST deploy overlay (~/.config/charly/charly.yml) plugin-side via LoadBundleConfigViaExecutor + the derived per-host config dir. The former `caller` label was host-side diagnostics only (threaded into the wire request); this path does not round-trip to a host handler, so no caller label is carried. Returns a NON-NIL &BundleConfig{} on an absent/empty overlay (matching deploykit.LoadBundleConfig's absent-file contract); (nil, nil) only when the config path itself can't be resolved (the dir=="" guard, matching LoadBundleConfig's path-error nil).

func LoadUnified added in v0.2026206.1046

func LoadUnified(dir string, seams LoadSeams) (*spec.UnifiedFile, bool, error)

LoadUnified reads <dir>/charly.yml and returns the fully loaded, validated *spec.UnifiedFile — the kind-blind orchestration ported verbatim from charly's former inline LoadUnified body. Every registry-coupled or standing-core- resident step is reached through seams; LoadUnified itself never imports or touches the provider registry.

func LoadUnifiedViaExecutor added in v0.2026213.1748

func LoadUnifiedViaExecutor(ctx context.Context, ex *sdk.Executor, dir string) (*spec.UnifiedFile, bool, error)

LoadUnifiedViaExecutor drives LoadUnified PLUGIN-SIDE over the reverse channel: the PURE LOAD-half seams run in-plugin and the registry-/host-coupled legs dispatch to charly's "loader-*" host legs via ex.HostBuild. dir is the project directory (a plugin obtains it from the "deploy-plugins-connect" host seam's DeployPluginsConnectReply.Dir). Returns the fully-merged, validated project the SAME way the compiled-in host loader (charly.LoadUnified) does.

func LoaderThreadedViaExecutor added in v0.2026217.313

func LoaderThreadedViaExecutor(ctx context.Context, ex *sdk.Executor) spec.Threaded

LoaderThreadedViaExecutor returns the CURRENT registry snapshot (∅ → spec.Threaded) over the reverse channel — the standalone form of executorLoaderExecutor.LoaderThreaded for a caller that needs just the registry-derived D-facts (e.g. Primaries) without driving a full LoadUnified.

func MarshalMaterialized added in v0.2026209.2002

func MarshalMaterialized(uf *spec.UnifiedFile) ([]byte, error)

MarshalMaterialized serializes a materialized spec.UnifiedFile for a reverse-leg []byte payload, PRESERVING PluginKinds (which a plain json.Marshal drops). Use it on EVERY loader leg that sends a materialized spec.UnifiedFile across the wire (the loader-materialize reply + the loader-*-validate requests).

func Materialize added in v0.2026206.837

Materialize folds ONE parsed entity node into acc via the registered Materializer plugin's not-found policy, calling back into the host-supplied seams for everything registry-coupled.

func MaterializeLoadedProject added in v0.2026209.2002

func MaterializeLoadedProject(lp *spec.LoadedProject, merged *spec.UnifiedFile, byID map[int64]*spec.UnifiedFile, seams MaterializeProjectSeams) error

MaterializeLoadedProject replays the per-document/per-namespace MATERIALIZE + root-wins MERGE over a walk envelope, reconstructing the typed *spec.UnifiedFile identically to charly's former inline loadUnifiedInto:

  1. each document (root file + flat imports, in walk order) — decode its reserved directives into a fresh sub spec.UnifiedFile, materialize its parsed nodes (registry kind-decode, via the seam), then root-wins merge the sub into merged (first-seen wins → root wins);
  2. the discovered manifests — register a lazy layer-candy `From:` reference OR materialize the node, explicit-entry-wins (via the seam, the SAME per-node handler ApplyDiscover uses, R3);
  3. the binary-embedded default vocabulary (project-wins, via the seam);
  4. the mounted namespace subtrees — recurse into merged.Namespaces[alias].

func NormalizeEntityNode added in v0.2026217.313

func NormalizeEntityNode(node *yaml.Node, t reflect.Type) error

NormalizeEntityNode canonicalizes a single entity's YAML node against the Go type t (the authored struct, e.g. CandyYAML). Mutates node in place.

func ParseCandyManifest added in v0.2026218.157

func ParseCandyManifest(path string, t spec.Threaded, vocab spec.CandyVocab) (*spec.CandyYAML, error)

ParseCandyManifest reads and unmarshals a candy manifest file. Strict schema:

  • Empty / comment-only file → zero-value spec.Candy.
  • Single top-level `candy:` key → decode its body as the candy body (canonical form).
  • `candy:` + other top-level keys → error (ambiguous shape).
  • Multi-document stream → error (the candy manifest is not a bundle file).
  • Flat form (no `candy:` wrapper) → error with migration hint.

t is the registry-derived kind-recognition snapshot the node-form parse needs; vocab is the build vocabulary the misplaced-section shape guard consults (a zero value fails the guard OPEN — no false positives — exactly as an unregistered vocabulary did in core).

func ParseDoc

func ParseDoc(doc *yaml.Node, t spec.Threaded) (directives map[string]*yaml.Node, pp spec.ParsedProject, err error)

ParseDoc decomposes a node-form document mapping into its reserved directives + the generic spec.ParsedProject (its top-level entity nodes, each with the opaque JSON body the host materializes). Faithful port of core's parseNodeTree + genericNodeToParsed.

func PickCandyVersion added in v0.2026209.2002

func PickCandyVersion(bareRef string, cands []spec.CandyCandidate) spec.CandyCandidate

PickCandyVersion arbitrates the candidates of ONE bare ref by per-entity version. Same per-entity version across different git tags => NO warning, the newest git tag wins (freshness). Different per-entity versions => warn once (naming the winner + a loser) and the newest per-entity version wins. This is the sole candy-version arbiter — direct and transitive refs both flow through it. cands is non-empty.

func PopulateCandyInitSystem added in v0.2026209.2002

func PopulateCandyInitSystem(scanned map[string]spec.ScannedCandy, initCfg *buildkit.InitConfig)

PopulateCandyInitSystem sets the per-candy CandyView.InitSystems map based on the init config — the cross-candy host-completion pass (#67 pattern): scanning a SINGLE candy can't know the project's init vocabulary, so this runs once, after EVERY candy in the project has been scanned, over the mutable pre-wrap map[string]spec.ScannedCandy (a spec.CandyReader is read-only from here, so this MUST run before the final FinalizeCandyRefs+NewSpecCandyModel wrap — see ResolveOpts.InitCfg's doc comment). Byte-identical logic to the pre-move *Candy.InitSystems population, retargeted at scanned[name].Model.Service / .Model.SourceDir / .View.InitSystems.

func ProjectCandiesScanned added in v0.2026218.157

func ProjectCandiesScanned(uf *spec.UnifiedFile, rootDir string, parseDoc func(path string) (*spec.CandyYAML, error)) (map[string]spec.ScannedCandy, error)

ProjectCandiesScanned scans or synthesizes a candy per uf.Candy entry, returning the pre-completion, pre-finalize spec.ScannedCandy values. A `from:` entry is a DIRECTORY-based candy scanned off disk through parseDoc; every other entry is INLINE and synthesized from the already- decoded body.

rootDir anchors a relative `from:` path and decides the outside-the-project Remote marking below. parseDoc is the per-document manifest parse (ParseCandyManifest bound to the caller's Threaded snapshot + build vocabulary).

func ProjectResolvedProject added in v0.2026209.2002

func ProjectResolvedProject(cfg *spec.Config, layers map[string]spec.CandyReader, uf *spec.UnifiedFile, distroCfg *buildkit.DistroConfig, builderCfg *buildkit.BuilderConfig, initCfg *buildkit.InitConfig, dir, version, calver string, seams ResolveProjectSeams, diags *spec.Diagnostics, preResolvedBoxes map[string]*buildkit.ResolvedBox) (*spec.ResolvedProject, error)

ProjectResolvedProject assembles the spec.ResolvedProject from already-loaded resolve-engine outputs — a DATA projection over the seams, no resolution logic of its own. boxes come from seams.ResolveBox (or preResolvedBoxes), candies from the scanned layers map, deploy from the folded uf.Bundle tree, calver is the wall-clock build tag threaded by the caller. When diags is nil it is FAIL-FAST (a per-box ResolveBox failure aborts with an error). When diags is non-nil it is ERROR-TOLERANT (the validate-project path): a ResolveBox failure appends a spec.Diagnostic and SKIPS that box.

preResolvedBoxes (the build-prep seam path) supplies boxes AS-IS — skipping the ResolveBox loop — so the render-prep caches (BakedMetadata/RenderCandyOrder/InitSystem/InitDef/ActiveInits/CandyCaps) are preserved on the ResolvedBoxView. nil (validate/inspect) resolves boxes fresh.

func QualifyRemoteSiblingDeps added in v0.2026198.2057

func QualifyRemoteSiblingDeps(repoPath, subPathPrefix string, refs *spec.CandyRefs)

QualifyRemoteSiblingDeps records, for a freshly-scanned remote candy, the fully-qualified "<repo>/<subpathprefix><dep>" map key of each plain-name require:/candy:/bake_plugin: dep — the same form the fetched-sibling scan keys under. It sets each ref's resolved key (CandyRefEntry.Resolved) and leaves CandyRefEntry.Raw intact, so the graph resolves on .Bare() (qualified) while the transitive fetch loop still keys on the original .Raw plain name. @-ref deps are left untouched — their bare path already resolves directly. Ported verbatim from the pre-move charly/layers.go qualifyRemoteSiblingDeps, retargeted at the CandyRefs carrier (the live *Candy's Require/IncludedCandy/BakePlugin fields this mutated in place pre-move).

func RefsSeamsFromContext added in v0.2026218.157

func RefsSeamsFromContext(ctx context.Context) (spec.RefsCollectSeams, error)

RefsSeamsFromContext builds the remote-repo fetch legs for one call. ctx MUST carry the host reverse channel; a caller without one is a contract bug, not a degraded mode, so it fails loudly rather than silently fetching nothing.

func RefsSeamsFromExecutor added in v0.2026218.157

func RefsSeamsFromExecutor(ctx context.Context, ex *sdk.Executor) spec.RefsCollectSeams

RefsSeamsFromExecutor is the explicit-executor form, for a caller that already holds the *sdk.Executor its Invoke was handed (candy/plugin-build's resolve legs) rather than fishing it back off ctx.

func ResolveAndroidEntityViaExecutor added in v0.2026217.313

func ResolveAndroidEntityViaExecutor(ctx context.Context, ex *sdk.Executor, dir, name string) (*spec.ResolvedAndroid, error)

ResolveAndroidEntityViaExecutor loads the project and resolves the named kind:android template entity via candy/plugin-substrate's OpResolve leg — the plugin-side self-load twin of the deleted "deploy-entity-resolve" seam's kind="android" branch.

func ResolveAndroidViaExecutor added in v0.2026209.2002

func ResolveAndroidViaExecutor(ctx context.Context, ex *sdk.Executor) func(json.RawMessage) (*spec.ResolvedAndroid, error)

ResolveAndroidViaExecutor builds the resolveAndroid callback ValidateAndroidDevices needs, dispatching each opaque android body to the substrate plugin's OpResolve leg (any of its 5 substrate words serves; "local" is the canonical entry, mirroring the host's invokeSubstrateTemplateResolve).

func ResolveK8sEntityViaExecutor added in v0.2026217.313

func ResolveK8sEntityViaExecutor(ctx context.Context, ex *sdk.Executor, dir, name string) (*spec.ResolvedK8s, error)

ResolveK8sEntityViaExecutor loads the project and resolves the named kind:k8s template entity via candy/plugin-substrate's OpResolve leg — the plugin-side self-load twin of the deleted "deploy-entity-resolve" seam's kind="k8s" branch.

func ResolveLifecycleDeployNodeViaExecutor added in v0.2026213.1748

func ResolveLifecycleDeployNodeViaExecutor(ctx context.Context, ex *sdk.Executor, box, instance string) (*spec.Deploy, string)

ResolveLifecycleDeployNodeViaExecutor is the drop-in replacement for the deleted deploykit.ResolveLifecycleDeployNodeViaSeam: it resolves the per-host deploy overlay entry for a start/stop/shell/cmd/logs/service verb PLUGIN-SIDE, threading the DATA a command:pod / command:cmd plugin passes into the single pod-lifecycle HostBuild request (spec.PodLifecycleRequest.Node, #55 W3 A10b) so the host's dispatchLifecycleTarget operates on the passed *spec.Deploy instead of re-reading the per-host config itself.

Byte-identical to the former core resolver: the dc.Bundle[key] lookup keyed by DeployKey, the container/""→pod Target normalization, and the {Target:pod} fallback for a bare image with no deploy entry (the former standalone-podman path). Returns (node, deployKey); node is never nil.

func ResolveMergedTreeViaExecutor added in v0.2026213.1748

func ResolveMergedTreeViaExecutor(ctx context.Context, ex *sdk.Executor, dir string) (map[string]spec.BundleNode, error)

ResolveMergedTreeViaExecutor is THE executor-driven merged deploy-node tree resolver: the merged project+operator deploy-node tree, ready for dotted-path traversal. It is the SOLE merged-tree read for both a genuine out-of-module plugin (over its reverse channel) AND charly-core's own host check seams (plugin_loader.go's resolveMergedDeployTree wraps it over an in-proc executor) — the #55 LOADER cone retired the former host-resident deploy_tree.go merged-tree read that this replaced. Its composition: the PROJECT config via LoadUnifiedViaExecutor + deploykit.ProjectBundleConfig, the per-host operator overlay via LoadHostBundleConfigViaExecutor (the cycle-free plugin-side read in this package — #55 coneC Unit C2 retired the former deploykit.LoadBundleConfigViaSeam host-handler round-trip; a plugin CANNOT call the bare deploykit.LoadBundleConfig, which silently no-ops outside charly-core's own init per the DeployStateHost placement class), merged root-wins via deploykit.MergeDeployConfigs. Returns (nil, nil) on an absent/empty project+overlay. This is the shared resolver the #55 Cone A Unit 3a seams (deploy-del-resolve / pod-config-project-volume / the check venue+gather host helpers) call so no host handler re-loads the tree with a host-resident deploykit projection+merge.

func ResolveResourceViaExecutor added in v0.2026209.2002

func ResolveResourceViaExecutor(ctx context.Context, ex *sdk.Executor) func(json.RawMessage) (*spec.ResolvedResource, error)

ResolveResourceViaExecutor builds the resolveResource callback ValidatePreemptible needs, dispatching each opaque resource body to the resource kind's OpResolve leg over InvokeProvider.

func ResolveRetentionDefaultsViaExecutor added in v0.2026218.157

func ResolveRetentionDefaultsViaExecutor(ctx context.Context, ex *sdk.Executor, dir string) (keepImages, keepCheckRuns int)

resolve_retention_defaults.go — the ONE shared plugin-side resolution of the project's defaults.keep_images / defaults.keep_check_runs, reached over the reverse channel. K-wave 2 cone R6: the loader is plugin-reachable (LoadUnifiedViaExecutor), so the former "retention-defaults" HostBuild seam (charly/host_build_retention_defaults.go, DELETED) is gone — every verb:retention caller resolves the tunables itself. The three consumers (candy/plugin-clean's own CLI, candy/plugin-check's post-run prune, candy/plugin-box's post-build prune) share this ONE function (R3 — no duplicated LoadConfig projection across three plugin modules). An absent / load-failing project degrades to 0/0 ("retention disabled"), matching the deleted seam's best-effort contract; ex nil (a placement without a reverse channel, e.g. the out-of-process CliMain path) degrades the same way — callers that must error on that placement guard for ex == nil themselves.

func ResolveVmEntityViaExecutor added in v0.2026217.313

func ResolveVmEntityViaExecutor(ctx context.Context, ex *sdk.Executor, dir, name string) (*spec.ResolvedVm, error)

ResolveVmEntityViaExecutor loads the project and resolves the named kind:vm template entity via candy/plugin-substrate's OpResolve leg — the plugin-side self-load twin of the deleted "deploy-entity-resolve" seam's kind="vm" branch.

func ResolveVmStateViaExecutor added in v0.2026218.157

func ResolveVmStateViaExecutor(ctx context.Context, ex *sdk.Executor, entity string) (*spec.VmDeployState, error)

ResolveVmStateViaExecutor reads a domain's persisted VmDeployState (instance-id, ssh_port, disk path) from the per-host deploy overlay PLUGIN-SIDE — the cycle-free replacement for the deleted "config-resolve" HostBuild seam's VmState leg (K-wave 2 cone R2 bank D). Three plugins consume it (candy/plugin-vm's hostConfigResolve, candy/plugin-deploy-vm's resolvePriorVmState, candy/plugin-kube's deployVMForwards), so it lives here once (R3). A miss or an unreadable overlay degrades to nil, matching the former seam's own swallow.

func ResolveVmViaExecutor added in v0.2026209.2002

func ResolveVmViaExecutor(ctx context.Context, ex *sdk.Executor) func(json.RawMessage) (*vmshared.VmSpec, error)

ResolveVmViaExecutor builds the resolveVm callback ValidatePreemptible needs. Returns nil for an empty/absent body (matching the host resolveVmViaPlugin).

func ResourceChildren added in v0.2026217.313

func ResourceChildren(pn spec.ParsedNode) []spec.ParsedNode

ResourceChildren returns pn's children whose discriminator is itself a resource/bundle kind (the markers of a bundle-shaped node). The deployable set is the CUE-derived resourceKindSet (#ResourceKind) — the fixed vocab alone, not the registry-derived external-substrate extension (mirrors the original's own scope).

func RunDiscover added in v0.2026206.1046

func RunDiscover(rootDir string, specs []spec.ScanSpec, seams spec.WalkSeams) ([]spec.DiscoveredManifest, error)

RunDiscover is the STANDALONE discover-only entry point (K1 keystone, task #24 unit 3): charly's ApplyDiscover (loader_threaded.go) calls this directly — reusing the SAME runDiscover/ parseDiscoveredManifest walker mechanism the whole-project Walk drives internally for its own depth-0 discover pass — rather than duplicating the walk+parse logic. Bypasses the spec.ProjectWalker plugin-swap indirection deliberately: ApplyDiscover has never gone through the whole-project walker (it talks to the parser/threaded seam primitives directly, mirroring Walk's OWN internal discover call), so a custom loader plugin overriding Walk's discover behavior was never honored by ApplyDiscover before this move either — this preserves that pre-existing scope exactly.

func ScanCandyFromLocal added in v0.2026209.2002

func ScanCandyFromLocal(localScanned map[string]spec.ScannedCandy, initCfg *buildkit.InitConfig, seams ScanSeams) (map[string]spec.CandyReader, error)

ScanCandyFromLocal is the scan pipeline's step-2-onward body (remote-ref collect, fix-point fetch, per-entity-version arbitration, host-completion + finalize), relocated verbatim from charly/layers.go's scanCandyFromLocal. A caller that already has a source of localScanned (the root project scan, or a namespace's own projectCandiesScanned set) reaches the SAME pipeline by supplying the seams. Behavior-identical to the pre-move function: same steps 2-5, same order. initCfg is the project init: vocabulary threaded into the FINAL finalize choke point (nil for a non-generate caller — matches the pre-move opts.InitCfg).

func ScanCandyManifest added in v0.2026198.2057

func ScanCandyManifest(path, name, manifestName string, parseDoc func(path string) (*spec.CandyYAML, error)) (spec.CandyModel, spec.CandyView, spec.CandyRefs, error)

ScanCandyManifest scans a single candy DIRECTORY (the `from:`-directory / legacy-discovered-candy path) and returns its two resolved envelope views. Named distinctly from the ESTABLISHED exported charly.ScanCandy(dir) (the whole-project scan-all-candies entry point) to avoid confusion between the two similarly-shaped-but-different scans while both exist side by side during the cutover. parseDoc is the injected per-document parse seam (charly's parseCandyYAML — registry-coupled, so it stays host-side; see the CandyScanner interface doc in sdk/spec/loader_seam.go).

func ScanInlineCandy added in v0.2026198.2057

func ScanInlineCandy(name, sourceDir string, ly *spec.CandyYAML) (spec.CandyModel, spec.CandyView, spec.CandyRefs)

ScanInlineCandy builds the two resolved envelope views + the rich pre-qualification refs for a candy declared INLINE in a unified charly.yml (no separate manifest file — ly is already the parsed body). sourceDir is the charly.yml's own directory (an inline candy's SourceDir always equals its declaring file's dir). Mirrors the pre-move charly/unified.go synthesizeInlineCandy, which called the SAME populateCandyFromYAML + fs-probe pair scanCandy did — the two paths can never drift because they share this one function.

func ScanRemoteCandy added in v0.2026198.2057

func ScanRemoteCandy(repoDir, repoPath string, wantRefs map[string]bool, parseDoc func(path string) (*spec.CandyYAML, error)) (map[string]spec.ScannedCandy, error)

ScanRemoteCandy scans specific candies out of a downloaded remote repository directory — only the bare refs in wantRefs (each "github.com/org/repo/candy/name" form, keyed by that same bare ref in the returned map). Ported verbatim from the pre-move charly/layers.go ScanRemoteCandy: for each wanted ref, ScanCandyManifest the candy dir, then set the resulting CandyView's Remote/RepoPath/SubPathPrefix (the post-scan "construct-then-mutate" step scan-time cannot do itself — a candy doesn't know it's REMOTE until the fetch layer resolves it there) and qualify its plain-name sibling deps (QualifyRemoteSiblingDeps) so the dependency graph + validator can resolve a remote candy's transitive deps against siblings pulled from the same repo, without per-call-site repo-path plumbing.

func SetBundleCrossRef added in v0.2026217.313

func SetBundleCrossRef(dn *spec.BundleNode, disc, ref string, t spec.Threaded)

SetBundleCrossRef sets the deploy's cross-ref from a scalar discriminator value (EDGE-INHERIT cutover B): DATA-driven via t.DeployTraits' ImageBacked trait (declared true for pod alone, per the canonical #DeployTraits table) rather than a kind-word switch — an image-backed substrate's scalar is the IMAGE it runs; every other substrate's scalar is the same-kind template it inherits (`from:`). A targetless word (traits == nil) sets neither.

func SingleCandyMappingNode added in v0.2026218.157

func SingleCandyMappingNode(path string, data []byte) (*yaml.Node, error)

SingleCandyMappingNode parses a candy manifest's bytes as a YAML multi-document stream and returns the single top-level mapping node (DocumentNode unwrapped). It returns (nil, nil) when the stream holds no non-empty document (an all-comment / null file → zero-value candy body), and errors on a multi-document stream or a non-mapping top level.

func StampBundleDescents added in v0.2026209.2002

func StampBundleDescents(uf *spec.UnifiedFile, t spec.Threaded)

StampBundleDescents stamps every deploy node's venue-hop descent descriptor from the DeployTraits DATA snapshot, replacing the former registry-live charly stampBundleDescents. Idempotent.

func UnmarshalMaterialized added in v0.2026209.2002

func UnmarshalMaterialized(data []byte, uf *spec.UnifiedFile) error

UnmarshalMaterialized reconstructs a materialized spec.UnifiedFile from a MarshalMaterialized payload INTO uf, re-attaching PluginKinds at every namespace level so the result is byte-identical to the source. uf must be non-nil (the leg's own `merged`/scratch spec.UnifiedFile).

func ValidateAndroidDevices added in v0.2026209.2002

func ValidateAndroidDevices(uf *spec.UnifiedFile, resolveAndroid func(json.RawMessage) (*spec.ResolvedAndroid, error)) error

ValidateAndroidDevices enforces the kind:android device source invariant: a device is EXACTLY ONE of an in-pod emulator (box:) XOR a remote/physical adb endpoint (adb:) — never both, never neither. This is the entity-level XOR the #Android CUE schema formerly expressed via a trailing `& ({box:_} | {adb:_})` disjunction (dropped because gengotypes collapses an entity-level disjunction to an empty struct — see schema/android.cue). Runs at LOAD time so EVERY command that resolves a device (charly bundle add android:, charly check run, charly box validate, …) sees the same friendly error. resolveAndroid projects one opaque android template body into its *spec.ResolvedAndroid via the registry (host-threaded).

func ValidateCandyManifestCUE added in v0.2026217.313

func ValidateCandyManifestCUE(path string, data []byte, t spec.Threaded, parser spec.DocParser) error

ValidateCandyManifestCUE validates a candy manifest. A legacy kind-keyed manifest validates the WHOLE document against #NodeDoc (the structural gate), then walks the parsed + DESUGARED node tree: each candy node's assembled body validates against #CandyValue concretely and every entity's plan steps type against the closed #Step (ValidateNodeFormSteps → ValidateEntityNodeRec) — the desugared tree is the validation subject, never the raw sugar bytes.

func ValidateCheckBeds added in v0.2026209.2002

func ValidateCheckBeds(uf *spec.UnifiedFile, t spec.Threaded) error

ValidateCheckBeds enforces the kind:check bed-specific invariants beyond the generic deploy validation (which already runs on the folded beds via ValidateDeploymentTree, covering the pod `box:` requirement). Runs at LOAD time so EVERY command that resolves a bed (charly check run, charly bundle add, charly config, charly box validate, …) sees the same friendly error.

func ValidateEntityCUE added in v0.2026218.157

func ValidateEntityCUE(kind, label string, entity cue.Value) error

ValidateEntityCUE is ValidateEntityClosedCUE's CONCRETE twin: it unifies a single entity with #<Kind> and validates it requiring concreteness, so it catches everything the closed check does PLUS missing-required fields and unresolved disjunctions (a PCI hostdev with no slot/function, a vm source: arm missing its discriminator-required field). charly core's own load-time validateKindValueCUE is the closedness-only gate for the #<Kind>Value-typed kinds; this is the stricter form the schema-tightening corpus asserts the schema still enforces, so that a future re-loosening of any modeled subtree fails loudly instead of silently accepting a broken entity.

func ValidateEntityClosedCUE added in v0.2026217.313

func ValidateEntityClosedCUE(kind, label string, entity cue.Value) error

ValidateEntityClosedCUE unifies a single entity with #<Kind> and validates it WITHOUT requiring concreteness — it catches closedness violations (unknown keys) and type/enum/regex conflicts, but not missing-required fields. This is the LOAD-time check (restores the deleted unmarshalers' typo-detection), AND (since c9befd83) the sole remaining `charly box validate` entity-schema gate: its former sibling validateEntityCUE (concrete-required) was a dead-code-radical-removal- batch deletion — every kind this project's schemas currently model has no meaningfully-required field concreteness would catch beyond what closedness already does (verified against #Box/#Builder: every field is optional or carries a default), and the modern load-time plugin-kind gate (RDD-verified live: `plugin kind:<X>: plugin_input fails #<X>Input`) is the actual production entity-schema enforcement path today, superseding the legacy per-kind Go-side validateVocabularyCollections/validateEntityCUE pair (also deleted) for every kind beyond box.

func ValidateEntityNodeRec added in v0.2026217.313

func ValidateEntityNodeRec(pn spec.ParsedNode, path string) error

ValidateEntityNodeRec assemble-validates one entity node (when its kind is CUE-registered) and recurses into its sub-entity children (bundle members, nested deploys), which carry their own steps. A candy node's DESUGARED body is additionally validated concretely against #CandyValue (version+description required, unknown inline fields rejected) — the box-validate counterpart of the load-time host-side validateKindValueCUE (which is closedness-only). Every pn.Children entry is an entity child by construction (the parse-time desugar already separates step/data children into the plan/body fields before a spec.ParsedNode ever reaches here), so no discClass filter is needed.

func ValidateEphemeralOnNode added in v0.2026209.2002

func ValidateEphemeralOnNode(name string, node *spec.BundleNode, t spec.Threaded, d *spec.Diagnostics)

ValidateEphemeralOnNode applies all ephemeral-related invariants to a single BundleNode, reading the substrate's DECLARED #DeployTraits from the DATA snapshot t (never the registry). Errors accumulate into d. Byte-equivalent to the former charly ValidateEphemeralOnNode.

func ValidateEphemeralUnified added in v0.2026209.2002

func ValidateEphemeralUnified(uf *spec.UnifiedFile, t spec.Threaded) error

ValidateEphemeralUnified is the LoadSeams.ValidateEphemeral entry point: it auto-promotes disposable:true on ephemeral entries and validates the ephemeral / vm-naming invariants across the spec.UnifiedFile's Bundle map, reading the DeployTraits DATA snapshot t. Moved verbatim (behaviour- preserving) from charly's validateEphemeralUnified.

func ValidateIterateBed added in v0.2026209.2002

func ValidateIterateBed(uf *spec.UnifiedFile, name string, node *spec.BundleNode) error

ValidateIterateBed enforces the iterate: benchmark invariants (replaces the former validateScoreNode/validateHarnessSemantics). An iterate bed is exempt from the deterministic R10 bed rules (target/disposable/cross-ref); instead: every iterate.agent[] references an entry in the `agent:` catalog; iterate.sandbox names a deployment (non-empty); and the bed's plan: carries at least one direct `check:` step. Pure — reads uf.PluginKinds["agent"] + node.Iterate + node.Plan.

func ValidateMembers added in v0.2026209.2002

func ValidateMembers(uf *spec.UnifiedFile) error

ValidateMembers enforces the member-specific invariants beyond the generic deploy validation (which already runs on the folded members): member keys carry no `.` (dots are reserved for nested dotted-path addressing) and reference a valid target kind. Pod-target members get the required-image: check via the generic validateDeploymentTree on the folded entry. Registry-free: the valid-target set is the CUE-derived spec.ResourceKinds (minus the targetless "group") — the SAME derivation the host's deployTargetWords uses (R3) — so a new deploy substrate is a valid member target without a core edit (boundary law clause D).

func ValidateNodeDocCUE added in v0.2026217.313

func ValidateNodeDocCUE(label string, data []byte) error

ValidateNodeDocCUE validates a unified node-form document (raw YAML bytes) by unifying EACH top-level entity node against #Node (the reserved document directives are skipped — the loader decodes those). It runs the CLOSEDNESS check (no cue.Concrete): it catches a typo'd discriminator, an unknown field in a kind-value, a wrong-kind child, and a child under a childless kind — but does NOT require every entity's required fields (that stays the concrete `charly box validate` gate). label identifies the document in errors.

Validation is PER ENTITY, not whole-document: unifying the whole document against a closed #NodeDoc forced CUE to resolve the per-child kind-disjunction across every entity at once — an O(entities × kinds × children) blow-up (a full-graph validate took ~30 CPU-minutes). One small entity at a time keeps each unification bounded by that entity's own size while preserving identical strictness.

func ValidateNodeFormSteps added in v0.2026217.313

func ValidateNodeFormSteps(path string, data []byte, t spec.Threaded, parser spec.DocParser) error

ValidateNodeFormSteps parses a node-form document and validates EVERY entity's (and nested sub-entity's) assembled body against its closed per-kind def — the step-typo gate for candies, boxes, pods, deploys, and check beds alike. Shared by ValidateCandyManifestCUE and candy/plugin-box's validateProjectCUESchemas (R3). t/parser are caller-supplied (the registry-derived Threaded snapshot + a spec.DocParser — normally the DocParser adapter in this package) — this function never queries the registry itself.

func ValidatePreemptible added in v0.2026209.2002

func ValidatePreemptible(uf *spec.UnifiedFile,
	resolveResource func(json.RawMessage) (*spec.ResolvedResource, error),
	resolveVm func(json.RawMessage) (*spec.ResolvedVm, error),
) error

ValidatePreemptible validates preemptible / requires_exclusive / requires_shared across a unified project's deploy map (which includes folded kind:check beds), plus the resource-vocabulary cross-check, returning the first batch of errors for the LoadUnified hard-fail path. resolveResource / resolveVm project the opaque resource: / vm: plugin-kind bodies into their resolved envelopes via the registry (host-threaded).

func ValidatePreemptibleOnNode added in v0.2026209.2002

func ValidatePreemptibleOnNode(name string, node *spec.BundleNode, d *spec.Diagnostics)

ValidatePreemptibleOnNode checks one deploy node's preemptible + requires_exclusive/requires_shared fields and accumulates problems into d:

  • preemptible.holds must be non-empty (a holder that holds nothing is meaningless — nothing for a claimant to contend over).
  • preemptible.stop must be "shutdown" (the ONLY mechanism that frees a VFIO passthrough device; pause/managedsave/destroy are rejected with a reason).
  • preemptible.restore must be "always" or "on-success".
  • requires_exclusive / requires_shared entries must be non-empty strings.
  • a node may not claim a resource BOTH exclusively and shared, nor both hold and require/share the SAME token (self-contention).

func ValidateVmNamingGuard added in v0.2026209.2002

func ValidateVmNamingGuard(name string, d *spec.Diagnostics)

ValidateVmNamingGuard enforces the reserved -eph- infix on user-authored entity names.

func VmStateFromBundleConfig added in v0.2026218.157

func VmStateFromBundleConfig(dc *deploykit.BundleConfig, entity string) *spec.VmDeployState

VmStateFromBundleConfig extracts a domain's persisted VmDeployState (the "vm:"+entity key) from a per-host deploy overlay BundleConfig — the pure lookup ResolveVmStateViaExecutor applies.

func Walk added in v0.2026194.1400

func Walk(rootDir string, rootData []byte, rootIdentity string, seams spec.WalkSeams) (spec.LoadedProject, error)

Walk walks a project rooted at rootDir and returns its kind-blind parse envelope. rootData is the (possibly bootstrap-transformed) bytes of the root charly.yml — used for the root file instead of re-reading; pass nil for a namespace child (read from disk). rootIdentity is the root project's own repo identity (the host's rootRepoIdentity), seeded into the cycle-break so a transitive self-import resolves to the in-progress root — mirrors LoadUnified seeding loadingRepos[rootRepoIdentity(dir)] = merged BEFORE the walk starts.

Types

type DocParser

type DocParser struct{}

DocParser is the default charly node-form parse as a spec.DocParser value.

func (DocParser) ParseDoc

func (DocParser) ParseDoc(doc *yaml.Node, t spec.Threaded) (map[string]*yaml.Node, spec.ParsedProject, error)

ParseDoc implements spec.DocParser by delegating to the ONE copy of the parse mechanism.

type LoadSeams added in v0.2026206.1046

type LoadSeams struct {
	// RunBootstrapPhase invokes every registered bootstrap-phase plugin
	// (sdk.PhaseBootstrap) on the raw root config bytes, returning the
	// (possibly transformed) bytes. A leg failure is a hard error — never a
	// silent fallback to the raw, un-bootstrapped bytes.
	RunBootstrapPhase func(data []byte) ([]byte, error)
	// WalkProject runs the kind-blind import/discover/namespace walk (the
	// registered spec.ProjectWalker, reached via the host's spec.WalkSeams) and
	// returns the generic spec.LoadedProject envelope — no materialize, no merge.
	WalkProject func(dir string, rootData []byte) (spec.LoadedProject, error)
	// MaterializeLoadedProject replays the host's per-document/per-namespace
	// MATERIALIZE + root-wins MERGE over the walk envelope, reconstructing merged
	// (registry kind-decode via the registered spec.Materializer).
	MaterializeLoadedProject func(lp *spec.LoadedProject, merged *spec.UnifiedFile, byID map[int64]*spec.UnifiedFile) error
	// FlattenBundleVenues stamps every plan step's execution venue from its
	// bundle-tree position and hoists member/child steps into the root Plan.
	FlattenBundleVenues func(uf *spec.UnifiedFile) error
	// FoldMembers copies every deploy node's `peer:` entries into the Bundle map
	// as top-level addressable entries. Its relocation (if any) is a FINAL/K5
	// decision (bundle_members.go) — it stays host-resident, reached only via
	// this seam.
	FoldMembers func(uf *spec.UnifiedFile) error
	// StampBundleDescents stamps every deploy node's venue-hop descent
	// descriptor (P9 DeployTraits, resolved from the provider registry).
	StampBundleDescents func(uf *spec.UnifiedFile)
	// ValidateEphemeral auto-promotes disposable:true on ephemeral entries and
	// validates the ephemeral / vm-naming invariants.
	ValidateEphemeral func(uf *spec.UnifiedFile) error
	// ValidateCheckBeds enforces the kind:check bed invariants (disposable,
	// cross-ref, external-substrate recognition via the provider registry).
	ValidateCheckBeds func(uf *spec.UnifiedFile) error
	// ValidateAndroidDevices enforces the kind:android box⊻adb XOR (resolves
	// android templates via the plugin-substrate provider).
	ValidateAndroidDevices func(uf *spec.UnifiedFile) error
	// ValidateMembers enforces the member-specific invariants beyond the generic
	// deploy validation. Paired with FoldMembers under the same FINAL/K5 ruling.
	ValidateMembers func(uf *spec.UnifiedFile) error
	// ValidatePreemptible validates preemptible/requires_exclusive/requires_shared
	// across the deploy map, including the resource-vocabulary cross-check
	// (resolves the `resource:` plugin kind via the provider registry).
	ValidatePreemptible func(uf *spec.UnifiedFile) error
}

LoadSeams bundles every registry-coupled or standing-core-resident step LoadUnified's orchestration calls out to. A nil field panics on use — the host wrapper (charly's LoadUnified, loader_threaded.go) is the SOLE constructor and always populates every field before calling LoadUnified.

func LoadSeamsFromExecutor added in v0.2026209.2002

func LoadSeamsFromExecutor(exec LoaderExecutor) LoadSeams

LoadSeamsFromExecutor builds a LoadSeams from a LoaderExecutor: the PURE, registry-free LOAD-half seams (relocated into loaderkit, K1-LOADER RELOCATION) are wired DIRECTLY; the registry-/host- coupled seams dispatch through exec. The DATA seams (descent stamp + ephemeral / check-bed validators) call exec.LoaderThreaded() FRESH inside their closures — matching charly's former per-seam loaderThreaded() calls, because the walk's connect pass mutates the registry mid-load so a build-time snapshot would be stale. This is the seam constructor a PLUGIN uses to run loaderkit.LoadUnified without importing charly core.

type LoaderExecutor added in v0.2026209.2002

type LoaderExecutor = spec.LoaderExecutor

LoaderExecutor is the typed host-leg contract for the registry-/host-coupled loader steps LoadUnified cannot do kind-blind. It is DEFINED in the dedicated spec module (spec.LoaderExecutor, #55 loader-keystone) so charly core can hold the host implementation while importing ONLY spec; this package-local ALIAS keeps loaderkit's own references (LoadSeamsFromExecutor, LoadUnifiedViaExecutor) terse. Two witnesses satisfy it structurally: charly's own hostLoaderExecutor (compiled-in, zero marshal) and this package's executorLoaderExecutor (load_via_executor.go — the ONE canonical plugin-side witness every reverse-channel consumer shares via LoadUnifiedViaExecutor).

type MaterializeProjectSeams added in v0.2026209.2002

type MaterializeProjectSeams = spec.MaterializeProjectSeams

MaterializeProjectSeams is DEFINED in the dedicated spec module (spec/spec/materialize_project_seams.go, #55 2b C3) — the host supplies it spec-typed via LoaderExecutor.MaterializeProjectSeams(). This alias keeps loaderkit's own orchestration (MaterializeLoadedProject below) terse.

type RemoteDownload added in v0.2026209.2002

type RemoteDownload = spec.RemoteDownload

RemoteDownload is DEFINED in the dedicated spec module (spec/spec/remote_download.go, #55 2b Class A); this alias keeps the scan mechanism that produces it (+ candy/plugin-build's resolve legs) terse.

type ResolveOpts added in v0.2026209.2002

type ResolveOpts = spec.ResolveOpts

resolve_opts.go — ResolveOpts (the loader scan/load OPTIONS) is DEFINED in the dedicated spec module (spec.ResolveOpts, #55 loader cascade) so the ~14 charly-core call sites that only NAME the options struct reach it through spec and drop their loaderkit import. Its fields are native spec build-vocabulary configs (*spec.InitConfig / *spec.DistroConfig / *spec.BuilderConfig) carrying their own resolve methods (spec init_config_methods.go / distro_config_methods.go) — spec owns the types AND the mechanism methods since #72, so ResolveOpts pulls in no buildkit dependency. This package-local alias keeps loaderkit's own resolve callers (resolve_project.go / finalize_candy.go) terse. DISTINCT from buildkit.ResolveOpts (the build-resolve options): this is the SCAN/LOAD options; the buildkit resolvers never read ExtraCandyRefs/InitCfg/RequestedBoxes.

type ResolveProjectSeams added in v0.2026209.2002

type ResolveProjectSeams struct {
	// ResolveBox resolves ONE box. The host closure captures the ResolveOpts (incl. the pre-filled
	// DistroCfg/BuilderCfg that short-circuit fillBuildConfigFallback), so the assembler passes no opts.
	ResolveBox func(cfg *spec.Config, name, calver, dir string) (*buildkit.ResolvedBox, error)
	// FillNamespacedBoxes folds each import namespace's boxes (qualified) + their OWN candy sets into rp.
	// The seam closure owns the WHOLE recursion — the assembler calls it ONCE with prefix "" and a fresh
	// visited set, and the closure descends uf.Namespaces itself (candy/plugin-build's
	// fillNamespacedBoxes: ProjectCandiesScanned + CollectRemoteRefsOpts + ScanCandyFromLocal +
	// deploykit.RawCandyPair + deploykit.FillNamespaceBoxViews per namespace). Its two predecessors —
	// the in-proc host namespaced-box fill (resolved_project_host.go) and the `buildengine-namespaced`
	// host leg that replaced it — are both deleted; nothing here crosses a process boundary.
	FillNamespacedBoxes func(uf *spec.UnifiedFile, initCfg *buildkit.InitConfig, prefix, calver, dir string, rp *spec.ResolvedProject, visited map[*spec.UnifiedFile]bool)
	// ResolveResources projects uf's `resource:` kind entities. HOST (per-node registry resolve);
	// becomes an InvokeProvider(ClassKind,"resource") leg at U5.
	ResolveResources func(uf *spec.UnifiedFile) map[string]*spec.ResolvedResource
	// ShouldIncludeDisabled reports whether a disabled box's `enabled: false` gate is bypassed (host opts).
	ShouldIncludeDisabled func(name string) bool
	// ComputeIntermediates adds auto-generated intermediate images (host: lifts cfg.Defaults).
	ComputeIntermediates func(boxes map[string]*buildkit.ResolvedBox, layers map[string]spec.CandyReader, cfg *spec.Config, tag string) (map[string]*buildkit.ResolvedBox, error)
	// ExternalizedBuilders is the registry D-FACT (which builder words are served out-of-process). A fixed
	// compiled-in constant, threaded so the assembler stays free of the package-main var.
	ExternalizedBuilders map[string]bool
}

ResolveProjectSeams carries the host-coupled legs the envelope assembler cannot run itself. Each is a closure the caller builds — the host over its in-proc ResolveOpts/registry, the plugin over its reverse channel. None inspects a concrete ResolveOpts here, so the assembler stays opts-agnostic.

type ScanSeams added in v0.2026209.2002

type ScanSeams = spec.ScanSeams

ScanSeams carries the host-coupled legs ScanCandyFromLocal reaches for the fetch fix-point. DEFINED in the dedicated spec module (spec/spec/scan_seams.go, #55 C3b-ii) so it can type the spec.ProjectLoader.ScanCandyFromLocal seam method charly core reaches instead of importing loaderkit; this forwarder (mirroring the RemoteDownload alias above) keeps loaderkit's own signature + candy/plugin-build's scanSeamsLeg call sites terse. The caller builds these as closures capturing its config/opts + host mechanisms (registry, refs backend); the pure fix-point below never inspects a package-main type.

Jump to

Keyboard shortcuts

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