fix(recipe): reject ComponentRefs with incoherent type/field combinations#1585
Conversation
Recipe evidence checkNo leaf overlays affected by this PR. This gate is warning-only and never blocks merge. |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis change adds coherence validation for Estimated code review effort: 3 (Moderate) | ~20 minutes Related issues: 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
c0ab56e to
c843ed0
Compare
There was a problem hiding this comment.
♻️ Duplicate comments (1)
pkg/recipe/componentref_coherence_test.go (1)
25-91: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winMissing coverage for
ComponentTypeDirect(or any third supported type).All negative test cases target Helm/Kustomize field misuse plus an empty/lowercase-string type. If
ComponentTypeDirectis a genuinely supported type (see the companion comment onpkg/recipe/metadata.go'scoherenceProblemdefault case), a coherent Direct-typed ref should be asserted aswantBad: falsehere — its current absence means this test suite would not have caught the potential regression.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@pkg/recipe/componentref_coherence_test.go` around lines 25 - 91, The coherence test for ComponentRef is missing coverage for the supported ComponentTypeDirect case, so add a coherent Direct-typed entry to TestComponentRefCoherenceProblem and assert wantBad is false. Update the table in TestComponentRefCoherenceProblem to include a valid ComponentRef using ComponentTypeDirect (or the third supported type referenced by coherenceProblem) so the test verifies the default-case behavior stays accepted.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Duplicate comments:
In `@pkg/recipe/componentref_coherence_test.go`:
- Around line 25-91: The coherence test for ComponentRef is missing coverage for
the supported ComponentTypeDirect case, so add a coherent Direct-typed entry to
TestComponentRefCoherenceProblem and assert wantBad is false. Update the table
in TestComponentRefCoherenceProblem to include a valid ComponentRef using
ComponentTypeDirect (or the third supported type referenced by coherenceProblem)
so the test verifies the default-case behavior stays accepted.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Enterprise
Run ID: 3639459e-1bd4-45ad-80b6-e977fd365709
📒 Files selected for processing (5)
pkg/client/v1/bundle.gopkg/recipe/componentref_coherence_test.gopkg/recipe/loader.gopkg/recipe/metadata.gopkg/recipe/metadata_store.go
c843ed0 to
fce65a5
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
api/aicr/v1/server.yaml (1)
943-984: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
fullRecipeexample wasn't updated alongsidesimpleRecipe; it would now fail the new coherence check if POSTed as-is.The
simpleRecipeexample at line 951 was updated to addtype: Helm, but the siblingfullRecipeexample'scomponentRefsentry (lines 980-984) still has notypefield. SinceadoptRecipenow callsValidateCoherence()on anyRecipeResultsubmitted toPOST /v1/bundle, and an enabled ref with an emptyTypefails closed withErrCodeInvalidRequest, this documented example would be rejected if used literally as a request body.📝 Proposed fix: add `type` to the fullRecipe example
componentRefs: - name: gpu-operator version: v25.3.3 + type: Helm order: 1 repository: https://helm.ngc.nvidia.com/nvidia🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@api/aicr/v1/server.yaml` around lines 943 - 984, The fullRecipe example in the server.yaml response body is missing the same componentRef type update that was added to simpleRecipe, so it will fail ValidateCoherence() when used as a POST /v1/bundle request. Update the fullRecipe section’s componentRefs entry in the recipe example to include the Helm type, matching the gpu-operator reference already shown in simpleRecipe, so adoptRecipe and its RecipeResult validation accept the documented payload.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@api/aicr/v1/server.yaml`:
- Around line 1464-1471: The schema for the deployment `type` field in
`server.yaml` is too strict because the `enum` only allows exact-case values
while the contract is case-insensitive. Update the `type` definition to stop
using a case-sensitive `enum` and instead document the accepted values in the
description, keeping it aligned with `coherenceProblem` in
`pkg/recipe/metadata.go` and the `simpleRecipe` examples/tests that accept
lowercase forms.
---
Outside diff comments:
In `@api/aicr/v1/server.yaml`:
- Around line 943-984: The fullRecipe example in the server.yaml response body
is missing the same componentRef type update that was added to simpleRecipe, so
it will fail ValidateCoherence() when used as a POST /v1/bundle request. Update
the fullRecipe section’s componentRefs entry in the recipe example to include
the Helm type, matching the gpu-operator reference already shown in
simpleRecipe, so adoptRecipe and its RecipeResult validation accept the
documented payload.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Enterprise
Run ID: 1fcc9d05-89af-4745-a3b9-33763e38e6b8
📒 Files selected for processing (7)
api/aicr/v1/server.yamlpkg/client/v1/aicr_internal_test.gopkg/client/v1/bundle.gopkg/recipe/componentref_coherence_test.gopkg/recipe/loader.gopkg/recipe/metadata.gopkg/recipe/metadata_store.go
fce65a5 to
fef6655
Compare
|
🌿 Preview your docs: https://nvidia-preview-fix-1584-componentref-coherence.docs.buildwithfern.com/aicr |
There was a problem hiding this comment.
♻️ Duplicate comments (1)
api/aicr/v1/server.yaml (1)
1472-1480: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winCase-sensitive
enumstill contradicts the case-insensitivetypecontract (unresolved from prior review).The description now explicitly says
typeis "Accepted case-insensitively and normalized to the canonical form," matchingcoherenceProblem()'sstrings.EqualFoldchecks and thesimpleRecipe/fullRecipeexamples. But theenum: [Helm, Kustomize]is still case-sensitive — a strict JSON Schema validator or generated client would reject a lowercase"helm"/"kustomize"payload that the server actually accepts.📝 Proposed fix: drop the case-sensitive enum
type: type: string - enum: [Helm, Kustomize] description: >- Deployment type. Accepted case-insensitively and normalized to the canonical form. A Helm ref must not carry `tag`/`path`; a Kustomize ref requires `path` (and, when it sets `tag`, a `source`). Incoherent or missing types on an enabled ref are rejected with 400.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@api/aicr/v1/server.yaml` around lines 1472 - 1480, The deployment type schema in the server spec still advertises a case-sensitive enum, which conflicts with the case-insensitive behavior in coherenceProblem() and the documented contract. Update the type field definition in server.yaml to remove the strict enum constraint (or replace it with a validation approach that accepts lowercase variants) so generated clients and schema validators do not reject values the API accepts; keep the description aligned with the Helm/Kustomize normalization behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Duplicate comments:
In `@api/aicr/v1/server.yaml`:
- Around line 1472-1480: The deployment type schema in the server spec still
advertises a case-sensitive enum, which conflicts with the case-insensitive
behavior in coherenceProblem() and the documented contract. Update the type
field definition in server.yaml to remove the strict enum constraint (or replace
it with a validation approach that accepts lowercase variants) so generated
clients and schema validators do not reject values the API accepts; keep the
description aligned with the Helm/Kustomize normalization behavior.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Enterprise
Run ID: a095ad76-8406-4c82-bb19-1237b2750b7a
📒 Files selected for processing (9)
api/aicr/v1/server.yamldocs/user/cli-reference.mdpkg/client/v1/aicr_internal_test.gopkg/client/v1/bundle.gopkg/recipe/componentref_coherence_test.gopkg/recipe/loader.gopkg/recipe/metadata.gopkg/recipe/metadata_store.gopkg/server/bundle_handler_test.go
fef6655 to
c008641
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@pkg/recipe/metadata.go`:
- Around line 297-333: The canonicalize-then-validate sequence is duplicated
across finalizeRecipeResult, LoadFromFileWithProvider, and adoptRecipe, which
makes it easy for a future boundary to call only one step or call
ValidateCoherence before CanonicalizeComponentTypes. Add a small helper that
performs both operations in order on RecipeResult, and update those call sites
to use it so the pairing is enforced consistently and no future edit can bypass
the canonicalization step.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Enterprise
Run ID: 553ce33b-8832-4cac-a717-0dea6f3873f7
📒 Files selected for processing (9)
api/aicr/v1/server.yamldocs/user/cli-reference.mdpkg/client/v1/aicr_internal_test.gopkg/client/v1/bundle.gopkg/recipe/componentref_coherence_test.gopkg/recipe/loader.gopkg/recipe/metadata.gopkg/recipe/metadata_store.gopkg/server/bundle_handler_test.go
c008641 to
de654ea
Compare
mchmarny
left a comment
There was a problem hiding this comment.
Solid fix — the core premise checks out: localformat.classify returns Kustomize whenever Tag/Path is set and ignores the declared Type, so an incoherent Helm+tag/path ref really would deploy as the wrong type and mismatch the attestation BOM. Enforcing at all three RecipeResult boundaries (resolve/load/adopt), case-insensitive canonicalization before defaulting, enabled-only + aggregated errors, and the OpenAPI/repository→source cleanup are all well done. Test coverage is thorough (per-ref table + REST/adopt + HTTP decode).
One medium point worth confirming before merge (inline on loader.go): the load/adopt boundaries don't run registry defaulting, so this effectively makes type required for hand-authored -r/POST /v1/bundle recipes — a type-less ref that resolves fine via criteria is rejected here. Likely intended given the OpenAPI required: [name, type] change, but flagging so it's a conscious call vs. back-filling empty type from the registry.
Non-blocking. CI checks were still in_progress at review time — confirm green before merge.
| // unchecked, and a lowercase type would deploy inconsistently. See #1584. | ||
| canonicalizeComponentTypes(rec.ComponentRefs) | ||
| if err := rec.ValidateCoherence(); err != nil { | ||
| return nil, err |
There was a problem hiding this comment.
This gate makes type effectively required on every enabled ref at load (same in adoptRecipe) — which is broader than rejecting incoherent refs. Unlike finalizeRecipeResult, this boundary never runs applyRegistryDefaults, so a hand-authored -r recipe.yaml with - name: gpu-operator (no type/tag/path) — today classified as UpstreamHelm by localformat.classify and bundled fine — now hits coherenceProblem's default case and is rejected as "unsupported type". The same refs resolved via criteria get Type: Helm back-filled from the registry (ApplyRegistryDefaults), so identical content is valid on one path and rejected on another.
If requiring type on hand-authored recipes is intended, this is fine — OpenAPI now marks it required and the docs example adds it. If not, consider back-filling empty type via the bound DataProvider's registry defaults here (and in adoptRecipe) before ValidateCoherence, so the gate only fails on genuine incoherence (Helm + tag/path) and preserves back-compat for type-less hand-authored recipes.
There was a problem hiding this comment.
Good catch — you're right, and it was breaking E2E (a type-less gpu-operator ref rejected on the -r load path). Fixed in 80cd94e by back-filling an empty type from the registry at the load and adopt boundaries (new RecipeResult.BackfillComponentTypes, mirroring ApplyRegistryDefaults on the resolve path) before ValidateCoherence. So a type-less registry ref now resolves the same on all three paths, and the gate only fails on genuine incoherence (Helm + tag/path) or a type-less non-registry component. Kept type: required in the OpenAPI schema as the documented contract.
e5af077 to
e30b951
Compare
39ce101 to
40426e4
Compare
40426e4 to
29fe635
Compare
…ions (NVIDIA#1584) Recipe resolution merged each ComponentRef field independently and applied registry defaults without checking the resulting deployment-shape fields were coherent, so a recipe could carry e.g. Type: Helm that also sets a Kustomize tag/path. The deployers do not trust the declared Type — Helm/Helmfile/ArgoCD drop it and localformat classifies any ref with a tag/path as Kustomize — so such a ref silently deploys as a different type than authored, and the signed attestation BOM (which reads the declared Type) records metadata that does not match what deploys. Validate coherence at every boundary that produces a RecipeResult, via a shared RecipeResult.ValidateCoherence(): - finalizeRecipeResult (criteria resolution), after registry defaults; - LoadFromFileWithProvider (a hydrated recipe.yaml read from disk); and - the client adoptRecipe path (POST /v1/bundle decodes a RecipeResult). The load and adopt boundaries are the primary external surface — a hand-authored or decoded hydrated recipe never passes through the resolver. 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 tag is only meaningful with a source; a Kustomize ref must not also declare post-manifests (manifestFiles) — pre-manifests remain supported. An unsupported Type fails closed. Only enabled refs are checked; disabled stubs are skipped. Offenders are aggregated into one ErrCodeInvalidRequest. Types are matched case-insensitively AND canonicalized to the exact ComponentType constant at each boundary (canonicalizeComponentTypes), so a lowercase "helm"/"kustomize" from the REST wire form or a hand-authored recipe is normalized before registry defaulting (exact-type switch) and before the result reaches the deployers (Flux rejects a lowercase "helm"; ArgoCD-Helm mis-handles a lowercase "kustomize") — no downstream inconsistency. Documents the componentRefs wire shape in the OpenAPI spec (api/aicr/v1/server.yaml): adds type (enum Helm/Kustomize), source, chart, tag, path; marks name/type required; drops the non-existent "repository" field; fixes the examples to use source/chart and a canonical type. Updates the hydrated-recipe example in docs/user/cli-reference.md to include type. No in-tree recipe trips the check (zero Kustomize components in the registry today); the full pkg/recipe suite and the bundler/client resolution consumers all still resolve cleanly. Adds table-driven tests for the per-ref predicate (each rejected + coherent combination, post/pre manifests, empty/unknown/lowercase type), the aggregating validator, a REST/adopt- boundary test (adoptRecipe rejects an incoherent ref and canonicalizes a lowercase-typed one), and an HTTP decode-to-bundle test (POST /v1/bundle with an incoherent ref returns 400). Back-fill missing types from the registry at the load and adopt boundaries (RecipeResult.BackfillComponentTypes) before validating. Those boundaries do not run ApplyRegistryDefaults, so a type-less ref for a registry component — valid before this change, since the deployers derive the type from the ref's fields — was wrongly rejected as "unsupported type" while the same ref resolved via criteria got its type back-filled. Registry components now resolve consistently on all paths; only genuinely incoherent refs (and type-less non-registry components) fail closed. Boundary hardening (cross-review): - adoptRecipe now registers as an in-flight cache user (inflight.Add under the read lock, defer Done) because PrepareAndValidate reads the component registry; without it a concurrent Close could evict the cache and adoption would repopulate it and return a result owned by a closed Client. - BackfillComponentTypes pre-scans for missing types (skipping the registry entirely when none need back-filling) and propagates a registry load/parse/timeout failure as ErrCodeInternal through PrepareAndValidate, instead of swallowing it into a misleading non-retryable unsupported-type rejection. - OpenAPI: document that a missing type on a known registry component is back-filled (unknown component still 400). - Adds a deterministic Close/adopt drain test and a registry-error propagation test. The back-fill pre-scan and loop also skip disabled refs (Type=="" && IsEnabled): ValidateCoherence skips disabled refs, so a type-less disabled legacy stub must not force a registry load that could fail an otherwise-usable recipe on a registry error. Covered by a failing-provider test. Also guard the public pkg/bundler entry point: DefaultBundler.Make now validates a provider-preserving defensive copy of the RecipeResult before generating, so a direct bundler caller (a documented public API whose Quick Start calls Make directly) cannot bypass the coherence gate the CLI/server boundaries apply. Adds a direct-bundler rejection test, a back-fill-loop disabled-skip test, and corrects the PrepareAndValidate/CanonicalizeComponentTypes and BackfillComponentTypes godoc (enabled-only back-fill; adoption calls the composite helper). OpenAPI accuracy (cross-review): the componentRefs item schema requires only name, not type — the server back-fills a missing type for a known registry component, so requiring type would make a spec-strict client reject a payload the server accepts. Also drop the phantom per-ref order field from the schema and examples: ComponentRef has no order member (ordering is the top-level deploymentOrder), so a submitted order is silently discarded. Signed-off-by: Yuan Chen <yuanchen97@gmail.com>
29fe635 to
dbab371
Compare
…oc (NVIDIA#1588) Follow-ups surfaced by the cross-review of NVIDIA#1585. ComponentRef.Patches is carried through resolution (deep-copied, overlay- merged) but applied by NO deployer — localformat's Component has no patches field and every generator omits it — so a recipe that declares `patches:` silently produces an unpatched bundle. Reject any ref that declares patches in coherenceProblem (fail loud) rather than dropping them silently, and mark the field's godoc as unapplied. No recipe uses `patches` in-tree or in the internal recipes repo, so this only turns a latent silent-drop into a clear error; implementing patch application (or removing the field) is the alternative, tracked in NVIDIA#1588. Also correct the coherenceProblem godoc: it over-generalized that "the deployers do not trust the declared Type." That holds for the field-classifying deployers (localformat and the Helm/Helmfile/ArgoCD generators built on it), but the Flux generator is Helm-only and switches on Type (it already rejects a non-Helm ref with a clear error). Clarify this. Fixes: NVIDIA#1588 Related: NVIDIA#1585 Document the rejected field for authors: add patches to the OpenAPI componentRefs schema (marked unsupported/rejected) and a note in docs/integrator/recipe-development.md, and clarify the Go field comment that only ENABLED refs are rejected (disabled are skipped). Also finish the type-handling wording: the godoc and the Helm-ref error no longer over-claim that all deployers classify by tag/path — the field-classifying deployers do (building a mismatched Helm ref as Kustomize), while the Flux deployer switches on canonical Type and rejects a non-Helm ref outright. Signed-off-by: Yuan Chen <yuanchen97@gmail.com>
…oc (NVIDIA#1588) Follow-ups surfaced by the cross-review of NVIDIA#1585. ComponentRef.Patches is carried through resolution (deep-copied, overlay- merged) but applied by NO deployer — localformat's Component has no patches field and every generator omits it — so a recipe that declares `patches:` silently produces an unpatched bundle. Reject any ref that declares patches in coherenceProblem (fail loud) rather than dropping them silently, and mark the field's godoc as unapplied. No recipe uses `patches` in-tree or in the internal recipes repo, so this only turns a latent silent-drop into a clear error; implementing patch application (or removing the field) is the alternative, tracked in NVIDIA#1588. Also correct the coherenceProblem godoc: it over-generalized that "the deployers do not trust the declared Type." That holds for the field-classifying deployers (localformat and the Helm/Helmfile/ArgoCD generators built on it), but the Flux generator is Helm-only and switches on Type (it already rejects a non-Helm ref with a clear error). Clarify this. Fixes: NVIDIA#1588 Related: NVIDIA#1585 Document the rejected field for authors: add patches to the OpenAPI componentRefs schema (marked unsupported/rejected) and a note in docs/integrator/recipe-development.md, and clarify the Go field comment that only ENABLED refs are rejected (disabled are skipped). Also finish the type-handling wording: the godoc and the Helm-ref error no longer over-claim that all deployers classify by tag/path — the field-classifying deployers do (building a mismatched Helm ref as Kustomize), while the Flux deployer switches on canonical Type and rejects a non-Helm ref outright. Review nits: correct the componentRefs schema description (order is the top-level deploymentOrder, not per-ref), add chart to the cli-reference Helm example for parity with the API examples, and make the blocking test DataProvider honor context cancellation. Signed-off-by: Yuan Chen <yuanchen97@gmail.com>
…oc (NVIDIA#1588) Follow-ups surfaced by the cross-review of NVIDIA#1585. ComponentRef.Patches is carried through resolution (deep-copied, overlay- merged) but applied by NO deployer — localformat's Component has no patches field and every generator omits it — so a recipe that declares `patches:` silently produces an unpatched bundle. Reject any ref that declares patches in coherenceProblem (fail loud) rather than dropping them silently, and mark the field's godoc as unapplied. No recipe uses `patches` in-tree or in the internal recipes repo, so this only turns a latent silent-drop into a clear error; implementing patch application (or removing the field) is the alternative, tracked in NVIDIA#1588. Also correct the coherenceProblem godoc: it over-generalized that "the deployers do not trust the declared Type." That holds for the field-classifying deployers (localformat and the Helm/Helmfile/ArgoCD generators built on it), but the Flux generator is Helm-only and switches on Type (it already rejects a non-Helm ref with a clear error). Clarify this. Fixes: NVIDIA#1588 Related: NVIDIA#1585 Document the rejected field for authors: add patches to the OpenAPI componentRefs schema (marked unsupported/rejected) and a note in docs/integrator/recipe-development.md, and clarify the Go field comment that only ENABLED refs are rejected (disabled are skipped). Also finish the type-handling wording: the godoc and the Helm-ref error no longer over-claim that all deployers classify by tag/path — the field-classifying deployers do (building a mismatched Helm ref as Kustomize), while the Flux deployer switches on canonical Type and rejects a non-Helm ref outright. Review nits: correct the componentRefs schema description (order is the top-level deploymentOrder, not per-ref), add chart to the cli-reference Helm example for parity with the API examples, and make the blocking test DataProvider honor context cancellation. Declare the top-level deploymentOrder property in the RecipeResponse schema (array of component names) and include it in the /v1/bundle example, so a GET->POST round trip through a generated client preserves deployment sequencing instead of dropping it. Correct the remaining Flux wording: a Helm ref carrying tag/path builds as Kustomize under the field-classifying deployers but as Helm under Flux (which switches on the declared Type) — it is not rejected by Flux; only an explicitly non-Helm type is. Fixed in the godoc bullet, the Helm-ref error, and the case-insensitive/default-branch comments. Signed-off-by: Yuan Chen <yuanchen97@gmail.com>
…oc (NVIDIA#1588) Follow-ups surfaced by the cross-review of NVIDIA#1585. ComponentRef.Patches is carried through resolution (deep-copied, overlay- merged) but applied by NO deployer — localformat's Component has no patches field and every generator omits it — so a recipe that declares `patches:` silently produces an unpatched bundle. Reject any ref that declares patches in coherenceProblem (fail loud) rather than dropping them silently, and mark the field's godoc as unapplied. No recipe uses `patches` in-tree or in the internal recipes repo, so this only turns a latent silent-drop into a clear error; implementing patch application (or removing the field) is the alternative, tracked in NVIDIA#1588. Also correct the coherenceProblem godoc: it over-generalized that "the deployers do not trust the declared Type." That holds for the field-classifying deployers (localformat and the Helm/Helmfile/ArgoCD generators built on it), but the Flux generator is Helm-only and switches on Type (it already rejects a non-Helm ref with a clear error). Clarify this. Fixes: NVIDIA#1588 Related: NVIDIA#1585 Document the rejected field for authors: add patches to the OpenAPI componentRefs schema (marked unsupported/rejected) and a note in docs/integrator/recipe-development.md, and clarify the Go field comment that only ENABLED refs are rejected (disabled are skipped). Also finish the type-handling wording: the godoc and the Helm-ref error no longer over-claim that all deployers classify by tag/path — the field-classifying deployers do (building a mismatched Helm ref as Kustomize), while the Flux deployer switches on canonical Type and rejects a non-Helm ref outright. Review nits: correct the componentRefs schema description (order is the top-level deploymentOrder, not per-ref), add chart to the cli-reference Helm example for parity with the API examples, and make the blocking test DataProvider honor context cancellation. Declare the top-level deploymentOrder property in the RecipeResponse schema (array of component names) and include it in the /v1/bundle example, so a GET->POST round trip through a generated client preserves deployment sequencing instead of dropping it. Correct the remaining Flux wording: a Helm ref carrying tag/path builds as Kustomize under the field-classifying deployers but as Helm under Flux (which switches on the declared Type) — it is not rejected by Flux; only an explicitly non-Helm type is. Fixed in the godoc bullet, the Helm-ref error, and the case-insensitive/default-branch comments. Signed-off-by: Yuan Chen <yuanchen97@gmail.com>
…oc (NVIDIA#1588) Follow-ups surfaced by the cross-review of NVIDIA#1585. ComponentRef.Patches is carried through resolution (deep-copied, overlay- merged) but applied by NO deployer — localformat's Component has no patches field and every generator omits it — so a recipe that declares `patches:` silently produces an unpatched bundle. Reject any ref that declares patches in coherenceProblem (fail loud) rather than dropping them silently, and mark the field's godoc as unapplied. No recipe uses `patches` in-tree or in the internal recipes repo, so this only turns a latent silent-drop into a clear error; implementing patch application (or removing the field) is the alternative, tracked in NVIDIA#1588. Also correct the coherenceProblem godoc: it over-generalized that "the deployers do not trust the declared Type." That holds for the field-classifying deployers (localformat and the Helm/Helmfile/ArgoCD generators built on it), but the Flux generator is Helm-only and switches on Type (it already rejects a non-Helm ref with a clear error). Clarify this. Fixes: NVIDIA#1588 Related: NVIDIA#1585 Document the rejected field for authors: add patches to the OpenAPI componentRefs schema (marked unsupported/rejected) and a note in docs/integrator/recipe-development.md, and clarify the Go field comment that only ENABLED refs are rejected (disabled are skipped). Also finish the type-handling wording: the godoc and the Helm-ref error no longer over-claim that all deployers classify by tag/path — the field-classifying deployers do (building a mismatched Helm ref as Kustomize), while the Flux deployer switches on canonical Type and rejects a non-Helm ref outright. Review nits: correct the componentRefs schema description (order is the top-level deploymentOrder, not per-ref), add chart to the cli-reference Helm example for parity with the API examples, and make the blocking test DataProvider honor context cancellation. Declare the top-level deploymentOrder property in the RecipeResponse schema (array of component names) and include it in the /v1/bundle example, so a GET->POST round trip through a generated client preserves deployment sequencing instead of dropping it. Correct the remaining Flux wording: a Helm ref carrying tag/path builds as Kustomize under the field-classifying deployers but as Helm under Flux (which switches on the declared Type) — it is not rejected by Flux; only an explicitly non-Helm type is. Fixed in the godoc bullet, the Helm-ref error, and the case-insensitive/default-branch comments. Signed-off-by: Yuan Chen <yuanchen97@gmail.com>
…oc (NVIDIA#1588) Follow-ups surfaced by the cross-review of NVIDIA#1585. ComponentRef.Patches is carried through resolution (deep-copied, overlay- merged) but applied by NO deployer — localformat's Component has no patches field and every generator omits it — so a recipe that declares `patches:` silently produces an unpatched bundle. Reject any ref that declares patches in coherenceProblem (fail loud) rather than dropping them silently, and mark the field's godoc as unapplied. No recipe uses `patches` in-tree or in the internal recipes repo, so this only turns a latent silent-drop into a clear error; implementing patch application (or removing the field) is the alternative, tracked in NVIDIA#1588. Also correct the coherenceProblem godoc: it over-generalized that "the deployers do not trust the declared Type." That holds for the field-classifying deployers (localformat and the Helm/Helmfile/ArgoCD generators built on it), but the Flux generator is Helm-only and switches on Type (it already rejects a non-Helm ref with a clear error). Clarify this. Fixes: NVIDIA#1588 Related: NVIDIA#1585 Document the rejected field for authors: add patches to the OpenAPI componentRefs schema (marked unsupported/rejected) and a note in docs/integrator/recipe-development.md, and clarify the Go field comment that only ENABLED refs are rejected (disabled are skipped). Also finish the type-handling wording: the godoc and the Helm-ref error no longer over-claim that all deployers classify by tag/path — the field-classifying deployers do (building a mismatched Helm ref as Kustomize), while the Flux deployer switches on canonical Type and rejects a non-Helm ref outright. Review nits: correct the componentRefs schema description (order is the top-level deploymentOrder, not per-ref), add chart to the cli-reference Helm example for parity with the API examples, and make the blocking test DataProvider honor context cancellation. Declare the top-level deploymentOrder property in the RecipeResponse schema (array of component names) and include it in the /v1/bundle example, so a GET->POST round trip through a generated client preserves deployment sequencing instead of dropping it. Correct the remaining Flux wording: a Helm ref carrying tag/path builds as Kustomize under the field-classifying deployers but as Helm under Flux (which switches on the declared Type) — it is not rejected by Flux; only an explicitly non-Helm type is. Fixed in the godoc bullet, the Helm-ref error, and the case-insensitive/default-branch comments. Signed-off-by: Yuan Chen <yuanchen97@gmail.com>
Summary
Reject
ComponentRefs whose deployment-shape fields are incoherent (e.g.Type: Helmthat also carries a Kustomizetag/path) at every boundary that produces aRecipeResult— criteria resolution, file load, and external adoption.Motivation / Context
Recipe resolution merges each
ComponentReffield (Type,Version,Tag,Path,Source,Chart) independently and applies registry defaults without checking the result is coherent. The deployers do not trust the declaredType— Helm/Helmfile/ArgoCD drop it, andlocalformat.classifytreats any ref carrying atag/pathas Kustomize — so an incoherent ref silently deploys as a different type than authored, and the signed attestation BOM (BuildAutoBOM, which reads the declaredType) records metadata that does not match what deploys. The primary exposure is externally-authored hydrated recipes (aicr bundle/validate -r recipe.yaml,POST /v1/bundle), which never pass through the resolver.Surfaced during the cross-review of #1580.
Fixes: #1584
Related: #1580
Type of Change
Component(s) Affected
pkg/recipe)pkg/client/v1(adopt boundary)pkg/bundler) — publicDefaultBundler.Makeboundaryapi/aicr/v1/server.yaml)docs/user/cli-reference.md)Implementation Notes
RecipeResult.ValidateCoherence()(pkg/recipe/metadata.go) is invoked at all four boundaries that produce or consume aRecipeResult, so no path is a bypass:finalizeRecipeResult(criteria resolution), afterapplyRegistryDefaultspopulatesType;LoadFromFileWithProvider(pkg/recipe/loader.go) — a hydratedrecipe.yamlread from disk (coversaicr bundle/validate -r); andadoptRecipepath (pkg/client/v1/bundle.go) — a decodedRecipeResultfromPOST /v1/bundle.ComponentRef.coherenceProblem()encodes the rules, mirroringpkg/bundler/deployer/localformat:tag/path;path;tagneeds asource;manifestFiles) —preManifestFilesremain supported;Typefails closed.helm/kustomize(the REST wire form / hand-authored recipes) is accepted and normalized to the exactComponentTypeconstant at each boundary before registry defaulting and before the result reaches the deployers — so it is not newly rejected AND does not deploy inconsistently (Flux rejects a lowercasehelm; ArgoCD-Helm mis-handles a lowercasekustomize; the registry-defaults switch is exact-match).ErrCodeInvalidRequest; the receiver is nil-safe.api/aicr/v1/server.yaml): thecomponentRefsschema previously omittedtype/source/chart/tag/path— added them (with thetypeenum[Helm, Kustomize]and a coherence-rule note), require onlyname(typeis back-filled from the registry for known components, so requiring it in the schema would reject payloads the server accepts), and dropped the non-existentrepositoryand phantomorderfields (the decoded Go field issource; per-reforderis ignored — ordering is the top-leveldeploymentOrder). Fixed all examplecomponentRefsto usesource/chartand a canonicaltype. Also addedtypeto the hydrated-recipe example indocs/user/cli-reference.md.An earlier revision validated only at
finalizeRecipeResult; cross-review correctly flagged that the load/adopt boundaries — the actual external surface — bypassed it (and that the stricter type check would reject the documented lowercase wire form), so the check now runs at all four (resolve, load, adopt, and the public bundler) and is case-insensitive.Testing
No in-tree recipe trips the new check (zero Kustomize components in the registry today), confirmed by the full
pkg/recipesuite and the bundler/client resolution consumers still resolving cleanly. New tests cover the per-ref predicate (each rejected + coherent combination, post/pre manifests, empty/unknown/lowercase type), the aggregating validator (enabled-only, nil-safe, error code, names all offenders), and a REST/adopt-boundary test (adoptReciperejects an incoherent ref and canonicalizes a coherent lowercase-typed one), an HTTP decode-to-bundle test (POST /v1/bundlewith an incoherent ref returns 400), and a direct-bundler rejection test (DefaultBundler.Makerejects an incoherent ref without mutating the caller's recipe).Risk Assessment
DefaultBundler.Make). No in-tree recipe is incoherent (confirmed by the full suite), so no shipping recipe is affected; case-insensitive typing means the documented lowercase wire form still works; a genuinely incoherent external recipe that previously resolved will now fail closed with a clear message naming the offending ref — which is the intent.Rollout notes: Fail-closed by design; the error names each offender and field combination. Easy to revert (one predicate + one method + four call sites + the OpenAPI schema additions).
Checklist
make testwith-race)make lint)componentRefsschema + example)git commit -S)