Skip to content

feat: select validations by suite and capability - #560

Open
abegnoche wants to merge 34 commits into
NVIDIA:mainfrom
abegnoche:feat/test-structure
Open

feat: select validations by suite and capability#560
abegnoche wants to merge 34 commits into
NVIDIA:mainfrom
abegnoche:feat/test-structure

Conversation

@abegnoche

@abegnoche abegnoche commented Jul 27, 2026

Copy link
Copy Markdown
Member

What

Introduces the suite / capability model for selecting which validations an
ISV runs, replacing the previous per-check platforms: axis.

  • A check lives in exactly one suite. Platform suites (kubernetes, vm,
    bare_metal, slurm) declare tests.platform; plain suites (storage,
    iam, network, …) declare per-check requires: [...].
  • requires is any-match: a check runs when its list is empty (core, runs
    for everyone) or names the capability in play. AND across capabilities is
    deliberately inexpressible — the four capabilities are mutually exclusive
    execution environments, so a check cannot need two at once.
  • New grammar: isvctl test run --provider P --suite S [--capability C].
    --capability is single-valued for the same reason.
  • Catalog envelope moves to schemaVersion: 2, carrying suite / requires
    per entry and the platforms / suites vocabularies at the top level.
  • Runs now report the (suite, capability) pair rather than a single inferred
    test_target_type.

Docs: configuration.md and external-validation-guide.md gain the full model,
and the my-isv scaffold README shows the real invocations.

Depends on the service side

The catalog envelope and the run-reporting payload both require the
corresponding ISV Lab Service change (V18: catalog suite/requires, test-run
suite/capability). That must be deployed first — a v2 catalog uploaded to an
older service will lose the new fields.

Known deferrals

Consciously deferred, so reviewers do not have to guess:

  • ISVCTL_ENFORCE_UNIQUE_WIRING gates a global wiring-name uniqueness check
    that is intentionally inert. The check is kept, not deleted, so it can be
    switched on in the PR that actually enforces it without rediscovering it.
  • CORE_REQUIREMENT_CONTEXT = "core" is a CLI-local sentinel meaning "filter to
    core only", because resolve_entries(capability=None) already means "do not
    filter". It is translated back to None before upload. An explicit
    three-state requirement context in isvtest would remove the pun.
  • --dry-run renders its own run/skip decision chain rather than reporting
    resolve_entries' SkipReason, so it under-reports some terminal skips
    (UNRELEASED, EXCLUDED, STEP_NOT_CONFIGURED).

Testing

make test (1279 isvtest + isvctl + 113 script tests), make lint,
make demo-test end-to-end across all my-isv suites, and
scripts/validate_suite_wiring.py --check all pass. Every commit is DCO
signed off.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Run validation suites with capability-based execution gates (VM, bare metal, Kubernetes, Slurm) via requires, with consistent skip behavior and label include/exclude filtering.
    • isvctl dry-runs and reporting now generate/forward suite and capability context; test catalogs use suite/capability requirement metadata (schema v2).
    • Image-registry and storage/image teardown are handled with environment-appropriate setup/cleanup separation.
    • Makefile demo targets now operate on suites (not domains) with capability-scoped demos.
  • Documentation
    • Expanded guides for requires-based gating, suite vs platform behavior, dry-run planning, and updated demo/testing examples.

abegnoche and others added 30 commits July 22, 2026 16:09
Introduce declarable capability vocabulary, expand_capabilities() for
compute inheritance from vm/bare_metal, per-check requires filtering
with explicit capability_requirement skip reasons, and validation for
requires values on plain-suite checks.

Signed-off-by: Alexandre Begnoche <abegnoche@nvidia.com>
Reject tests.module, tests.platform=compute, requires on platform suites,
and legacy per-check platforms fields. Rewrite validate_suite_wiring for
plain vs platform suite rules and globally unique wiring names.

Signed-off-by: Alexandre Begnoche <abegnoche@nvidia.com>
Resolve platform or plain suite configs via unified --suite selection,
parse optional --capabilities filter context (rejecting compute), apply
capability filtering in orchestration with human-readable dry-run plans,
and skip setup steps when all bound checks are filtered out.

Signed-off-by: Alexandre Begnoche <abegnoche@nvidia.com>
Build catalog entries from canonical suite YAML with source, suite,
platform, and requires metadata. Bump schemaVersion to 2, replace
platforms with capabilities in the envelope, and remove the legacy
catalog_platforms registry module.

Signed-off-by: Alexandre Begnoche <abegnoche@nvidia.com>
Remove tests.platform from plain capability suites, add per-check requires
from requires-worksheet, move kubernetes storage checks into storage.yaml
with ensure_cluster wiring, add my-isv demo stub and AWS reference setup,
and drop provider platform keys from plain-suite command groups.

Signed-off-by: Alexandre Begnoche <abegnoche@nvidia.com>
Align merger, nico provider, and capacity config tests with plain-suite
capability resolution and catalog schema v2 entry shape.

