> This page is for AI Cluster Runtime, version Latest · v1.0.0 (default).
> For other versions, use one of these documentation indexes:
> - Latest · v1.0.0 (default): https://docs.nvidia.com/aicr/latest/llms.txt
> - v1.0.0: https://docs.nvidia.com/aicr/v1.0.0/llms.txt
> - v0.22.0: https://docs.nvidia.com/aicr/v0.22.0/llms.txt
> - v0.21.1: https://docs.nvidia.com/aicr/v0.21.1/llms.txt

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.nvidia.com/aicr/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.nvidia.com/aicr/_mcp/server.

# CLI Configuration File (AICRConfig)

`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:

```shell
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](/aicr/user-guide/cli-reference) shows per-command usage in its
[Snapshot](/aicr/user-guide/cli-reference#snapshot-config-file-mode),
[Recipe](/aicr/user-guide/cli-reference#config-file-mode-recommended),
[Validate](/aicr/user-guide/cli-reference#validate-config-file-mode),
[Bundle](/aicr/user-guide/cli-reference#bundle-config-file-mode), and
[Verify](/aicr/user-guide/cli-reference#verify-config-file-mode) config-file-mode sections.
The schema's source of truth is
[`pkg/config`](https://github.com/NVIDIA/aicr/tree/main/pkg/config).

## Document Envelope

```yaml
kind: AICRConfig               # required, exactly this value
apiVersion: aicr.run/v1beta1   # required; the only accepted 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.

Reading is per-command; **validation is not**. Every section present in the file
is checked when the file loads, whichever command loaded it — so a malformed
`spec.bundle` fails `aicr snapshot --config` too, naming the spec path that is
wrong. Keep that in mind for a single document spanning several sections: an
error can name a section the running command never reads.

`AICRConfig` is an authored file, so its `apiVersion` is yours to set. The
value is `aicr.run/v1beta1`, and as of v1.0.0 it is the only one accepted: the
superseded `aicr.run/v1alpha2` was read-with-a-warning in v0.22 and is rejected
now, as are empty and unknown values. A config still carrying it must be edited
by hand — AICR has no conversion layer. The full release-by-release table
is in
[Catalog and binary compatibility](/aicr/integrator-guide/data-extension#catalog-and-binary-compatibility);
the policy behind it is
[ADR-022](https://github.com/NVIDIA/aicr/blob/main/docs/design/022-artifact-maturity-and-deprecation.md).

## 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

```yaml
kind: AICRConfig
apiVersion: aicr.run/v1beta1
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: matches the CLI version (:latest on dev/-next builds)
      imagePullSecrets: []
      # jobName is an optional PREFIX, not a name — the run ID is always
      # appended. serviceAccountName is exact-if-exists: an existing
      # ServiceAccount of exactly that name is used verbatim and the run
      # then creates and deletes NO RBAC; otherwise it is a prefix too.
      # Omit both to take the run-scoped defaults.
      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
    #   gke:                         # only valid for the h100 gke-cos kubeflow training leaf
    #     tcpxoInterfaces:           # required when that leaf ships torch-distributed-tcpxo;
    #       - {interfaceName: eth1, network: <cluster>-gpu-nic-0}  # ordered eth1..eth8 (8 entries);
    #                                                          # names from the cluster provisioner
    # 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
      draEvictionNodeLabel: nvidia.com/dra-kubelet-plugin=true
      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: []
      # Optional; omitted here so the defaults apply (both aicr-validate).
      # jobName is always a prefix with the run ID appended;
      # serviceAccountName is exact-if-exists — an existing ServiceAccount
      # of exactly that name is used verbatim and the run creates no RBAC.
      nodeSelector:
        nodeGroup: gpu-worker
      tolerations:
        - nvidia.com/gpu=present:NoSchedule
      requireGpu: false
    execution:
      phases: [deployment, conformance, performance]
      skipChecks: []                 # checks this run cannot satisfy; empty = run them all
      failOnError: true              # tri-state; absent = CLI default (true)
      failFast: false
      noCluster: false
      noCleanup: false
      timeout: 5m                    # aicr validate: live-capture agent Job only; inert here (input.snapshot is set)
    evidence:
      cncf:                          # CNCF AI Conformance markdown
        dir: ./evidence
        cncfSubmission: false        # requires dir
        features: []                 # empty = all features
      attestation:                   # recipe-evidence bundle (ADR-007)
        out: ./attestation           # directory; 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 |
| `agent.jobName` | string | Optional **prefix**, never an exact name — the run ID is always appended (`<prefix>-<run-id>`). Omit it unless you need a custom prefix (default: `aicr`) |
| `agent.serviceAccountName` | string | Optional and **exact-if-exists**, so unlike `jobName` it is not always a prefix. When a ServiceAccount of exactly this name already exists in `agent.namespace`, the pod runs as it verbatim, the run creates and deletes **no** ServiceAccount, Role, RoleBinding, ClusterRole, or ClusterRoleBinding, and per-run permission isolation is waived — concurrent runs share that identity's persistent grants. When it does not exist, the value is a prefix, the run ID is appended (`<prefix>-<run-id>`), and the run owns a full run-scoped RBAC set it deletes at cleanup. Omitting the field is not the same as writing `aicr` into it: an omitted name is never probed, so the run always takes the run-scoped `aicr-<run-id>`. See [Using an existing ServiceAccount](/aicr/user-guide/agent-deployment#using-an-existing-serviceaccount-irsa-and-workload-identity) |
| `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` |
| `configuration.runtimeInventory.mode` | string | Runtime AI inventory (`k8s-aibom`) selection: `enabled` \| `disabled`; mirrors `--runtime-inventory` |
| `configuration.gke.tcpxoInterfaces` | []object | Ordered `interfaceName`/`network` pairs (eth1–eth8) for the `torch-distributed-tcpxo` runtime; mirrors `--gke-tcpxo-interfaces` |
| `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`, `draEvictionNodeLabel`, `workloadGate`, `workloadSelector`, `nodes`, `storageClass`, `sharedStorageClass`. Selectors are YAML maps; tolerations use the CLI's `key=value:effect` strings. `draEvictionNodeLabel` accepts one `key=value` label and has no default: setting it opts in to DRA eviction coordination, which AICR applies only when DRA and GPU Operator are both enabled. |
| `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 | The **live snapshot-capture** Job pod `aicr validate` deploys when `input.snapshot` is empty; same fields and nil-vs-empty semantics as `spec.snapshot.agent` (minus `runtimeClassName`/`os`/`requests`/`limits`). Neither `jobName` nor `serviceAccountName` names the validator Jobs, whose RBAC is always run-scoped |
| `agent.jobName` | string | Optional **prefix**, never an exact name — the run ID is always appended (`<prefix>-<run-id>`). Omit it to take the default (`aicr-validate`) |
| `agent.serviceAccountName` | string | Optional and **exact-if-exists**, with the same two branches as `spec.snapshot.agent.serviceAccountName`: an existing ServiceAccount of exactly this name in `agent.namespace` is used verbatim, the run creates and deletes **no** RBAC, and per-run permission isolation is waived — concurrent runs share that identity's persistent grants; when it does not exist the value is a prefix with the run ID appended (`<prefix>-<run-id>`) and the run owns a full run-scoped RBAC set. Omitting the field is not the same as writing `aicr-validate` into it: an omitted name is never probed, so the run always takes the run-scoped `aicr-validate-<run-id>`. See [Using an existing ServiceAccount](/aicr/user-guide/agent-deployment#using-an-existing-serviceaccount-irsa-and-workload-identity) |
| `execution.phases` | []string | e.g. `[deployment, conformance, performance]` |
| `execution.skipChecks` | []string | Checks to withhold from every phase that runs, one level below `phases`. For a caller that cannot satisfy a check the recipe declares, e.g. a lane deploying a subset of the recipe. Each named check is **reported as skipped**, not dropped, so the CTRF report and the recipe-evidence bundle still account for it. Rejected before any validation resource is created when a name matches no check, when the list would leave a requested phase with nothing to run, or when it is set alongside `evidence.cncf.dir` (that renderer omits skipped checks, so a submission would silently lose the requirement). A `cm://` recipe is read from the cluster first, so that form contacts the API server before the list is judged. Mirrors `--skip-check` |
| `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 | `aicr validate`: timeout for the live snapshot-capture agent Job (`--timeout`, default `5m`); inert when `input.snapshot` is set, and validator Jobs keep their per-check timeouts. Go SDK: `Config.ValidateSettings().Timeout` carries the same value for `WithValidationTimeout`, which caps the whole `ValidateState` run (see [Go library](/aicr/integrator-guide/go-library-integration#what-validatesettings-does-and-does-not-carry)) |
| `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 | Output directory for the recipe-evidence bundle (predicateType v3; same as `--emit-attestation`), which receives `summary-bundle/` and `pointer.yaml` — 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.criteria` and `spec.recipe.input.snapshot` are mutually
  exclusive.
- When **both** `spec.recipe.output.path` and `spec.bundle.input.recipe` are
  set, they must reference the same file (compared after `filepath.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](/aicr/user-guide/cli-reference) — per-command flags and config-file-mode
  examples
- [Validation](/aicr/user-guide/validation) — validation phases and evidence workflow
- [Bundling](/aicr/user-guide/generating-bundles) — deployers and bundle outputs