Skip to content

docs: add troubleshooting guide for test runs stuck in STARTED - #4

Merged
abegnoche merged 4 commits into
mainfrom
docs/troubleshooting-started-tests
Feb 11, 2026
Merged

docs: add troubleshooting guide for test runs stuck in STARTED#4
abegnoche merged 4 commits into
mainfrom
docs/troubleshooting-started-tests

Conversation

@aanimasaun-nvda

Copy link
Copy Markdown
Collaborator
  • Add docs/guides/troubleshooting-started-tests.md with RCA, fix (single-command flow), split-flow guidance, and cleanup steps for existing STARTED runs
  • Link from getting-started.md (--lab-id note) and isvreporter.md
  • Add guide to docs/README.md under Guides

Comment thread docs/guides/troubleshooting-started-tests.md Outdated
Comment thread docs/guides/troubleshooting-started-tests.md Outdated
Comment thread docs/guides/troubleshooting-started-tests.md Outdated
abegnoche and others added 3 commits February 11, 2026 16:34
Signed-off-by: aanimasaun-nvda <aanimasaun@nvidia.com>
- Add docs/guides/troubleshooting-started-tests.md with RCA, fix (single-command
  flow), split-flow guidance, and cleanup steps for existing STARTED runs
- Link from getting-started.md (--lab-id note) and isvreporter.md
- Add guide to docs/README.md under Guides

Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: aanimasaun-nvda <aanimasaun@nvidia.com>
@aanimasaun-nvda
aanimasaun-nvda force-pushed the docs/troubleshooting-started-tests branch from 89c95a5 to 47544bc Compare February 11, 2026 21:44
Remove Mirantis-specific references; guide applies to all ISV Lab partners.

Signed-off-by: aanimasaun-nvda <aanimasaun@nvidia.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@abegnoche
abegnoche merged commit c26cdf6 into main Feb 11, 2026
1 check passed
@abegnoche
abegnoche deleted the docs/troubleshooting-started-tests branch February 12, 2026 16:06
saiyam1814 added a commit to saiyam1814/ISV-NCP-Validation-Suite that referenced this pull request May 16, 2026
… GKE

