build/skills/validate/SKILL.md
MUST be invoked before any work involving: charly box validate command, validation rules, common validation errors, or checking charly.yml and layer definitions.
npx skillsauth add overthinkos/overthink-plugins validateInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Invoked as charly box validate. See /charly-image:image for the family overview.
charly box validate checks charly.yml and all layer definitions for errors. Validation collects all errors at once rather than failing on the first.
The param schema these rules enforce is CUE-single-source: the @go()-annotated charly/schema/*.cue defs are the sole source for both the Go param structs (generated into charly/spec by task cue:gen) and the load-time validation, so changing a validated field is a CUE edit → task cue:gen → see the /charly-internals:go recipe "Updating Go code when an ingress CUE schema changes". The authoring surface is node-form only (name-first <name>: {<kind>: …}); a legacy kind-keyed document is rejected at load with a charly migrate hint.
| Action | Command | Description |
|--------|---------|-------------|
| Validate all | charly box validate | Check charly.yml + all layers (enabled images only) |
| Validate disabled too | charly box validate --include-disabled | Extend the validation pass to images marked enabled: false (does not modify charly.yml) |
| Check version | charly version | Verify CalVer computation |
| Inspect image | charly box inspect <image> | Show resolved config |
| Code | Meaning | |------|---------| | 0 | Validation passed | | 1 | Validation or user error | | 2 | Internal error |
charly.yml with a non-empty plan: (carrying run: steps) or a rpm: / deb: / pac: / aur: packages section; an auto-detected builder manifest (pixi.toml, pyproject.toml, environment.yml, package.json, Cargo.toml); an external_builder: selection (the candy's content is the multi-stage build its selected external builder plugin bakes — see /charly-image:layer); a plugin: block; or a candy: composition field (pure composition layers are valid).description: string AND a plan: with at least one deterministic check: step (enforced by validateCandyContents; a hard error names the missing field). A fetched remote candy is exempt — its compliance is its own repo's concern, same scope as the mandatory version: rule. See /charly-check:check.depends must reference existing layers (local or remote).volumes names must match ^[a-z0-9]+(-[a-z0-9]+)*$.aliases in charly.yml require both name and command.^[a-zA-Z0-9][a-zA-Z0-9._-]*$.PATH directly in env is an error (use path_append).pixi.toml, pyproject.toml, or environment.yml).run: Step RulesA run: step carries the deterministic state-change op inline (the same verb
catalog the legacy task: map used). See /charly-image:layer for the full
verb catalog. The validator enforces:
run: step. A run: step must carry exactly one of cmd / mkdir / copy / write / link / download / setcap / build. Zero op verbs → the step has no action; more than one → conflicting actions.copy → to: (destination) required; copy: value must be relative to the layer directory, no .. traversalwrite → content: required (non-empty)link → target: required (what the symlink points to)download → to: required unless extract: sh (piped install scripts)setcap with non-empty caps: → caps pattern check (cap_name=flags[,cap_name=flags])~/-prefixed, or ${HOME}-prefixed. copy: source must exist under the layer directory at generate time.mode: must match ^0[0-7]{3,4}$ (octal) if present.tar.gz / tar.xz / tar.zst / zip / none / sh or empty.cache: rules: only valid on cmd: / download: run: steps; each path must be absolute (or ~/ / ${HOME}-prefixed). Declares extra BuildKit cache mounts (owned per the step run_as:).build: value: must be "all" (initial implementation; specific builder names reserved for future use).run_as: format: must be root, ${USER}, a literal name matching ^[a-z_][a-z0-9_-]*$, or numeric <uid>:<gid>. Unresolved ${VAR} in run_as: errors.var: Rules^[A-Z_][A-Z0-9_]*$ (shell identifier).USER, UID, GID, HOME, ARCH, BUILD_ARCH).env: keys.${VAR} Reference Resolutionto, target, etc.), every ${NAME} reference must resolve against var: ∪ auto-exports. Unresolved references error at validate time.cmd: values, write: content:), references are passed through verbatim and resolved by bash at build time.base must reference a valid external image or another image in charly.ymllayers field is requiredbuilder must reference an existing imagebootc: true requires appropriate base imagecommand defaults to name if omittedenv accepts list of KEY=VALUE strings (runtime only)env_file accepts a path string (validated at runtime)security at image level overrides layer-level security (not an error)name and path required, name must match volume name regexhost must be empty; Plain: host requiredgocryptfs not in PATH when encrypted mounts existfqdn requiredbootc: true requires appropriate base imagevm.rootfs must be ext4, xfs, or btrfsvm.backend must be auto, libvirt, or qemuport:ports must be "all" or omittedports: all, the image must have at least one port definedtcp: prefix is supported@github ref) referenced via two different git tags is not
an error — the git tag is only the FETCH coordinate. The resolver reads each
fetched layer's OWN version: and dedups by it: same per-entity version → no
warning (a re-tag of an unchanged layer is silent); different per-entity
versions → warns once and resolves to the newest (highest CalVer). Run
charly box reconcile to align the pins and clear any warning. A fetched layer
with no version: IS a hard error (the layer kind requires one).base:/builder: chains are fetched. See /charly-internals:go
"Remote-layer resolver" and /charly-build:reconcile.Layers form a dependency cycle. Check depends fields.
A depends entry or charly.yml layer references a non-existent layer.
Use path_append in charly.yml instead of env: PATH: ....
Volume names must be unique within a candy.
charly box validate && charly box build my-image
charly box validate 2>&1 # See all errors at once
charly box inspect <image> # Check resolved config
charly box list candies # Verify layer exists
charly box validate resolves charly.yml via os.Getwd(). Override with -C <dir> / --dir <dir> / CHARLY_PROJECT_DIR=<dir>. See /charly-image:image "Project directory resolution".
charly box family siblings/charly-image:image -- Family overview + charly.yml composition reference/charly-build:build -- Building validated images/charly-build:generate -- Containerfile generation after validation/charly-build:inspect -- Inspect a specific image after validation/charly-build:list -- Enumerate images/layers to validate/charly-build:merge -- Post-build layer consolidation/charly-build:new -- Scaffold new candies before validation/charly-build:pull -- Pull prebuilt images (orthogonal to validation)/charly-image:layer — Canonical reference for the run:/check: step verb catalog, var: substitution, YAML anchors, execution order. The validator rules above enforce what's documented there./charly-build:generate — What the generator emits from validated input (per-verb emitters, cache-mount inheritance, inline-content staging)./charly-internals:generate-source — Internal architecture of the run-step emission pipeline./charly-internals:egress — the egress counterpart: charly box validate proves the INPUT config; /charly-internals:egress validates the config files charly WRITES to a system (cloud-init, k8s, units, ssh_config, libvirt XML) before the bytes hit disk./charly-check:check — charly box validate schema-checks every check: entry: exactly-one-verb, attribute types, scope/variable consistency (build-scope can't reference runtime-only vars), id: uniqueness per section, matcher operator allowlist, unroutable-check rejection. The live-container verbs (cdp/wl/dbus/vnc/mcp/record/kube/adb/appium/spice/libvirt) are out-of-process plugins: their method-name enum is enforced by CUE on core #Op (an unknown method is rejected with the allowed set listed) and their required-modifier checks run in the plugin at dispatch (deploy-scope-only)./charly-build:charly-mcp-cmd — the standalone reference for the mcp: verb: required modifiers (tool: for call, uri: for read), the 7-method allowlist, and the URL-rewrite / port-publishing behavior that authors occasionally hit./charly-check:cdp, /charly-check:wl, /charly-check:dbus, /charly-check:vnc — per-verb references for the other four live-container verbs.The unified form is name-first: every entity flattens to a top-level <name>: key whose first child is its kind discriminator (candy: / pod: / vm: / k8s: / local: / android: / group: / …). There is no box: KIND — both images and layers are candy: nodes (an image carries base:/from:, a layer carries neither). Two consequences for charly box validate:
candy/redis/charly.yml (a candy: node with no base:/from:) and an image box/redis/charly.yml (a candy: node carrying base:/from:) resolve to distinct internal maps (uf.Candy vs uf.Box, routed by base:/from: presence), and the validator does NOT flag them. Verbs disambiguate by command context (ResolveDeployRef is image-first; --add-candy <name> selects the layer-first path).charly.yml carrying a pod + a vm + a local) MUST NOT share a name — they would collide on one YAML key. charly box validate flags the collision; rename one (the convention: keep the user-facing deploy, suffix the template it deploys — a check-local deploy + a check-local-app local template).The loader still raises hard load-time errors on obsolete keys — the obsolete qc / cachyos-dx deployment names, and any obsolete kind: deployment doc or root-key deployment: (the deploy kind is now the name-first substrate kind: pod: / vm: / k8s: / local: / android: / group:). Every such error points at charly migrate. See CLAUDE.md and /charly-build:migrate.
MUST be invoked when the task involves charly box validate command, validation rules, common validation errors, or checking charly.yml and layer definitions. Invoke this skill BEFORE reading source code or launching Explore agents.
Workflow position: Pre-build. Validate before building to catch errors early.
tools
Use when authoring or modifying a charly PLUGIN — a candy with a `plugin:` block that contributes Providers (verbs/kinds/deploy-targets/steps/builders/commands), its own CUE schema, builtin (compiled-in) or external (out-of-tree git repo). Covers the unified Provider model, the per-plugin CUE-schema contract (single source → Go params for dev + schema-over-Describe RPC for runtime), the SDK, and the loader.
tools
The CUE data-validation / configuration CLI (cue), pinned to v0.16.1. Use when working with the cue candy, installing the cue binary into a box or onto a target:local dev host, or running the offline schema-vendoring pipeline that feeds charly's egress validation.
tools
CUE EGRESS validation — validating (and, where it adds value, generating) the config files charly WRITES to a system BEFORE the bytes hit disk. MUST be invoked before working on charly/egress.go, the vendored schemas under candy/plugin-egress/egress-schemas/vendor/, the ValidateEgress / registerVendoredEgressKind path, the offline `task cue:vendor` pipeline, or adding an egress schema for any written artifact (cloud-init, k8s manifests, traefik routes, runtime config, install ledger, systemd/quadlet units, ssh_config, libvirt XML).
tools
Kubernetes cluster-probe declarative check verb — the `kube:` check verb (nodes, pods, ingress, storage class, addon health, apply/delete, and arbitrary resource GETs) served out-of-process by the candy/plugin-kube plugin (vendored client-go; no external kubectl required).