Documentation
¶
Overview ¶
Package aicr is the public, compatibility-reviewed Go library surface for external consumers of the AI Cluster Runtime.
External projects should import THIS package and use the types and constructors re-exported here. The underlying pkg/* packages are public and will remain importable, but this facade is the reviewed compatibility contract the project intends to stabilize at v1.0.
Surface ¶
Client exposes the end-to-end operations the CLI / server share:
- ResolveRecipe / ResolveRecipeFromCriteria / ResolveRecipeFromSnapshot and LoadRecipe — produce or load a *RecipeResult.
- BundleComponents — resolve Helm values and stitched manifests for each component in a *RecipeResult.
- CollectSnapshot — deploy the snapshotter Job and retrieve a *Snapshot.
- LoadSnapshot — read a previously captured *Snapshot from a file, URL, or cm:// ConfigMap, for the common case where the snapshot already exists and no cluster is needed.
- DiffSnapshots — compare two loaded or collected snapshots in memory and return facade-owned field-level changes for drift detection.
- ValidateState — evaluate a resolved recipe against a snapshot, running deployment / conformance / performance phases.
- LoadConfig — read and validate the AICRConfig a team commits, from a file or an HTTP(S) URL. WrapConfig lifts one already parsed elsewhere; it does no parsing itself. Either way the resulting Config DERIVES options (Config.BundleVerifyOptions, Config.RecipeSource, Config.RecipeCriteria, ...) rather than applying them: a Config never attaches to a Client and is never consulted implicitly, so caller precedence stays one readable line at the call site.
Resolution behavior is tuned per call with RecipeResolveOption — WithProfile, WithAccountingMode, and WithSnapshotCriteriaRelaxation (the relax-and-retry policy behind `aicr recipe --snapshot`, which takes the criteria dimensions the caller stated explicitly and may clear the rest).
The supply-chain half covers both producing and checking artifacts:
- VerifyBundle — check a deployment bundle's checksums and attestation chain, and evaluate a trust-floor / creator / version policy.
- VerifyEvidence — check a recipe-evidence bundle's signature and hash chain, from a pointer file, an OCI reference, or a directory.
- VerifyCatalog / SignCatalog — check or produce the Sigstore signature over this Client's recipe catalog.
- RecipeDigest — the canonical recipe digest an evidence predicate records, for CI gates detecting stale evidence.
- EmitRecipeEvidence / PublishEvidence — build, then sign and push, a recipe-evidence bundle.
- VerifyBinaryAttestation — package-level; prove an aicr binary was built by NVIDIA CI.
All facade types (Snapshot, SnapshotDiff, SnapshotChange, AgentConfig, Criteria, RecipeRequest, RecipeResult, ComponentBundle, ComponentRef, PhaseResult, AllowLists) are facade-owned structs translated to and from the upstream pkg/* shapes, so internal field renames don't churn external callers.
Seven types remain deliberate transparent aliases: BundleConfig, BundleAttester, BundleArtifact, OIDCResolveOptions, CriteriaRegistry, BundleVerifyReport, and EvidenceVerification. They preserve direct interoperability with the configuration builders, attestation implementations, bundle results, and provider-scoped criteria registry used elsewhere in AICR. The API compatibility gate compares their repository-local reachable type closure without freezing unrelated exports in the evolving target packages.
Example ¶
client, err := aicr.NewClient(
aicr.WithRecipeSource(aicr.FilesystemSource("/etc/aicr/recipes")),
)
if err != nil {
return err
}
defer func() {
if closeErr := client.Close(); closeErr != nil {
slog.Error("failed to close AICR client", "error", closeErr)
}
}()
result, err := client.ResolveRecipe(ctx, aicr.RecipeRequest{
Service: "eks",
Region: "us-east-1",
Accelerator: "h100",
Nodes: 8, // worker-node count, not GPU count
Intent: "training",
})
Stability ¶
AICR is currently pre-1.0. Under Go module versioning, a v0 minor release may contain breaking API changes. The project detects and explicitly records incompatible changes to this facade, but v0 consumers must pin a patch version and audit upgrades.
Starting with v1.0, this package's exported API follows semantic versioning: breaking changes require a major release, minor releases may add API, and patch releases contain compatible fixes. The underlying pkg/* packages may continue to evolve under the stability tiers documented in docs/integrator/public-api.md.
Concurrency and Client lifecycle ¶
Each Client owns its own DataProvider and per-DataProvider cached metadata store, component registry, and criteria registry. Multiple Clients constructed from different sources can resolve recipes concurrently without clobbering each other — a property multi-tenant consumers (e.g., a controller managing one Client per per-tenant configuration) rely on. This is a v0.12+ guarantee; earlier facade builds mutated a process-global DataProvider via recipe.SetDataProvider and were unsafe to construct concurrently.
**Retain and reuse Client instances.** The recipe package keys its internal caches on DataProvider identity (pointer-equality of the interface value). Each call to NewClient builds a fresh DataProvider, so two Clients constructed from the same recipe source still produce distinct cache entries and do their own directory walk on first use. Long-running consumers should cache Clients keyed by their configuration (e.g., a content hash of the recipe-source settings) rather than constructing one per request.
**Call Close when done.** When a Client is no longer needed (cache eviction, controller shutdown), call Close to drop its metadata store, component registry, and criteria registry from the recipe package's internal caches. Without this, memory grows monotonically with the number of unique DataProviders ever observed.
See docs/integrator/go-library.md for the integration guide.
Example ¶
Example is the quick start: build a Client over the embedded recipe data and resolve a recipe from explicit criteria.
package main
import (
"context"
"fmt"
"log"
aicr "github.com/NVIDIA/aicr/pkg/client/v1"
)
func main() {
ctx := context.Background()
client, err := aicr.NewClient(
aicr.WithRecipeSource(aicr.EmbeddedSource()),
aicr.WithVersion("v0.19.0"),
)
if err != nil {
log.Print(err)
return
}
defer func() { _ = client.Close() }()
result, err := client.ResolveRecipeFromCriteria(ctx, &aicr.Criteria{
Service: "eks",
Accelerator: "h100",
Intent: "training",
})
if err != nil {
log.Print(err)
return
}
// Name is the resolved criteria's canonical string. Unstated dimensions
// still render, with an empty value.
fmt.Println(result.Name)
}
Output: criteria(service=eks, accelerator=h100, intent=training, os=, platform=)
Example (BundleAndVerify) ¶
Example_bundleAndVerify is the integrator path end to end: resolve a recipe, render its deployment bundle, then check what was written.
It runs hermetically against the embedded catalog, into a temporary directory, with no signing and no network — which is why it can assert its output, and why that output is "unverified".
Reading the result ¶
Failure arrives on THREE independent channels, and checking one is not enough:
- the returned error — the bundle could not be produced at all;
- BundleArtifact.HasErrors() — per-bundler failures that did not abort the run, so files exist but the set is incomplete;
- on verification, BundleVerification.PolicyFailure (the trust floor was not met) AND Report.Errors (a check itself failed, e.g. a bad checksum).
Why "unverified" ¶
BundleOptions.Attester is nil here, so MakeBundle uses the no-op attester — the same default as `aicr bundle` without --attest. An unsigned bundle can reach "unverified" (checksums valid, no attestation) and no higher, so demanding MinTrustLevel "verified" would fail every time. Leaving it empty selects the "max" default: verify against the highest level this bundle can actually achieve. To reach "verified", pass an Attester and a binary attestation.
package main
import (
"context"
"fmt"
"log"
"os"
aicr "github.com/NVIDIA/aicr/pkg/client/v1"
)
func main() {
ctx := context.Background()
client, err := aicr.NewClient(
aicr.WithRecipeSource(aicr.EmbeddedSource()),
aicr.WithVersion("v0.19.0"),
)
if err != nil {
log.Print(err)
return
}
defer func() { _ = client.Close() }()
result, err := client.ResolveRecipeFromCriteria(ctx, &aicr.Criteria{
Service: "eks",
Accelerator: "h100",
Intent: "training",
})
if err != nil {
log.Print(err)
return
}
// Per-component Helm values and stitched manifests, without touching disk.
bundles, err := client.BundleComponents(ctx, result)
if err != nil {
log.Print(err)
return
}
for _, b := range bundles {
_ = b.Component.Name
_ = b.HelmValues
_ = b.Manifests
}
// Or write a full bundle directory.
outputDir, err := os.MkdirTemp("", "aicr-bundle-")
if err != nil {
log.Print(err)
return
}
defer func() { _ = os.RemoveAll(outputDir) }()
artifact, err := client.MakeBundle(ctx, result, aicr.BundleOptions{
OutputDir: outputDir,
})
if err != nil {
log.Print(err)
return
}
// Non-fatal per-bundler failures: files were written, but not all of them.
if artifact.HasErrors() {
log.Printf("bundle completed with %d errors", len(artifact.Errors))
return
}
verification, err := client.VerifyBundle(ctx, outputDir, aicr.BundleVerifyOptions{
// Empty means "max": verify against the highest level achievable.
MinTrustLevel: "",
})
if err != nil {
log.Print(err)
return
}
if verification.PolicyFailure != "" {
log.Printf("policy: %s", verification.PolicyFailure)
return
}
if len(verification.Report.Errors) > 0 {
log.Printf("verification: %s", verification.Report.Errors[0])
return
}
fmt.Println(verification.Report.TrustLevel)
}
Output: unverified
Example (CommittedConfig) ¶
Example_committedConfig resolves from an AICRConfig a team commits alongside their code, so snapshot / recipe / bundle / verify settings are not retyped on each invocation.
The ORDER matters and is the reason this example exists. Criteria membership is validated against a CriteriaRegistry, which is per-DataProvider — so the Client must exist and its catalog must be loaded before RecipeCriteria can resolve a value an external --data overlay contributed. Calling RecipeCriteria first works only for values in the embedded catalog.
package main
import (
"context"
"fmt"
"log"
aicr "github.com/NVIDIA/aicr/pkg/client/v1"
)
func main() {
ctx := context.Background()
cfg, err := aicr.LoadConfig(ctx, "aicr-config.yaml")
if err != nil {
log.Print(err)
return
}
// spec.recipe.data, when the document sets one.
source, ok := cfg.RecipeSource()
if !ok {
source = aicr.EmbeddedSource()
}
client, err := aicr.NewClient(aicr.WithRecipeSource(source))
if err != nil {
log.Print(err)
return
}
defer func() { _ = client.Close() }()
// Seeds the registry RecipeCriteria validates against.
if err = client.LoadCatalog(ctx); err != nil {
log.Print(err)
return
}
criteria, err := cfg.RecipeCriteria(client.CriteriaRegistry())
if err != nil {
log.Print(err)
return
}
opts, err := cfg.RecipeResolveOptions()
if err != nil {
log.Print(err)
return
}
result, err := client.ResolveRecipeFromCriteriaWithOptions(ctx, criteria, opts...)
if err != nil {
log.Print(err)
return
}
fmt.Println(result.Name)
}
Output:
Example (CriteriaDimensions) ¶
Example_criteriaDimensions lists the criteria dimensions subject to the coverage post-condition — the values WithSnapshotCriteriaRelaxation accepts.
nodes is deliberately absent: no overlay gates on it, so it never participates in overlay selection or coverage.
package main
import (
"fmt"
aicr "github.com/NVIDIA/aicr/pkg/client/v1"
)
func main() {
for _, dim := range aicr.AllCriteriaDimensions() {
fmt.Println(dim)
}
}
Output: service accelerator intent os platform
Example (ErrorCodes) ¶
Example_errorCodes shows the error-handling contract. Every facade error is a *pkg/errors.StructuredError carrying an ErrorCode, and StructuredError.Is matches on that code — so errors.Is works through wrap chains without unwrapping by hand.
package main
import (
"context"
stderrors "errors"
"fmt"
"log"
aicr "github.com/NVIDIA/aicr/pkg/client/v1"
aicrerrors "github.com/NVIDIA/aicr/pkg/errors"
)
func main() {
ctx := context.Background()
client, err := aicr.NewClient(aicr.WithRecipeSource(aicr.EmbeddedSource()))
if err != nil {
log.Print(err)
return
}
defer func() { _ = client.Close() }()
// A service no catalog defines. Membership is checked against this
// Client's CriteriaRegistry, so the request is rejected rather than
// silently resolving something broader.
_, err = client.ResolveRecipeFromCriteria(ctx, &aicr.Criteria{Service: "no-such-service"})
switch {
case err == nil:
fmt.Println("resolved")
case stderrors.Is(err, aicrerrors.New(aicrerrors.ErrCodeInvalidRequest, "")):
fmt.Println("invalid request")
case stderrors.Is(err, aicrerrors.New(aicrerrors.ErrCodeNotFound, "")):
fmt.Println("not found")
default:
fmt.Println("other")
}
}
Output: invalid request
Example (ResolveFromSnapshot) ¶
Example_resolveFromSnapshot approximates `aicr recipe --snapshot`: resolve against a captured snapshot with the relax-and-retry policy the CLI applies.
The facade does not derive criteria from the snapshot ¶
ResolveRecipeFromSnapshotWithOptions takes your Criteria verbatim and uses the snapshot only to evaluate constraints and drive snapshot-aware post-processing. Producing criteria from a snapshot's measurements is the CLI's job, and there is no facade entry point for it yet — the integration guide shows the pkg/fingerprint escape hatch and the coupling it costs.
So SUPPLY every dimension yourself, then use WithSnapshotCriteriaRelaxation to say which ones the user actually typed. Below, intent was typed and service and os were derived, so only those two may be relaxed.
These values are chosen so relaxation genuinely fires: no kind overlay states an os, so the derived os comes back uncovered and is cleared, while the stated intent is protected. Two ways to make the policy inert, both silent: name every dimension you supplied (a specified-and-stated dimension is never cleared), or leave dimensions unset (the coverage post-condition only reports dimensions you SPECIFIED, so an unset one is never uncovered).
package main
import (
"context"
"fmt"
"log"
aicr "github.com/NVIDIA/aicr/pkg/client/v1"
)
func main() {
ctx := context.Background()
client, err := aicr.NewClient(aicr.WithRecipeSource(aicr.EmbeddedSource()))
if err != nil {
log.Print(err)
return
}
defer func() { _ = client.Close() }()
// File path, HTTP(S) URL, or cm://namespace/name ConfigMap.
snap, err := client.LoadSnapshot(ctx, "snapshot.yaml", "")
if err != nil {
log.Print(err)
return
}
criteria := &aicr.Criteria{
Service: "kind", // derived: read off the snapshot
OS: "ubuntu", // derived, and uncovered by every kind overlay
Intent: "inference", // stated: the user asked for this
}
result, err := client.ResolveRecipeFromSnapshotWithOptions(ctx, criteria, snap,
aicr.WithSnapshotCriteriaRelaxation(aicr.DimensionIntent))
if err != nil {
log.Print(err)
return
}
// Prints "relaxed os" for the criteria above: no kind overlay distinguishes
// ubuntu, so the derived os is cleared and the retry succeeds. Intent can
// never appear here — it was declared stated.
for _, dim := range result.RelaxedDimensions {
fmt.Printf("relaxed %s; resolved recipe is broader than requested\n", dim)
}
}
Output:
Example (TrustLevels) ¶
Example_trustLevels enumerates the bundle trust levels BundleVerifyOptions.MinTrustLevel accepts. The CLI's --min-trust-level completion is generated from this same list.
Two properties to note before validating input against it. The order is ALPHABETICAL, not by rank — do not treat position as severity. And the list is not the full accepted set: the default "max" (auto-detect the highest achievable level) and the empty string are both valid and both absent here, so a membership check built from this list alone rejects the option's own default.
package main
import (
"fmt"
aicr "github.com/NVIDIA/aicr/pkg/client/v1"
)
func main() {
for _, level := range aicr.TrustLevels() {
fmt.Println(level)
}
}
Output: attested unknown unverified verified
Index ¶
- Constants
- func RenderEvidenceJSON(r *EvidenceVerification) ([]byte, error)
- func RenderEvidenceMarkdown(r *EvidenceVerification) string
- func SelectFromRecipe(r *RecipeResult, selector string) (any, error)
- func SelectFromRecipeWithContext(ctx context.Context, r *RecipeResult, selector string) (any, error)
- func ToInternalAllowLists(al *AllowLists) *recipe.AllowLists
- func ToInternalCriteria(c *Criteria) *recipe.Criteria
- func TrustLevels() []string
- func ValidateIdentityPattern(pattern string) error
- func VerifyBinaryAttestation(ctx context.Context, opts BinaryAttestationVerifyOptions) (string, error)
- func WriteSnapshotDiffTable(w io.Writer, result *SnapshotDiff) error
- type AgentConfig
- type AllowLists
- type BinaryAttestationVerifyOptions
- type BundleArtifact
- type BundleAttester
- type BundleConfig
- type BundleOptions
- type BundleVerification
- type BundleVerifyOptions
- type BundleVerifyReport
- type CatalogEntry
- type CatalogSignOptions
- type CatalogSignResult
- type CatalogVerification
- type CatalogVerifyOptions
- type Client
- func (c *Client) AdoptRecipe(ctx context.Context, rec *recipe.RecipeResult) (*RecipeResult, error)
- func (c *Client) BundleComponents(ctx context.Context, r *RecipeResult) ([]ComponentBundle, error)
- func (c *Client) Close() error
- func (c *Client) CollectSnapshot(ctx context.Context, cfg *AgentConfig) (*Snapshot, error)
- func (c *Client) ComputeHealth(ctx context.Context, filter *Criteria) (*health.Report, error)
- func (c *Client) CriteriaRegistry() *CriteriaRegistry
- func (c *Client) DiffSnapshots(ctx context.Context, baseline, target *Snapshot, opts SnapshotDiffOptions) (*SnapshotDiff, error)
- func (c *Client) EmitRecipeEvidence(ctx context.Context, rec *RecipeResult, snap *Snapshot, results []*PhaseResult, ...) error
- func (c *Client) ListCatalog(ctx context.Context, filter *Criteria) ([]CatalogEntry, error)
- func (c *Client) LoadCatalog(ctx context.Context) error
- func (c *Client) LoadRecipe(ctx context.Context, path, kubeconfig string) (*RecipeResult, error)
- func (c *Client) LoadSnapshot(ctx context.Context, path, kubeconfig string) (*Snapshot, error)
- func (c *Client) MakeBundle(ctx context.Context, recipe *RecipeResult, opts BundleOptions) (BundleArtifact, error)
- func (c *Client) MergeReports(results []*PhaseResult) *ctrf.Report
- func (c *Client) PublishEvidence(ctx context.Context, opts EvidencePublishOptions) error
- func (c *Client) RecipeDigest(ctx context.Context, opts RecipeDigestOptions) (string, error)
- func (c *Client) ResolveRecipe(ctx context.Context, req RecipeRequest) (*RecipeResult, error)
- func (c *Client) ResolveRecipeFromCriteria(ctx context.Context, criteria *Criteria) (*RecipeResult, error)
- func (c *Client) ResolveRecipeFromCriteriaWithOptions(ctx context.Context, criteria *Criteria, opts ...RecipeResolveOption) (*RecipeResult, error)
- func (c *Client) ResolveRecipeFromCriteriaWithProfile(ctx context.Context, criteria *Criteria, profile string) (*RecipeResult, error)
- func (c *Client) ResolveRecipeFromSnapshot(ctx context.Context, criteria *Criteria, snap *Snapshot) (*RecipeResult, error)
- func (c *Client) ResolveRecipeFromSnapshotWithOptions(ctx context.Context, criteria *Criteria, snap *Snapshot, ...) (*RecipeResult, error)
- func (c *Client) ResolveRecipeFromSnapshotWithProfile(ctx context.Context, criteria *Criteria, snap *Snapshot, profile string) (*RecipeResult, error)
- func (c *Client) SignCatalog(ctx context.Context, opts CatalogSignOptions) (*CatalogSignResult, error)
- func (c *Client) ValidateState(ctx context.Context, recipe *RecipeResult, snap *Snapshot, ...) ([]*PhaseResult, error)
- func (c *Client) VerifyBundle(ctx context.Context, bundleDir string, opts BundleVerifyOptions) (*BundleVerification, error)
- func (c *Client) VerifyCatalog(ctx context.Context, bundlePath string, opts CatalogVerifyOptions) (*CatalogVerification, error)
- func (c *Client) VerifyEvidence(ctx context.Context, opts EvidenceVerifyOptions) (*EvidenceVerification, error)
- type ComponentBundle
- type ComponentRef
- type Config
- func (c *Config) BundleVerifyOptions() (BundleVerifyOptions, error)
- func (c *Config) IsCriteriaStrict() bool
- func (c *Config) RecipeAccountingMode() (string, bool, error)
- func (c *Config) RecipeCriteria(reg *CriteriaRegistry) (*Criteria, error)
- func (c *Config) RecipeProfile() string
- func (c *Config) RecipeResolveOptions() ([]RecipeResolveOption, error)
- func (c *Config) RecipeRuntimeInventoryMode() (string, bool, error)
- func (c *Config) RecipeSource() (RecipeSourceOption, bool)
- func (c *Config) SnapshotPath() string
- func (c *Config) Unwrap() *appconfig.AICRConfig
- type Criteria
- type CriteriaDimension
- type CriteriaRegistry
- type EvidenceOptions
- type EvidencePublishOptions
- type EvidenceVerification
- type EvidenceVerifyOptions
- type OIDCResolveOptions
- type Option
- type Phase
- type PhaseResult
- type ProfileSummary
- type RecipeDigestOptions
- type RecipeRequest
- type RecipeResolveOption
- type RecipeResult
- type RecipeSourceOption
- type ReportSummary
- type SelectedProfile
- type Snapshot
- type SnapshotChange
- type SnapshotChangeKind
- type SnapshotChangeSeverity
- type SnapshotDiff
- type SnapshotDiffOptions
- type SnapshotDiffSummary
- type ValidateOption
- func WithValidationCleanup(cleanup bool) ValidateOption
- func WithValidationCommit(commit string) ValidateOption
- func WithValidationFailFast(failFast bool) ValidateOption
- func WithValidationImagePullSecrets(secrets []string) ValidateOption
- func WithValidationImageRegistryOverride(registry string) ValidateOption
- func WithValidationImageTagOverride(tag string) ValidateOption
- func WithValidationKubeconfig(kubeconfig string) ValidateOption
- func WithValidationNamespace(namespace string) ValidateOption
- func WithValidationNoCluster(noCluster bool) ValidateOption
- func WithValidationNodeSelector(nodeSelector map[string]string) ValidateOption
- func WithValidationPhases(phases ...Phase) ValidateOption
- func WithValidationRunID(runID string) ValidateOption
- func WithValidationTimeout(d time.Duration) ValidateOption
- func WithValidationTolerations(tolerations []corev1.Toleration) ValidateOption
Examples ¶
- Package
- Package (BundleAndVerify)
- Package (CommittedConfig)
- Package (CriteriaDimensions)
- Package (ErrorCodes)
- Package (ResolveFromSnapshot)
- Package (TrustLevels)
- Client.CollectSnapshot
- Client.DiffSnapshots
- Client.LoadRecipe
- Client.PublishEvidence
- Client.RecipeDigest
- Client.SignCatalog
- Client.ValidateState
- Client.VerifyCatalog
- Client.VerifyEvidence
- VerifyBinaryAttestation
Constants ¶
const ( // CatalogSourceEmbedded is the Source value for built-in OSS overlays. CatalogSourceEmbedded = recipe.CatalogSourceEmbedded // CatalogSourceExternal is the Source value for overlays loaded via --data. CatalogSourceExternal = recipe.CatalogSourceExternal )
CatalogSource constants for CatalogEntry.Source comparisons.
const ( // EvidenceExitValidPassed: bundle valid, every check passed. EvidenceExitValidPassed = evverifier.ExitValidPassed // EvidenceExitValidPhaseFailures: bundle valid, but the validator // results recorded inside it show phase failures. Informational — the // evidence itself is sound. EvidenceExitValidPhaseFailures = evverifier.ExitValidPhaseFailures // EvidenceExitInvalid: bundle invalid (signature, schema, or integrity // failure). EvidenceExitInvalid = evverifier.ExitInvalid // EvidenceExitIncomplete: verification did not complete, so no verdict // was reached. Read FailureCause.Class to tell an environmental fault // from an operator abort (EvidenceCauseCanceled). EvidenceExitIncomplete = evverifier.ExitIncomplete )
Evidence verification verdicts, mirroring the "exit" field on an EvidenceVerification. Re-exported so a consumer can branch on the verdict without importing pkg/evidence/verifier.
The verdict is NOT the process exit code. VerifyEvidence returns a verdict and a nil error whenever verification ran to completion — including when it concluded the bundle is invalid. A non-nil error means verification could not be performed at all (bad options, closed Client).
const EvidenceCauseCanceled = evverifier.CauseCanceled
EvidenceCauseCanceled is the EvidenceVerification.FailureCause.Class value marking an operator-aborted run, as opposed to the environmental faults that also produce EvidenceExitIncomplete. A CI gate branches on this to tell "we could not check this" from "the run was canceled".
const TrustedIdentityPattern = bundleverifier.TrustedRepositoryPattern
TrustedIdentityPattern is the default certificate-identity regexp that binary-attestation verification pins to: NVIDIA's release workflow on tag refs.
Override it only to pin a DIFFERENT WORKFLOW within NVIDIA/aicr — a pre-release or e2e build, say. Verifying a fork is not possible through this option and is not meant to be: ValidateIdentityPattern requires every override to begin with https://github.com/NVIDIA/aicr/, so a fork's certificate identity can never satisfy it.
Variables ¶
This section is empty.
Functions ¶
func RenderEvidenceJSON ¶ added in v0.20.0
func RenderEvidenceJSON(r *EvidenceVerification) ([]byte, error)
RenderEvidenceJSON renders an EvidenceVerification as the structured JSON document `aicr evidence verify --format json` emits.
func RenderEvidenceMarkdown ¶ added in v0.20.0
func RenderEvidenceMarkdown(r *EvidenceVerification) string
RenderEvidenceMarkdown renders an EvidenceVerification as the Markdown summary `aicr evidence verify` prints by default. Returns an empty string for a nil result.
func SelectFromRecipe ¶
func SelectFromRecipe(r *RecipeResult, selector string) (any, error)
SelectFromRecipe is the context-less form of SelectFromRecipeWithContext, kept for source compatibility. It derives a defaults.FileReadTimeout-bounded context so hydration's values reads stay bounded, but the caller cannot cancel them.
Prefer SelectFromRecipeWithContext wherever a context.Context is available.
func SelectFromRecipeWithContext ¶ added in v0.19.0
func SelectFromRecipeWithContext(ctx context.Context, r *RecipeResult, selector string) (any, error)
SelectFromRecipeWithContext hydrates a resolved recipe and extracts a dot-path selector (e.g. "components.gpu-operator.values.driver.version"). An empty selector returns the entire hydrated structure. Mirrors `aicr query`, and is the implementation both the CLI query command and the REST query handler run.
Hydration reads each component's values through the DataProvider bound to the recipe, so ctx bounds real I/O: a canceled or expired context aborts the hydration rather than running to completion.
The recipe must carry internal pkg/recipe state — obtain one from Client.ResolveRecipe, Client.LoadRecipe, Client.AdoptRecipe, or WrapResolved. A facade RecipeResult constructed any other way is rejected with ErrCodeInvalidRequest.
Error contract ¶
The OUTERMOST structured error code distinguishes the two failure stages, so a caller (e.g. an HTTP handler mapping to a status code) can shape its response without a parallel hydrate+select implementation:
- ErrCodeNotFound — the selector path does not exist in the hydrated recipe. This code is only ever produced by the selection stage.
- ErrCodeInvalidRequest — ctx or r is nil, or r carries no internal state.
- Anything else (ErrCodeInternal, ErrCodeTimeout, ...) — hydration failed. Hydration never surfaces ErrCodeNotFound as the outermost code: pkg/recipe.HydrateResultWithContext wraps every per-component values failure as ErrCodeInternal, so a missing values file cannot be mistaken for a missing selector path.
Inspect the outermost code with stderrors.As, NOT stderrors.Is — Is walks the wrap chain and would match an ErrCodeNotFound cause nested inside a hydration failure.
func ToInternalAllowLists ¶
func ToInternalAllowLists(al *AllowLists) *recipe.AllowLists
ToInternalAllowLists translates a facade AllowLists into the pkg/recipe.AllowLists enum-typed shape the resolver consumes. The string values are wrapped in the corresponding pkg/recipe enum types without validation; registry-strict mode at resolve time rejects unknown values.
Exposed so in-tree adapters (e.g., the REST handler's pre-check) share the same facade→internal projection as the Client's internal backstop, instead of inlining a parallel mapping that can drift if AllowLists gains a field.
func ToInternalCriteria ¶ added in v0.20.0
ToInternalCriteria projects a facade Criteria back onto the upstream pkg/recipe shape, parsing the plain-string fields into their enum types. Returns nil for nil input.
The counterpart to WrapCriteria, and the same bridge role ToInternalAllowLists plays: a caller that derived criteria from an AICRConfig via Config.RecipeCriteria but must hand them to a pkg/recipe API needs a supported way across, rather than reconstructing the enums by hand.
func TrustLevels ¶ added in v0.20.0
func TrustLevels() []string
TrustLevels returns the bundle trust levels that BundleVerifyOptions.MinTrustLevel accepts, sorted alphabetically (NOT by rank). Intended for building help text, shell completions, and input validation. The meta-value "max" is deliberately absent: it is a policy instruction rather than a level a bundle can be at.
That matters when validating BundleVerifyOptions.MinTrustLevel input: this list alone is NOT the accepted set. Accept "max" and the empty string too, or the check rejects the very default the option documents.
Each call returns a fresh slice, so a caller may sort or filter it.
func ValidateIdentityPattern ¶ added in v0.20.0
ValidateIdentityPattern reports whether pattern is usable as a certificate-identity override, rejecting anything that does not stay pinned to the NVIDIA/aicr repository. Call it to validate operator-supplied input before handing it to VerifyBundle or VerifyBinaryAttestation, both of which apply the same check internally.
func VerifyBinaryAttestation ¶ added in v0.20.0
func VerifyBinaryAttestation(ctx context.Context, opts BinaryAttestationVerifyOptions) (string, error)
VerifyBinaryAttestation verifies an aicr binary's own provenance attestation against a certificate identity and the binary's digest, returning the verified certificate subject.
Use it to prove the aicr binary being embedded or executed was built by NVIDIA CI, before trusting anything it produces. It is package-level rather than a Client method because it involves no recipe catalog and no configurable policy — there is nothing for a Client to contribute.
Example ¶
ExampleVerifyBinaryAttestation proves an aicr binary was built by NVIDIA CI. It is package-level rather than a Client method: verifying a binary needs no recipe data, so it requires no Client.
package main
import (
"context"
"fmt"
"log"
aicr "github.com/NVIDIA/aicr/pkg/client/v1"
)
func main() {
ctx := context.Background()
builder, err := aicr.VerifyBinaryAttestation(ctx, aicr.BinaryAttestationVerifyOptions{
Attestation: []byte(`{}`), // the .intoto.jsonl bundle shipped with the release
BinaryDigest: []byte{
0x00, 0x01, 0x02, 0x03,
},
// Defaults to the release workflow on tag refs. An override must still
// begin with the NVIDIA/aicr repository prefix; ValidateIdentityPattern
// reports whether a candidate is acceptable before you use it.
IdentityRegexp: aicr.TrustedIdentityPattern,
})
if err != nil {
log.Print(err)
return
}
fmt.Println(builder)
}
Output:
func WriteSnapshotDiffTable ¶ added in v0.20.0
func WriteSnapshotDiffTable(w io.Writer, result *SnapshotDiff) error
WriteSnapshotDiffTable writes a human-readable snapshot diff table.
Types ¶
type AgentConfig ¶
type AgentConfig struct {
Kubeconfig string
Namespace string
Image string
ImagePullSecrets []string
JobName string
ServiceAccountName string
NodeSelector map[string]string
Tolerations []corev1.Toleration
Timeout time.Duration
Cleanup bool
Debug bool
Privileged bool
RequireGPU bool
RuntimeClassName string
TemplatePath string
MaxNodesPerEntry int
OS string
Requests corev1.ResourceList
Limits corev1.ResourceList
// Output selects where the agent Job stages its result. A cm://namespace/name
// URI makes that ConfigMap the delivery vehicle — the Job writes there and
// CollectSnapshot leaves it in place. A malformed cm:// URI is rejected
// with ErrCodeInvalidRequest before any cluster access, so a typo never
// costs a deployed Job. Any other value (including empty) stages to an
// internal ConfigMap in Namespace; delivering the snapshot to a file,
// stdout, or a template is then the caller's job — pass Snapshot.Raw to
// snapshotter.DeliverSnapshot.
Output string
// ClusterConfigPath asks the in-pod network collector to ingest a
// pre-existing k8s-launch-kit (l8k) cluster-config.yaml at this path.
// The path must resolve INSIDE the agent pod, which the Job does not yet
// mount — so CollectSnapshot rejects a non-empty value with
// ErrCodeInvalidRequest. Use DiscoverNetwork for live discovery from a
// Job. Local (in-pod) collection honors the file, but that path is
// outside the facade; see Client.CollectSnapshot.
ClusterConfigPath string
// DiscoverNetwork enables the in-pod network collector's live l8k
// discovery. Discovery is NOT read-only — it writes
// nvidia.kubernetes-launch-kit.* node labels and patches NicClusterPolicy
// via server-side-apply, so the agent's RBAC must allow those writes.
DiscoverNetwork bool
// AKSGPUPoolsPath points at an operator-supplied
// `az aks nodepool list -o json` dump on the machine running this
// client. The snapshotter projects it into the snapshot's
// K8s.aks-gpu-pools.gpu-driver reading (ADR-015 DD3) — controller-
// side, before any cluster work; the file never enters the cluster.
// Required for AKS profile-qualified resolution from a collected
// snapshot; empty disables the projection.
AKSGPUPoolsPath string
}
AgentConfig is the deployment-time configuration for the snapshot- collection Job passed to Client.CollectSnapshot. Facade-owned; field-for-field mirror of pkg/snapshotter.AgentConfig. Tolerations keep k8s.io/api/core/v1.Toleration since kubernetes/api is itself stable. Nil Tolerations use a tolerate-all default; a non-nil empty slice explicitly disables that default.
The mirror is enforced, not conventional: TestAgentConfigMirrorsInternal fails when either struct gains, drops, or retypes a field, and every in-tree snapshot Job (`aicr snapshot`, `aicr validate`) is deployed through this type — so an unplumbed field is a test failure rather than a silent zero value.
type AllowLists ¶
type AllowLists struct {
// Accelerators is the set of accepted accelerator identifiers
// (e.g., "h100", "b200"). Empty = accept all.
Accelerators []string
// Services is the set of accepted service identifiers
// (e.g., "eks", "gke"). Empty = accept all.
Services []string
// Intents is the set of accepted intent identifiers
// (e.g., "training", "inference"). Empty = accept all.
Intents []string
// OSTypes is the set of accepted OS identifiers
// (e.g., "ubuntu", "rhel"). Empty = accept all.
OSTypes []string
}
AllowLists fences which criteria values the resolve path accepts on a Client constructed via WithAllowLists. Facade-owned; the typed-enum fields on pkg/recipe.AllowLists project to plain string slices so the facade does not propagate pkg/recipe's enum identifiers across the semver boundary. A nil receiver, or an AllowLists whose slices are all empty, accepts every value (the documented "no fencing" mode). An "any" value on a Criteria field is always accepted regardless of the allowlist, matching the pkg/recipe behavior.
func ParseAllowListsFromEnv ¶
func ParseAllowListsFromEnv() (*AllowLists, error)
ParseAllowListsFromEnv builds an AllowLists from the AICR_ALLOWED_* environment variables (AICR_ALLOWED_ACCELERATORS, AICR_ALLOWED_SERVICES, AICR_ALLOWED_INTENTS, AICR_ALLOWED_OS). Returns nil when none are set — WithAllowLists treats a nil AllowLists as allow-all. Pass the result to WithAllowLists.
func WrapAllowLists ¶
func WrapAllowLists(al *recipe.AllowLists) *AllowLists
WrapAllowLists projects a pkg/recipe.AllowLists into the facade AllowLists shape. Use at the boundary where in-tree callers parse allowlists from configuration (e.g., recipe.ParseAllowListsFromEnv) and hand them to the facade. Returns nil for nil input.
The facade slices are independent copies; mutating either side after wrap does not affect the other.
type BinaryAttestationVerifyOptions ¶ added in v0.20.0
type BinaryAttestationVerifyOptions struct {
// Attestation is the raw Sigstore bundle for the binary. Required.
//
// Taking bytes rather than a path is deliberate: it lets a caller
// verify the EXACT content it is about to use, with no
// verify-then-reread window in which the file could change.
Attestation []byte
// BinaryDigest is the RAW (not hex-encoded) SHA-256 of the binary the
// attestation must cover. Required.
BinaryDigest []byte
// IdentityRegexp overrides the certificate identity the attestation is
// pinned to. Empty uses TrustedIdentityPattern. Must BEGIN with
// "https://github.com/NVIDIA/aicr/" (a leading "^" is allowed) and must
// not use top-level alternation, so the override stays confined to the
// repository; see ValidateIdentityPattern.
IdentityRegexp string
}
BinaryAttestationVerifyOptions configures VerifyBinaryAttestation.
type BundleArtifact ¶
BundleArtifact summarizes a completed bundle generation: file count, total size, duration, per-bundler results, and the output directory the files were written to. Deliberate transparent alias of pkg/bundler/result.Output. Inspect HasErrors() for non-fatal per-bundler failures; the bundle files themselves are on disk under OutputDir.
type BundleAttester ¶
type BundleAttester = attestation.Attester
BundleAttester signs bundle content. Deliberate transparent alias of pkg/bundler/attestation.Attester. The zero value of BundleOptions leaves this nil, in which case MakeBundle uses the bundler's no-op attester (the same default bundler.New applies when --attest is not set).
type BundleConfig ¶
BundleConfig is the bundler configuration — deployer mode, value overrides, node selectors, tolerations, vendoring, app/chart names, etc. Deliberate transparent alias of pkg/bundler/config.Config. Construct one with config.NewConfig(config.WithDeployer(...), ...) — the same builder the CLI bundle command and the REST /v1/bundle handler use, so MakeBundle reproduces their exact output byte-for-byte.
type BundleOptions ¶
type BundleOptions struct {
// Config carries the bundler configuration (deployer mode, value
// overrides, node selectors/tolerations, vendoring, app/chart
// names). When nil, MakeBundle uses config.NewConfig() — the same
// default bundler.New applies (Helm deployer, no overrides).
Config *BundleConfig
// Attester signs bundle content. When nil, MakeBundle uses the
// no-op attester (matching bundler.New's default when --attest is
// not set). The CLI builds this via attestation.ResolveAttesterLazy
// when --attest is passed.
Attester BundleAttester
// BinaryAttestation, when non-empty, is a pre-verified binary attestation
// (Sigstore bundle bytes) embedded into attested bundles as tool provenance.
// The caller (e.g. the aicrd server, which verifies its in-image attestation
// once at startup) is responsible for having verified these bytes. Empty
// leaves the bundler's default per-run discover-and-verify path unchanged
// (the CLI passes nil and relies on the attestation shipped next to its
// install-script binary).
BinaryAttestation []byte
// OutputDir is the directory bundle files are written to. Empty
// means the current directory ("."), matching Make's default.
OutputDir string
// Timeout optionally caps the bundle run. When > 0, MakeBundle wraps
// the caller's context with context.WithTimeout(ctx, Timeout) so the
// run is bounded by the smaller of this and any tighter parent
// deadline. When 0 (the zero value), MakeBundle imposes NO
// facade-level deadline and runs under the caller's ctx as-is —
// large bundles, --vendor-charts, and attestation/signing can each
// exceed a fixed cap. The REST /v1/bundle handler sets this to
// defaults.BundleHandlerTimeout to preserve its 60s request boundary;
// the CLI bundle command leaves it 0 so long bundles are uncapped.
Timeout time.Duration
}
BundleOptions configures a MakeBundle call. It mirrors exactly what bundler.New / (*DefaultBundler).Make accept so the facade reproduces the same full deployer-mode bundle artifact the CLI bundle command and REST /v1/bundle handler produce today.
type BundleVerification ¶ added in v0.20.0
type BundleVerification struct {
// Report is the per-check verification outcome. Never nil on a nil
// error.
Report *BundleVerifyReport
// PolicyFailure describes the first policy assertion the bundle failed
// (trust floor, required creator, version constraint), or is empty
// when every assertion passed.
//
// A policy failure is DATA, not an error: VerifyBundle still returns
// the full Report so a caller can render or log why the bundle fell
// short. Callers that need a failed policy to abort should check this
// field (and Report.Errors) explicitly.
PolicyFailure string
}
BundleVerification pairs the verification report with the outcome of the policy assertions in BundleVerifyOptions.
type BundleVerifyOptions ¶ added in v0.20.0
type BundleVerifyOptions struct {
// CertificateIdentityRegexp overrides the identity pattern that binary
// attestation verification pins to. Must BEGIN with
// "https://github.com/NVIDIA/aicr/" (a leading "^" is allowed) and must
// not use top-level alternation, so it stays confined to the repository;
// VerifyBundle rejects a pattern that does not before doing any work.
// Empty uses TrustedIdentityPattern.
CertificateIdentityRegexp string
// Key verifies a key-signed bundle attestation instead of a keyless
// one: a KMS key URI (awskms:// | gcpkms:// | azurekms:// |
// hashivault://) or a path to a local PEM public key. Independent of
// CertificateIdentityRegexp, which pins the separate binary
// attestation; the two coexist.
Key string
// TrustRoot is a path to a private Sigstore trusted_root.json (from a
// self-hosted Fulcio/Rekor). ADDITIVE to AICR's built-in public-good
// root, so NVIDIA-signed and privately-signed bundles both verify.
TrustRoot string
// MinTrustLevel is the trust floor the verified bundle must reach: one
// of "verified", "attested", "unverified", "unknown", or the
// meta-value "max".
//
// EMPTY MEANS "max" — auto-detect the highest level this bundle could
// achieve and require it. This deliberately differs from the
// underlying pkg/bundler/verifier.Policy, where an empty value skips
// the trust check entirely: a caller who does not think about the
// trust floor should get the strict default, not no gate. Lower the
// floor by naming a level explicitly.
MinTrustLevel string
// RequireCreator pins the OIDC identity on the bundle attestation's
// signing certificate. Empty accepts any creator.
RequireCreator string
// CLIVersionConstraint constrains the aicr version recorded in the
// attestation predicate. Supports >=, >, <=, <, ==, != ; a bare
// version (e.g. "0.16.0") is treated as ">= 0.16.0".
CLIVersionConstraint string
// IgnoreTLog skips transparency-log verification so a bundle produced
// by `bundle --signing-key ... --tlog-upload=false` verifies with no
// transparency-log network calls. REQUIRES Key — the air-gapped path
// is key-based, and VerifyBundle rejects the combination otherwise.
// Insecure relative to the default: with no transparency log there is
// no trusted timestamp proving when the signature was made.
IgnoreTLog bool
}
BundleVerifyOptions configures Client.VerifyBundle.
The fields mirror pkg/config.VerifySpec one-for-one so a caller holding an AICRConfig can populate this struct without a translation table: the first three come from spec.verify.trust and the next three from spec.verify.policy. IgnoreTLog has no config counterpart by design — it weakens the trust floor by dropping the transparency-log requirement, and keeping it out of the schema means a checked-in file can never silently disable that check.
type BundleVerifyReport ¶ added in v0.20.0
type BundleVerifyReport = bundleverifier.VerifyResult
BundleVerifyReport is the per-check outcome of bundle verification.
Deliberate transparent alias of pkg/bundler/verifier.VerifyResult. It is a flat, read-only report whose fields are already the documented JSON contract of `aicr verify --format json`; owning a translated copy would duplicate that contract without insulating consumers from anything, since any field change would have to propagate to stay useful.
type CatalogEntry ¶ added in v0.15.0
type CatalogEntry struct {
// Name is the overlay name, e.g. "h100-eks-ubuntu-training".
Name string `json:"name" yaml:"name"`
// Criteria is the set of dimensions this overlay targets.
Criteria Criteria `json:"criteria" yaml:"criteria"`
// IsLeaf is true when this overlay is a catalog leaf (no other
// overlay inherits from it).
IsLeaf bool `json:"is_leaf" yaml:"is_leaf"`
// Source is the data provenance: "embedded" or "external".
Source string `json:"source" yaml:"source"`
// Profile is the effective declaration after inheritance and co-match
// resolution. It is nil for an unprofiled catalog entry.
Profile *ProfileSummary `json:"profile,omitempty" yaml:"profile,omitempty"`
}
CatalogEntry describes one overlay in the recipe catalog, returned by Client.ListCatalog.
IsLeaf is true when the overlay is a leaf — no other overlay in the catalog lists this one as its spec.base. Leaf overlays are the most specific recipes for a given criteria combination.
Source is one of CatalogSourceEmbedded or CatalogSourceExternal.
type CatalogSignOptions ¶ added in v0.20.0
type CatalogSignOptions struct {
// Output is the path the Sigstore bundle is written to. Empty computes
// and signs the digest but writes no file, leaving the serialized
// bundle available on CatalogSignResult.BundleJSON.
Output string
// OIDCResolve carries the keyless-signing token-resolution inputs.
// SignCatalog sets Attest itself — signing is the whole operation —
// so leaving that field false does not disable it.
//
// Four fields are REJECTED rather than passed through, because
// VerifyCatalog cannot verify what they would produce: SigningKey,
// FulcioURL, RekorURL, and DisableTLogUpload. See SignCatalog's godoc
// for the full statement of that constraint, including what it does
// NOT cover.
OIDCResolve OIDCResolveOptions
}
CatalogSignOptions configures Client.SignCatalog.
type CatalogSignResult ¶ added in v0.20.0
type CatalogSignResult struct {
// Digest is the hex-encoded SHA-256 of the combined catalog content
// that was signed.
Digest string
// BundleJSON is the serialized Sigstore bundle. Never nil on a nil
// error.
BundleJSON []byte
}
CatalogSignResult is what SignCatalog returns on success.
type CatalogVerification ¶ added in v0.20.0
type CatalogVerification struct {
// Identity is the SubjectAlternativeName claim from the signing
// certificate.
Identity string
// Digest is the hex-encoded SHA-256 of the catalog content that was
// verified.
Digest string
}
CatalogVerification is what VerifyCatalog returns on success.
type CatalogVerifyOptions ¶ added in v0.20.0
type CatalogVerifyOptions struct {
// CertificateIdentityRegexp overrides the NVIDIA CI identity pattern.
// Must BEGIN with "https://github.com/NVIDIA/aicr/" (a leading "^" is
// allowed) and must not use top-level alternation. Empty uses
// TrustedIdentityPattern.
CertificateIdentityRegexp string
}
CatalogVerifyOptions configures Client.VerifyCatalog.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is the single entry point for external Go consumers.
Concurrent ResolveRecipe calls are safe — the Builder itself is thread-safe over its read-only state. The mu guards the small window where Close swaps builder/dp to nil; without it, concurrent ResolveRecipe + Close on the same Client is a data race because the field write in Close is unsynchronised against the field read at the top of ResolveRecipe.
func NewClient ¶
NewClient constructs a Client with the supplied functional options. OCI source construction uses a bounded compatibility context; callers that need cancellation or a tighter deadline should use NewClientContext. Callers must provide a recipe source via WithRecipeSource.
For FilesystemSource, the external directory is layered OVER the embedded recipe data — files in the directory override embedded equivalents, and recipes must include a registry.yaml at the root.
OCI sources require an immutable sha256 manifest digest. One defaults.OCIRecipeConstructionTimeout deadline bounds the complete OCI source construction; nested per-phase pull deadlines can only shorten that shared budget.
func NewClientContext ¶ added in v0.20.0
NewClientContext constructs a Client with the supplied functional options and derives all OCI source I/O from ctx. The complete operation remains bounded by defaults.OCIRecipeConstructionTimeout when the caller provides a longer deadline or no deadline.
func (*Client) AdoptRecipe ¶
func (c *Client) AdoptRecipe(ctx context.Context, rec *recipe.RecipeResult) (*RecipeResult, error)
AdoptRecipe wraps a raw pkg/recipe.RecipeResult — typically decoded from an external source such as a REST /v1/bundle POST body — into a Client-owned *RecipeResult ready for MakeBundle. The returned RecipeResult is bound to this Client's DataProvider and owner-stamped, so it passes MakeBundle's ownership and provider-isolation checks exactly as a LoadRecipe result does.
Use this when the caller already holds a fully-hydrated RecipeResult (not a criteria request or a file path) and needs to bundle it through the facade. In-process consumers that resolve via ResolveRecipe / LoadRecipe should use those results directly; AdoptRecipe is for the decode-then-bundle boundary.
func (*Client) BundleComponents ¶
func (c *Client) BundleComponents(ctx context.Context, r *RecipeResult) ([]ComponentBundle, error)
BundleComponents resolves Helm values and rendered manifests for each component in a previously-resolved RecipeResult. The returned slice mirrors r.Components 1:1 — same order, same length — so callers correlate by index.
When to call ¶
Call AFTER ResolveRecipe; pass that call's *RecipeResult unchanged. BundleComponents reads the internal pkg/recipe.RecipeResult that ResolveRecipe attached to the facade RecipeResult — it does NOT re-resolve from criteria. A RecipeResult constructed by the caller (rather than returned from ResolveRecipe) has a nil internal field and BundleComponents returns ErrCodeInvalidRequest.
Per-Client DataProvider isolation ¶
Both values-file reads (Helm components) and manifest-file reads (Helm supplemental + Kustomize) are bound to this Client's own DataProvider via the WithProvider variants on the recipe package (recipe.RecipeResult.GetValuesForComponentWithProvider, recipe.GetManifestContentWithProvider). Two Clients constructed from different recipe sources can BundleComponents concurrently without contaminating each other's bundle output.
History: pre-v0.2 the values and manifest paths short-circuited through recipe.GetDataProvider() — the process-global DataProvider singleton. With two Clients A and B pointing at different sources, an eviction+repopulate sequence on A's cache followed by a B BundleComponents call could return values or manifests resolved against A's recipe source. That gap is closed; the metadata store and component registry were already per-Client at the time and stayed correct throughout, so ResolveRecipe results never drifted.
Read-once value coherence ¶
Each component's effective Helm values are resolved exactly once per call, and that same snapshot feeds the accounting check, the registry-declared component validations (including the gpu-operator driver-ownership coherence gate), and the returned ComponentBundle. A DataProvider need not be stable — LayeredDataProvider re-reads external --data files on every call — so a gate that resolved values independently could validate one set of values while a different set was returned. Pinning the snapshot removes that window: what the gates examined is what you get back (issue #1873 item A).
Synchronization ¶
Read-locks Client.mu so a concurrent Close can't race the values load. The lock is held only across the snapshot of c.builder and c.dp; the values and manifest reads themselves run unlocked (consistent with ResolveRecipe's pattern). The DataProvider snapshot is the per-Client provider this Client owns — the same one its Builder is bound to via recipe.WithDataProvider.
func (*Client) Close ¶
Close releases this Client's cached metadata store, component registry, and criteria registry from the recipe package's internal caches. Call when a Client is no longer needed (cache eviction in a higher-level memoiser, controller shutdown) to prevent unbounded memory growth — the recipe package keys its caches on DataProvider identity and does not auto-evict, so a process that observes many distinct recipe sources over time would otherwise grow memory monotonically.
Safe to call on a nil receiver and safe to call concurrently or multiple times. Every non-nil caller waits for the same teardown and receives the same cached cleanup result. OCI-backed Clients remove only the private child workspace they own; a removal failure is returned with its structured code.
func (*Client) CollectSnapshot ¶
CollectSnapshot deploys the snapshotter Job to the cluster identified by cfg.Kubeconfig and returns the captured Snapshot.
This is the single Job-mode collection path in the tree: `aicr snapshot` and `aicr validate` both run it, so the facade AgentConfig mirror is exercised on every snapshot AICR takes.
CollectSnapshot does NOT consult the Client's recipe data provider — the Client is required only to keep the facade surface uniform (every public operation goes through a Client) and to leave room for future per-Client telemetry hooks or cluster-connection caching without breaking signatures. CollectSnapshot is therefore safe even on a Client whose recipe source is unrelated to the target cluster.
cfg.Kubeconfig is the path (or empty for in-cluster). cfg.Namespace, cfg.Image, cfg.ServiceAccountName must be set; other fields fall back to package defaults documented on snapshotter.AgentConfig.
Output and delivery ¶
The returned Snapshot carries both the parsed form and, in Snapshot.Raw, the exact bytes the agent emitted. CollectSnapshot does not write them anywhere except when cfg.Output names a ConfigMap (cm://namespace/name), which the Job writes directly. Persisting to a file, stdout, or a Go template is the caller's step — pass Snapshot.Raw to snapshotter.DeliverSnapshot, as `aicr snapshot` does. Delivering Raw rather than re-serializing the parsed snapshot is what keeps the output byte-identical to the agent's when a newer agent image emits fields this binary does not model.
Fail-before-mutate ¶
Inputs that can be rejected without contacting the cluster are checked before the Kubernetes client is built, so a rejection never leaves RBAC or a Job behind (with cfg.Cleanup false — the zero value — they would persist). That covers a malformed cfg.Output ConfigMap URI and a non-empty cfg.ClusterConfigPath, both ErrCodeInvalidRequest.
Deliberately outside this method ¶
Two snapshot capabilities are NOT reachable through CollectSnapshot, by design, because neither deploys a Job:
- Local (in-pod) collection — the mode the agent container itself runs under AICR_AGENT_MODE=true, and the dev bypass of the same name. It runs collectors in-process against the local node instead of deploying an agent, so it needs a collector.Factory and a serializer.Serializer, types the semver-stable facade deliberately does not expose. Use snapshotter.NodeSnapshotter directly, as pkg/cli/snapshot.go does. It takes no AgentConfig, so leaving it out costs no coverage of the field mirror: every deployed Job still projects through this method.
- cfg.ClusterConfigPath — an l8k cluster-config.yaml ingested by the in-pod network collector. The path must resolve inside the pod and the Job does not mount it, so Job mode rejects a non-empty value with ErrCodeInvalidRequest. Use cfg.DiscoverNetwork for live discovery from a Job, or the local mode above to read a host file.
Timeout ¶
The operation is bounded by cfg.Timeout + defaults.SnapshotOperationGrace (or defaults.SnapshotOperationTimeout + grace when cfg.Timeout is unset), so a caller passing context.Background() still gets a bounded run. The grace exists because cfg.Timeout budgets Job completion only — deployment and result retrieval sit outside it, and a bare cap would silently shrink the completion budget. A tighter caller deadline always wins.
Errors:
- ErrCodeInvalidRequest when the Client is nil, cfg is nil, or the Client has been Closed.
- All snapshotter errors propagate unwrapped — they already carry the appropriate pkg/errors codes (ErrCodeInternal for deployment failures, ErrCodeTimeout for context expiry, etc.).
Concurrent CollectSnapshot calls are safe; each call constructs an independent run.
Example ¶
ExampleClient_CollectSnapshot captures cluster state by deploying the snapshotter Job, for callers that do not already have a snapshot file. Requires a reachable cluster and RBAC to create the Job.
Image, JobName, and ServiceAccountName are required here ¶
DeployAndCollect validates only Namespace; the rest are copied straight into the Job and RBAC objects. The CLI supplies defaults from its own flags, which the facade does not share — so leaving these empty produces an empty ServiceAccount name and an empty container image, and the API server rejects the ServiceAccount before the Job is ever created. Set all three.
package main
import (
"context"
"log"
"os"
aicr "github.com/NVIDIA/aicr/pkg/client/v1"
)
func main() {
ctx := context.Background()
client, err := aicr.NewClient(aicr.WithRecipeSource(aicr.EmbeddedSource()))
if err != nil {
log.Print(err)
return
}
defer func() { _ = client.Close() }()
snap, err := client.CollectSnapshot(ctx, &aicr.AgentConfig{
Namespace: "aicr-system",
Image: "ghcr.io/nvidia/aicr:v0.19.0",
JobName: "aicr",
ServiceAccountName: "aicr",
Cleanup: true,
})
if err != nil {
log.Print(err)
return
}
// Persist Raw rather than re-serializing: a newer agent image can emit
// fields this binary's Snapshot type does not model, and a typed round
// trip silently drops them.
if err = os.WriteFile("snapshot.yaml", snap.Raw, 0o600); err != nil {
log.Print(err)
return
}
}
Output:
func (*Client) ComputeHealth ¶ added in v0.15.0
ComputeHealth scores the structural health of every leaf recipe in this Client's catalog and returns a deterministic *health.Report, optionally narrowed by filter. Computation is delegated wholesale to pkg/health.Compute; this facade only binds the Client's own DataProvider and version so health is scored against the same catalog (including any --data overlays) the Client resolves with — never the process-global embedded catalog.
filter narrows enumeration to leaf overlays carrying every explicitly set criteria dimension; nil scores all leaf combos. Empty/"any" filter dimensions place no constraint.
health.Compute applies its own catalog-wide timeout (defaults.HealthComputeTimeout), so this method does not impose the shorter per-operation timeout the resolve methods use.
Returns ErrCodeInvalidRequest on a nil/closed Client or nil context, and propagates the underlying structured code (or ErrCodeInternal) if health computation fails.
func (*Client) CriteriaRegistry ¶
func (c *Client) CriteriaRegistry() *CriteriaRegistry
CriteriaRegistry returns the per-DataProvider criteria registry for THIS Client. CLI/library callers use it to parse criteria values (so --data overlay contributions validate) and to apply strict mode against the same provider the Client resolves with. Call LoadCatalog first so the registry is seeded from the provider's overlays before parsing.
Returns the registry for this Client's provider via recipe.GetCriteriaRegistryFor. On a nil or closed Client this returns a fresh ephemeral registry so callers can defensively call without nil-checking, matching the existing lenient accessor behavior.
func (*Client) DiffSnapshots ¶ added in v0.20.0
func (c *Client) DiffSnapshots( ctx context.Context, baseline, target *Snapshot, opts SnapshotDiffOptions, ) (*SnapshotDiff, error)
DiffSnapshots compares two facade snapshots in memory.
Both snapshots must carry a usable measurement payload from LoadSnapshot, CollectSnapshot, or WrapSnapshot. A hand-constructed Snapshot has no such payload; a wrapped payload containing no typed measurement is equally unusable. Both are rejected rather than being reported as a false no-drift result. Source labels in opts are copied to the result for JSON, YAML, and table consumers; they do not influence comparison semantics.
The operation performs no cluster, filesystem, or recipe-catalog I/O and adds no facade timeout. The caller's context governs unchanged.
Example ¶
ExampleClient_DiffSnapshots compares two previously captured snapshots in memory. Loading local files needs no cluster access; cm:// sources use the kubeconfig argument passed to LoadSnapshot.
package main
import (
"context"
"fmt"
"log"
aicr "github.com/NVIDIA/aicr/pkg/client/v1"
)
func main() {
ctx := context.Background()
client, err := aicr.NewClient(aicr.WithRecipeSource(aicr.EmbeddedSource()))
if err != nil {
log.Print(err)
return
}
defer func() { _ = client.Close() }()
baseline, err := client.LoadSnapshot(ctx, "before.yaml", "")
if err != nil {
log.Print(err)
return
}
target, err := client.LoadSnapshot(ctx, "after.yaml", "")
if err != nil {
log.Print(err)
return
}
result, err := client.DiffSnapshots(ctx, baseline, target, aicr.SnapshotDiffOptions{
BaselineSource: "before.yaml",
TargetSource: "after.yaml",
})
if err != nil {
log.Print(err)
return
}
if result.HasDrift() {
fmt.Printf("detected %d change(s)\n", result.Summary.Total)
}
}
Output:
func (*Client) EmitRecipeEvidence ¶ added in v0.16.0
func (c *Client) EmitRecipeEvidence( ctx context.Context, rec *RecipeResult, snap *Snapshot, results []*PhaseResult, opts EvidenceOptions, ) error
EmitRecipeEvidence builds (and optionally pushes) a recipe-evidence attestation bundle from a completed validation run — predicateType v1 for unprofiled recipes, v2 when the recipe carries a configuration profile (metadata.selectedProfile). It is the facade counterpart to the logic the CLI previously assembled inline: it converts the facade PhaseResults back to the internal shape, loads the validator catalog against THIS Client's data source and version, and delegates to the evidence attestation package.
Interactive keyless-signing disclosure is intentionally NOT performed here — that is a UI concern the caller handles (the CLI prompts before calling). This method does no prompting and can run unattended from a server/library.
func (*Client) ListCatalog ¶ added in v0.15.0
ListCatalog returns catalog entries for all overlays known to this Client, optionally narrowed by the filter criteria. Call LoadCatalog first so the catalog is fully populated before calling this.
Each entry carries the overlay name, its criteria, whether it is a leaf (IsLeaf=true means no other overlay inherits from it), its data provenance ("embedded" or "external"), and its effective configuration profile — the declaration reachable from that overlay after inheritance and co-match resolution, nil when the overlay reaches none. Entries are returned in ascending name order for deterministic output.
When filter is non-nil, only overlays whose criteria carry the exact values specified in each non-empty/non-"any" filter dimension are returned. Setting a filter dimension to "" or "any" places no constraint on that dimension.
Returns ErrCodeInvalidRequest on a nil or closed Client, or when an overlay reachable from the filtered set declares an invalid profile — profile declarations are validated during projection, so a malformed declaration fails the whole call rather than yielding a partial catalog. Returns ErrCodeTimeout if ctx is canceled during projection, and propagates ErrCodeInternal if the underlying metadata store cannot be loaded.
func (*Client) LoadCatalog ¶
LoadCatalog eagerly loads (and caches) this Client's metadata store, which has the side effect of seeding THIS Client's per-provider criteria registry from every overlay's spec.criteria. Call it before parsing criteria through CriteriaRegistry so values contributed by a FilesystemSource --data overlay are admitted by the registry's lookups.
This mirrors the pre-facade eager recipe.LoadCatalog the CLI ran after SetDataProvider, but seeds the Client's OWN provider registry rather than the process-global one — so two Clients built from different sources keep isolated criteria registries.
Errors propagate with their structured codes preserved (a malformed overlay surfaces as ErrCodeInvalidRequest, not masked as ErrCodeInternal) via PropagateOrWrap.
The same guards as the resolve methods apply: nil receiver and nil context are rejected with ErrCodeInvalidRequest, and a closed Client is rejected.
func (*Client) LoadRecipe ¶
LoadRecipe loads a recipe from a file path (or cm:// ConfigMap URI, honoring kubeconfig) through THIS Client's data provider, and returns it as a Client-owned *RecipeResult ready for ValidateState / BundleComponents. Overlay inputs (kind: RecipeMetadata) are hydrated against the Client's provider, so an external --data overlay resolves against the same recipe source the Client was constructed with rather than the package global. An already-hydrated RecipeResult file is returned with its provider bound to the Client's provider.
The returned RecipeResult is owner-stamped with this Client, so it passes ValidateState / BundleComponents' assertOwns check — same as a RecipeResult produced by ResolveRecipe.
Errors:
- ErrCodeInvalidRequest when the Client is nil, ctx is nil, path is empty, or the Client has been Closed.
- All loader errors propagate with their structured codes (e.g., ErrCodeInvalidRequest for an overlay without criteria, ErrCodeInternal for a read or parse failure).
Example ¶
ExampleClient_LoadRecipe reads a recipe emitted earlier by `aicr recipe -o`, instead of resolving a new one. The result is interchangeable with a resolved one: bundle it, or validate it against a snapshot.
package main
import (
"context"
"fmt"
"log"
aicr "github.com/NVIDIA/aicr/pkg/client/v1"
)
func main() {
ctx := context.Background()
client, err := aicr.NewClient(aicr.WithRecipeSource(aicr.EmbeddedSource()))
if err != nil {
log.Print(err)
return
}
defer func() { _ = client.Close() }()
// A local file path, an HTTP(S) URL, or a cm://namespace/name ConfigMap
// URI. The kubeconfig argument is consulted only for the cm:// form.
result, err := client.LoadRecipe(ctx, "recipe.yaml", "")
if err != nil {
log.Print(err)
return
}
fmt.Println(result.Name)
}
Output:
func (*Client) LoadSnapshot ¶ added in v0.20.0
LoadSnapshot reads a previously captured snapshot and returns it in the facade shape, ready to hand to ValidateState, ResolveRecipeFromSnapshot, or EmitRecipeEvidence.
It is the counterpart to CollectSnapshot for the case an integrator hits far more often: the snapshot already exists. A pipeline captures cluster state in one stage and resolves or validates against it in another, or the snapshot is committed to a repository and replayed.
path accepts the same forms the CLI does — a local file, an HTTP(S) URL, or a cm://namespace/name ConfigMap URI. kubeconfig resolves the cm:// form; empty uses the standard KUBECONFIG, ~/.kube/config, then in-cluster discovery chain, and is ignored entirely for the other two.
Cluster access is therefore source-dependent: a local file or an HTTP(S) URL needs none, while a cm:// URI reads a ConfigMap through the Kubernetes API and needs working credentials for that cluster. Only CollectSnapshot needs a cluster unconditionally, since it deploys an agent Job.
The loader FAILS CLOSED on a document that is not a snapshot this build can consume: a wrong kind (an AICRConfig, say), or an apiVersion this binary does not understand. That matters because snapshot deserialization is non-strict — any YAML mapping would otherwise decode into a zero-value Snapshot, derive criteria(any), and silently produce a fallback recipe with exit 0. Empty kind and apiVersion are tolerated for snapshots that predate those fields.
Raw is not populated ¶
Snapshot.Raw carries the exact bytes a collection agent emitted and is set only by CollectSnapshot. A loaded snapshot leaves it empty, because the source is already the durable artifact — re-exposing its bytes here would invite callers to round-trip a stored snapshot through the parsed type, which is what Raw exists to discourage.
A caller needing the bytes can read the source again, but note what that does and does not give you: re-reading returns the source's CURRENT contents, which for a URL or a ConfigMap (and for a file someone rewrote) need not be the bytes this call parsed. If byte-for-byte identity with the loaded snapshot matters — hashing what you validated, say — capture the source contents yourself and load from that capture, rather than reading the source a second time afterwards.
This method does not touch the Client's recipe catalog, so any open Client will do; it hangs off Client to keep the surface uniform and to give config-driven loading a home.
func (*Client) MakeBundle ¶
func (c *Client) MakeBundle(ctx context.Context, recipe *RecipeResult, opts BundleOptions) (BundleArtifact, error)
MakeBundle generates the full deployer-mode bundle for a previously resolved or loaded RecipeResult, writing the bundle files under opts.OutputDir and returning a BundleArtifact summary. Unlike BundleComponents (which returns per-component Helm values + manifests in memory), MakeBundle produces the SAME complete artifact the CLI bundle command emits — README, deploy.sh, per-component directories, checksums — in the deployer layout selected by opts.Config.Deployer() (helm, argocd, argocd-helm, flux, helmfile).
When to call ¶
Call AFTER Client.ResolveRecipe or Client.LoadRecipe; pass that call's *RecipeResult unchanged. MakeBundle bundles from recipe.Resolved() (the full pkg/recipe.RecipeResult), which carries this Client's own DataProvider — so provider-scoped lookups (values files, manifest files) resolve against the Client's recipe source rather than the package global.
Allowlist enforcement ¶
When the Client was constructed WithAllowLists, MakeBundle validates the recipe's criteria against the allowlist before bundling — same fencing the resolve path and the REST /v1/bundle handler apply. A recipe whose criteria fall outside the allowlist is rejected with the allowlist's structured error. A recipe with nil Criteria (a loaded, already-hydrated or bare RecipeResult file) skips the check, matching the handler's `recipeResult.Criteria != nil` guard.
Synchronization ¶
Read-locks Client.mu so a concurrent Close can't race the bundle, and registers in the inflight WaitGroup so Close drains before evicting caches — the same protocol as BundleComponents. A facade-level timeout is opt-in via opts.Timeout: when set (> 0) it bounds the run by the smaller of opts.Timeout and any tighter caller deadline; when unset (0) MakeBundle runs under the caller's context with NO added cap. The REST /v1/bundle handler sets opts.Timeout = defaults.BundleHandlerTimeout to keep its 60s request boundary; the CLI bundle command leaves it 0 so large bundles, --vendor-charts, and attestation/signing are uncapped.
Errors:
- ErrCodeInvalidRequest when the Client, ctx, or recipe is nil, when recipe lacks internal state (constructed outside Resolve/Load), when the recipe was produced by a different Client, or when the Client has been Closed.
- Allowlist and bundler errors propagate with their structured codes.
func (*Client) MergeReports ¶ added in v0.16.0
func (c *Client) MergeReports(results []*PhaseResult) *ctrf.Report
MergeReports merges the per-phase CTRF reports from a ValidateState run into a single combined report, stamped with the tool name "aicr" and this Client's version. Library and server callers use it to produce the same combined CTRF document the CLI writes, without reaching into pkg/validator/ctrf merge internals. Nil results and phases with a nil Report contribute nothing.
func (*Client) PublishEvidence ¶ added in v0.20.0
func (c *Client) PublishEvidence(ctx context.Context, opts EvidencePublishOptions) error
PublishEvidence signs and pushes an already-emitted on-disk evidence bundle, then writes pointer.yaml beside it.
It is the off-network second leg of the workflow whose first leg is an evidence-emitting validation run that did not push: that step produces the unsigned on-disk bundle this one consumes. Splitting them lets the cluster-bound step run where the cluster is reachable and the Sigstore-bound step run where Fulcio and Rekor are. The result is content-identical to the one-shot path, because the predicate signed here is read verbatim from the bundle's statement.intoto.json.
Interactive keyless-signing disclosure is intentionally NOT performed here, matching EmitRecipeEvidence: prompting is a UI concern the caller owns, and this method must be able to run unattended from a server or library.
Example ¶
ExampleClient_PublishEvidence signs a recipe-evidence bundle and pushes it to an OCI registry, the producing half of ExampleClient_VerifyEvidence.
package main
import (
"context"
"log"
aicr "github.com/NVIDIA/aicr/pkg/client/v1"
)
func main() {
ctx := context.Background()
client, err := aicr.NewClient(aicr.WithRecipeSource(aicr.EmbeddedSource()))
if err != nil {
log.Print(err)
return
}
defer func() { _ = client.Close() }()
if err = client.PublishEvidence(ctx, aicr.EvidencePublishOptions{
BundleDir: "./evidence",
Push: "ghcr.io/example/aicr-evidence:v1",
}); err != nil {
log.Print(err)
return
}
}
Output:
func (*Client) RecipeDigest ¶ added in v0.20.0
RecipeDigest returns the canonical digest of a resolved recipe — the lowercase hex SHA-256 of its canonical YAML, byte-for-byte the value stored in predicate.recipe.digest by an evidence-emitting validation run.
It is the producer-side companion to VerifyEvidence: a CI gate verifies a committed evidence pointer, reads predicate.recipe.digest out of the verified bundle, and compares it against RecipeDigest of the recipe on the branch to detect evidence that has gone stale.
Hydration resolves against THIS Client's DataProvider, so the digest reflects the same catalog the Client would resolve and validate with.
Example ¶
ExampleClient_RecipeDigest computes the canonical digest an evidence predicate records. A CI gate compares this against the digest inside a published evidence bundle to detect evidence that has gone stale relative to the recipe it claims to describe.
package main
import (
"context"
"fmt"
"log"
aicr "github.com/NVIDIA/aicr/pkg/client/v1"
)
func main() {
ctx := context.Background()
client, err := aicr.NewClient(aicr.WithRecipeSource(aicr.EmbeddedSource()))
if err != nil {
log.Print(err)
return
}
defer func() { _ = client.Close() }()
digest, err := client.RecipeDigest(ctx, aicr.RecipeDigestOptions{
Path: "recipe.yaml",
})
if err != nil {
log.Print(err)
return
}
fmt.Println(digest)
}
Output:
func (*Client) ResolveRecipe ¶
func (c *Client) ResolveRecipe(ctx context.Context, req RecipeRequest) (*RecipeResult, error)
ResolveRecipe maps a RecipeRequest to a concrete validated recipe. It wraps pkg/recipe.Builder.BuildFromCriteria with a stable external request shape so AICR's internal Criteria type can evolve without breaking consumers.
Pinned recipe references (req.PinnedName / req.PinnedVersion) are not yet supported by the facade and return ErrCodeUnavailable. The field is reserved so callers can adopt it without API churn when the underlying builder gains pinning support.
func (*Client) ResolveRecipeFromCriteria ¶
func (c *Client) ResolveRecipeFromCriteria(ctx context.Context, criteria *Criteria) (*RecipeResult, error)
ResolveRecipeFromCriteria resolves a facade Criteria into a facade RecipeResult. The Components projection mirrors ResolveRecipe; callers needing the full upstream recipe (constraints, deployment order, metadata) access it via the returned result's Resolved() helper.
Use this when the caller already speaks the facade Criteria type (e.g., a REST handler that parsed criteria from an HTTP request and translated via WrapCriteria) rather than the RecipeRequest shape ResolveRecipe takes.
Allowlist enforcement (WithAllowLists) applies here just as it does on the shared resolve path: criteria outside the configured allowlist are rejected before the recipe is built.
The same guards and synchronization as ResolveRecipe apply: nil receiver, nil context, and nil criteria are rejected with ErrCodeInvalidRequest; a closed Client is rejected; a facade-level timeout bounds the resolve.
func (*Client) ResolveRecipeFromCriteriaWithOptions ¶ added in v0.19.0
func (c *Client) ResolveRecipeFromCriteriaWithOptions( ctx context.Context, criteria *Criteria, opts ...RecipeResolveOption, ) (*RecipeResult, error)
ResolveRecipeFromCriteriaWithOptions is ResolveRecipeFromCriteria with optional per-resolution behavior such as profile selection and Slurm accounting ownership.
func (*Client) ResolveRecipeFromCriteriaWithProfile ¶ added in v0.19.0
func (c *Client) ResolveRecipeFromCriteriaWithProfile( ctx context.Context, criteria *Criteria, profile string, ) (*RecipeResult, error)
ResolveRecipeFromCriteriaWithProfile resolves criteria with an optional name=value profile selection.
func (*Client) ResolveRecipeFromSnapshot ¶
func (c *Client) ResolveRecipeFromSnapshot(ctx context.Context, criteria *Criteria, snap *Snapshot) (*RecipeResult, error)
ResolveRecipeFromSnapshot resolves a recipe from explicit Criteria and evaluates its constraints against an observed cluster Snapshot, mirroring `aicr recipe --snapshot`. It returns the facade RecipeResult; callers needing the upstream recipe (ComponentRefs, deployment order, per- constraint evaluation results) access it via Resolved().
Unlike ResolveRecipeFromCriteria — which builds the recipe without observing the cluster — this variant threads a constraint evaluator that runs each resolution constraint against snap via pkg/constraints.Evaluate. The CLI's `recipe --snapshot` path does the same: it derives criteria from the snapshot fingerprint, then calls BuildFromCriteriaWithEvaluator so the resolved recipe records whether each constraint passed against the observed state.
Allowlist enforcement (WithAllowLists) applies here just as it does on the shared resolve path: criteria outside the configured allowlist are rejected before the recipe is built.
The criteria-coverage post-condition (issue #1542) is STRICT by default here: every stated criteria dimension must be honored by an applied overlay or resolution fails with ErrCodeInvalidRequest carrying details.uncovered.
To reproduce `aicr recipe --snapshot`, which additionally relaxes dimensions derived from the snapshot fingerprint and retries once, pass WithSnapshotCriteriaRelaxation and name the dimensions you received explicitly:
result, err := client.ResolveRecipeFromSnapshotWithOptions(ctx, criteria, snap,
aicr.WithSnapshotCriteriaRelaxation(aicr.DimensionIntent))
Only the caller knows which dimensions a user stated versus which it derived, so the facade cannot infer that — but it does accept it as a parameter and applies the policy itself. Dimensions actually cleared are reported in RecipeResult.RelaxedDimensions.
The same guards and synchronization as ResolveRecipeFromCriteria apply: nil receiver, nil context, nil criteria, and nil snapshot are rejected with ErrCodeInvalidRequest; a closed Client is rejected; a facade-level timeout bounds the resolve. Builder errors propagate as-is (they already carry the appropriate pkg/errors code) rather than being re-wrapped.
func (*Client) ResolveRecipeFromSnapshotWithOptions ¶ added in v0.19.0
func (c *Client) ResolveRecipeFromSnapshotWithOptions( ctx context.Context, criteria *Criteria, snap *Snapshot, opts ...RecipeResolveOption, ) (*RecipeResult, error)
ResolveRecipeFromSnapshotWithOptions is ResolveRecipeFromSnapshot with optional per-resolution behavior such as profile selection and Slurm accounting ownership.
func (*Client) ResolveRecipeFromSnapshotWithProfile ¶ added in v0.19.0
func (c *Client) ResolveRecipeFromSnapshotWithProfile( ctx context.Context, criteria *Criteria, snap *Snapshot, profile string, ) (*RecipeResult, error)
ResolveRecipeFromSnapshotWithProfile is the snapshot-filtered profile resolution path.
func (*Client) SignCatalog ¶ added in v0.20.0
func (c *Client) SignCatalog(ctx context.Context, opts CatalogSignOptions) (*CatalogSignResult, error)
SignCatalog computes the deterministic digest over this Client's recipe catalog (registry.yaml plus validators/catalog.yaml), signs it via Sigstore keyless OIDC, and optionally writes the resulting bundle to opts.Output. VerifyCatalog is its counterpart.
As with VerifyCatalog, the digest is computed over THIS Client's DataProvider, so what gets signed is the catalog the Client resolves with.
Signing modes are constrained to what VerifyCatalog can verify ¶
VerifyCatalog verifies against the public-good Sigstore root, requires a transparency-log entry, and pins the certificate to the GitHub Actions OIDC issuer. It exposes no key, no trust-root, and no offline option, because the recipe catalog is a release artifact NVIDIA signs — not something a consumer re-signs privately.
SignCatalog therefore REJECTS the four OIDCResolve settings that would produce a signature its own counterpart could not check:
- SigningKey — a key-signed catalog has no verification path at all.
- FulcioURL — a certificate from a private CA does not chain to the public-good root.
- RekorURL — an entry in a private transparency log cannot be verified against the public-good root either. The flag can name a public-good v1 URL as well, which would verify, but the two are indistinguishable from the URL alone, so this fails closed.
- DisableTLogUpload — verification requires a transparency-log entry.
Each is rejected with ErrCodeInvalidRequest before any signing work runs, so the failure is immediate and explains itself rather than surfacing later as an unverifiable artifact.
This is a guard, not a decision procedure, and the residual gap is worth stating: SigningConfigPath passes through because the release path requires it, and a signing config can itself name a private Fulcio or Rekor. Every rejected setting above exists ONLY to depart from the public-good defaults, which is what makes rejecting them unambiguous; a signing config does not.
If private catalog signing is ever needed, both halves have to move together — widening this without widening VerifyCatalog is what this guard exists to prevent.
A signature that yields no bundle is treated as a failure rather than a silent success: it means the attester could not obtain an OIDC token.
Example ¶
ExampleClient_SignCatalog signs a recipe catalog with keyless Sigstore.
This is a release-CI flow, not a local one ¶
VerifyCatalog pins the certificate identity to this repository's tag-release workflow. A catalog signed anywhere else verifies against nothing, so this is only useful from that workflow, with ambient credentials supplied.
Leaving OIDCResolve zero-valued is the trap: SelectOIDCSource then falls through to the interactive BROWSER flow, which blocks on a human and mints a certificate issued by oauth2.sigstore.dev. SignCatalog succeeds and emits a bundle VerifyCatalog rejects — it fails the issuer pin before identity matching is even reached.
SignCatalog does reject settings that break verifiability — a signing key, a private Fulcio or Rekor, or a disabled transparency-log upload — but it does NOT police the identity SOURCE, which is the asymmetry an SDK caller is most likely to hit.
Signing is also deliberately not bounded by a facade timeout, because keyless OIDC can block on a human.
package main
import (
"context"
"fmt"
"log"
"os"
aicr "github.com/NVIDIA/aicr/pkg/client/v1"
)
func main() {
ctx := context.Background()
client, err := aicr.NewClient(aicr.WithRecipeSource(aicr.FilesystemSource("/etc/aicr/recipes")))
if err != nil {
log.Print(err)
return
}
defer func() { _ = client.Close() }()
signed, err := client.SignCatalog(ctx, aicr.CatalogSignOptions{
Output: "recipe-catalog.sigstore.json",
OIDCResolve: aicr.OIDCResolveOptions{
// Ambient workload credentials. Both must be set, or resolution
// falls through to the browser flow described above.
AmbientURL: os.Getenv("ACTIONS_ID_TOKEN_REQUEST_URL"),
AmbientToken: os.Getenv("ACTIONS_ID_TOKEN_REQUEST_TOKEN"),
},
})
if err != nil {
log.Print(err)
return
}
fmt.Printf("signed catalog digest %s (%d bytes)\n", signed.Digest, len(signed.BundleJSON))
}
Output:
func (*Client) ValidateState ¶
func (c *Client) ValidateState( ctx context.Context, recipe *RecipeResult, snap *Snapshot, opts ...ValidateOption, ) ([]*PhaseResult, error)
ValidateState evaluates a resolved recipe against an observed cluster snapshot, runs the selected validation phases (by default PhaseDeployment, PhaseConformance, PhasePerformance) in order, and returns one PhaseResult per phase run. Pass WithValidationPhases to restrict the run to a subset.
recipe must come from a prior Client.ResolveRecipe call on this Client — it carries the unexported internal recipe state needed to drive constraint evaluation. Passing a RecipeResult constructed by the caller (or one produced by a different Client whose internal has since been evicted) returns ErrCodeInvalidRequest.
snap is the Snapshot returned by Client.CollectSnapshot or by any other snapshotter source.
opts configure the validator run. Pass WithValidationNoCluster(true) from unit tests so no Kubernetes resources are created and every check reports as "skipped". WithValidationNamespace, WithValidationRunID, WithValidationCleanup, WithValidationTolerations, WithValidationNodeSelector, WithValidationKubeconfig, and WithValidationPhases cover the production-controller knobs. The validator catalog loads through this Client's own DataProvider, so a Client built from FilesystemSource validates against that recipe source rather than the package global.
Errors:
- ErrCodeInvalidRequest when the Client, recipe, or snap is nil, when recipe lacks internal state, or when the Client has been Closed.
- All validator errors propagate unwrapped — readiness-check failures surface as ErrCodeInvalidRequest, infrastructure failures as ErrCodeInternal.
All phases run by default and produce results regardless of earlier failures. Pass WithValidationFailFast(true) to stop after the first failed phase (useful for skipping expensive checks like inference-perf when deployment already failed). Callers wanting per-phase control can reach into pkg/validator.ValidatePhase directly.
Example ¶
ExampleClient_ValidateState evaluates a resolved recipe against observed cluster state.
Validation comprises three phases, executed in order: deployment, conformance, performance. This example narrows to the first two with WithValidationPhases; omitting that option runs all three.
WithValidationNoCluster(true) keeps constraint evaluation but skips everything needing a cluster — the mode CI uses to check a recipe against a captured snapshot without provisioning hardware.
package main
import (
"context"
"fmt"
"log"
aicr "github.com/NVIDIA/aicr/pkg/client/v1"
)
func main() {
ctx := context.Background()
client, err := aicr.NewClient(aicr.WithRecipeSource(aicr.EmbeddedSource()))
if err != nil {
log.Print(err)
return
}
defer func() { _ = client.Close() }()
recipe, err := client.LoadRecipe(ctx, "recipe.yaml", "")
if err != nil {
log.Print(err)
return
}
snap, err := client.LoadSnapshot(ctx, "snapshot.yaml", "")
if err != nil {
log.Print(err)
return
}
phases, err := client.ValidateState(ctx, recipe, snap,
aicr.WithValidationNoCluster(true),
aicr.WithValidationPhases(aicr.PhaseDeployment, aicr.PhaseConformance),
)
if err != nil {
log.Print(err)
return
}
for _, p := range phases {
fmt.Printf("%s: %d passed, %d failed\n", p.Phase, p.Summary.Passed, p.Summary.Failed)
}
}
Output:
func (*Client) VerifyBundle ¶ added in v0.20.0
func (c *Client) VerifyBundle(ctx context.Context, bundleDir string, opts BundleVerifyOptions) (*BundleVerification, error)
VerifyBundle verifies a deployment bundle's checksums and attestation chain, then evaluates the policy assertions carried in opts.
Verification is offline: the checksum and attestation chain resolve against the locally cached or embedded Sigstore trusted root. The one network path is a KMS URI in opts.Key, which makes a live GetPublicKey call to resolve the key.
A non-nil error means verification could not be performed — bad options, a missing or unreadable bundle directory, a malformed trust root. A bundle that verified but failed is reported through the returned value: Report.Errors for verification failures and PolicyFailure for policy ones.
This method does not touch the Client's recipe catalog. It hangs off Client so that a single configured Client is the one object a consumer needs, and so config-driven verification has a home. Any open Client will do — a hot-path caller can construct one against EmbeddedSource and reuse it, rather than building one per verification.
func (*Client) VerifyCatalog ¶ added in v0.20.0
func (c *Client) VerifyCatalog(ctx context.Context, bundlePath string, opts CatalogVerifyOptions) (*CatalogVerification, error)
VerifyCatalog recomputes the deterministic digest over this Client's recipe catalog (registry.yaml plus validators/catalog.yaml) and verifies it against the Sigstore bundle at bundlePath, pinning to NVIDIA CI identity. The bundle ships as the recipe-catalog.sigstore.json release asset alongside each tagged aicr binary.
The digest is computed over THIS Client's DataProvider, not the process-wide embedded catalog — the same binding ComputeHealth uses. A Client built on an EmbeddedSource verifies the catalog NVIDIA signed. A Client whose source layers external data over the embedded tree is verifying different content, so verification will not match the released signature; that is the correct answer to "is the catalog I am resolving against the signed one", not a bug.
A verification failure is returned as an error, not as a report: unlike bundle and evidence verification there is no partial verdict to render.
Example ¶
ExampleClient_VerifyCatalog checks the Sigstore signature over this Client's recipe catalog — that the recipe data resolution is about to use was published by NVIDIA CI and has not been altered. The bundle ships as the recipe-catalog.sigstore.json release asset.
The digest is computed over THIS Client's DataProvider. A Client layering external data over the embedded tree is verifying different content, so it will not match the released signature — that is the correct answer to "is the catalog I am resolving against the signed one", not a failure to work around.
package main
import (
"context"
"fmt"
"log"
aicr "github.com/NVIDIA/aicr/pkg/client/v1"
)
func main() {
ctx := context.Background()
client, err := aicr.NewClient(aicr.WithRecipeSource(aicr.FilesystemSource("/etc/aicr/recipes")))
if err != nil {
log.Print(err)
return
}
defer func() { _ = client.Close() }()
verification, err := client.VerifyCatalog(ctx, "recipe-catalog.sigstore.json", aicr.CatalogVerifyOptions{})
if err != nil {
log.Print(err)
return
}
fmt.Printf("signed by %s over %s\n", verification.Identity, verification.Digest)
}
Output:
func (*Client) VerifyEvidence ¶ added in v0.20.0
func (c *Client) VerifyEvidence(ctx context.Context, opts EvidenceVerifyOptions) (*EvidenceVerification, error)
VerifyEvidence verifies a recipe-evidence bundle's signature (when present) and manifest hash chain, and surfaces the predicate it recovered.
A non-nil error means verification could not be attempted (bad options, closed Client). Everything else — including "this bundle is invalid" — comes back as a verdict on the returned value; read EvidenceVerification.Exit and compare against the EvidenceExit* constants.
Unlike bundle verification this can reach the network: a pointer or OCI input pulls the artifact from its registry.
That makes one interaction worth knowing. The call is capped by defaults.VerifyOperationTimeout, which is an unconditional ceiling rather than a deadline-less fallback, so a slow registry can trip it even when the caller's own context allowed longer. A cap breach returns an ERROR, not EvidenceExitIncomplete — so a gate that distinguishes "could not check this" from "checked it and it failed" must treat a context-deadline error as the former, alongside the Incomplete verdict.
The Client's recipe catalog is not consulted, so any open Client will do — including one a hot-path caller keeps around and reuses.
Example ¶
ExampleClient_VerifyEvidence checks a recipe-evidence bundle's signature and hash chain. Input accepts a pointer file, a directory, or an OCI reference.
package main
import (
"context"
"fmt"
"log"
aicr "github.com/NVIDIA/aicr/pkg/client/v1"
)
func main() {
ctx := context.Background()
client, err := aicr.NewClient(aicr.WithRecipeSource(aicr.EmbeddedSource()))
if err != nil {
log.Print(err)
return
}
defer func() { _ = client.Close() }()
verification, err := client.VerifyEvidence(ctx, aicr.EvidenceVerifyOptions{
Input: "evidence.json",
})
if err != nil {
log.Print(err)
return
}
switch verification.Exit {
case aicr.EvidenceExitValidPassed:
fmt.Println("valid, all phases passed")
case aicr.EvidenceExitValidPhaseFailures:
fmt.Println("valid, but phases failed")
case aicr.EvidenceExitInvalid:
fmt.Println("invalid")
case aicr.EvidenceExitIncomplete:
fmt.Println("incomplete")
}
fmt.Println(aicr.RenderEvidenceMarkdown(verification))
}
Output:
type ComponentBundle ¶
type ComponentBundle struct {
// Component is the matching ComponentRef from the recipe.
Component ComponentRef
// HelmValues are YAML-encoded Helm values, or nil for
// non-Helm components.
HelmValues []byte
// Manifests are rendered manifest bytes. Non-nil for
// Kustomize components, and also non-nil for Helm components
// whose recipe attaches supplemental manifestFiles. nil when
// the component has no manifest files of its own.
Manifests []byte
}
ComponentBundle is the resolved deployable artifact for one recipe component. The slice returned by Client.BundleComponents mirrors RecipeResult.Components 1:1 — same order, same length — so callers can correlate by index when threading bundles back through their own state.
Component identity (Name, Kind, Version) duplicates the matching RecipeRef so callers passing bundles around without the original RecipeResult retain enough context to dispatch on kind.
HelmValues vs Manifests population — read carefully, the rule is per-Kind, not "exactly one":
- Helm components: HelmValues holds YAML-encoded values that downstream consumers pass to `helm install --values`. Manifests MAY ALSO be non-nil when the recipe attaches supplemental manifest files to the Helm component (e.g., gpu-operator's overlay attaches a dcgm-exporter manifest; h100-gke-cos-training attaches gke-nccl-tcpxo manifests). Downstream consumers should apply Manifests alongside the Helm release. Skipping Manifests on a Helm component will silently drop those resources.
- Kustomize / raw-manifest components: Manifests holds the rendered manifest bytes. HelmValues is nil.
- Components with neither (rare — a recipe component with no valuesFile, no overrides, and no manifestFiles): both fields are nil; the component is still listed for ordering / status purposes.
type ComponentRef ¶
type ComponentRef struct {
// Name is the component identifier, e.g. "gpu-operator".
Name string
// Kind is the deployment kind, e.g. "Helm" or "Kustomize".
Kind string
// Version is the component chart/manifest version.
Version string
// Source is the upstream artifact location: a Helm chart
// repository URL for Helm components (e.g.
// "https://helm.ngc.nvidia.com/nvidia"), or a Kustomize source
// repo for Kustomize components. Empty when the recipe
// registry leaves it unset.
Source string
// Chart is the Helm chart name as it appears in the upstream
// repository (e.g. "gpu-operator"). Empty for non-Helm
// components. Defaults to Name when the registry leaves it
// unset.
Chart string
// Namespace is the install namespace recommended by the recipe
// (e.g. "gpu-operator"). Consumers SHOULD honor it so the
// deployed layout matches what AICR validation expects to find.
// Empty when the recipe leaves it unset.
Namespace string
}
ComponentRef identifies a deployable recipe component.
The Name/Chart distinction matters: Name is AICR's identifier (e.g. "nfd"), while Chart is the Helm chart name (e.g. "node-feature-discovery"). Most components have Name == Chart, but the registry's helm.defaultChart override allows them to differ. Consumers building Helm Releases must use Chart, not Name, as spec.forProvider.chart.name.
type Config ¶ added in v0.20.0
type Config struct {
// contains filtered or unexported fields
}
Config is a parsed AICRConfig document — the version-controlled file a team commits so their snapshot / recipe / bundle / validate / verify settings live beside the code they configure, rather than being retyped on each invocation.
Deriving options, not applying them ¶
A Config does not attach to a Client and is never consulted implicitly. Instead each method below DERIVES a populated options value, which the caller may then override:
cfg, err := aicr.LoadConfig(ctx, "aicr-config.yaml") opts, err := cfg.BundleVerifyOptions() opts.MinTrustLevel = "verified" // caller wins, visibly v, err := client.VerifyBundle(ctx, dir, opts)
That shape is deliberate. The facade's options are plain structs, so a field left at its zero value is indistinguishable from one a caller set to the zero value on purpose — there is no equivalent of the CLI's cmd.IsSet. An implicit merge would therefore have to guess, and would silently hand back the config's value to a caller who deliberately cleared a setting. Deriving makes precedence one readable line at the call site instead of a merge rule the caller has to remember.
It also matches what the CLI does: build options from config, then let an explicitly-set flag win. The flag half necessarily stays in pkg/cli, which is the only layer that knows a flag was set.
Nil safety ¶
Every method tolerates a nil Config and nil spec sections, returning zero values rather than erroring. A caller that did not supply a config can derive unconditionally and get "nothing configured", which is what the CLI does when --config is absent.
func LoadConfig ¶ added in v0.20.0
LoadConfig reads and validates an AICRConfig from a file path or an HTTP(S) URL.
Errors keep the loader's structured codes — ErrCodeNotFound for a missing file, ErrCodeInvalidRequest for malformed input or a strict-decode rejection, ErrCodeUnavailable for an HTTP failure — rather than being flattened.
Criteria values are validated later, not here ¶
Loading checks structure, not criteria MEMBERSHIP. Whether "eks" or some value your own catalog defines is legal depends on the CriteriaRegistry, which is per-DataProvider — and the provider named by spec.recipe.data does not exist yet at load time. Validating here could only check the embedded catalog, which would reject every externally-contributed value and make a config-driven external catalog unusable.
So membership is checked at RecipeCriteria, where a registry is in hand:
cfg, err := aicr.LoadConfig(ctx, path) // structure source, _ := cfg.RecipeSource() // spec.recipe.data client, err := aicr.NewClient(aicr.WithRecipeSource(source)) err = client.LoadCatalog(ctx) // seeds the registry criteria, err := cfg.RecipeCriteria(client.CriteriaRegistry()) // membership
A value in no catalog still fails — at that last step rather than the first.
func WrapConfig ¶ added in v0.20.0
func WrapConfig(c *appconfig.AICRConfig) *Config
WrapConfig lifts an AICRConfig parsed elsewhere into the facade type, for callers that already hold one from pkg/config. Returns nil for nil input, mirroring WrapSnapshot.
func (*Config) BundleVerifyOptions ¶ added in v0.20.0
func (c *Config) BundleVerifyOptions() (BundleVerifyOptions, error)
BundleVerifyOptions derives Client.VerifyBundle options from spec.verify.
The mapping is one-to-one: spec.verify.trust supplies CertificateIdentityRegexp, Key, and TrustRoot, and spec.verify.policy supplies MinTrustLevel, RequireCreator, and CLIVersionConstraint. That alignment is not a coincidence — BundleVerifyOptions was shaped to mirror VerifySpec so this stayed a copy rather than a translation table.
IgnoreTLog has no config counterpart and is left false. It weakens the trust floor by dropping the transparency-log requirement, and keeping it command-line-only means a checked-in file can never silently disable that check.
An empty MinTrustLevel is preserved rather than defaulted here, so VerifyBundle applies its own "max" default. Setting it in this layer would hide which of the two chose the floor.
Returns an error when spec.verify is present but malformed.
func (*Config) IsCriteriaStrict ¶ added in v0.20.0
IsCriteriaStrict reports spec.recipe.criteriaStrict, which rejects criteria values outside the embedded catalog — hiding registry entries contributed by a --data overlay.
Exposed as a plain read rather than applied inside RecipeCriteria on purpose: strictness is a property of the CriteriaRegistry, which is shared per-DataProvider, so a derivation method that set it would mutate state the caller shares with every other operation on that Client. The caller applies it deliberately, or not at all.
func (*Config) RecipeAccountingMode ¶ added in v0.20.0
RecipeAccountingMode returns the Slurm accounting mode from spec.recipe.configuration.slurm.accounting.mode, and reports whether the document set one. Same raw-accessor rationale as RecipeProfile.
Returns an error when the configured value is not a valid accounting mode.
func (*Config) RecipeCriteria ¶ added in v0.20.0
func (c *Config) RecipeCriteria(reg *CriteriaRegistry) (*Criteria, error)
RecipeCriteria derives resolve criteria from spec.recipe.criteria, parsed against the supplied registry so a value contributed by a --data overlay validates against the same DataProvider the Client resolves with. Pass Client.CriteriaRegistry(); a nil registry falls back to the embedded catalog.
Returns an empty (non-nil) Criteria when the document states none, so the result is always safe to hand to a resolve call or to overwrite field by field.
func (*Config) RecipeProfile ¶ added in v0.20.0
RecipeProfile returns spec.recipe.profile, the configuration-profile selection in name=value form. Empty when unset.
RecipeResolveOptions already folds this into a ready-to-use option; this raw accessor exists for callers that must apply their own precedence first, which is exactly what the CLI does when overlaying an explicitly-set --profile flag. Reach for the options form unless you need the raw value.
func (*Config) RecipeResolveOptions ¶ added in v0.20.0
func (c *Config) RecipeResolveOptions() ([]RecipeResolveOption, error)
RecipeResolveOptions derives the resolve options spec.recipe carries: the configuration profile selection (spec.recipe.profile) and the Slurm accounting mode (spec.recipe.configuration.slurm.accounting.mode).
Returns a nil slice when the document sets neither, so it can be appended to a caller's own options unconditionally:
opts, err := cfg.RecipeResolveOptions() opts = append(opts, aicr.WithProfile(flagProfile)) // caller wins: later option overwrites
func (*Config) RecipeRuntimeInventoryMode ¶ added in v0.20.0
RecipeRuntimeInventoryMode returns spec.recipe.configuration.runtimeInventory.mode and whether the document set one. Same raw-accessor rationale as RecipeAccountingMode.
Returns an error when the configured value is not a valid mode.
func (*Config) RecipeSource ¶ added in v0.20.0
func (c *Config) RecipeSource() (RecipeSourceOption, bool)
RecipeSource derives the Client recipe source from spec.recipe.data, and reports whether the document configured one.
This is the piece that lets a committed config stand up a Client at all: a non-empty data directory yields a FilesystemSource layered over the embedded recipe data, matching `aicr recipe --data`. When false is returned the caller supplies its own source, normally EmbeddedSource.
Deliberately NOT folded into a Client option. Recipe source is fixed at construction — a Client owns its DataProvider for its whole lifetime — so this belongs in the NewClient call rather than in a per-operation options value.
func (*Config) SnapshotPath ¶ added in v0.20.0
SnapshotPath returns spec.recipe.input.snapshot, the snapshot a committed config resolves against. Empty when unset; hand a non-empty value to Client.LoadSnapshot.
func (*Config) Unwrap ¶ added in v0.20.0
func (c *Config) Unwrap() *appconfig.AICRConfig
Unwrap returns the underlying AICRConfig, for callers that need a spec field this facade does not project. Returns nil for a nil Config.
Reaching for this is a signal worth acting on: it means the facade is missing a derivation someone needs. Prefer opening an issue over building on the raw document, since pkg/config carries no stability guarantee.
type Criteria ¶
type Criteria struct {
Service string
Accelerator string
Intent string
OS string
Platform string
Nodes int
}
Criteria is the facade-owned, semver-stable shape of a recipe-resolution query. Mirrors pkg/recipe.Criteria field-for-field with the enum-typed pkg/recipe values projected to plain strings so the facade contract does not pin consumers to pkg/recipe's enum identifiers (an internal enum rename or addition stays internal). Construct one directly or wrap an upstream pkg/recipe.Criteria via WrapCriteria.
Field meanings match the pkg/recipe.Criteria documentation:
- Service: Kubernetes service flavor (eks/gke/aks/oke/kind/lke/bcm).
- Accelerator: GPU model identifier (h100/h200/b200/gb200/a100/l40/l40s/rtx-pro-6000).
- Intent: workload intent (training/inference).
- OS: worker-node OS (ubuntu/rhel/cos/amazonlinux/talos/ol).
- Platform: framework overlay (dynamo/kubeflow/nim/runai/slurm).
- Nodes: worker-node count hint (0 = unspecified).
Empty string is the "unspecified" sentinel for every field except Nodes, where 0 plays that role. A non-empty string that the registry does not recognize is rejected at resolve time with ErrCodeInvalidRequest.
func WrapCriteria ¶
WrapCriteria projects a pkg/recipe.Criteria into the facade Criteria shape. Use this at the boundary where in-tree callers (CLI/API handlers) hand a parsed criteria — produced by recipe.ParseCriteriaFromRequest or recipe.BuildCriteriaWithRegistry — to facade methods such as Client.ResolveRecipeFromCriteria. Returns nil for nil input.
Round-trip: WrapCriteria(c) then toInternalCriteria projects back to the pkg/recipe.Criteria enum-typed shape; the round-trip is lossless because the facade carries plain strings for the same set of named enum fields (Service/Accelerator/Intent/OS/Platform) plus Nodes.
type CriteriaDimension ¶ added in v0.20.0
type CriteriaDimension string
CriteriaDimension names one criteria dimension subject to the criteria-coverage post-condition (issue #1542): the five dimensions an applied overlay can honor, and therefore the five a coverage failure can report as uncovered.
nodes is deliberately absent. No overlay gates on nodes, so it never participates in overlay selection or coverage — see pkg/recipe/coverage.go.
const ( DimensionService CriteriaDimension = "service" DimensionAccelerator CriteriaDimension = "accelerator" DimensionIntent CriteriaDimension = "intent" DimensionOS CriteriaDimension = "os" DimensionPlatform CriteriaDimension = "platform" )
The criteria dimensions subject to the coverage post-condition. The string values match pkg/recipe.CoverageDimensionNames exactly; a test asserts that equality, because a mismatch would silently unmark a stated dimension and let WithSnapshotCriteriaRelaxation clear something the caller stated.
func AllCriteriaDimensions ¶ added in v0.20.0
func AllCriteriaDimensions() []CriteriaDimension
AllCriteriaDimensions returns every dimension subject to the coverage post-condition, in canonical order. Useful for declaring that every dimension was caller-stated:
aicr.WithSnapshotCriteriaRelaxation(aicr.AllCriteriaDimensions()...)
which enables the policy but permits nothing to be relaxed — equivalent to strict resolution, stated explicitly.
type CriteriaRegistry ¶
type CriteriaRegistry = recipe.CriteriaRegistry
CriteriaRegistry is the per-DataProvider set of valid criteria values, returned by Client.CriteriaRegistry so CLI/library callers parse and validate criteria against the SAME provider the Client resolves with.
Intentionally kept as a transparent alias of pkg/recipe.CriteriaRegistry rather than wrapped into a facade-owned type, for two reasons:
- The registry is behavior-rich (ParseService/ParseAccelerator/..., SetStrict, Values, AllAcceleratorTypes, etc.) — wrapping it would require translating every method through, with no semver win because these methods are already used to construct pkg/recipe.Criteria instances in CLI / API call paths.
- The registry carries mutable shared state (strict mode, registered values) keyed by per-Client DataProvider identity. A facade wrapper would either copy state (breaking the per-Client identity coupling) or hold a pointer (no isolation win over the alias).
External callers receive the same pkg/recipe.CriteriaRegistry the Client's resolve path uses. If the underlying API evolves, this alias is the single canary; the facade can absorb it by hand-writing a wrapper then.
type EvidenceOptions ¶ added in v0.16.0
type EvidenceOptions struct {
// OutDir is the directory to write the recipe-evidence bundle to
// (summary-bundle/, optionally logs-bundle/, and pointer.yaml). Required.
OutDir string
// BOMPath optionally embeds a CycloneDX BOM; when empty a recipe-bound
// BOM is synthesized from the recipe's component refs and the validator
// catalog images that ran.
BOMPath string
// Push, when set, is the OCI reference to push the (optionally signed)
// summary bundle to.
Push string
// PlainHTTP / InsecureTLS control the OCI transport for Push (local /
// self-signed registries).
PlainHTTP bool
InsecureTLS bool
// NoSign pushes an unsigned bundle and writes a pointer with an empty
// signer block (requires Push); defers Fulcio/Rekor signing.
NoSign bool
// Full disables evidence minimization (ships the raw snapshot and CTRF
// payloads instead of the redacted defaults).
Full bool
// Commit is the build commit used to resolve the validator catalog for
// the bundle's BOM. The Client's version is used for the catalog version
// and stamped as AICRVersion; commit has no Client-level home, so it is
// supplied per call.
Commit string
// OIDCResolve carries keyless-signing token-resolution inputs, consumed
// only when Push is set and NoSign is false.
OIDCResolve OIDCResolveOptions
}
EvidenceOptions configures Client.EmitRecipeEvidence. It is the facade-owned mirror of the inputs the CLI used to assemble inline, minus the interactive signing-disclosure prompt, which is a UI concern the caller owns.
type EvidencePublishOptions ¶ added in v0.20.0
type EvidencePublishOptions struct {
// BundleDir is the on-disk evidence directory: either the output
// directory an evidence-emitting validation run wrote (which holds
// summary-bundle/ and receives pointer.yaml) or the summary-bundle/
// directory itself. Required.
BundleDir string
// Push is the OCI reference the summary bundle is pushed to. Required
// — a publish with nothing to push is a no-op.
Push string
// PlainHTTP forces HTTP for registry traffic (local-registry tests).
PlainHTTP bool
// InsecureTLS disables registry TLS verification (self-signed certs).
InsecureTLS bool
// NoSign pushes the bundle unsigned and writes a pointer with an empty
// signer block, deferring the Fulcio/Rekor leg. No OIDC flow runs, so
// OIDCResolve is ignored.
NoSign bool
// OIDCResolve carries keyless-signing token-resolution inputs,
// consumed only when NoSign is false. Resolution is deferred until
// adjacent to signing so Fulcio's nonce-binding window is respected.
OIDCResolve OIDCResolveOptions
}
EvidencePublishOptions configures Client.PublishEvidence.
type EvidenceVerification ¶ added in v0.20.0
type EvidenceVerification = evverifier.VerifyResult
EvidenceVerification is the outcome of recipe-evidence bundle verification: the verdict, the recovered predicate and pointer, the signer's claims, and the per-step results.
Deliberate transparent alias of pkg/evidence/verifier.VerifyResult. The result is a deep tree — it reaches into the evidence pointer, the in-toto predicate, the per-step records, and the signer claims — and every field is read-only output. A facade-owned copy would mean owning five more nested types whose shape is still evolving alongside the evidence predicate, for no consumer benefit: a caller reads the verdict and the predicate through `:=` and never names this type.
type EvidenceVerifyOptions ¶ added in v0.20.0
type EvidenceVerifyOptions struct {
// Input selects the bundle, in any of three auto-detected forms: a
// pointer file path (recipes/evidence/<recipe>/<source>/<digest>.yaml),
// an OCI reference (with or without an oci:// prefix), or an unpacked
// bundle directory. Required.
Input string
// BundleRef overrides the OCI reference when Input does not carry one
// — a pointer file whose bundle.oci is empty.
BundleRef string
// ExpectedIssuer pins the OIDC issuer URL on the signing certificate.
// Empty allows any issuer.
ExpectedIssuer string
// ExpectedIdentityRegexp pins the signer's SubjectAlternativeName via
// regex. Empty allows any identity.
ExpectedIdentityRegexp string
// PlainHTTP forces HTTP for registry traffic (local-registry tests).
PlainHTTP bool
// InsecureTLS disables registry TLS verification (self-signed certs).
InsecureTLS bool
// AllowUnpinnedTag opts into accepting an OCI reference that resolves
// to a tag rather than a digest. Off by default because a tag can be
// rewritten by the registry, so "verify this artifact at this tag" is
// not content-addressable.
AllowUnpinnedTag bool
}
EvidenceVerifyOptions configures Client.VerifyEvidence.
type OIDCResolveOptions ¶ added in v0.16.0
type OIDCResolveOptions = bundleattest.ResolveOptions
OIDCResolveOptions configures keyless-signing OIDC token resolution for a pushed evidence bundle. Deliberate transparent alias of pkg/bundler/attestation.ResolveOptions, mirroring how BundleOptions exposes BundleAttester: the caller (CLI/server) builds the resolution inputs and the facade threads them through to attestation.Emit, which resolves the token adjacent to signing. The zero value is valid (no token sources → ambient or interactive flows handled by the caller before invoking the facade).
type Option ¶
type Option func(*Client)
Option configures a Client.
func WithAllowLists ¶
func WithAllowLists(al *AllowLists) Option
WithAllowLists fences which criteria values the Client's resolve path accepts. A resolve whose criteria fall outside the allowlist is rejected before the recipe is built. Pass nil (or omit the option) to allow all values. Construct an AllowLists directly or via ParseAllowListsFromEnv.
func WithOCISourceTempDir ¶ added in v0.20.0
WithOCISourceTempDir sets the existing writable parent directory beneath which an OCI recipe source creates its unique, private per-Client workspace. Client.Close removes only the child workspace; it never removes parent.
When omitted, the system temporary directory is used. Supplying this option with EmbeddedSource or FilesystemSource is invalid.
func WithRecipeSource ¶
func WithRecipeSource(s RecipeSourceOption) Option
WithRecipeSource sets the recipe source on the Client. Construct the argument with EmbeddedSource, FilesystemSource, or OCISource.
func WithVersion ¶
WithVersion sets the version string stamped into resolved recipe metadata (RecipeResult.Metadata.Version). Threaded through to the underlying recipe.Builder via recipe.WithVersion. Typically the consuming binary's build version.
type Phase ¶
type Phase string
Phase identifies a single validation phase. Facade-owned so the stable surface does not propagate pkg/validator type-shape changes. Values match pkg/validator/v1 constants verbatim for direct wire compatibility.
type PhaseResult ¶
type PhaseResult struct {
Phase Phase
Status string
Duration time.Duration
Summary ReportSummary
RawReport []byte
Report *ctrf.Report
}
PhaseResult is the outcome of running all validators in a single phase. Facade-owned. Summary holds the CTRF count breakdown for the common pass/fail check; RawReport carries the marshaled CTRF JSON for callers needing per-test detail; Report is the typed CTRF report retained for in-tree consumers that merge per-phase reports via ctrf.MergeReports.
type ProfileSummary ¶ added in v0.19.0
type ProfileSummary struct {
Name string `json:"name" yaml:"name"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Default string `json:"default" yaml:"default"`
Values []string `json:"values" yaml:"values"`
}
ProfileSummary is the compact catalog discovery shape.
type RecipeDigestOptions ¶ added in v0.20.0
type RecipeDigestOptions struct {
// Path is a recipe or overlay file, as a path, an HTTP(S) URL, or a
// cm://namespace/name ConfigMap URI. Required.
Path string
// Kubeconfig resolves cm:// URIs. Empty uses the standard KUBECONFIG,
// ~/.kube/config, then in-cluster discovery chain.
Kubeconfig string
// Profile is a name=value configuration-profile selection, with the
// same semantics as `aicr recipe --profile`. It applies only to
// overlay inputs: an already-hydrated recipe carries its own
// metadata.selectedProfile, and combining the two is rejected.
Profile string
}
RecipeDigestOptions configures Client.RecipeDigest.
type RecipeRequest ¶
type RecipeRequest struct {
// Service is the target Kubernetes service identifier, e.g.
// "eks", "gke", "aks", "oke", "kind", "lke", or "any". Mapped
// to pkg/recipe CriteriaService. Note that this is the K8s
// FLAVOR (eks vs gke), not the cloud vendor (aws vs gcp);
// callers that think in cloud-vendor terms must map first
// (aws→eks, gcp→gke, etc.).
Service string
// Region is the cloud region. Informational only — not part of
// pkg/recipe.Criteria today; captured on the request so consumers
// can audit the call without a separate field.
Region string
// Accelerator is the GPU model identifier, e.g. "h100", "b200".
Accelerator string
// Nodes is the worker-node count hint. Mapped to CriteriaNodes.
// Note that this is the NUMBER OF NODES, not the number of
// accelerators — a 64-GPU cluster on 8-GPU nodes has Nodes=8.
// Zero means "unspecified, AICR picks the default-sized recipe."
// Negative values are rejected with ErrCodeInvalidRequest.
Nodes int32
// Intent is the workload intent. Mapped to CriteriaIntent.
// Supported values are defined by pkg/recipe.GetCriteriaIntentTypes
// — today "training" and "inference".
Intent string
// OS is the worker-node operating system. Mapped to CriteriaOS.
// Supported values: "ubuntu", "rhel", "cos", "amazonlinux", "talos", "ol".
// Empty means "unspecified" — recipe resolution will not select
// OS-pinned leaf overlays (e.g., h100-eks-ubuntu-training,
// h100-gke-cos-training) and will fall back to the OS-agnostic
// ancestor. Set this when the cluster's OS is known so OS-specific
// constraints and mixins (kernel version, driver tuning) are
// included.
//
// Note: some service+accelerator combinations (e.g. OKE with L40S)
// have no OS-agnostic recipe and require an explicit OS value;
// omitting it returns ErrCodeInvalidRequest.
OS string
// Platform is the workload platform overlay. Mapped to
// CriteriaPlatform. Supported values are defined by
// pkg/recipe.GetCriteriaPlatformTypes — today "", "any", "dynamo",
// "kubeflow", "nim".
Platform string
// Profile is an optional name=value configuration profile selection.
// Empty applies the resolved declaration's mandatory default.
Profile string
// AccountingMode selects the Slurm accounting database ownership model.
// It is valid only when Platform is "slurm". Empty defaults to "disabled"
// for newly resolved Slurm recipes.
AccountingMode string
// PinnedName reserves space for future pinned-recipe support.
// Currently rejected with ErrCodeUnavailable; set the criteria
// fields above instead.
PinnedName string
// PinnedVersion reserves space for future pinned-recipe support.
// Currently rejected with ErrCodeUnavailable.
PinnedVersion string
}
RecipeRequest is the stable external request shape. The Client translates this into pkg/recipe.Criteria.
type RecipeResolveOption ¶ added in v0.19.0
type RecipeResolveOption func(*recipeResolveConfig)
RecipeResolveOption configures one recipe resolution request.
func WithAccountingMode ¶ added in v0.19.0
func WithAccountingMode(mode string) RecipeResolveOption
WithAccountingMode selects the Slurm accounting ownership model for a criteria- or snapshot-based resolve call. It is valid only when the resolved platform is Slurm. An empty or invalid mode is rejected when the resolve call runs; omit this option to keep the recipe default.
func WithProfile ¶ added in v0.19.0
func WithProfile(profile string) RecipeResolveOption
WithProfile selects a name=value configuration profile for a criteria- or snapshot-based resolve call. Empty applies the declaration default.
func WithRuntimeInventoryMode ¶ added in v0.20.0
func WithRuntimeInventoryMode(mode string) RecipeResolveOption
WithRuntimeInventoryMode selects whether the runtime AI inventory component is installed by a criteria- or snapshot-based resolve call. It is valid only when the resolved recipe declares that component; an empty or invalid mode is rejected when the resolve call runs. Omit this option to keep the recipe's own declaration.
Unlike a bundle-time value override, the selection is recorded in the emitted recipe and removes the component's health check along with the component, which is the contract ADR-019 requires for stock adoption.
func WithSnapshotCriteriaRelaxation ¶ added in v0.20.0
func WithSnapshotCriteriaRelaxation(stated ...CriteriaDimension) RecipeResolveOption
WithSnapshotCriteriaRelaxation enables the relax-and-retry policy that `aicr recipe --snapshot` applies on top of a snapshot resolve, and declares which criteria dimensions the caller received explicitly.
What it does ¶
A snapshot resolve is STRICT by default: every stated criteria dimension must be honored by an applied overlay, or resolution fails with ErrCodeInvalidRequest carrying details.uncovered. That is the right default for criteria a user typed, but wrong for criteria DERIVED from a snapshot fingerprint — a Kind-style overlay tree can be deliberately OS-agnostic while the fingerprint still detects a concrete os on the node. No recipe content distinguishes that detected value, so failing on it would reject a legitimate query.
With this option, a coverage failure whose uncovered dimensions were ALL derived (i.e. absent from stated) clears those dimensions back to unstated and retries the resolve exactly once. The dimensions actually cleared are reported in RecipeResult.RelaxedDimensions.
What is never relaxed ¶
Three cases propagate the original coverage error instead of retrying:
- A dimension named in stated. Relaxing a value the caller explicitly asked for would silently resolve a different recipe than requested.
- A CONSTRAINT-EXCLUDED dimension: an overlay carrying it exists, but the observed cluster failed its constraints. Relaxing there would turn "this cluster does not meet the overlay's requirements" into a broader recipe that resolves cleanly — discarding the finding the operator most needs.
- A relaxation that would leave NO stated coverage dimension, whose resolve matches every overlay and emits the generic fallback recipe at exit 0 (the fail-open behind issue #1888). Note this is not the same as "criteria is empty": a fingerprint-derived Nodes value survives the clear and still renders in Criteria.String(), but no overlay gates on nodes, so it selects nothing.
Passing no dimensions is meaningful ¶
Presence of the option enables the policy; the argument only narrows what may be relaxed. Calling it with no dimensions is the common case — every dimension came from the fingerprint and all are relaxable:
aicr.WithSnapshotCriteriaRelaxation() // relax anything uncovered aicr.WithSnapshotCriteriaRelaxation(aicr.DimensionOS) // relax anything but os
Omitting the option entirely preserves strict behavior. Note the difference from an option keyed on a non-empty argument list: the all-derived case is exactly the query the policy exists to serve, so it must not silently fall back to strict.
Scope ¶
Valid only on the snapshot resolve path (ResolveRecipeFromSnapshot and friends). On ResolveRecipeFromCriteria there is no fingerprint — every dimension is caller-supplied — so the option is rejected with ErrCodeInvalidRequest rather than ignored: silently dropping it would leave a caller believing they had `--snapshot` semantics when they did not.
An unrecognized dimension name is rejected when the resolve call runs. It is not treated as an unknown-but-harmless label, because the failure mode is leaving a stated dimension unmarked and relaxing it.
type RecipeResult ¶
type RecipeResult struct {
// Name is a stable identifier derived from the resolved criteria.
// Because AICR recipes are keyed by criteria (not by a standalone
// name), this field is the criteria string representation rather
// than an independent label.
Name string
// Version is the recipe metadata version (set by the CLI that
// generated the recipe data).
Version string
// TranslatedAt is the wall-clock time the facade completed the
// translation of the internal RecipeResult into this shape. This
// is NOT the time the underlying recipe was built — AICR's
// internal RecipeResult currently carries no build timestamp.
TranslatedAt time.Time
// Components lists the deployable components in the recipe — enabled
// component refs only. Disabled refs are omitted; call Resolved() for
// the full underlying ComponentRefs (enabled and disabled).
Components []ComponentRef
// SelectedProfile is present when the resolved composition declares a
// configuration profile.
SelectedProfile *SelectedProfile
// RelaxedDimensions lists the criteria dimensions cleared by
// WithSnapshotCriteriaRelaxation because no applied overlay
// distinguished the derived value, in the order the coverage failure
// reported them. A non-empty value means the resolved recipe is BROADER
// than the criteria originally requested.
//
// It is non-empty only when the first attempt failed coverage on derived
// dimensions AND the retry succeeded. Every other outcome — the option was
// not passed, the first attempt succeeded, relaxation was refused, or the
// retry itself failed — leaves it empty or returns no RecipeResult at all.
// So this field reports what a successful resolve gave up; it is never how
// a caller detects a failure, which is always the returned error.
//
// The CLI surfaces the same fact as a slog.Warn per dimension; this is
// the programmatic form, for callers that need to branch on it or report
// it to their own users.
RelaxedDimensions []CriteriaDimension
// contains filtered or unexported fields
}
RecipeResult is the stable external result shape.
func WrapResolved ¶ added in v0.19.0
func WrapResolved(r *recipe.RecipeResult) *RecipeResult
WrapResolved wraps an already-resolved pkg/recipe.RecipeResult — typically one obtained from RecipeResult.Resolved() and then projected by the caller — back into the facade shape so it can be handed to SelectFromRecipeWithContext. Returns nil for nil input.
The wrapped result is QUERYABLE ONLY. It carries no owning Client, so Client.MakeBundle, Client.BundleComponents, and Client.ValidateState reject it with ErrCodeInvalidRequest; use Client.AdoptRecipe for those. The caller-supplied result keeps whatever DataProvider it was already bound to, so hydration resolves against the same source that produced it — no deep copy, no re-validation, no provider rebinding.
The REST query handler is the motivating in-tree caller: it projects a legacy (/v1) view of a resolved recipe before selecting, and WrapResolved lets it run the facade's selector rather than an inlined hydrate+select pair that can drift.
func (*RecipeResult) Resolved ¶
func (r *RecipeResult) Resolved() *recipe.RecipeResult
Resolved returns the complete underlying recipe (the full pkg/recipe.RecipeResult) that this result wraps. The facade RecipeResult exposes only Name/Version/TranslatedAt/Components/SelectedProfile; callers that need constraints, validation config, deployment order, or metadata (e.g. evidence emission) use this. Returns nil if the result was not produced by the Client.
Lifetime: the returned pointer is borrowed from the facade RecipeResult. Do not mutate; do not retain past the facade RecipeResult's lifetime. Marshal/serialize first if persistence is needed.
type RecipeSourceOption ¶
type RecipeSourceOption struct {
// contains filtered or unexported fields
}
RecipeSourceOption identifies where recipes are sourced from.
func EmbeddedSource ¶
func EmbeddedSource() RecipeSourceOption
EmbeddedSource uses only AICR's built-in (embedded) recipe data, no overlay.
func FilesystemSource ¶
func FilesystemSource(path string) RecipeSourceOption
FilesystemSource describes a local filesystem path containing AICR recipes.
func OCISource ¶
func OCISource(repository, selector string) RecipeSourceOption
OCISource describes an OCI repository containing an AICR recipe tree rooted at registry.yaml. repository may optionally begin with "oci://", but must not contain a tag or digest: selector is the single source of truth for the artifact version.
selector is required and must be a sha256:<64-hex-character> manifest digest supplied by trusted configuration. Tags and implicit "latest" are rejected so mutable registry state cannot change the selected catalog.
type ReportSummary ¶
ReportSummary is the high-level pass/fail count breakdown of a validation phase's CTRF report. Facade-owned (not aliased to ctrf.Summary); fields mirror the CTRF spec summary contract.
type SelectedProfile ¶ added in v0.19.0
type SelectedProfile struct {
// Name is the declaration this selection came from, e.g. "gpuStack".
Name string
// Value is the selected value within that declaration.
Value string
// Advertiser declares that a platform-managed component outside the
// recipe advertises nvidia.com/gpu (ADR-015 GKE allocation-policy
// amendment). It is "external" on selections whose advertiser is
// platform-owned — e.g. GKE's managed device plugin on the gpuStack
// gke-default value (the GKE default) — and empty when no external
// advertiser is declared: the recipe's own components then determine
// advertisement (the GPU operator's device plugin, or DRA
// resources.gpus.enabled). "external" is the only non-empty value.
Advertiser string
// OwnedPaths maps each locked component to its sorted dotted value
// paths, and is the union across every value of the declaration rather
// than only the selected one. Every listed component carries the
// synthetic "enabled" path recording that the profile owns its
// presence. Overriding any listed path is rejected at bundle time
// unless the override agrees with the selected value.
OwnedPaths map[string][]string
}
SelectedProfile is the stable facade projection of a recipe profile. It is populated only for aicr.run/v1alpha3 results; an unprofiled composition leaves it nil.
type Snapshot ¶
type Snapshot struct {
APIVersion string
Kind string
CapturedAt time.Time
// Raw is the exact YAML document the collection agent emitted, set by
// Client.CollectSnapshot and empty on snapshots obtained any other way
// (WrapSnapshot, a hand-constructed Snapshot).
//
// Persist THESE bytes rather than re-serializing the parsed snapshot: a
// newer agent image can emit fields this binary's Snapshot type does not
// model, and a typed round trip silently drops them. `aicr snapshot`
// writes Raw for exactly that reason.
Raw []byte
// contains filtered or unexported fields
}
Snapshot is the captured cluster-state artifact returned by Client.CollectSnapshot. Facade-owned so the stable surface does not propagate pkg/snapshotter type-shape changes. APIVersion / Kind / CapturedAt are the high-level identifying metadata; the full measurement payload is held in an unexported internal field for zero-copy round-trip through ValidateState. Consumers needing measurement-level inspection import pkg/snapshotter directly.
func WrapSnapshot ¶
func WrapSnapshot(s *snapshotter.Snapshot) *Snapshot
WrapSnapshot wraps a pkg/snapshotter.Snapshot in the facade Snapshot type so callers that load snapshots externally (e.g., the CLI reading a YAML file) can pass them to facade methods. Returns nil for nil input.
func (*Snapshot) Unwrap ¶ added in v0.19.0
func (s *Snapshot) Unwrap() *snapshotter.Snapshot
Unwrap returns the underlying pkg/snapshotter.Snapshot — the inverse of WrapSnapshot, and the analog of RecipeResult.Resolved(). In-tree callers (the CLI's validate path) use it to reach measurement-level detail the facade's public fields intentionally do not project.
A Snapshot constructed outside the facade (no internal payload) yields a minimal pkg/snapshotter.Snapshot rebuilt from the public fields, so callers never have to nil-check the result of a non-nil receiver. Returns nil for a nil receiver. The returned pointer is the facade's own — treat it as read-only.
type SnapshotChange ¶ added in v0.20.0
type SnapshotChange struct {
Kind SnapshotChangeKind `json:"kind" yaml:"kind"`
Severity SnapshotChangeSeverity `json:"severity" yaml:"severity"`
Path string `json:"path" yaml:"path"`
Baseline *string `json:"baseline,omitempty" yaml:"baseline,omitempty"`
Target *string `json:"target,omitempty" yaml:"target,omitempty"`
}
SnapshotChange is one field-level difference between two snapshots. Baseline and Target are pointers so an absent side remains distinguishable from a present value whose string representation is empty.
type SnapshotChangeKind ¶ added in v0.20.0
type SnapshotChangeKind string
SnapshotChangeKind describes how a snapshot value changed.
const ( // SnapshotChangeAdded indicates a value exists only in the target snapshot. SnapshotChangeAdded SnapshotChangeKind = "added" // SnapshotChangeRemoved indicates a value exists only in the baseline snapshot. SnapshotChangeRemoved SnapshotChangeKind = "removed" // SnapshotChangeModified indicates a value differs between the snapshots. SnapshotChangeModified SnapshotChangeKind = "modified" )
type SnapshotChangeSeverity ¶ added in v0.20.0
type SnapshotChangeSeverity string
SnapshotChangeSeverity classifies the impact of a snapshot change.
const ( // SnapshotChangeSeverityInfo indicates an informational snapshot change. SnapshotChangeSeverityInfo SnapshotChangeSeverity = "info" )
type SnapshotDiff ¶ added in v0.20.0
type SnapshotDiff struct {
BaselineSource string `json:"baselineSource,omitempty" yaml:"baselineSource,omitempty"`
TargetSource string `json:"targetSource,omitempty" yaml:"targetSource,omitempty"`
Changes []SnapshotChange `json:"changes" yaml:"changes"`
Summary SnapshotDiffSummary `json:"summary" yaml:"summary"`
}
SnapshotDiff contains the complete field-level comparison of two snapshots.
func (*SnapshotDiff) HasDrift ¶ added in v0.20.0
func (r *SnapshotDiff) HasDrift() bool
HasDrift reports whether the diff contains any field-level changes.
type SnapshotDiffOptions ¶ added in v0.20.0
SnapshotDiffOptions configures labels attached to a snapshot diff result. The labels identify the inputs in serialized output; they do not affect the comparison.
type SnapshotDiffSummary ¶ added in v0.20.0
type SnapshotDiffSummary struct {
Added int `json:"added" yaml:"added"`
Removed int `json:"removed" yaml:"removed"`
Modified int `json:"modified" yaml:"modified"`
Total int `json:"total" yaml:"total"`
}
SnapshotDiffSummary contains aggregate snapshot change counts.
type ValidateOption ¶
type ValidateOption func(*validateConfig)
ValidateOption configures a validation run launched via Client.ValidateState. It is a facade-owned functional option type: each WithValidation* factory below captures its argument into an internal validateConfig, and Client.ValidateState translates the captured config into pkg/validator options at call time.
The wrap insulates the facade's semver contract from pkg/validator's own evolving Option signature. Adding a field to pkg/validator's Validator struct, renaming validator.WithXxx, or changing the validator.Option function signature can all be absorbed inside the translation function without breaking facade consumers.
func WithValidationCleanup ¶
func WithValidationCleanup(cleanup bool) ValidateOption
WithValidationCleanup controls whether validator-emitted Jobs, ConfigMaps, and RBAC are deleted at the end of the run. Default: true. Set to false to leave artifacts behind for post-mortem inspection.
func WithValidationCommit ¶
func WithValidationCommit(commit string) ValidateOption
WithValidationCommit sets the git commit SHA threaded into the validator (validator.WithCommit). Used to resolve dev-build validator images to SHA-tagged images. An empty string is the "unset" sentinel — no validator option is emitted, matching the validator's own behavior where an empty commit influences nothing.
func WithValidationFailFast ¶ added in v0.15.0
func WithValidationFailFast(failFast bool) ValidateOption
WithValidationFailFast controls whether ValidateState stops after the first phase that reports StatusFailed. Default: false (all phases run and produce results). Set true to restore stop-on-first-failure behavior.
func WithValidationImagePullSecrets ¶
func WithValidationImagePullSecrets(secrets []string) ValidateOption
WithValidationImagePullSecrets sets imagePullSecrets on the validator pods. Use this when the validator images live in a private registry whose credentials live in a Secret in the validation namespace.
The input is defensively copied; a caller that mutates the slice after this returns won't race with ValidateState reading it on a goroutine. nil-in maps to nil stored (preserves the "unset" sentinel downstream), empty-in maps to an empty-non-nil copy.
func WithValidationImageRegistryOverride ¶
func WithValidationImageRegistryOverride(registry string) ValidateOption
WithValidationImageRegistryOverride overrides the registry prefix on validator container images (validator.WithImageRegistryOverride), e.g. to point at a local registry mirror. Empty means "no override" — the validator keeps its default registry.
func WithValidationImageTagOverride ¶
func WithValidationImageTagOverride(tag string) ValidateOption
WithValidationImageTagOverride overrides the tag on every validator container image (validator.WithImageTagOverride), intended for feature-branch dev builds whose commit SHA has no published image. Empty means "no override" — the validator keeps its resolved tag.
func WithValidationKubeconfig ¶ added in v0.18.0
func WithValidationKubeconfig(kubeconfig string) ValidateOption
WithValidationKubeconfig sets an explicit, run-scoped kubeconfig path for every Kubernetes API operation performed by Client.ValidateState, including namespace, RBAC, ConfigMap, validator Job, and result operations. The file is reloaded for each validation run. Empty uses the shared default Kubernetes client and its standard KUBECONFIG, ~/.kube/config, then in-cluster discovery chain.
func WithValidationNamespace ¶
func WithValidationNamespace(namespace string) ValidateOption
WithValidationNamespace sets the Kubernetes namespace where validation Jobs run. Default: "aicr-validation".
func WithValidationNoCluster ¶
func WithValidationNoCluster(noCluster bool) ValidateOption
WithValidationNoCluster enables dry-run mode: no Kubernetes resources are created, all checks report as "skipped - no-cluster mode (test mode)". Constraints are still evaluated inline (they don't need cluster access). Use this for unit tests that exercise the facade surface without a live cluster.
func WithValidationNodeSelector ¶
func WithValidationNodeSelector(nodeSelector map[string]string) ValidateOption
WithValidationNodeSelector passes a node selector through to the validation workload pods. Use when GPU nodes carry non-standard labels and the platform-default selector wouldn't match. Does NOT affect the orchestrator Job itself.
The input is defensively copied; without this, a caller mutating the map after handing off would race with the validator's map iteration (potential "concurrent map iteration and map write" panic in serializeNodeSelector).
func WithValidationPhases ¶
func WithValidationPhases(phases ...Phase) ValidateOption
WithValidationPhases restricts the run to the named phases, in the order given. Valid values are PhaseDeployment, PhasePerformance, and PhaseConformance. When omitted (or called with no phases), all phases run in their canonical order — the default behavior. ValidateState rejects any unrecognized phase value with ErrCodeInvalidRequest before touching the cluster, so a typo cannot silently produce an empty run.
The input is defensively copied so a caller mutating the slice after this returns won't race with ValidateState reading it.
func WithValidationRunID ¶
func WithValidationRunID(runID string) ValidateOption
WithValidationRunID overrides the auto-generated identifier shared across the Jobs and resources produced by a single validation run. Use this to make repeated runs in the same namespace distinguishable (e.g., a controller's reconcile-key suffix).
func WithValidationTimeout ¶
func WithValidationTimeout(d time.Duration) ValidateOption
WithValidationTimeout opts into a facade-level deadline for the ValidateState run. By default (option unset) ValidateState wraps the caller's context with defaults.ValidationOperationTimeout (75m), which suits controllers that pass an unbounded context. Pass a positive duration to set an explicit cap, or 0 to impose NO facade cap — the run then proceeds under the caller's context unchanged so per-validator timeouts (e.g. the 65m inference-perf check) govern. The CLI validate command passes 0 so an all-phase run isn't cut short by a fixed cap.
func WithValidationTolerations ¶
func WithValidationTolerations(tolerations []corev1.Toleration) ValidateOption
WithValidationTolerations passes tolerations through to the validation workload pods (e.g. NCCL benchmark pods). Does NOT affect the orchestrator Job itself, which runs with snapshotter.DefaultTolerations.
The input is defensively copied; mutation after this returns won't race with downstream serialization on a validator goroutine.