4 new conformance test skips found during live run (total now 10):
- HostPort validates that there is no conflict: HostPort binding not
  supported in vCluster (same root cause as HostPorts skip NVIDIA#1).
- should be able to create a functioning NodePort service: NodePort
  reachability fails because virtual node InternalIPs (34.118.x.x on
  GKE) differ from the host VPC IP where kube-proxy binds (same root
  cause as ExternalName-to-NodePort skip NVIDIA#4).
- should run through the lifecycle of a PV and a PVC: PV patch races
  with vCluster's PV sync, causing a spurious not-found error.
- should schedule a Pod requesting a RuntimeClass and initialize its
  Overhead: RuntimeClass overhead is not factored into virtual node
  allocatable resources on synced nodes.

K8sDriverVersionCheck fix (belt-and-suspenders):
- setup.sh: unconditionally clear driver_version on GKE (was conditional
  on 'unknown'; GKE never publishes nvidia.com/cuda.driver.* labels).
- k8s.yaml: add kubernetes.checks.K8sDriverVersionCheck.driver_version=""
  override so the check skips even if setup.sh output changes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
saiyam1814 added a commit to saiyam1814/ISV-NCP-Validation-Suite that referenced this pull request May 16, 2026
New failures caught by continuous monitoring:
- should be able to switch session affinity for NodePort service: same
  NodePort IP mismatch root cause as the other NodePort failures (NVIDIA#4).
- CustomResourceConversionWebhook should be able to convert a non
  homogeneous list of CRs: the virtual control plane (syncer pod) cannot
  reach conversion webhook pods in the tenant because the virtual
  apiserver runs in the host namespace, separated from tenant workloads
  by vCluster's network isolation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
saiyam1814 added a commit to saiyam1814/ISV-NCP-Validation-Suite that referenced this pull request May 16, 2026
Two new conformance failures discovered in the fresh clean run:

1. "should support a Service with multiple ports specified in multiple
   EndpointSlices" — same NodePort IP mismatch root cause as the existing
   "multiple endpoint IPs" skip; two distinct EndpointSlice conformance
   tests both exercise the same broken GKE vCluster network path.

2. "should apply changes to a pv/pvc status" — same PV sync race as the
   existing PV lifecycle skip; the test fetches a PV that hasn't synced
   back to the tenant cluster yet, causing a "not found" error.

Also updated comments for NVIDIA#4 (EndpointSlice) and NVIDIA#5 (PV) to cover both
variants of each failure.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
sinorga referenced this pull request in sinorga/ai-cloud-validation May 18, 2026
Three P1 fixes to clear phase2 review (p1=0 p2=1; the lone P2 is an
oracle-mirroring cleanup-ordering weakness, not a target regression):

1. isvctl/configs/providers/gcp/config/vm.yaml: trim start_instance
   step timeout from 900s -> 600s (2x the AWS oracle's 300s cap rather
   than 3x). Internal cloud-init / SSH waits are bounded by deadlines
   inside the stub, so the cap is not a sum of internal waits. Live
   evidence: start_instance ran in ~74s in the full-chain live gate
   (test-b9cc97cd), well under the new 600s cap.
2. isvctl/configs/providers/shared/teardown_nim.py: add a sentinel-skip
   path (success=True, skipped=True, rc=0) when --host or --key-file is
   the canonical 'none' / 'null' / '' sentinel that vm.yaml forwards on
   upstream skip/failure. Mirrors deploy_nim's NGC_API_KEY skip shape
   and the docs/existing-patterns/common.md 'forwarded inter-step Jinja
   args' rule. AWS teardown_nim path forwards real values so this
   change is a no-op for AWS.
3. isvctl/pyproject.toml + uv.lock: tighten google-cloud-compute floor
   from >=1.29.0 to >=1.30.0,<2.0.0 to match the reviewed GCP knowledge
   floor at .factory/context/ncp-knowledge/gcp/vm.yaml:887-888. Locked
   resolution stays at 1.47.0; uv.lock refreshed offline.

Gates: live (full chain incl. console_rbac, 1419s), phase2 review
(attempt #4, p1=0)

Changed paths: isvctl/configs/providers/gcp/config/vm.yaml,
isvctl/configs/providers/shared/teardown_nim.py,
isvctl/pyproject.toml, uv.lock
sinorga referenced this pull request in sinorga/ai-cloud-validation May 18, 2026
… probe

POST-PATCH FIX #4 on integration branch agent/gcp-workers-286265d1-…,
addressing the only iteration-5 review P1 that's a real bug worth fixing
under the operator's "fix critical, skip concern-cases" criterion.

## Trigger

Post-fix final-run review test-d31fe561 (P1=2) flagged
`vm.yaml:112` `console_rbac` as missing `--network` flow-through from
`launch_instance.vpc_id`. Reviewer's required action: thread the
launched-network through to the probe so probe VMs in non-default-
network projects validate against the same network as the launched
instance.

## Why fix this one (and not the other iteration-5 finding)

The other iteration-5 P1 (start_instance step timeout vs SSH stack)
was identified as a FALSE POSITIVE — the reviewer interpreted
`wait_for_ssh_stable` + `wait_for_cloud_init` as a single "SSH
stability gate" (semantic grouping), but the audit rule at
`test_harness/review/agents/ncp-review-contract-config.md:80-89`
explicitly says "longest SINGLE wait" means one `wait_for_X(timeout=K)`
call, not a sum of sequential waits forming a logical readiness check.
The longest single wait in start_instance.py is
`wait_for_cloud_init(timeout=600)` = 600s, equal to the step cap. NOT
under-cap by the rule's literal definition. F-073 + F-103 in
docs/tracking/FACTORY-ISSUE-TRACKING.md (closed/RETIRED 2026-05-15)
established that step timeout is a CAP, not a SUM — operator's "we
don't need to sum up all operation's timeout" guidance reaffirms this.
Tracker entry for the false-positive prevention rule extension lands
separately in factory inputs (common.md + ncp-review-contract-config.md
clarifying language).

## What this commit changes

`vm.yaml:112-122` adds two args to `console_rbac`:

    - "--network"
    - "{{steps.launch_instance.vpc_id | default(network, true)}}"

mirroring the pattern used by `list_instances` (line 72 in the same
file). `console_rbac.py:925` already accepts `--network` in its
argparse and threads it through to `network=args.network` at
`console_rbac.py:990` — the gap was solely in the provider config's
wiring.

## Iteration lineage on this branch

- 7ed96e6 (post-patch fix #1, 2026-05-17): cleanup-bool capture +
  iam-retry envelope. Driven by 05-17 post-review P1#1.
- 0d985d6 (post-patch fix #2, 2026-05-18): broaden URLError catch
  with TimeoutError/OSError. Driven by code-review against #1.
- c135fc7 (post-patch fix #3, 2026-05-18): zone-walk contract
  compliance — `other_in_region` semantics + structured error handling
  in `_list_region_zones` + PREFERRED_ZONES drift. Driven by post-fix
  final-run review iterations #1 + #2.
- THIS COMMIT (post-patch fix #4, 2026-05-18): thread vpc_id to
  console_rbac. Driven by post-fix final-run review iteration #3
  (the non-false-positive half of the P1=2 verdict).

The PR doc's §5 Human-in-loop edit log will render these as four
separate rows with distinct triggers.

## Validation

- static: test-1c462ac1 PASS (46/46 incl. no_internal_issue_ids 10/0/10).
- Live gate not re-run for this single yaml change — the wiring
  adds one arg pair the consuming script already supports. Prior
  live gates on this branch lineage (test-489a0efc, test-9fbc9ac7,
  test-b36f66d1) all PASSED with default-network setups; the new
  arg falls back to `network` (default) via `default(network, true)`
  on operator envs that don't set vpc_id.

## Factory-side rule landing (separate, NOT in this commit)

The reviewer's iteration-3 finding for this class did NOT have a
matching reviewer audit before today; the rule will land alongside
F-105/F-106/F-107 as "operator argument flow-through to dependent
probes" in a subsequent factory commit. False-positive prevention
for the under-cap rule (clarify "longest SINGLE wait" definition)
also lands separately as a `common.md` + `ncp-review-contract-config.md`
edit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
abegnoche added a commit to abegnoche/ai-cloud-validation that referenced this pull request Jul 24, 2026
…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>
abegnoche added a commit that referenced this pull request Jul 28, 2026
* feat(isvtest): add requires capability expand and filter

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>

* feat(isvctl): validate requires and reject legacy suite axes

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>

* feat(isvctl): add --suite/--capabilities CLI and step gating

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>

* feat(catalog): migrate envelope to schema v2 requires model

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>

* refactor(suites): migrate plain suites to requires model

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>

* test: update provider merge and catalog expectations

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>

* fix(make): filter storage demo to vm capability context

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>

* fix(catalog): align upload envelope with backend platforms/suites

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.

* feat: add --dry-run alias for catalog push --no-upload

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)

* fix(docs): sync test-plan labels with storage suite wiring

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>

* chore(make): say suites, not domains, in demo output

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Alexandre Begnoche <abegnoche@nvidia.com>

* refactor(suites): use any-match requirement semantics

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

* fix(storage): tear down cluster fixture

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

* refactor(suites): defer unique wiring names

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

* fix(cli): reject unknown test options

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

* refactor(cli): make --capability single-valued

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>

* feat(catalog): enforce disjoint capability/suite namespaces

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>

* feat(config): harden requires validation at validate and lint time

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>

* fix(storage): gate EKS lifecycle by capability

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

* fix(selection): default plain suites to core checks

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

* fix(reporting): upload complete catalog envelope

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

* fix(selection): compose suite and label filters

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

* fix(selection): gate teardown steps by capability

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>

* fix(my-isv): align the scaffold with the reference lifecycle

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>

* fix(selection): resolve the capability context once, for every entry 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 #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 #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>

* docs: document the capability model

`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>

* feat(reporting): report the (suite, capability) pair on test runs

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>

* refactor(suites): share the capability vocabulary and stop re-parsing 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>

* chore: move tests

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

* refactor: state the requires rule and suite-name canonicalization once

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>

* fix(tests): strip rich styling before matching the rejected option name

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>

* fix: name only the four declarable capabilities in schema and docs

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>

* test: gate every discovered provider and document the new helpers

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>

* build: stop warning about the overridden demo-image-registry recipe

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>

* refactor(vocab): rename tests.platform to tests.capability

`capability` was already the shared word across the client, backend, and
frontend (`--capability`, `DECLARABLE_CAPABILITIES`, `isv_version.capabilities`,
`CapabilityChip`). `platform` was the odd one out, and it meant three different
things: the capability a platform suite declares (`tests.platform:`), the
lifecycle command groups (`commands.<key>`, which also span plain suites like
`iam` and `security`), and the capability-valued half of the catalog namespace.

Delete it as a synonym for capability. `requires` stays as-is: it is the
predicate on a check, `capability` is the value, and collapsing the two would
lose the distinction between "this check requires kubernetes" and "this run ran
under kubernetes".

- `tests.platform:` -> `tests.capability:` in the four platform suites and every
  reader (schema, suite_resolution, cli/test, orchestrator/loop, isvtest catalog,
  isvreporter platform detection, the suite wiring validator).
- The legacy key now raises rather than being ignored. `ValidationConfig` allows
  extras, so an unmigrated `tests.platform:` would have landed in `model_extra`,
  demoted the suite to a plain suite, and gated every check on a capability it no
  longer declared. Both validators report the rename, matching how `tests.module`
  is handled.
- `commands.<key>` keys are unchanged; their descriptions and KeyError messages
  now say "test target", since the keys span capabilities and plain suites.
- `isvctl test validate` prints `Capability:` instead of `Platform:`.
- Regenerated config.schema.json.

The wire format is deliberately untouched: the catalog envelope still carries
`platforms`/`suites` and entries still carry `platform` xor `suite` plus
`requires`. Renaming those columns is a separate migration.

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

* refactor(vocab): emit capabilities/capability in the catalog payload

Client half of the catalog wire rename. The document envelope's
`platforms` becomes `capabilities` and a per-entry `platform` becomes
`capability`, matching the renamed backend columns and the word the CLI
already uses for this axis (`--capability`, tests.capability).

- isvtest.catalog: the envelope key and the per-check suite map.
- isvreporter.client.upload_test_catalog: the `capabilities` kwarg and
  the JSON body it POSTs.
- isvctl catalog push/list read the renamed keys.

The backend accepts both spellings on upload (@JsonAlias), so a lab still
running an older isvctl is unaffected; this only changes what a current
client sends.

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

* fix(reporting): upload the renamed catalog envelope, and pin the fixture to it

Addresses review feedback on #561. The first item is a live break I
introduced in 02acfc0.

**Catalog upload was silently dead.** `isvctl.reporting.update_test_run`
still did `catalog_document["platforms"]` and passed `platforms=` to
`upload_test_catalog`. The envelope key is now `capabilities` and the
parameter was renamed in the same commit, so every automatic upload
raised KeyError before the call was even made. It is wrapped in
`except Exception -> logger.warning`, so nothing failed loudly: coverage
data just stopped arriving. `isvreporter report update --test-catalog`
had the same break reading a saved catalog file; that path now prefers
`capabilities` and falls back to `platforms`, since v1 files on disk
outlive the rename.

**The test suite could not have caught it.** The fixture in
test_forwards_complete_catalog_document is a literal that kept the old
`platforms` key, so it exercised a shape no producer emits and stayed
green while the real path raised. Updated, and pinned: a new assertion
compares the envelope keys against `catalog_document()` itself, so the
next rename fails here instead of in production.

Also from the review:

- `_declared_platform` and `build_catalog` docstrings still described the
  entry key as `platform`; it is `capability`.
- `docs/packages/isvreporter.md` documented `--platform` as taking three
  values and omitted vm. It takes all four; the flag keeps its spelling
  because it maps to the service's deprecated `test_target_type`.
- `PlatformCommands` claimed skip works "at phase level" -- there is no
  per-phase skip field, only target and step -- and contradicted the
  corrected sentence four lines below it.
- Two lines over the 120-char guideline, and the regenerated schema.
- `resolve_suite` classified every YAML in a provider's config dir
  without catching SuiteResolutionError, so one malformed file broke
  `--suite <anything>` for that whole provider. `resolve_suite_name`
  already skipped such files; this now matches it. Pre-existing, not from
  the rename.

Verified both upload call sites bind against the real signature and a
real envelope, including the v1 legacy fallback. 1452 + 58 + 1279 + 113
tests pass, lint and pre-commit clean.

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

* refactor(vocab): rename leftover platform locals after capability rename

The YAML key and catalog envelope already say capability/capabilities;
align the local names in the suite wiring validator and catalog builder
so the post-rename vocabulary is consistent.

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

* style: shorten the comments added in this branch

Rationale blocks belong in commit messages, not above the code. Also
drops a duplicated skip sentence from the PlatformCommands docstring and
regenerates the schema.

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

* test: scope the default(...) guard per Jinja expression

Addresses the last open finding on #561.

`_unguarded_references` returned nothing as soon as `default(` appeared
anywhere in a string, so a mixed value like

    {{ steps.a.id | default('') }} {{ steps.b.id }}

reported no dependency at all -- the guarded expression vouched for its
unguarded neighbour, and the gating test would have passed a step that
reads a skipped step's output. Now each `{{ ... }}` is checked on its
own.

Latent, not masking anything: every provider config still passes under
all five contexts with the stricter helper. Adds unit tests for the
helper itself, including the mixed-string case, since it had none.

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

* fix: preserve platform-suite reporting identity

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

* fix(schema): reject empty tests.capability as invalid

Truthiness treated capability: "" as an omitted plain-suite axis, so an
empty value bypassed the vocabulary check and could carry requires.

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

---------

Signed-off-by: Alexandre Begnoche <abegnoche@nvidia.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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.

2 participants