Skip to content

[Epic]: Library-readiness hardening for in-process Go consumers #983

Description

@mchmarny

Summary

AICR's pkg/recipe package is currently designed around a single-tenant process model: one CLI invocation or one API server instance, with a process-global DataProvider and sync.Once-cached metadata store and component registry. That works for our shipping consumers but blocks AICR from being a safe, reusable Go library for in-process consumers that need to maintain more than one recipe source at a time — operators, controllers, multi-tenant automation, in-process testing harnesses, and tooling like the BOM generator (see #966).

This is a tracking issue for the related hardening work. Each child issue is independently reviewable and mergeable; the order below reflects landing order, not file order.

Motivation

Two concrete pressures expose the gap:

  1. chore(recipes): make resolved recipes the single source of truth for chart versions #966's BOM-per-leaf rewrite needs to resolve "effective values" (base → values file → overrides) for every enumerated leaf. The merge logic exists but is package-private, and the bundler — the only public consumer — writes to disk. The BOM rewrite either reimplements values merging (drift hazard) or duplicates bundler internals (coupling hazard) unless we promote the resolution primitives to public API.

  2. In-process consumers of AICR cannot safely construct two Builders in the same process. The global DataProvider plus the sync.Once-cached metadata store and component registry mean that whichever Builder's source populates the cache first wins for every concurrent Builder, regardless of what WithDataProvider-style option they were given (today, there is no such option). The hazard is dormant only because no in-tree consumer triggers it.

The underlying patterns are already called out as anti-patterns in .claude/CLAUDE.md:

sync.Once caching state that depends on a settable global (e.g., a registry tied to a DataProvider) … key the cache by a generation counter the setter increments; recompute on miss so late-bound configuration takes effect.

Deep-copy helper that recurses into maps but copies []any by reference — slice aliasing leaks mutations across overlay merges.

This work makes those guidelines true of the recipe package itself.

Scope

Four child issues, in landing order:

Dependency graph

#984 (correctness fix)  ──┐
                          ├──▶ no hard order between these three
#985 (public helpers)   ──┤    (recommend landing in listed order)
                          │
#986 (provider isolation) ┘
        │
        ▼
#987 (deprecation)        ── hard dep on #986

Soft sequencing rationale:

Out of scope

  • Publishing a top-level github.com/NVIDIA/aicr facade package. The fixes here are necessary preconditions for any such facade, but the facade itself is a separate design conversation with its own API-stability commitments.
  • OCI recipe sources, pinned recipe references, and other forward-looking source kinds — not part of the hardening surface.
  • CLI / API server behavior changes. All four children preserve the current package-global path as a back-compat shim until feat(recipe): deprecate SetDataProvider / GetDataProvider globals #987's deprecation step, and feat(recipe): deprecate SetDataProvider / GetDataProvider globals #987 itself only marks deprecated — it does not remove or migrate call sites.

Acceptance

Metadata

Metadata

Assignees

No one assigned

    Type

    Fields

    No fields configured for Epic.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions