Skip to content

Resolver does not reject ComponentRefs with incoherent type/field combinations #1584

Description

@yuanchen8911

Problem

Recipe resolution does not validate that a resolved ComponentRef has a coherent deployment type/field combination. mergeComponentRef merges each field (Type, Version, Tag, Path, Source, Chart) independently, and ApplyRegistryDefaults only fills empty fields — so a recipe can resolve to an incoherent ref, e.g.:

  • Type: Helm that also carries a Kustomize Tag or Path
  • a Kustomize ref (or one with Tag/Path) that lacks the Path Kustomize requires
  • a Tag with no Source/Repository
  • a Kustomize ref that also declares post-manifests (ManifestFiles)
  • an unsupported/empty Type on an externally-supplied ref

Nothing rejects these at resolution — or at the load/adopt boundaries.

Impact

  • Deployers build a different type than the recipe declares (primary). The deployers do not trust the declared Type: Helm/Helmfile/ArgoCD drop it, and localformat.classify (pkg/bundler/deployer/localformat/writer.go) treats any ref carrying a Tag/Path as Kustomize. So an incoherent ref silently deploys as a different type than authored — and this runs on every aicr bundle (not just validate/attestation).
  • Signed attestation records the wrong metadata (second-order). BuildAutoBOM (pkg/evidence/attestation/bom.go) selects the pinned version/source by the ref's declared Type, so the CycloneDX attestation — a signed supply-chain artifact — advertises metadata that does not match what actually deploys.
  • Reachable via external recipes. The main exposure is externally-authored hydrated RecipeResults (aicr bundle/validate -r recipe.yaml, POST /v1/bundle), which never pass through the resolver. No in-tree recipe is incoherent today (zero Kustomize components in the registry).

Surfaced during the cross-review of #1580.

Fix (implemented in #1585)

A shared RecipeResult.PrepareAndValidate() (back-fill missing types → canonicalize → ValidateCoherence) invoked at every boundary that produces or consumes a RecipeResult, so no path is a bypass:

  • finalizeRecipeResult (criteria resolution), after applyRegistryDefaults populates Type;
  • LoadFromFileWithProvider (a hydrated recipe.yaml read from disk); and
  • the client adoptRecipe path (POST /v1/bundle decodes a RecipeResult).
  • the public DefaultBundler.Make (pkg/bundler) entry point — reachable without the CLI/server boundaries (its Quick Start calls Make directly); validates a provider-preserving defensive copy before generating.

Rules mirror the deployer requirements in pkg/bundler/deployer/localformat:

  • a Helm ref must not carry Tag/Path;
  • a Kustomize ref needs a Path;
  • a Kustomize Tag needs a Source;
  • a Kustomize ref must not also declare post-manifests (ManifestFiles) — PreManifestFiles remain supported;
  • an unsupported/empty Type fails closed.

Only enabled refs are checked (disabled stubs are excluded from the bundle); offenders are aggregated into one ErrCodeInvalidRequest.

Additional refinements from cross-review of #1585:

  • Type is matched case-insensitively. The resolver emits canonical Helm/Kustomize, but the REST wire format and hand-authored recipes may use lowercase; since the deployers classify by tag/path rather than this field, helm and Helm are treated the same, so the check does not newly reject the documented lowercase wire form.
  • OpenAPI contract updated (api/aicr/v1/server.yaml): the componentRefs schema previously omitted type/source/chart/tag/path; those are now documented (with the type enum and coherence-rule note) and the /v1/bundle example canonicalized to type: Helm, so spec-following clients can populate the fields the check validates. Includes a REST/adopt-boundary test.

Acceptance

  • Resolving, loading, or adopting a recipe with an incoherent (enabled) ComponentRef fails with a clear ErrCodeInvalidRequest naming the offending field combination. ✅
  • The resolver's rules match localformat's. ✅ (kept in lockstep by comment; a fully-shared predicate across the two different Component/ComponentRef types is a possible future refactor)
  • Every existing overlay/mixin still resolves — verified by the pkg/recipe suite and the bundler/client resolution consumers. ✅
  • Unit tests cover each rejected combination and the coherent cases. ✅

Metadata

Metadata

Assignees

No one assigned

    Type

    Fields

    No fields configured for Task.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions