Skip to content

feat(recipe): merge external validator catalog with embedded when provided through DataProvider#588

Merged
mchmarny merged 4 commits intoNVIDIA:mainfrom
njhensley:feat/external-catalog-merge
Apr 16, 2026
Merged

feat(recipe): merge external validator catalog with embedded when provided through DataProvider#588
mchmarny merged 4 commits intoNVIDIA:mainfrom
njhensley:feat/external-catalog-merge

Conversation

@njhensley
Copy link
Copy Markdown
Contributor

@njhensley njhensley commented Apr 15, 2026

Summary

Route the validator catalog through the DataProvider so the --data flag can overlay external validators/catalog.yaml with merge-by-name semantics, matching how registry.yaml already works.

Motivation / Context

catalog.Load() read exclusively from the embedded filesystem (recipes.FS), bypassing the DataProvider interface that --data already uses for registry, overlays, and component values. Teams needing custom or cluster-specific validation checks (e.g., ComputeDomain-aware DRA tests for GB200 clusters) had to fork and rebuild the CLI.

Fixes: #528
Related: N/A

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update
  • Refactoring (no functional changes)
  • Build/CI/tooling

Component(s) Affected

  • CLI (cmd/aicr, pkg/cli)
  • API server (cmd/aicrd, pkg/api, pkg/server)
  • Recipe engine / data (pkg/recipe)
  • Bundlers (pkg/bundler, pkg/component/*)
  • Collectors / snapshotter (pkg/collector, pkg/snapshotter)
  • Validator (pkg/validator)
  • Core libraries (pkg/errors, pkg/k8s)
  • Docs/examples (docs/, examples/)
  • Other: ____________

Implementation Notes

Merge semantics (same as registry.yaml):

  • External validators override embedded by name
  • New external validators are appended
  • Embedded order is preserved
  • External validators/catalog.yaml is optional (unlike registry.yaml which is required)

Key design decisions:

  1. Merge in LayeredDataProvider, not in catalog package. The provider handles merge transparently — catalog.Load() just reads from the provider and gets pre-merged bytes. This matches how registry merging works and keeps the DataProvider.ReadFile() contract clean.

  2. catalogForMerge proxy type with map[string]interface{}. The provider (pkg/recipe) cannot import pkg/validator/catalog because catalog already imports pkg/recipe for GetDataProvider() — that would be circular. The proxy type preserves all YAML fields through the round-trip. Validation of the merged result still happens in catalog.Parse()validate().

  3. Generic mergeByName[T any] and mergeEmbeddedAndExternal[T any]. Both registry and catalog merge shared identical algorithms. These were extracted into generic helpers to eliminate the duplication. The fileReader interface keeps mergeEmbeddedAndExternal decoupled from the full DataProvider.

Backward compatibility: When no --data flag is set, GetDataProvider() returns the default EmbeddedDataProvider, which reads from recipes.FS — identical behavior to before.

Testing

make qualify

8 new tests in pkg/recipe/provider_test.go:

  • MergesCatalog — new external validators appended (16 + 1 = 17)
  • CatalogOverrideByName — same-name replacement (16 + 1 same-name = 16, image/desc changed)
  • CatalogMergePreservesOrder — embedded order preserved, new entries at end
  • CatalogNoCatalogInExternal — fallback to embedded when no external catalog
  • CatalogSourceMergedSource() reports "merged" when external exists
  • CachedCatalogsync.Once returns identical bytes on repeated calls
  • InvalidExternalCatalog — malformed YAML returns error, no panic
  • CatalogMergePreservesOrder — override + append in one operation

1 new integration test in pkg/validator/catalog/catalog_test.go:

  • TestLoadWithExternalCatalog — end-to-end: sets LayeredDataProvider globally, calls catalog.Load(), verifies typed ValidatorEntry structs from both embedded and external sources

Coverage delta:

  • pkg/recipe: 90.3% → 90.7% (+0.4%)
  • pkg/validator/catalog: 80.4% → 80.4% (no change)

Risk Assessment

  • Low — Isolated change, well-tested, easy to revert

Rollout notes: No migration needed. The --data flag behavior is additive — existing users without validators/catalog.yaml in their external directory see no change. The catalog merge path is only entered when the external file exists.

Checklist

  • Tests pass locally (make test with -race)
  • Linter passes (make lint)
  • I did not skip/disable tests to make CI green
  • I added/updated tests for new functionality
  • I updated docs if user-facing behavior changed
  • Changes follow existing patterns in the codebase
  • Commits are cryptographically signed (git commit -S) — GPG signing info

Summary by CodeRabbit

  • New Features

    • Validator catalog entries from external directories now merge with embedded catalogs, with external entries taking precedence by name
    • Updated --data flag documentation to clarify that both registry components and validator catalog entries are merged when overlaying external data
  • Tests

    • Added comprehensive test coverage for validator catalog merging behavior with external data sources

njhensley and others added 2 commits April 15, 2026 16:28
…rge-by-name

Route catalog loading through the global DataProvider so the --data flag
enables external validator catalog overlays with the same merge-by-name
semantics used for the component registry. Extract a generic
mergeEmbeddedAndExternal[T] helper to deduplicate the shared
load-unmarshal-merge-marshal pipeline between registry and catalog merging.
Copy link
Copy Markdown
Contributor

@ArangoGutierrez ArangoGutierrez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work on this one. The design is clean:

  • mergeByName[T] generic avoids duplicating the merge-by-name logic between registry and catalog.
  • mergeEmbeddedAndExternal[T] extracts the load-unmarshal-merge-serialize pattern into a reusable function.
  • catalogForMerge using map[string]interface{} to avoid the circular dependency with pkg/validator/catalog is the right call.
  • sync.Once caching mirrors the existing registry pattern.
  • Test coverage is thorough: merge, override-by-name, fallback when no external catalog, source tracking, caching, invalid YAML, and order preservation.

The GPU workflow failures (training/inference) are unrelated to this change -- this PR only touches recipe provider and catalog code, and all core CI (unit tests, lint, E2E, security scan) pass.

One minor note: the Load() error message changed from "failed to read embedded catalog" to "failed to read catalog" which makes sense since it now reads from the layered provider, not just embedded.

LGTM. Welcome to the project.

@mchmarny mchmarny enabled auto-merge (squash) April 16, 2026 12:54
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 16, 2026

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

This change extends the layered data provider to merge external validators/catalog.yaml with embedded catalog content (external validators override by name and new ones are appended), updates the catalog loader to source from the global DataProvider instead of the embedded filesystem, and adds comprehensive test coverage for the new behavior.

Changes

Cohort / File(s) Summary
CLI Help Text
pkg/cli/root.go
Updated --data flag description to clarify that both "Registry components and validator catalog entries" are merged with embedded data (external takes precedence by name), replacing mention of registry components only.
Provider Catalog Merge Logic
pkg/recipe/provider.go
Implemented cached, lazily-computed merged validator catalog with new fields (mergedCatalogOnce, mergedCatalog, mergedCatalogErr), new getMergedCatalog() and mergeCatalogs() methods, catalog file name constant, and merge-by-name semantics. Refactored registry merging to use new generic mergeEmbeddedAndExternal[T] and mergeByName[T] helpers. Extended Source() to report merged provenance and updated ReadFile() to special-case validators/catalog.yaml with merge behavior.
Provider Tests
pkg/recipe/provider_test.go
Added test utilities (testExternalCatalogContent, setupCatalogTestDir) and comprehensive test suite (TestLayeredDataProvider_*) verifying merged catalog behavior: external/embedded content inclusion, name-based override semantics, fallback to embedded when external is absent, merged source reporting, caching, error handling for invalid YAML, and validator order preservation.
Catalog Loader
pkg/validator/catalog/catalog.go
Updated Load() to read validators/catalog.yaml from the global DataProvider instead of embedded filesystem, adjusted error messaging from "embedded catalog" to "catalog", expanded doc comment to describe merge-by-name semantics, and changed import from recipes to pkg/recipe.
Catalog Loader Tests
pkg/validator/catalog/catalog_test.go
Added TestLoadWithExternalCatalog verifying that external and embedded validators are merged correctly, external validators override embedded ones by name, and the combined catalog is loaded via the layered DataProvider.

Sequence Diagrams

sequenceDiagram
    participant CLI as CLI (--data flag)
    participant Provider as DataProvider
    participant EmbedFS as Embedded FS
    participant ExtFS as External FS
    participant Catalog as catalog.Load()

    rect rgba(100, 150, 200, 0.5)
    Note over CLI,Catalog: Before: Direct Embedded Read
    Catalog->>EmbedFS: ReadFile(validators/catalog.yaml)
    EmbedFS-->>Catalog: embedded catalog YAML
    Catalog-->>CLI: loaded catalog
    end

    rect rgba(150, 200, 100, 0.5)
    Note over CLI,Catalog: After: Layered Merge via DataProvider
    CLI->>Provider: ReadFile(validators/catalog.yaml)
    Provider->>ExtFS: check external catalog exists
    alt External catalog found
        Provider->>ExtFS: read external catalog
        Provider->>EmbedFS: read embedded catalog
        Provider->>Provider: merge by name (external overrides)
        Provider-->>Catalog: merged YAML (cached)
    else No external catalog
        Provider->>EmbedFS: read embedded catalog
        Provider-->>Catalog: embedded YAML
    end
    Catalog-->>CLI: loaded catalog
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 twitches whiskers with glee
External catalogs now hop on by,
No need to rebuild the CLI so high!
Validators merge with gentle grace—
Custom checks find their rightful place,
The data flag spreads wide and far,
While rabbits celebrate like a star! ⭐

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 76.47% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: enabling external validator catalog merging through the DataProvider when provided via the --data flag.
Linked Issues check ✅ Passed All objectives from issue #528 are implemented: catalog loading uses DataProvider, merge-by-name semantics applied with external overriding embedded by name, external entries validated, and external catalog is optional.
Out of Scope Changes check ✅ Passed All changes directly support the linked objective of enabling external validator catalog overlay via --data flag; no unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@mchmarny mchmarny merged commit 94fb041 into NVIDIA:main Apr 16, 2026
21 of 22 checks passed
@njhensley njhensley deleted the feat/external-catalog-merge branch April 16, 2026 15:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(validator): allow external catalog overlay via --data flag

3 participants