Signed-off-by: Alexandre Begnoche <abegnoche@nvidia.com>
my-isv storage demo exercises compute-path checks only; pass
--capabilities vm so kubernetes requires checks are skipped with
explicit capability_requirement reasons.

Signed-off-by: Alexandre Begnoche <abegnoche@nvidia.com>
Emit declarable capabilities as `platforms` and plain suite names as
`suites` in catalog documents and upload payloads so backend catalog
ingestion matches the feat/test-structure contract.
Both flags now build and save the catalog locally without uploading,
using --dry-run as the primary name for consistency with other commands.

Signed-off-by: Alexandre Begnoche <abegnoche@nvidia.com>
(cherry picked from commit 1550697)
K8S23-04..07 gained the storage label in the suite YAML; the test-plan
coverage guardrail requires the doc to carry the same label union.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Alexandre Begnoche <abegnoche@nvidia.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Alexandre Begnoche <abegnoche@nvidia.com>
Signed-off-by: Alexandre Begnoche <abegnoche@nvidia.com>
Signed-off-by: Alexandre Begnoche <abegnoche@nvidia.com>
Signed-off-by: Alexandre Begnoche <abegnoche@nvidia.com>
Signed-off-by: Alexandre Begnoche <abegnoche@nvidia.com>
The four capabilities (kubernetes, slurm, vm, bare_metal) are mutually
exclusive execution environments — you run on one, never a combination —
so a comma-separated multi-value context never made sense.

- Rename the flag --capabilities -> --capability (single value); reject
  comma-separated input with an explanatory error.
- parse_capabilities -> parse_capability (returns str | None).
- requirements_satisfied / resolve_entries take one capability instead of
  a set: `not requires or capability in requires`.
- Thread a single capability through the orchestrator and dry-run.

`requires` (per-check) stays a list: any-match applicability across the
isolated contexts, not a request to combine them.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Alexandre Begnoche <abegnoche@nvidia.com>
A plain suite named after a declarable capability (vm/bare_metal/kubernetes/slurm) would collapse the merged frontend test-target list and cross-wire the backend's flat-selection re-split (selection ∩ catalog.platforms). Reject the collision at the upload chokepoint (catalog_document) and in the wiring lint (validate_suite_wiring.py), reusing DECLARABLE_CAPABILITIES, so the capability-vs-suite distinction stays recoverable by construction.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Alexandre Begnoche <abegnoche@nvidia.com>
test validate now rejects unknown/duplicate requires values via the Pydantic suite-shape validator, matching the run-time and wiring checks. validate_suite_wiring flags a requires naming a capability with no platform suite (no ISV can declare it, so the check is unreachable).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Alexandre Begnoche <abegnoche@nvidia.com>
Signed-off-by: Alexandre Begnoche <abegnoche@nvidia.com>
Signed-off-by: Alexandre Begnoche <abegnoche@nvidia.com>
Signed-off-by: Alexandre Begnoche <abegnoche@nvidia.com>
Signed-off-by: Alexandre Begnoche <abegnoche@nvidia.com>
Capability gating skips a step when it carries an explicit `requires:` or
when every validation bound to it is requirement-filtered. Teardown steps
have no bound validations, so inference cannot reach them: under the
core-by-default `--suite` path they kept running against fixtures that were
never created. Only storage carried explicit gates; three other suites had
the same hole.

- image-registry: teardown deleted both the core upload_image resources and
  the vm-only launch_instance ones, so it referenced a skipped step and was
  abandoned whole with "missing step reference" - leaking the image, disks
  and bucket. Split by owner into teardown_instance (requires: [vm]) and
  teardown_image (core); the scripts already guard each resource
  independently, so no script change was needed. Rebind the suite's
  teardown check to teardown_image, the half that always runs.
- observability: gate launch_host/teardown_host on [vm, bare_metal] so a
  core run no longer launches the reference metal host only to skip every
  check that needed it.
- security: gate topology_block_teardown/capacity_teardown on
  [vm, bare_metal], mirroring the gate inferred on the test-phase steps
  that allocate those resources.

StorageCapacityTelemetryCheck moves from core to [vm, bare_metal]. It
probes the volumes attached to a running instance via the same script and
--instance-id as its StoragePerformanceTelemetryCheck twin, which already
declared that gate; with no instance it reports "no volumes attached", so
it could never pass as a core check. Needs a catalog re-upload.

Add test_capability_step_gating.py: instead of three point tests it walks
every plain-suite provider config across every context and asserts no
surviving step reads a gated-off step's output without a `default(...)`, so
new suites inherit the guarantee.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Alexandre Begnoche <abegnoche@nvidia.com>
my-isv is the folder ISVs copy, so a guarantee that holds only in aws/
teaches the wrong lifecycle. Three divergences, all in the capability-gated
paths added on this branch.

