Summary
RecipeMetadataSpec.Merge in pkg/recipe/metadata.go:459-482 treats each validation phase (readiness, deployment, performance, conformance) as a per-phase pointer replace. If a leaf overlay defines deployment:, the leaf's block fully overwrites the inherited one — including checks: and constraints:. This is the only list-shaped field in the spec with replace semantics; every other field unions.
Proposal: switch validation.{phase}.checks (and likely .constraints) to the same union-by-name semantics already used at the top level. The merge stays explicit about precedence; leaves only need to declare additions and per-name overrides, not repeat the inherited list.
Background
This footgun surfaced while closing #969. The fix added a baseline deployment.checks list to the 6 service-root overlays (aks, eks, gke-cos, kind, lke, oke) so descendants inherit it. The 9 existing leaf overlays that already define deployment: happen to repeat the full 4-check list verbatim, so they coexist with the inherited one by accident — but the replace semantics mean any future leaf that adds a deployment: block (to add a constraint, or one extra check) silently drops every inherited check unless the author remembers to re-state them.
The 6 service-root overlays were annotated with a YAML comment about this (see PR closing #969), but a comment is a weak guard. The merge semantics themselves should make accidental drops impossible.
Current semantics by field (for reference)
| Field |
Semantics |
constraints (metadata.go:420-434) |
Union by name; same-name overwrites. |
componentRefs (metadata.go:437-457) |
Union by name; same-name uses per-field merge via mergeComponentRef (overlay-wins-if-non-empty). |
mixins (metadata.go:488-498) |
Dedupe and append, preserving order. |
validation.{phase} (metadata.go:459-482) |
Per-phase pointer replace — outlier. |
Proposed semantics
For each phase pointer that is non-nil on the overlay:
- If the destination's phase is nil → clone the overlay phase (current behavior, unchanged).
- If both are non-nil → merge field-by-field:
Checks: deduplicated union, preserving order (overlay's additions appended after inherited entries).
Constraints: union by Name, overlay wins on same-name (mirroring the top-level constraint merge).
NodeSelection: unclear — proposal is overlay-wins-if-non-nil (full struct replace) since the struct is small and per-field semantics aren't obvious. Open for discussion.
This makes the validation merge a direct analogue of the existing top-level constraint and component merges.
Risks / migration
- No existing overlay relies on replace semantics for checks today (all 9 existing
deployment: leaves re-state the full inherited list verbatim). Switching to union merge produces identical resolved output for them.
- The
TestOverlayValidationPhaseFloor floor gate keeps working — it checks phase presence, not list contents.
- New test coverage required: a few cases in
pkg/recipe/metadata_test.go exercising the union/dedupe behavior, plus a constraint same-name override case.
Done when
RecipeMetadataSpec.Merge unions validation.{phase}.checks and validation.{phase}.constraints per the proposal.
- The replace-semantics warning comments in
recipes/overlays/{aks,eks,gke-cos,kind,lke,oke}.yaml are removed.
- New unit tests cover union, dedupe, and per-name override.
- Documentation in
docs/contributor/data.md or the equivalent recipe-development page describes the merge semantics for validation phases alongside constraints and components.
Related
Summary
RecipeMetadataSpec.Mergeinpkg/recipe/metadata.go:459-482treats each validation phase (readiness,deployment,performance,conformance) as a per-phase pointer replace. If a leaf overlay definesdeployment:, the leaf's block fully overwrites the inherited one — includingchecks:andconstraints:. This is the only list-shaped field in the spec with replace semantics; every other field unions.Proposal: switch
validation.{phase}.checks(and likely.constraints) to the same union-by-name semantics already used at the top level. The merge stays explicit about precedence; leaves only need to declare additions and per-name overrides, not repeat the inherited list.Background
This footgun surfaced while closing #969. The fix added a baseline
deployment.checkslist to the 6 service-root overlays (aks,eks,gke-cos,kind,lke,oke) so descendants inherit it. The 9 existing leaf overlays that already definedeployment:happen to repeat the full 4-check list verbatim, so they coexist with the inherited one by accident — but the replace semantics mean any future leaf that adds adeployment:block (to add a constraint, or one extra check) silently drops every inherited check unless the author remembers to re-state them.The 6 service-root overlays were annotated with a YAML comment about this (see PR closing #969), but a comment is a weak guard. The merge semantics themselves should make accidental drops impossible.
Current semantics by field (for reference)
constraints(metadata.go:420-434)componentRefs(metadata.go:437-457)mergeComponentRef(overlay-wins-if-non-empty).mixins(metadata.go:488-498)validation.{phase}(metadata.go:459-482)Proposed semantics
For each phase pointer that is non-nil on the overlay:
Checks: deduplicated union, preserving order (overlay's additions appended after inherited entries).Constraints: union byName, overlay wins on same-name (mirroring the top-level constraint merge).NodeSelection: unclear — proposal is overlay-wins-if-non-nil (full struct replace) since the struct is small and per-field semantics aren't obvious. Open for discussion.This makes the validation merge a direct analogue of the existing top-level constraint and component merges.
Risks / migration
deployment:leaves re-state the full inherited list verbatim). Switching to union merge produces identical resolved output for them.TestOverlayValidationPhaseFloorfloor gate keeps working — it checks phase presence, not list contents.pkg/recipe/metadata_test.goexercising the union/dedupe behavior, plus a constraint same-name override case.Done when
RecipeMetadataSpec.Mergeunionsvalidation.{phase}.checksandvalidation.{phase}.constraintsper the proposal.recipes/overlays/{aks,eks,gke-cos,kind,lke,oke}.yamlare removed.docs/contributor/data.mdor the equivalent recipe-development page describes the merge semantics for validation phases alongside constraints and components.Related