CLI Configuration File (AICRConfig)
August 17, 2026 · View on GitHub
AICRConfig is a Kubernetes-style YAML/JSON document that captures the inputs
to the five workflow commands — aicr snapshot, aicr recipe, aicr bundle,
aicr validate, and aicr verify — so an end-to-end run version-controls as a
single file instead of a shell script full of flags. Each command accepts it
through the same --config flag:
aicr snapshot --config aicr-config.yaml
aicr recipe --config aicr-config.yaml
aicr bundle --config aicr-config.yaml
aicr validate --config aicr-config.yaml
aicr verify ./my-bundle --config aicr-config.yaml # bundle dir is positional
The first four are the producer pipeline; spec.verify is the consumer side, so
one document can carry both how an artifact is built and the trust floor a
downstream consumer enforces against it.
This page documents the complete document schema in one place. The
CLI Reference shows per-command usage in its
Snapshot,
Recipe,
Validate,
Bundle, and
Verify config-file-mode sections.
The schema's source of truth is
pkg/config.
Document Envelope
kind: AICRConfig # required, exactly this value
apiVersion: aicr.run/v1alpha2 # required, exactly this value
metadata:
name: gke-h100-training # optional, identifying only
spec:
snapshot: {} # each section optional —
recipe: {} # at least ONE must be present
bundle: {}
validate: {}
verify: {}
Each spec.* section is optional and each command reads only its own section,
so a file may carry just one section or any combination. A document with none
of the five sections is rejected.
Loading, Precedence, and Secrets
Sources. --config accepts a local file path or an HTTP/HTTPS URL
(format detected from the extension; fetches are timeout- and size-bounded).
ConfigMap cm:// URIs are intentionally rejected — extract the data with
kubectl and pass the resulting file.
Precedence. A CLI flag always wins over the matching config field. For
slice and map fields (tolerations, selectors, --set), a flag given on the
command line replaces the file's value; it does not append.
Nil vs. empty. For agent selectors and tolerations, omitting the field
entirely (nil) inherits the compiled-in defaults (tolerations defaults to
tolerate all taints), while an explicit empty value ({} / []) clears the
default. Several booleans are tri-state for the same reason: absent means
"inherit the CLI default", an explicit false is an opt-out
(spec.validate.execution.failOnError, failFast,
spec.snapshot.execution.privileged, spec.recipe.criteriaStrict,
spec.validate.evidence.*).
Secrets are never part of the schema. The cosign OIDC identity token used
by attestation and evidence push is deliberately absent — supply it via the
COSIGN_IDENTITY_TOKEN environment variable or the --identity-token flag.
Complete Example
kind: AICRConfig
apiVersion: aicr.run/v1alpha2
metadata:
name: eks-h100-training
spec:
snapshot:
output:
path: snapshot.yaml # same shape as -o
format: yaml # yaml | json | table
template: "" # optional Go template path
agent: # in-cluster snapshot Job pod
namespace: aicr-validation
image: "" # default: ghcr.io/nvidia/aicr:latest
imagePullSecrets: []
jobName: aicr
serviceAccountName: aicr
nodeSelector:
nodeGroup: gpu-worker
tolerations:
- dedicated=gpu-workload:NoSchedule
requireGpu: false
runtimeClassName: "" # mutually exclusive with requireGpu
os: "" # ubuntu | rhel | cos | amazonlinux | ol | talos
requests: "" # "cpu=500m,memory=1Gi"
limits: ""
execution:
timeout: 5m
noCleanup: false
privileged: true # false for PSS-restricted namespaces
maxNodesPerEntry: 0 # 0 = unlimited topology entries
recipe:
criteria: # mutually exclusive with input.snapshot
service: eks
accelerator: h100
intent: training
os: ubuntu
platform: kubeflow
nodes: 2
profile: "" # optional name=value; empty uses the declared default
# configuration: # typed desired-state inputs (not matched on)
# slurm: # only valid when the resolved recipe platform is slurm
# accounting:
# mode: disabled # disabled | customer-managed | aicr-provided
# input:
# snapshot: snapshot.yaml # derive criteria from a snapshot instead
output:
path: recipe.yaml
format: yaml # yaml | json | table
data: "" # optional data-overlay dir/archive
criteriaStrict: false # reject criteria outside the embedded catalog
bundle:
input:
recipe: recipe.yaml # must match recipe.output.path when both set
output:
target: ./bundles # local dir or oci:// URI
imageRefs: "" # external digest file; OCI output only
deployment:
deployer: helmfile # helm | helmfile | argocd | argocd-helm | flux | ...
repo: ""
set: [] # value overrides, "key:path=value"
dynamic: []
vendorCharts: false
appName: "" # argocd parent Application name override
scheduling:
systemNodeSelector: {}
systemNodeTolerations: []
acceleratedNodeSelector:
nodeGroup: gpu-worker
acceleratedNodeTolerations:
- nvidia.com/gpu=present:NoSchedule
workloadGate: ""
workloadSelector: {}
nodes: 2
storageClass: ""
sharedStorageClass: "" # RWX class for opt-in shared filesystems
attestation:
enabled: false
certificateIdentityRegexp: ""
oidcDeviceFlow: false
fulcioURL: "" # private Sigstore overrides; empty = public good
rekorURL: ""
signingKey: "" # KMS key ref (awskms:// | gcpkms:// | ...); empty = keyless OIDC
registry: # OCI transport for oci:// push
insecureTLS: false
plainHTTP: false
validate:
input:
recipe: recipe.yaml
snapshot: snapshot.yaml
agent: # same nil-vs-empty semantics as snapshot.agent
namespace: aicr-validation
image: ""
imagePullSecrets: []
jobName: aicr
serviceAccountName: aicr
nodeSelector:
nodeGroup: gpu-worker
tolerations:
- nvidia.com/gpu=present:NoSchedule
requireGpu: false
execution:
phases: [deployment, conformance, performance]
failOnError: true # tri-state; absent = CLI default (true)
failFast: false
noCluster: false
noCleanup: false
timeout: 40m
evidence:
cncf: # CNCF AI Conformance markdown
dir: ./evidence
cncfSubmission: false # requires dir
features: [] # empty = all features
attestation: # recipe-evidence bundle (ADR-007)
out: evidence-result.json # setting this enables the path
bom: ""
push: "" # OCI ref to push the signed bundle
plainHTTP: false
insecureTLS: false
verify: # consumer side: policy for `aicr verify`
policy: # assertions checked after verification
minTrustLevel: verified # unknown | unverified | attested | verified | max
requireCreator: ci@myorg.example.com
cliVersionConstraint: ">= 0.16.0"
trust: # material verification runs against
certificateIdentityRegexp: "" # when set, must BEGIN with https://github.com/NVIDIA/aicr/
key: "" # KMS URI or local PEM public-key path
trustRoot: "" # private Sigstore trusted_root.json
Field Reference
spec.snapshot
Inputs to aicr snapshot. There is no input section — the snapshot is
produced from the live cluster.
| Field | Type | Notes |
|---|---|---|
output.path | string | Output file path (same as -o) |
output.format | string | yaml | json | table |
output.template | string | Optional Go template path |
agent.* | object | In-cluster capture Job pod: namespace, image, imagePullSecrets, jobName, serviceAccountName, nodeSelector, tolerations, requireGpu, runtimeClassName (mutually exclusive with requireGpu), os, requests, limits. Mirrors spec.validate.agent so one file pins matching placement for both |
execution.timeout | duration string | e.g. 5m |
execution.noCleanup | bool | Keep the capture Job after completion |
execution.privileged | bool (tri-state) | Set false for PSS-restricted namespaces |
execution.maxNodesPerEntry | int | 0 = unlimited topology entries |
spec.recipe
Inputs to aicr recipe. criteria and input.snapshot are mutually
exclusive — query by criteria or derive from a snapshot, not both.
| Field | Type | Notes |
|---|---|---|
criteria.service / .accelerator / .intent / .os / .platform | string | Same names and values as the CLI flags |
criteria.nodes | int | Target GPU node count |
profile | string | Optional configuration profile selection in name=value form. Empty applies the resolved declaration's default. |
configuration.slurm.accounting.mode | string | Slurm accounting ownership: disabled (default) | customer-managed | aicr-provided; mirrors --slurm-accounting-mode. Only valid when the resolved recipe platform is slurm (whether from criteria.platform, a snapshot, or --platform) — an explicit mode (even disabled) on any other platform is rejected with INVALID_REQUEST |
input.snapshot | string | Snapshot path to derive the recipe from |
output.path | string | Recipe output path |
output.format | string | yaml | json | table |
data | string | Data-overlay directory/archive (same as --data) |
criteriaStrict | bool (tri-state) | Reject criteria values outside the embedded catalog; mirrors --criteria-strict / AICR_CRITERIA_STRICT (any of the three enables it) |
spec.bundle
Inputs to aicr bundle.
| Field | Type | Notes |
|---|---|---|
input.recipe | string | Recipe to bundle |
output.target | string | Local directory or oci:// URI |
output.imageRefs | string | Optional external image-reference output file for an OCI output.target only. Local output is rejected. Its parent must be an existing real directory, and the target must be outside and not aliased to the planned or completed bundle. |
deployment.deployer | string | Deployer choice (same values as --deployer) |
deployment.repo | string | GitOps repo for repo-shaped deployers |
deployment.set / .dynamic | []string | Value overrides, key:path=value |
deployment.vendorCharts | bool | Vendor charts into the bundle |
deployment.appName | string | Argo CD parent Application name override (multi-bundle installs sharing a namespace) |
scheduling.* | object | systemNodeSelector/Tolerations, acceleratedNodeSelector/Tolerations, workloadGate, workloadSelector, nodes, storageClass, sharedStorageClass. Selectors are YAML maps; tolerations use the CLI's key=value:effect strings |
attestation.enabled | bool | Enable bundle attestation (signing); keyless OIDC by default, KMS-backed when signingKey is set |
attestation.certificateIdentityRegexp | string | Expected signer identity |
attestation.oidcDeviceFlow | bool | Device-code flow for headless signing |
attestation.fulcioURL / .rekorURL | string | Private Sigstore endpoints; empty = public-good defaults |
attestation.signingKey | string | KMS key reference for key-based signing (awskms:// | gcpkms:// | azurekms:// | hashivault://); empty = keyless OIDC. Mutually exclusive with the keyless-only inputs (oidcDeviceFlow, fulcioURL, --identity-token) |
registry.insecureTLS / .plainHTTP | bool | OCI transport options for push |
When output.imageRefs is set, AICR writes the published OCI digest through a
mode-0600 temporary file and an anchored same-directory rename. The target
may be absent or an existing retained regular file; directories, symlinks,
other non-regular files, and bundle aliases are rejected. The final validation
and rename are ordered but are not one atomic identity-conditioned filesystem
operation, so no other process should mutate the target directory while the
bundle command runs.
spec.validate
Inputs to aicr validate.
| Field | Type | Notes |
|---|---|---|
input.recipe / .snapshot | string | Recipe + snapshot to validate |
agent.* | object | In-cluster validation Job pod; same fields and nil-vs-empty semantics as spec.snapshot.agent (minus runtimeClassName/os/requests/limits) |
execution.phases | []string | e.g. [deployment, conformance, performance] |
execution.failOnError | bool (tri-state) | Absent = CLI default (true); explicit false opts out |
execution.failFast | bool (tri-state) | Stop after the first failed phase |
execution.noCluster | bool | Test mode: no cluster access, constraints evaluated inline |
execution.noCleanup | bool | Keep validation Jobs after completion |
execution.timeout | duration string | e.g. 40m |
evidence.cncf.dir | string | CNCF AI Conformance evidence directory (--evidence-dir) |
evidence.cncf.cncfSubmission | bool (tri-state) | Emit submission layout; requires dir |
evidence.cncf.features | []string | Empty = all features; honored only with cncfSubmission |
evidence.attestation.out | string | Recipe-evidence result path (v1 for unprofiled recipes, v2 for profiled ones) — setting it enables the attestation path |
evidence.attestation.bom / .push | string | BOM input; OCI ref for the signed bundle push |
evidence.attestation.plainHTTP / .insecureTLS | bool (tri-state) | Push transport options |
spec.verify
Verification policy for aicr verify, the one consumer-side section. The two
sub-sections mirror how the command consumes them: policy holds assertions
checked after verification runs, trust holds the material it verifies against.
| Field | Type | Notes |
|---|---|---|
policy.minTrustLevel | string | unknown | unverified | attested | verified, or max (the CLI default) to auto-detect the highest level the bundle can reach |
policy.requireCreator | string | Pins the OIDC identity in the bundle attestation's signing certificate |
policy.cliVersionConstraint | string | Constrains the aicr version in the attestation predicate; supports >=, >, <=, <, ==, !=, and a bare version means >= |
trust.certificateIdentityRegexp | string | Certificate identity pattern for binary attestation verification; must begin with https://github.com/NVIDIA/aicr/ (leading ^ allowed) and must not use top-level alternation, so it stays confined to the repository |
trust.key | string | KMS key URI (awskms:// | gcpkms:// | azurekms:// | hashivault://) or local PEM public-key path; the verify counterpart to spec.bundle.attestation.signingKey |
trust.trustRoot | string | Path to a private Sigstore trusted_root.json, additive to the built-in public-good root |
policy.minTrustLevel sets operator policy, not an org-enforced guardrail.
A committed value lowers the effective floor as readily as it raises it
(unknown makes the trust check a no-op, since every level meets it), so treat
it as a reviewable choice rather than a control that cannot be relaxed.
A lowered floor admits any bundle whose actual trust level reaches it, which is
broader than unsigned bundles. It also covers chains that legitimately degraded:
an attested bundle whose binary attestation is absent, or one carrying external
--data, both report attested against a verified maximum, so the default
max rejects them while a lowered floor does not.
What no policy value can wave through: checksum failures, and attestations that are present but fail verification. Those are rejected regardless of the floor.
aicr verify logs the floor at INFO when config is what supplies it: that is,
when --min-trust-level is absent and the configured value is anything other
than max. An explicit flag takes precedence instead, and that override is
logged separately by the flag-precedence path.
Every field is a durable, non-secret reference or policy value; no private key
material is part of the schema. Three aicr verify flags are deliberately
excluded: the bundle directory (a positional argument), --format
(presentation, not policy), and --insecure-ignore-tlog, which weakens the
trust floor and so stays command-line-only rather than something a committed
file can silently enable. It still composes with a config-supplied trust.key.
Cross-Section Rules
- At least one of the five
spec.*sections must be present. spec.recipe.criteriaandspec.recipe.input.snapshotare mutually exclusive.- When both
spec.recipe.output.pathandspec.bundle.input.recipeare set, they must reference the same file (compared afterfilepath.Clean; mixing absolute and relative forms is rejected). Mismatched paths in a workflow file are almost always a typo, so the loader fails up-front. - Enum-valued fields (
criteria.*, output formats, phases) are validated with the same parsers the CLI flags use, so error messages match the CLI's.
See Also
- CLI Reference — per-command flags and config-file-mode examples
- Validation — validation phases and evidence workflow
- Bundling — deployers and bundle outputs