- storage: setup_cluster carried no `requires: [kubernetes]` and had no
  teardown_cluster at all, so the scaffold demonstrated acquiring a cluster
  and never releasing it - the opposite of the setup_cluster/teardown_cluster
  contract aws/ has carried since 607c121. Add both, with
  STORAGE_SKIP_TEARDOWN mirroring AWS_SKIP_TEARDOWN, and a teardown_cluster.py
  stub in the house style. A no-op teardown is a valid answer when the stub
  reuses a long-lived cluster; the step still has to exist so a standalone
  storage run cannot leak a cluster it provisioned.
- kubernetes/slurm: no config existed, so `--suite kubernetes --provider
  my-isv` errored - and that is exactly what the frontend emits for its
  default provider. The scripts were already there; only the wiring was
  missing. Steps are overridden rather than inherited because the canonical
  suites point at ../providers/my-isv/scripts/*, which resolves wrong from a
  provider config directory. Neither joins `make demo-test`: both drive real
  kubectl/sinfo, so a dummy-success stub has nothing to return.

Also fixes a pre-existing scaffold break found while verifying the above:
`--suite storage --capability kubernetes` failed with "'cluster_name' is a
required property". The step name auto-detects the platform-suite `cluster`
schema, which demands cluster_name/node_count - inventory no storage check
reads. Set `output_schema: generic` on both providers; scoping it to my-isv
would have recreated the divergence this commit removes, and per the JSON
contract discipline storage must not require cluster inventory of an ISV.

test_storage_cleanup_steps_have_explicit_capability_gates encoded the
divergence as `if provider == "aws":`; both providers now assert the same
gates. Add coverage that every declarable capability resolves to a my-isv
platform suite, so a UI-emitted `--suite <capability>` cannot silently
become an error again.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Alexandre Begnoche <abegnoche@nvidia.com>
…path

The same config ran a different set of checks depending on how it was named:
`--suite network` gave 26 checks (core default), `-f .../network.yaml` gave 35
(unfiltered), and `--provider X --label network` gave 35 as well. The label
path is the one requirements.md NVIDIA#4 calls out by name - label selection must
not run checks gated on a capability the ISV does not support - and it did.

Resolve the context after config validation instead of inside the `--suite`
branch, so one rule covers `--suite`, `-f` and `--label` discovery alike: a
plain suite with no `--capability` runs its core checks. Platform suites
declare no `requires:`, so they keep the unfiltered context. Label discovery
inherits this through its recursive call, which closes NVIDIA#4 without special
casing it.

This drops the "no filtering" pseudo-context as a default. It modelled no real
ISV situation - nobody runs on vm and kubernetes at once - so a plain suite now
always carries exactly one context.

Warn when `--capability` names something no check in the suite requires
(`--suite iam --capability kubernetes`): the run proceeds with core checks, but
a flag that silently does nothing reads as a typo. UI-composed commands are
unaffected.

`-f` used to hand the demos their gated checks for free, so the Makefile now
names a capability per suite to keep exercising those stubs; without it
`make demo-test` would quietly shrink by ~29 checks and stop covering the
launch_instance / install_image_bm / install_config_bm scripts while still
printing all-green. image-registry splits its gated checks between vm and
bare_metal, so it runs once per context.

Document the rule in configs/suites/README.md, which described neither
`requires:` nor the selection grammar.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Alexandre Begnoche <abegnoche@nvidia.com>
`requires:`, `--suite` and `--capability` appeared in no user-facing doc. The
model existed only in the code and in decision.md, an internal design record,
so an ISV had no way to learn what governs which checks they owe.

- configs/suites/README.md already gained the rule when the default changed;
  this adds the full model to the two guides an ISV actually reads.
- configuration.md: a "Capabilities and requires" section (the four mutually
  exclusive capabilities, platform vs plain suites, any-match semantics and why
  AND is deliberately inexpressible, the one selection rule, opt-in scope, the
  shared namespace), plus `requires` in the step field table with the
  gate-both-halves-of-a-fixture guidance.
- external-validation-guide.md: the same model stated for an ISV audience, and
  a Running Validations section that shows the --suite/--capability grammar,
  --dry-run, label composition and single-check rerun by pytest passthrough.
- my-isv scaffold README: real invocations rather than only the -f form, plus
  what governs which checks run.

Also corrects drift found in the scaffold's Domains table: 7 of 11 script
counts were wrong, and k8s/slurm listed no provider YAML - true until those
configs landed in 3735674. Both now note they need a real cluster and so sit
outside `make demo-test`. Drops a stale MY_ISV_DOMAINS reference for the
MY_ISV_SUITES / DEMO_CAP_<suite> variables that replaced it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Alexandre Begnoche <abegnoche@nvidia.com>
A run's signal is the pair, not the suite alone: `network` and
`network --capability vm` execute different checks, so recording only
one axis loses the distinction at the point where it matters most.
Neither axis was transmitted at all -- the service inferred a single
test_target_type from the first command, which collapsed both.

Two client-side spellings have to be translated before they leave the
process. CORE_REQUIREMENT_CONTEXT is this module's word for "no
capability", which the service records as NULL rather than a sentinel.
And a platform suite carries no explicit context because its own
platform *is* the capability it runs under, so report that instead of
dropping the axis for every platform-suite run.

resolve_suite_name recovers the suite from every entry path, including
`-f lab.yaml -f commands.yaml -f suites/k8s.yaml` where the first
config is not the suite.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… suites

The pre-commit validator carried a hand-copied DECLARABLE_CAPABILITIES
literal, so the hook that exists to enforce the vocabulary was the one
place that could silently drift from the schema it enforces. It now
imports the shared constant, and the REQUIREMENT_VOCABULARY alias (a
pure alias with no external importers) is gone.

Each suite YAML was also read three to four times and parsed two to
three times per run: _build_suite_map parsed a file and then called
iter_config_checks, which re-read and re-parsed the same file, and
wiring_errors globbed the directory twice while reading each file twice
more in its main loop. Both now expose iter_checks_from_data so a caller
that has already parsed the document reuses it, and wiring_errors reads
and parses each file exactly once.

The suite vocabularies are cached: resolve_suite_name alone scanned the
suites directory twice per invocation.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Alexandre Begnoche <abegnoche@nvidia.com>
Signed-off-by: Alexandre Begnoche <abegnoche@nvidia.com>
Two rules had each been written out four times.

`requires` validation (a list, drawn from DECLARABLE_CAPABILITIES, no
duplicates) lived in StepConfig.validate_requires, in
ValidationConfig.validate_suite_shape, in _validate_entry_shape, and in
the wiring script -- with four separately maintained message strings, so
"requires must contain only" and "requires must be a list containing
only" were the same verdict spelled two ways. requires_error() states it
next to the vocabulary it validates; each caller adds only its own
prefix. The pre-commit hook and the runtime validator can no longer
disagree about what a valid suite file is.

Suite-name canonicalization was worse, because suite name is the join
key between a test run and its catalog entries: `_normalize_name` did
lower + `-`/`_` + the k8s alias, `catalog.py` did the replace alone in
two places, and the wiring script hardcoded `if stem == "k8s"`. Adding a
second aliased filename meant finding all four. canonical_suite_name()
in isvtest.core.resolution is now the single authority -- isvctl already
depends on isvtest, so nothing new is coupled.

Alongside those, three smaller consolidations:

iter_checks_from_data existed twice, differing only in whether the
category is yielded and in a branch the script's copy had dropped
(dict-form categories without a `checks` key). The three-tuple version
in isvtest.catalog is now the only walker of the wiring shape; no suite
YAML uses the missing branch today, so the script only gains coverage.

catalog_document parsed the suites directory three times -- once via
_build_suite_map, then again in each vocabulary builder.
_iter_suite_docs makes it one pass, and suite_vocabularies returns both
lists from it.

CONFIGS_ROOT moves from cli/test.py to config/suite_resolution.py, so
`deploy` no longer imports the whole `test` command module -- typer app,
orchestrator and all -- to obtain one Path. _suite_name gains @cache,
since classifying a config merges it with the suite it imports and a
single run asks both resolve_suite and resolve_suite_name for it.

test_capability_step_gating copied the run/skip cascade out of
_apply_capability_step_gates into its own _gated_step_names, so it would
have kept passing against the old rule if the real gate changed. It now
calls the gate.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Alexandre Begnoche <abegnoche@nvidia.com>
@abegnoche
abegnoche requested a review from a team as a code owner July 27, 2026 20:43
@github-actions

Copy link
Copy Markdown

🔐 TruffleHog Secret Scan

No secrets or credentials found!

Your code has been scanned for 700+ types of secrets and credentials. All clear! 🎉

🔗 View scan details

🕐 Last updated: 2026-07-27 20:43:51 UTC | Commit: 7a012db

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change replaces platform-based suite selection with capability-aware suite resolution and step gating, adds suite and capability metadata to catalogs and test runs, updates provider lifecycle configurations, and expands CLI, documentation, and validation coverage.

Changes

Capability suite execution

Layer / File(s) Summary
Capability contracts and suite resolution
isvtest/src/isvtest/core/resolution.py, isvctl/src/isvctl/config/*, scripts/validate_suite_wiring.py
Defines capability vocabularies, validates requires, resolves plain and platform suites, and validates suite wiring.
Orchestration and CLI gating
isvctl/src/isvctl/orchestrator/loop.py, isvctl/src/isvctl/cli/test.py, isvctl/src/isvctl/reporting.py
Propagates capability and label filters through execution, gates steps and validations, renders dry-run plans, and reports suite metadata.
Catalog schema and upload metadata
isvtest/src/isvtest/catalog.py, isvreporter/src/isvreporter/*, isvctl/src/isvctl/cli/catalog.py
Moves catalogs to schema version 2 with suite, platform, and requirement metadata and forwards complete catalog envelopes.
Provider suite lifecycle wiring
isvctl/configs/providers/*, isvctl/configs/suites/*
Adds capability-gated cleanup and Kubernetes storage lifecycle steps, separates image teardown, and converts configurations to plain suites where applicable.
Demo and execution documentation
Makefile, docs/guides/*, isvctl/configs/suites/README.md, isvctl/configs/providers/my-isv/scripts/README.md
Documents suite and capability selection and updates demo targets to invoke capability-specific runs.
Validation coverage
isvctl/tests/*, isvtest/tests/*, isvreporter/tests/*, scripts/tests/*
Adds coverage for capability filtering, suite resolution, catalog payloads, CLI behavior, lifecycle gates, and wiring rules.

Estimated code review effort: 5 (Critical) | ~120 minutes

Suggested reviewers: daluz

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 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: selecting validations by suite and capability.
Docstring Coverage ✅ Passed Docstring coverage is 94.94% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
scripts/validate_suite_wiring.py (1)

219-226: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

--check help text still advertises removed validators.

Line 229 now runs only wiring_errors(); the catalog-platform registration and isvreporter consistency checks are gone, so "unregistered suites, or isvreporter platform mismatches" no longer describes anything this flag can report.

📝 Proposed help-text update
     parser.add_argument(
         "--check",
         action="store_true",
-        help=(
-            "Exit 1 on wiring violations (missing test_id/labels, unregistered suites, "
-            "or isvreporter platform mismatches)."
-        ),
+        help=(
+            "Exit 1 on wiring violations (missing test_id/labels/suite label, "
+            "invalid tests.platform, or invalid/unreachable requires)."
+        ),
     )
🤖 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 `@scripts/validate_suite_wiring.py` around lines 219 - 226, Update the --check
argument help text in the validate_suite_wiring parser to describe only the
validations currently performed by wiring_errors(), removing references to
unregistered suites and isvreporter platform mismatches.
isvreporter/src/isvreporter/main.py (1)

306-322: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Legacy/incomplete catalog JSON silently uploads with an empty v2 axis vocabulary.

catalog_data.get("schemaVersion", 1), .get("platforms", []), .get("suites", []) let a stale or malformed test_catalog.json (missing these v2 keys) sail through as a "valid" upload with schemaVersion: 1 but the new v2-shaped entry fields (source/suite/platform/requires). This undercuts the intent behind making these three params required (no defaults) on upload_test_catalog itself — the guard is bypassed here via optimistic defaults instead of failing loudly on an incomplete envelope.

🐛 Proposed fix: fail fast on missing v2 catalog metadata
-            upload_test_catalog(
-                endpoint=endpoint,
-                jwt_token=jwt_token,
-                isv_test_version=catalog_version,
-                entries=catalog_entries,
-                schema_version=catalog_data.get("schemaVersion", 1),
-                platforms=catalog_data.get("platforms", []),
-                suites=catalog_data.get("suites", []),
-            )
+            if "schemaVersion" not in catalog_data or "platforms" not in catalog_data or "suites" not in catalog_data:
+                typer.echo(
+                    f"Warning: {test_catalog} is missing schemaVersion/platforms/suites; skipping upload", err=True
+                )
+            else:
+                upload_test_catalog(
+                    endpoint=endpoint,
+                    jwt_token=jwt_token,
+                    isv_test_version=catalog_version,
+                    entries=catalog_entries,
+                    schema_version=catalog_data["schemaVersion"],
+                    platforms=catalog_data["platforms"],
+                    suites=catalog_data["suites"],
+                )
🤖 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 `@isvreporter/src/isvreporter/main.py` around lines 306 - 322, Update the test
catalog upload block around upload_test_catalog to validate that catalog_data
contains the required v2 metadata keys schemaVersion, platforms, and suites
before uploading. Remove the optimistic fallback defaults for these fields, and
fail loudly through the existing error-handling path when any required key is
missing.
🧹 Nitpick comments (2)
isvtest/src/isvtest/catalog.py (1)

216-243: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

_declared_platform classifies differently than suite_vocabularies for a non-declarable platform value.

_build_suite_map (line 233) accepts any non-empty tests.platform string as the suite name, while suite_vocabularies (line 342) only treats it as a capability when it is in DECLARABLE_CAPABILITIES and otherwise falls back to the filename stem. A suite declaring tests.platform: foo in bar.yaml would emit entries with suite: "foo" while the envelope advertises bar — and suite is the documented join key between a test run and its catalog rows. isvctl/src/isvctl/config/schema.py and scripts/validate_suite_wiring.py both reject non-declarable platform values, so this is unreachable today; folding the same filter into _declared_platform would keep the two readers in this file from ever disagreeing.

♻️ Proposed change
 def _declared_platform(data: dict[str, Any]) -> str | None:
-    """Return the platform key a suite document declares, or None for a plain suite."""
+    """Return the declarable capability a suite document declares, or None for a plain suite."""
     tests = data.get("tests") or {}
     platform = tests.get("platform") if isinstance(tests, dict) else None
-    return platform if isinstance(platform, str) and platform else None
+    return platform if isinstance(platform, str) and platform in DECLARABLE_CAPABILITIES else None

suite_vocabularies then simplifies to if platform is not None:.

Also applies to: 330-346

🤖 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 `@isvtest/src/isvtest/catalog.py` around lines 216 - 243, Update
_declared_platform to return a platform only when tests.platform is a non-empty
string included in DECLARABLE_CAPABILITIES; otherwise return None so
_build_suite_map falls back to the filename stem. Then simplify
suite_vocabularies to rely on the filtered platform and only check whether it is
not None, keeping both readers’ suite keys consistent.
isvctl/src/isvctl/config/suite_resolution.py (1)

43-51: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Silently skipping unreadable suite YAML turns a parse error into a confusing "unknown capability".

If suites/k8s.yaml fails to parse, kubernetes drops out of the vocabulary and parse_capability("kubernetes", ...) reports Unknown or non-declarable capability: kubernetes instead of the actual YAML error. Surfacing the parse failure (as _raw_imports on line 90 already does) would keep the diagnosis on the real cause.

♻️ Proposed change
     platforms: set[str] = set()
     for path in (configs_root / "suites").glob("*.yaml"):
         try:
             data = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
-        except (OSError, yaml.YAMLError):
-            continue
+        except (OSError, yaml.YAMLError) as exc:
+            raise SuiteResolutionError(f"Failed to read suite {path}: {exc}") from exc
         platform = (data.get("tests") or {}).get("platform") if isinstance(data, dict) else None
🤖 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 `@isvctl/src/isvctl/config/suite_resolution.py` around lines 43 - 51, Update
the suite-platform discovery loop in the function containing `platforms` and
`yaml.safe_load` so YAML parse failures are surfaced rather than silently
skipped, matching the error propagation behavior used by `_raw_imports`.
Continue handling unreadable files as appropriate, but let `yaml.YAMLError`
propagate with its original context so callers report the actual suite YAML
failure.
🤖 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 `@isvctl/configs/providers/aws/config/observability.yaml`:
- Around line 64-70: Update the requires gates so core-only runs also skip
capability-specific fixtures and cleanup steps. Apply this to launch_host in
isvctl/configs/providers/aws/config/observability.yaml:64-70; instance teardown
in isvctl/configs/providers/aws/config/image-registry.yaml:110-113; host
teardown in isvctl/configs/providers/aws/config/observability.yaml:347-352;
topology-block and capacity cleanup in
isvctl/configs/providers/aws/config/security.yaml:337-343 and 369-372; EKS
setup, block-fixture teardown, and EKS teardown in
isvctl/configs/providers/aws/config/storage.yaml:62-75, 213-228, and 242-249;
instance teardown in
isvctl/configs/providers/my-isv/config/image-registry.yaml:119-122; and cluster
setup, VM-volume teardown, and cluster teardown in
isvctl/configs/providers/my-isv/config/storage.yaml:55-64, 250-265, and 279-282.
Ensure each step’s requires configuration is interpreted when capability is
None, while preserving normal capability filtering.

In `@isvctl/configs/suites/README.md`:
- Around line 14-16: Update the platform suite capability values documented in
the README to use `kubernetes` instead of `k8s`, while retaining `k8s` only as
the suite filename where applicable. Ensure the documented `tests.platform`
value matches capability validation.

In `@isvctl/src/isvctl/cli/test.py`:
- Around line 125-159: The _resolve_capability_context and _reported_capability
helpers must recognize legacy root-level platform configurations in addition to
config.tests.platform. Reuse the same platform-detection behavior as
_detect_platform, including model_extra["platform"], so legacy platform suites
bypass core-only filtering and report their platform capability correctly.

In `@isvctl/src/isvctl/config/schema.py`:
- Around line 394-415: The ValidationConfig.platform description is stale and
lists unsupported capabilities. Update the platform Field description near
validate_suite_shape in isvctl/src/isvctl/config/schema.py to list only
lowercase vm, bare_metal, kubernetes, and slurm, and state that plain suites
omit platform and use per-check requires; then regenerate
isvctl/schemas/config.schema.json so ValidationConfig.platform receives the same
description, without hand-editing the generated schema.

In `@isvctl/src/isvctl/reporting.py`:
- Line 220: Add a brief comment explaining the lazy import of
client_upload_catalog from isvreporter.client inside update_test_run(), or move
it into the existing optional-import block if that preserves behavior. Keep the
import’s current lazy-loading behavior unchanged.

In `@isvctl/tests/test_capability_step_gating.py`:
- Line 34: Update the PROVIDERS definition used by the all-provider gating
invariant to include nico, or replace the hard-coded list with dynamic discovery
of provider configuration directories. Ensure plain nico suites are included
when validating capability-gated references.

In `@isvctl/tests/test_reporting.py`:
- Around line 187-192: Update isvctl/tests/test_reporting.py lines 187-192 in
_config with a concise PEP 257 docstring and annotate raw as dict[str, Any]
instead of bare dict; also add a concise PEP 257 docstring to the method at
lines 208-211.

In `@isvreporter/tests/test_client.py`:
- Around line 97-111: Update the helper methods _posted_payload and _response
with concise PEP 257 docstrings, and add a concise PEP 257 docstring to
test_sends_suite_and_capability. Change _posted_payload’s return annotation from
dict to dict[str, Any], importing Any if needed, while preserving the existing
behavior.

---

Outside diff comments:
In `@isvreporter/src/isvreporter/main.py`:
- Around line 306-322: Update the test catalog upload block around
upload_test_catalog to validate that catalog_data contains the required v2
metadata keys schemaVersion, platforms, and suites before uploading. Remove the
optimistic fallback defaults for these fields, and fail loudly through the
existing error-handling path when any required key is missing.

In `@scripts/validate_suite_wiring.py`:
- Around line 219-226: Update the --check argument help text in the
validate_suite_wiring parser to describe only the validations currently
performed by wiring_errors(), removing references to unregistered suites and
isvreporter platform mismatches.

---

Nitpick comments:
In `@isvctl/src/isvctl/config/suite_resolution.py`:
- Around line 43-51: Update the suite-platform discovery loop in the function
containing `platforms` and `yaml.safe_load` so YAML parse failures are surfaced
rather than silently skipped, matching the error propagation behavior used by
`_raw_imports`. Continue handling unreadable files as appropriate, but let
`yaml.YAMLError` propagate with its original context so callers report the
actual suite YAML failure.

In `@isvtest/src/isvtest/catalog.py`:
- Around line 216-243: Update _declared_platform to return a platform only when
tests.platform is a non-empty string included in DECLARABLE_CAPABILITIES;
otherwise return None so _build_suite_map falls back to the filename stem. Then
simplify suite_vocabularies to rely on the filtered platform and only check
whether it is not None, keeping both readers’ suite keys consistent.
🪄 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: CHILL

Plan: Enterprise

Run ID: 28248da6-e2fa-40c9-bca4-4ac414e597a1

📥 Commits

Reviewing files that changed from the base of the PR and between 18a050a and 7a012db.

📒 Files selected for processing (59)
  • Makefile
  • docs/guides/configuration.md
  • docs/guides/external-validation-guide.md
  • docs/test-plan.yaml
  • isvctl/configs/providers/aws/config/image-registry.yaml
  • isvctl/configs/providers/aws/config/observability.yaml
  • isvctl/configs/providers/aws/config/security.yaml
  • isvctl/configs/providers/aws/config/storage.yaml
  • isvctl/configs/providers/my-isv/config/image-registry.yaml
  • isvctl/configs/providers/my-isv/config/k8s.yaml
  • isvctl/configs/providers/my-isv/config/observability.yaml
  • isvctl/configs/providers/my-isv/config/slurm.yaml
  • isvctl/configs/providers/my-isv/config/storage.yaml
  • isvctl/configs/providers/my-isv/scripts/README.md
  • isvctl/configs/providers/my-isv/scripts/storage/setup_cluster.py
  • isvctl/configs/providers/my-isv/scripts/storage/teardown_cluster.py
  • isvctl/configs/providers/nico/config/control-plane.yaml
  • isvctl/configs/providers/nico/config/iam.yaml
  • isvctl/configs/providers/nico/config/network.yaml
  • isvctl/configs/providers/nico/config/observability.yaml
  • isvctl/configs/suites/README.md
  • isvctl/configs/suites/control-plane.yaml
  • isvctl/configs/suites/iam.yaml
  • isvctl/configs/suites/image-registry.yaml
  • isvctl/configs/suites/k8s.yaml
  • isvctl/configs/suites/network.yaml
  • isvctl/configs/suites/observability.yaml
  • isvctl/configs/suites/security.yaml
  • isvctl/configs/suites/storage.yaml
  • isvctl/schemas/config.schema.json
  • isvctl/src/isvctl/cli/catalog.py
  • isvctl/src/isvctl/cli/deploy.py
  • isvctl/src/isvctl/cli/test.py
  • isvctl/src/isvctl/config/schema.py
  • isvctl/src/isvctl/config/suite_resolution.py
  • isvctl/src/isvctl/orchestrator/loop.py
  • isvctl/src/isvctl/reporting.py
  • isvctl/tests/providers/nico/test_nico_provider.py
  • isvctl/tests/test_capability_step_gating.py
  • isvctl/tests/test_capacity_config.py
  • isvctl/tests/test_catalog_cli.py
  • isvctl/tests/test_cli_streams.py
  • isvctl/tests/test_merger.py
  • isvctl/tests/test_orchestrator_loop.py
  • isvctl/tests/test_reporting.py
  • isvctl/tests/test_schema.py
  • isvctl/tests/test_suite_resolution.py
  • isvctl/tests/test_test_cli_labels.py
  • isvreporter/src/isvreporter/client.py
  • isvreporter/src/isvreporter/main.py
  • isvreporter/tests/test_catalog_upload.py
  • isvreporter/tests/test_client.py
  • isvtest/src/isvtest/catalog.py
  • isvtest/src/isvtest/catalog_platforms.py
  • isvtest/src/isvtest/core/resolution.py
  • isvtest/tests/test_catalog.py
  • isvtest/tests/test_resolution.py
  • scripts/tests/test_validate_suite_wiring.py
  • scripts/validate_suite_wiring.py
💤 Files with no reviewable changes (7)
  • isvctl/configs/providers/nico/config/iam.yaml
  • isvtest/src/isvtest/catalog_platforms.py
  • isvctl/configs/providers/nico/config/network.yaml
  • isvctl/configs/providers/nico/config/control-plane.yaml
  • isvctl/configs/providers/nico/config/observability.yaml
  • isvctl/configs/providers/my-isv/config/observability.yaml
  • isvctl/configs/suites/k8s.yaml

Comment thread isvctl/configs/providers/aws/config/observability.yaml
Comment thread isvctl/configs/suites/README.md Outdated
Comment thread isvctl/src/isvctl/cli/test.py
Comment thread isvctl/src/isvctl/config/schema.py
Comment thread isvctl/src/isvctl/reporting.py
Comment thread isvctl/tests/test_capability_step_gating.py Outdated
Comment thread isvctl/tests/test_reporting.py
Comment thread isvreporter/tests/test_client.py
Typer forces a terminal under GITHUB_ACTIONS, so the reported option name
arrives spliced with escape codes and the substring match fails in CI while
passing locally.

Signed-off-by: Alexandre Begnoche <abegnoche@nvidia.com>
tests.platform now rejects anything outside vm, bare_metal, kubernetes and
slurm, but its field description (and the generated JSON schema) still listed
the old vocabulary, and four config examples declared values that raise at
load. Plain suites omit the key entirely.

Signed-off-by: Alexandre Begnoche <abegnoche@nvidia.com>
The gating invariant listed aws and my-isv by name, so nico's plain suites
escaped it; discovering the config directories keeps a new provider covered.

Signed-off-by: Alexandre Begnoche <abegnoche@nvidia.com>
The explicit two-capability rule overrode the static pattern rule, so every
make invocation printed an overriding-recipe warning.

Signed-off-by: Alexandre Begnoche <abegnoche@nvidia.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
isvctl/schemas/config.schema.json (1)

235-246: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Encode the platform capability allowlist in the schema.

The description restricts platform suites to vm, bare_metal, kubernetes, and slurm, but the actual schema still accepts any string. Independent JSON-schema consumers can therefore validate unsupported values such as network or gpu. Add the same allowlist as an enum (while preserving null) so the schema enforces the runtime contract.

🤖 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 `@isvctl/schemas/config.schema.json` around lines 235 - 246, Update the
“platform” schema property to restrict string values to the documented
capabilities “vm”, “bare_metal”, “kubernetes”, and “slurm” by adding an enum,
while preserving null as an accepted value and retaining the existing default
and metadata.
🤖 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.

Outside diff comments:
In `@isvctl/schemas/config.schema.json`:
- Around line 235-246: Update the “platform” schema property to restrict string
values to the documented capabilities “vm”, “bare_metal”, “kubernetes”, and
“slurm” by adding an enum, while preserving null as an accepted value and
retaining the existing default and metadata.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 3fe83980-74d3-4edb-8952-bec979090974

📥 Commits

Reviewing files that changed from the base of the PR and between 7a012db and 8a9d214.

📒 Files selected for processing (12)
  • Makefile
  • docs/guides/configuration.md
  • docs/guides/external-validation-guide.md
  • docs/guides/local-development.md
  • isvctl/configs/providers/aws/scripts/control-plane/docs/aws-control-plane.md
  • isvctl/configs/suites/README.md
  • isvctl/schemas/config.schema.json
  • isvctl/src/isvctl/config/schema.py
  • isvctl/tests/test_capability_step_gating.py
  • isvctl/tests/test_reporting.py
  • isvctl/tests/test_test_cli_labels.py
  • isvreporter/tests/test_client.py
💤 Files with no reviewable changes (4)
  • isvctl/configs/providers/aws/scripts/control-plane/docs/aws-control-plane.md
  • docs/guides/local-development.md
  • docs/guides/external-validation-guide.md
  • docs/guides/configuration.md
🚧 Files skipped from review as they are similar to previous changes (7)
  • isvreporter/tests/test_client.py
  • isvctl/tests/test_capability_step_gating.py
  • isvctl/src/isvctl/config/schema.py
  • isvctl/tests/test_reporting.py
  • isvctl/configs/suites/README.md
  • Makefile
  • isvctl/tests/test_test_cli_labels.py

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant