Data Extension via —data
Data Extension via —data
Extend AICR’s embedded recipe catalog with your own overlays, components, and criteria values at runtime — no fork, no rebuild. This is how operators add private/proprietary content (internal cloud providers, in-house GPU SKUs, commercial platforms, customer-specific scheduling) on top of the OSS catalog shipped with the binary.
The --data <dir> flag layers an external directory on top of the embedded
catalog. The embedded catalog is precedence-low; your directory is
precedence-high. Adding a file under the right path either supplements (for
catalog content) or overrides (for component files) the embedded equivalent.
Use cases
Folder layout
The external directory mirrors AICR’s embedded recipes/ tree. Drop only the
paths you need; AICR loads any subset.
The loader walks the tree recursively (filepath.WalkDir), so subdirectories
inside overlays/ are supported and useful for organizing by service / customer
/ team:
registry.yaml is required
Even if your directory only adds overlays (no new components), AICR requires a
registry.yaml at the root. The minimal stub is:
External components in this file are merged with the embedded registry; on
name collision, the external definition wins. An entry replaces its embedded
counterpart wholesale — to override just helm.defaultVersion, copy the
full embedded entry and change that one field, or the other coordinates
(repository, chart, scheduling paths) are silently dropped.
Overriding a component’s defaultVersion this way takes effect for every
embedded overlay that references the component: embedded overlays do not pin
versions that equal the registry default, so resolution falls back to your
merged default. The exception is an overlay with an explicit, intentionally
divergent pin — an explicit pin always wins over the registry default at
resolution. (The versionPinExemptions list is CI policy for recipes
embedded in the AICR repo, not a runtime input: the guard test that
consumes it never walks --data trees, so your external overlays may pin
freely without declaring anything or rebuilding AICR.) See issue
#1616 and
Chart Version Pinning.
Catalog and binary compatibility
AICR validates every catalog header before it is used, so a catalog authored against a binary you are not running fails loudly instead of resolving something plausible and wrong. The accepted values per kind, and the releases in which they change, are defined by ADR-022.
Each catalog kind migrated from a retired value to a target value:
The Retired column is history as of v1.0.0: those values are rejected, not accepted-with-a-warning. A catalog still carrying one fails to load with an error naming the value and the release that withdrew it.
Which binary accepts which catalog:
v0.20 and earlier cannot tell you whether your catalog is compatible. Those
releases did not gate catalog headers at all: an external registry.yaml whose
apiVersion differed was merged anyway and restamped with the embedded value,
and RecipeMetadata and RecipeMixin headers were never checked. So a
target-stamped catalog does not fail on them — it loads silently and may resolve
something you did not intend. Closing that fail-open behavior is what ADR-022 §8
did in v0.21 (issue
#1812). Treat v0.20 as unable to
validate your catalog rather than as a compatibility floor you can rely on.
v1.0.0 and later accept the target value only. Catalogs are authored inputs, so moving one off a retired value is a manual edit in your tree. AICR does not rewrite them, and there is no conversion layer.
Empty, unknown, or wrong-kind AICR catalog headers fail with INVALID_REQUEST
naming the value observed, the values expected for that kind, and the
remediation. AICR validates the raw external registry.yaml header before
merging it with the embedded registry, and checks metadata and mixin headers
before hydration, so an unaccepted external header is never replaced by an
embedded one and silently carried forward. Unrelated YAML in the tree keeps its
existing skip behavior, and ValidatorCatalog sits on a separate API domain
outside this contract.
This gate follows the document, not the entry point. Passing a single overlay
directly — aicr bundle -r overlay.yaml, aicr validate -r overlay.yaml —
applies the same check as a --data catalog scan, so a RecipeMetadata
with a missing or empty apiVersion is rejected on both paths. Through v0.20
the direct path accepted it and hydrated silently
(#2421); if you author overlays
outside a catalog tree, confirm each one carries a header. The empty-value
tolerance that remains is for hydrated RecipeResult inputs only, and it
retires in v1.0.0.
Adding a criteria value
Criteria value validation (service, accelerator, intent, os,
platform) is data-driven: the static OSS list is the fast path, and the
runtime criteria registry picks up any value declared in a loaded overlay’s
spec.criteria. So adding a new value to an overlay automatically makes it
a valid CLI / API input. No code change, no rebuild.
Example overlay for an internal NCP:
Run it:
Without --data, --service ncp-internal is rejected (the value isn’t in
the embedded catalog and the registry hasn’t been seeded). With --data
pointing at the overlay above, the registry registers ncp-internal at
catalog-load time and the CLI admits it.
The same applies to accelerator, intent, os, and platform — any
field on a RecipeMetadata’s spec.criteria.
Validating a recipe with a new criteria value. Most validation checks
apply to an external recipe as-is: the deployment and conformance phases gate
on component presence and cluster state, not criteria. The NCCL performance
benchmarks additionally key their default applicability to embedded
service + accelerator pairs, so a recipe with a new service or
accelerator value would skip them (new intent, os, or platform values
alone do not affect NCCL applicability) — declare an nccl-benchmark-profile
performance constraint (e.g. gb200/eks) in the overlay’s validation block
to opt into one of the embedded benchmarks. The profile selects the benchmark
template and fabric handling; node identification still follows the recipe’s
own accelerator. See
Opting external recipes into a benchmark profile.
If the private service’s fabric matches none of the embedded templates, ship the
benchmark itself: put a Kubeflow TrainingRuntime in your --data tree at
validators/performance/testdata/{accelerator}/{service}/runtime.yaml and point
the nccl-benchmark-runtime-ref constraint at it ({accelerator}/{service}).
Pass the same --data <dir> to aicr validate so it can resolve the referenced
file; validation then gates it keyed on the recipe’s own criteria with no
compiled entry required, and the file is a drop-in should the runtime ever be
upstreamed.
See Supplying a benchmark runtime for a private service.
Adding a component
registry.yaml declares the component’s identity and source:
…or, for a Kustomize-shipped component:
Component values are not auto-discovered by filename. For a Helm
component, a values file under components/<name>/ is consumed only when
an overlay’s componentRef names it via a
valuesFile: path relative to the data directory (a componentRef — and thus
valuesFile — lives on an overlay, not on the registry.yaml entry); the merge order is base
values → valuesFile → inline overrides. For a Kustomize component, the
deployable source is built from the registry entry’s defaultSource /
defaultPath / defaultTag (overridable per componentRef) — there is no
implicit kustomization.yaml pickup.
Reference the component from an overlay’s componentRefs: to include it in
recipes that match the overlay’s criteria.
Helm components must resolve with an effective chart version. Declare
helm.defaultVersion in the registry entry (preferred — it keeps the version
in one place, matching the embedded catalog’s convention; see
Chart Version Pinning), or pin
version: on every componentRef that references the component. A Helm componentRef that
resolves without one is rejected at recipe resolution (INVALID_REQUEST)
rather than passed through — several deployers would otherwise emit the empty
version verbatim and Helm would silently install “latest” at deploy time.
Whitespace-only versions and a bare v count as absent — Flux and Argo CD
strip a leading v for non-OCI outputs (Helm resolves the empty remainder as
“latest”), non-vendored Helm/Helmfile and OCI outputs preserve it, and
vendored wrappers substitute a fabricated default; a bare v is rejected
uniformly to avoid output-dependent chart identities. Also,
chart, source, and version values carrying surrounding whitespace are
rejected outright, since deployers consume those fields verbatim. Manifest-only Helm components are exempt: a ref
whose chart and source are both empty and that ships at least one primary
manifestFiles entry has no chart version to pin. preManifestFiles alone
do not qualify — pre-manifests are auxiliary to a primary release, so a ref
with only pre-manifests is rejected as having no deployable primary.
Precedence rules
When in doubt, aicr --debug recipe ... --data <dir> logs the resolved source
(embedded / external / merged) for every loaded file.
Converting a family to a configuration profile
recipes/overlays/aks.yaml declares the gpuStack configuration profile
(azure-managed default, operator-managed alternative) over the GPU driver/toolkit
ownership paths, and recipes/overlays/gke-cos.yaml declares its own gpuStack
(gke-default default, bundle-installer alternative) over device-plugin ownership —
the GKE default value (gke-default) additionally declares
advertiser: external, and both GKE values trigger the #1327
allocation-policy closure, so their effective lock set is larger than the
declared paths (see
Configuration Profiles).
When an external data directory replaces a declaring overlay, or converts a
family to a profile, the replacement rules above interact with the profile
mechanics:
-
A profile-only file extends the declaration. An external overlay at the path of an embedded one that carries only
apiVersion,kind,metadata.nameandspec.profileadds its values to the embedded overlay’s profile. Nothing else in the embedded overlay is replaced, so the catalog does not fork the overlay or re-sync it on upgrade. Thisoverlays/gke-cos.yamladds acustomvalue togpuStack:A patch can only add values. Catalog load fails when it redeclares an embedded value, sets
defaultordescription, names a different profile, or adds no values. Union totality is checked over the merged values, so each added value must assign exactly the path set the embedded values assign. A value that assigns other paths or leaves some out fails closed. An overlay that also carriesspec.criteria,componentRefsor any other field is not a patch and follows the replacement rule below. -
A same-path replacement replaces the declaration too. An external
overlays/aks.yamlthat carries more than a profile block completely replaces the embedded file, including itsspec.profileblock. Keep the declaration in the replacement. Dropping it while keeping profile apiVersionaicr.run/v1beta2fails catalog validation (the version⟺declaration cross-check). That guardrail protects an integrator editing a profile-track file: de-profiling one requires BOTH removing the declaration AND downgrading the overlay to the legacy apiVersion. It does NOT protect the upgrade path — a pre-existingaicr.run/v1beta1externaloverlays/aks.yaml, authored before the family’s conversion, already satisfies both conditions. Upgrading AICR with such a catalog in--datasilently preserves the unprofiled family: resolution succeeds with no error, noselectedProfileon the recipe, and noK8s.aks-gpu-pools.gpu-driverconstraint. On upgrade, diff each external overlay against its embedded counterpart and port thespec.profiledeclaration plus theapiVersionbump (or consciously keep the fork unprofiled). A load-time detection of an external overlay shadowing an embedded profile declaration (with an explicit opt-out) is a candidate follow-up; it is not shipped. -
Static assignments to now-owned paths are superseded. Descendant or external overlays that statically assign profile-owned paths (for the AKS declaration:
gpu-operatordriver.enabled,operator.runtimeClass,toolkit.enabled;nvidia-dra-driver-gpunvidiaDriverRoot) are overwritten by the selected value’s fragment at resolution. Theenabledentries inownedPathsare different: they are the SYNTHETIC component-presence locks — fragments are forbidden from assigningenabled, and the lock instead rejects removing (or bundle-subsetting away) an owned component. Review those overlays when converting a family — a static assignment that used to take effect no longer does. -
Owned paths lock per surface. The enforcement matrix:
aicr bundlestatic overrides (ANY static source —--set,--set-json,--set-file, or a config-file override) on an ordinary owned value path (e.g.driver.enabled,operator.runtimeClass): a divergent value fails closed; an identical value is accepted. For the syntheticenabledpresence key, typed sources (--set-json,--set-file) are ALWAYS rejected — even when the value is identical to the selected one — because routing the toggle through a typed flag would write a stray literalenabled:chart value instead of toggling the component (pkg/cli/bundle_config.go).aicr mirror listexposes only the repeatable scalar--set(no typed flags), with the same identical-accepted / divergent-rejected rule; it does not apply a config file’sspec.bundle.deployment.setoverrides (pkg/cli/mirror.go).--dynamicexports: rejected on mere INTERSECTION with an owned path, regardless of value — install-time mutability of a locked path is itself the violation.- argocd-helm install-time values: ANY install-time key whose path
equals, contains, or is contained by an owned path fails closed at
Helm render time, even when the value is identical — key presence
alone trips the guard
(
pkg/bundler/deployer/argocdhelm/argocdhelm.go). - Component presence (the synthetic
enabledowned path): not changeable by profile reselection — fragments cannot assignenabled, so no--profilechoice adds or removes a component; the lock rejects removing (or bundle-subsetting away) an owned component. Presence changes are catalog/composition changes. Reselecting a profile changes owned VALUE paths only.
-
Selection is explicit. Pick a value with
aicr recipe --profile name=value(orspec.recipe.profilein--config); omission applies the declared default. -
Evidence identity gains a profile path segment. A profiled recipe’s evidence identity appends a lowercase
-<name>-<value>segment, so per-value evidence lands in distinct directories rather than colliding on the criteria-only path.
Strict mode — gating the OSS catalog
--criteria-strict (or AICR_CRITERIA_STRICT=1, or
spec.recipe.criteriaStrict: true in --config) rejects any criteria value
not in the embedded OSS catalog, ignoring --data contributions entirely.
This is intended for CI gates in the OSS repo so the upstream catalog
cannot accidentally start depending on internal-only values during
development. Integrator workflows that legitimately need --data-supplied
values should leave it off.
make qualify in the OSS repo runs unit tests with AICR_CRITERIA_STRICT=1
exported automatically.
Verifying what loaded
Use aicr --debug to inspect external-data discovery and per-file source
resolution:
Sample output (truncated):
Tab-completion for --service / --accelerator / --os / --intent /
--platform reflects values from the registry at the moment the help text is
rendered. Run with --data early in the command line to populate it before
shell completion kicks in.
Pinning your extension catalog
Treat your --data directory like any other artifact: tag it (git tag, OCI
tag, semver) and pin which AICR binary version it was tested against. The
overlay schema is the AICR YAML schema; bumping AICR may add new optional
fields but rarely changes existing ones, so backward compatibility is the
default — but check the AICR release notes when you upgrade the binary.
Typical organization patterns:
- One repo per team / customer. Each team owns its overlay catalog and releases it independently of AICR.
- One central internal repo. A single org-wide
--datacatalog with per-team subdirectories (overlays/team-a/,overlays/team-b/). - OCI distribution. Package the directory into an OCI artifact and pull
on demand;
aicritself doesn’t care about source, only that the path contains aregistry.yamland the expected sub-tree.
Related
- Recipe Development — overlay schema, criteria fields, mixins, base recipe
- Data Architecture — internals of the layered data provider and criteria registry
- CLI Reference —
--data,--criteria-strict,--debugflag definitions - Component Catalog — embedded component list (the baseline you’re extending)
- Validator Extension — adding custom validators (also via
--data)