Skip to content

fix(validator): disclose cordoned RDMA nodes in expected-resources - #1981

Open
njhensley wants to merge 1 commit into
NVIDIA:mainfrom
njhensley:fix/issue-1952-node-scope-disclosure
Open

fix(validator): disclose cordoned RDMA nodes in expected-resources#1981
njhensley wants to merge 1 commit into
NVIDIA:mainfrom
njhensley:fix/issue-1952-node-scope-disclosure

Conversation

@njhensley

Copy link
Copy Markdown
Member

Summary

rdmaFabricProbe now enumerates RDMA GPU nodes via helper.FindGpuNodes and explicitly discloses cordoned Mellanox RDMA nodes (reported as skipped (cordoned) and counted in nodesTotal) instead of silently narrowing scope to the schedulable subset. Coverage is surfaced through EmitExtra so it survives redaction.

Motivation / Context

#1668/#1936 fixed the same undisclosed narrowing in check-nvidia-smi. rdmaFabricProbe (reached from checkExpectedResources) had the identical spuriously-narrowed-pass shape: it built its RDMA cohort from helper.FindSchedulableGpuNodes and printed a count over the schedulable subset only, so a cordoned RDMA-capable node was silently excluded from both the cohort and the printed count with no disclosure. This applies the #1936 disclosure pattern to rdmaFabricProbe and completes the audit called for in the issue.

Fixes: #1952
Related: #1936, #1668, #1951

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

  • Validator (pkg/validator)
  • Docs/examples (docs/, examples/)

Implementation Notes

  • Disclosure + Extra wiring: rdmaFabricProbe now enumerates every RDMA GPU node via helper.FindGpuNodes, validates only the schedulable subset, and discloses cordoned nodes as skipped (cordoned) while counting them in nodesTotal. The coverage is emitted via EmitExtra (nodesValidated/nodesTotal) so it survives redaction, following the #1951 pattern.
  • PolicyVersion unchanged (v2): the fix reuses the existing nodesValidated/nodesTotal allowlist keys, so no new redaction-allowlist entries are introduced and PolicyVersion stays at v2.
  • Performance call sites deliberately left unchanged: nccl_all_reduce_bw and inference_perf also call helper.FindSchedulableGpuNodes, but they were audited and intentionally not modified — they assert workload placement/sizing, not node-coverage, so cordoned-node disclosure does not apply to them.

Testing

unset GITLAB_TOKEN
golangci-lint run -c .golangci.yaml ./validators/deployment/... ./pkg/evidence/redact/...   # 0 issues
go test -race ./validators/deployment/... ./pkg/evidence/redact/...                          # ok
  • Targeted lint (0 issues) and go test -race (pass) on both affected packages.
  • New table-driven tests in validators/deployment/expected_resources_test.go cover the cordoned-RDMA-node disclosure path (skipped (cordoned) + nodesTotal accounting); pkg/evidence/redact/redact_test.go confirms the emitted coverage keys survive redaction.
  • Full make qualify intentionally not run for this validator + docs change (e2e disproportionately heavy); the CI gate will run it.

Risk Assessment

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

Rollout notes: No schema or PolicyVersion change (reuses existing v2 allowlist keys); backwards compatible. N/A migration.

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)

@njhensley
njhensley requested a review from a team as a code owner July 31, 2026 22:31
@njhensley njhensley added the theme/validation Constraint evaluation, health checks, and conformance evidence label Jul 31, 2026
@njhensley njhensley self-assigned this Jul 31, 2026
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

RDMA readiness now enumerates GPU nodes, validates only schedulable Mellanox nodes, and reports cordoned nodes in coverage totals. Probe results preserve coverage on success, failure, cancellation, and empty-node paths. Structured evidence emits nodesValidated and nodesTotal. Tests and contributor documentation cover the updated behavior.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

  • NVIDIA/aicr#1973: Adds the structured CTRF coverage mechanism extended here for RDMA readiness.

Suggested labels: area/validator, area/tests

Suggested reviewers: yuanchen8911

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely identifies the main change: disclosing cordoned RDMA nodes in expected-resources validation.
Description check ✅ Passed The description directly explains the RDMA coverage change, motivation, implementation, testing, and rollout impact.
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
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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 `@validators/deployment/expected_resources.go`:
- Around line 1128-1160: Update the listCtx.Done() branch in the node-scanning
loop to return the accumulated coverage variable instead of a new empty
rdmaFabricCoverage value, while preserving the timeout error and cancellation
details. Keep the existing accumulation of cordoned and schedulable nodes
unchanged.
- Around line 1112-1120: Update rdmaFabricProbeCoverage to return the existing
err from helper.FindGpuNodes directly when node discovery fails, rather than
wrapping it with errors.Wrap, so ErrCodeTimeout and other structured error codes
are preserved.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 1d25fab5-25b3-4f5f-85a3-a183c8fbc694

📥 Commits

Reviewing files that changed from the base of the PR and between c482c2e and a19fb0e.

📒 Files selected for processing (4)
  • docs/contributor/validator.md
  • pkg/evidence/redact/redact_test.go
  • validators/deployment/expected_resources.go
  • validators/deployment/expected_resources_test.go

Comment thread validators/deployment/expected_resources.go
Comment thread validators/deployment/expected_resources.go
@njhensley
njhensley force-pushed the fix/issue-1952-node-scope-disclosure branch from a19fb0e to 12a2c0c Compare July 31, 2026 22:44
@njhensley

Copy link
Copy Markdown
Member Author

Addressed both CodeRabbit findings. (1) Cancellation mid-scan now returns the accumulated coverage (cordoned nodes already seen) with schedulable set from the scanned cohort, instead of an empty partition — matching the function contract and all other error paths. (2) The FindGpuNodes error path: verified helper.FindGpuNodes returns a coded StructuredError (ErrCodeInternal on the node-List failure — the realistic path — and ErrCodeTimeout only on ctx cancel, which the in-loop listCtx.Done() branch surfaces on the accumulated coverage), so the ErrCodeInternal wrap adds gate context without clobbering the code; kept with a clarifying comment. Lint + go test -race ./validators/deployment/... green.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
validators/deployment/expected_resources.go (1)

1112-1126: 🎯 Functional Correctness | 🟠 Major

Still clobbers ErrCodeTimeout with ErrCodeInternal on cancellation inside FindGpuNodes.

helper.FindGpuNodes runs its own ctx.Done() check in its internal loop over nodeList.Items and can return an ErrCodeTimeout-coded error from that loop, before this function's own loop at line 1135 ever starts. The new comment at Lines 1118-1123 states that "the listCtx.Done() branch below already surfaces as ErrCodeTimeout on the accumulated coverage," but that branch only fires for cancellation during this function's loop over gpuNodes, not for cancellation inside FindGpuNodes itself. When FindGpuNodes returns an ErrCodeTimeout-coded error, errors.Wrap(errors.ErrCodeInternal, ...) still overwrites that code, discarding the caller's actual cancellation classification.

The coding guidelines state to not double-wrap errors that already carry the correct structured code. Use errors.PropagateOrWrap so an existing StructuredError code from FindGpuNodes (including ErrCodeTimeout) passes through unchanged, while a plain error still gets ErrCodeInternal and the added context.

🐛 Proposed fix to preserve the structured error code
 	gpuNodes, err := helper.FindGpuNodes(listCtx, ctx.Clientset)
 	if err != nil {
-		// helper.FindGpuNodes already returns a coded *errors.StructuredError —
-		// ErrCodeInternal on the realistic node-List failure (the same code used
-		// here, so this wrap adds gate context without clobbering it) and
-		// ErrCodeTimeout only on ctx cancellation, which the listCtx.Done() branch
-		// below already surfaces as ErrCodeTimeout on the accumulated coverage.
-		// Wrapping mirrors the pre-existing FindSchedulableGpuNodes gate path.
-		return rdmaFabricCoverage{}, errors.Wrap(errors.ErrCodeInternal,
-			"failed to list nodes for the RDMA fabric readiness gate", err)
+		// helper.FindGpuNodes may already return a coded *errors.StructuredError
+		// (ErrCodeInternal on the List failure, ErrCodeTimeout if cancellation
+		// interrupts its own internal node scan). PropagateOrWrap preserves that
+		// code unchanged instead of clobbering it with ErrCodeInternal.
+		return rdmaFabricCoverage{}, errors.PropagateOrWrap(err, errors.ErrCodeInternal,
+			"failed to list nodes for the RDMA fabric readiness gate")
 	}

To confirm PropagateOrWrap's exact propagation behavior in this repository's current pkg/errors package:

#!/bin/bash
# Description: Inspect PropagateOrWrap and Wrap implementations for code-preservation semantics.
rg -n -A 20 'func PropagateOrWrap\(' pkg/errors
rg -n -A 15 'func Wrap\(' pkg/errors
🤖 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 `@validators/deployment/expected_resources.go` around lines 1112 - 1126, In
rdmaFabricProbeCoverage, replace the errors.Wrap call handling
helper.FindGpuNodes failures with errors.PropagateOrWrap, preserving existing
StructuredError codes such as ErrCodeTimeout while assigning ErrCodeInternal and
the existing contextual message to plain errors. Remove or update the misleading
explanatory comment so it matches the corrected propagation behavior.

Source: Coding guidelines

🤖 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 `@validators/deployment/expected_resources_test.go`:
- Around line 1471-1528: Add a test for rdmaFabricProbeCoverage that cancels its
scan context after at least one cordoned node has been collected, then verifies
the returned error reflects cancellation while the partial coverage still
includes that cordoned node and counts it in total(). Exercise the
listCtx.Done() branch directly, using the existing RDMA node helpers and test
patterns from TestRDMAFabricProbeCoverage_DisclosesCordoned and
TestRDMAFabricProbeCoverage_CountsCordonedOnFailClosed.

---

Duplicate comments:
In `@validators/deployment/expected_resources.go`:
- Around line 1112-1126: In rdmaFabricProbeCoverage, replace the errors.Wrap
call handling helper.FindGpuNodes failures with errors.PropagateOrWrap,
preserving existing StructuredError codes such as ErrCodeTimeout while assigning
ErrCodeInternal and the existing contextual message to plain errors. Remove or
update the misleading explanatory comment so it matches the corrected
propagation behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 231ab9d9-a451-400b-abb5-1172526ebe40

📥 Commits

Reviewing files that changed from the base of the PR and between a19fb0e and 12a2c0c.

📒 Files selected for processing (4)
  • docs/contributor/validator.md
  • pkg/evidence/redact/redact_test.go
  • validators/deployment/expected_resources.go
  • validators/deployment/expected_resources_test.go

Comment on lines +1471 to +1528
// TestRDMAFabricProbeCoverage_DisclosesCordoned is the end-to-end proof of #1952:
// a cordoned Mellanox RDMA GPU node is enumerated (via helper.FindGpuNodes) and
// surfaced in the coverage partition — visible and counted — while still being
// excluded from the validated cohort. Under the pre-fix code path
// (FindSchedulableGpuNodes) the cordoned node vanished entirely, so this test
// fails without the production change.
func TestRDMAFabricProbeCoverage_DisclosesCordoned(t *testing.T) {
t.Parallel()

clientset := k8sfake.NewClientset(
rdmaGPUNode("rdma-gpu-0", 8, 1000), // schedulable, fabric ready → validated cohort
cordon(rdmaGPUNode("rdma-drain-0", 8, -1)), // cordoned RDMA node → disclosed, not dropped
)
ctx := &validators.Context{Ctx: context.Background(), Clientset: clientset}

cov, err := rdmaFabricProbeCoverage(ctx)
if err != nil {
t.Fatalf("rdmaFabricProbeCoverage() error = %v, want nil (the one schedulable RDMA node carries the fabric)", err)
}
if cov.schedulable != 1 {
t.Errorf("schedulable cohort = %d, want 1 (cordoned node excluded from validation)", cov.schedulable)
}
if len(cov.cordoned) != 1 || cov.cordoned[0] != "rdma-drain-0" {
t.Errorf("cordoned = %v, want [rdma-drain-0] (must be disclosed, not silently dropped)", cov.cordoned)
}
if got := cov.total(); got != 2 {
t.Errorf("total() = %d, want 2 (schedulable + cordoned, never narrowed)", got)
}
}

// TestRDMAFabricProbeCoverage_CountsCordonedOnFailClosed proves the cordoned
// disclosure survives the fail-closed paths too: when the sole schedulable RDMA
// node has not finished rolling out the fabric, the probe returns an error AND
// still reports the cordoned node in the coverage so the terminal disclosure can
// name it.
func TestRDMAFabricProbeCoverage_CountsCordonedOnFailClosed(t *testing.T) {
t.Parallel()

clientset := k8sfake.NewClientset(
rdmaGPUNode("rdma-gpu-0", 8, -1), // schedulable but fabric absent → not ready
cordon(rdmaGPUNode("rdma-drain-0", 8, -1)), // cordoned RDMA node → still disclosed
)
ctx := &validators.Context{Ctx: context.Background(), Clientset: clientset}

cov, err := rdmaFabricProbeCoverage(ctx)
if err == nil {
t.Fatal("expected a fail-closed error while the fabric is absent, got nil")
}
if !strings.Contains(err.Error(), "not yet allocatable") {
t.Fatalf("unexpected error: %v", err)
}
if len(cov.cordoned) != 1 || cov.cordoned[0] != "rdma-drain-0" {
t.Errorf("cordoned = %v, want [rdma-drain-0] even on the fail-closed path", cov.cordoned)
}
if got := cov.total(); got != 2 {
t.Errorf("total() = %d, want 2 (cordoned counted even on failure)", got)
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add a test for cancellation mid-scan with partial coverage.

TestRDMAFabricProbeCoverage_DisclosesCordoned and TestRDMAFabricProbeCoverage_CountsCordonedOnFailClosed cover the cordoned-disclosure and fail-closed paths, but no test exercises the listCtx.Done() branch in rdmaFabricProbeCoverage (production file, Lines 1138-1149) with cordoned nodes already collected before cancellation. This is the exact regression the earlier review flagged and the fix addressed; a dedicated test locks in that behavior.

🤖 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 `@validators/deployment/expected_resources_test.go` around lines 1471 - 1528,
Add a test for rdmaFabricProbeCoverage that cancels its scan context after at
least one cordoned node has been collected, then verifies the returned error
reflects cancellation while the partial coverage still includes that cordoned
node and counts it in total(). Exercise the listCtx.Done() branch directly,
using the existing RDMA node helpers and test patterns from
TestRDMAFabricProbeCoverage_DisclosesCordoned and
TestRDMAFabricProbeCoverage_CountsCordonedOnFailClosed.

@github-actions

Copy link
Copy Markdown
Contributor

Recipe evidence check

No leaf overlays affected by this PR.

This gate is warning-only and never blocks merge.

@njhensley
njhensley force-pushed the fix/issue-1952-node-scope-disclosure branch from 12a2c0c to bb384ad Compare July 31, 2026 23:00
@njhensley

Copy link
Copy Markdown
Member Author

Applied CodeRabbit fix: FindGpuNodes error path now uses errors.PropagateOrWrap, so an inner ErrCodeTimeout (FindGpuNodes cancelling in its own node scan before this loop) propagates unchanged while a plain List failure still gets ErrCodeInternal. Updated TestRDMAFabricProbe_FailsClosedOnListError to assert the propagated ErrCodeInternal code + fail-closed behavior instead of the removed gate-context message string. Lint + go test -race ./validators/deployment/... green.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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)
validators/deployment/expected_resources_rdma_test.go (1)

208-234: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add a test for ErrCodeTimeout propagation through PropagateOrWrap.

This test only exercises the plain-error branch of PropagateOrWrap (a List() failure, which helper.FindGpuNodes itself already wraps as ErrCodeInternal). The PR's stated purpose is preserving an inner structured code that differs from the fallback — specifically ErrCodeTimeout when cancellation interrupts FindGpuNodes' own node scan. No test drives that path and confirms rdmaFabricProbeCoverage returns ErrCodeTimeout unchanged instead of overwriting it with ErrCodeInternal.

Add a test that creates at least one node, pre-cancels the context passed to rdmaFabricProbeCoverage, and asserts stderrors.Is(err, errors.New(errors.ErrCodeTimeout, "")).

🤖 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 `@validators/deployment/expected_resources_rdma_test.go` around lines 208 -
234, Add a test alongside TestRDMAFabricProbe_FailsClosedOnListError that
creates at least one node, passes an already-canceled context to
rdmaFabricProbeCoverage, and verifies the returned error matches ErrCodeTimeout
via stderrors.Is with errors.New(errors.ErrCodeTimeout, ""). Ensure the test
exercises cancellation during FindGpuNodes’ node-scan path and confirms
PropagateOrWrap preserves the inner timeout code rather than replacing it with
ErrCodeInternal.
🤖 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 `@validators/deployment/expected_resources_rdma_test.go`:
- Around line 208-234: Add a test alongside
TestRDMAFabricProbe_FailsClosedOnListError that creates at least one node,
passes an already-canceled context to rdmaFabricProbeCoverage, and verifies the
returned error matches ErrCodeTimeout via stderrors.Is with
errors.New(errors.ErrCodeTimeout, ""). Ensure the test exercises cancellation
during FindGpuNodes’ node-scan path and confirms PropagateOrWrap preserves the
inner timeout code rather than replacing it with ErrCodeInternal.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 3eaea061-99df-4340-b440-e708e55a35ef

📥 Commits

Reviewing files that changed from the base of the PR and between 12a2c0c and bb384ad.

📒 Files selected for processing (5)
  • docs/contributor/validator.md
  • pkg/evidence/redact/redact_test.go
  • validators/deployment/expected_resources.go
  • validators/deployment/expected_resources_rdma_test.go
  • validators/deployment/expected_resources_test.go

@njhensley

Copy link
Copy Markdown
Member Author

Thanks @coderabbitai — verified all three inline comments on `bb384ad6` against the current code. All three are non-actionable:

  1. `expected_resources.go:1122` — "return the existing structured error" (Major): Not a defect. `helper.FindGpuNodes` returns a `*errors.StructuredError` on every error path (`ErrCodeInternal` on the List failure, `ErrCodeTimeout` on its own scan cancellation), so `PropagateOrWrap` preserves `ErrCodeTimeout` identically to a bare `return err`. The difference is only in the (currently unreachable) plain-error case, where `PropagateOrWrap` adds the gate-context message rather than leaking an uncoded error — strictly more robust. This is the deliberate resolution from the previous round; keeping it.

  2. `expected_resources.go` Done-branch — "cancellation discards accumulated coverage" (Major): Already fixed / stale. The `listCtx.Done()` branch already returns the accumulated `coverage` (with `coverage.schedulable = len(cohort)`), not a zero `rdmaFabricCoverage{}`. This matches your own committable suggestion. No change needed.

  3. `expected_resources_test.go` — "add a cancellation mid-scan test" (nitpick): Skipping. The `listCtx.Done()` branch in `rdmaFabricProbeCoverage` is not deterministically reachable through the public function: `FindGpuNodes` runs on the same `listCtx` and short-circuits first with an identical `ctx.Done()` guard, so any cancellation state trips there (→ `ErrCodeTimeout` via the path above) before the probe's own loop iterates. Exercising it directly would require adding a production cancellation seam purely for the test — unwarranted churn for a defensive `select`. The disclosure contract on the nil-error and fail-closed paths is already locked by `TestRDMAFabricProbeCoverage_DisclosesCordoned` and `_CountsCordonedOnFailClosed`.

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

fd -a '^expected_resources(\.go|_test\.go|_rdma_test\.go)$' validators/deployment
ast-grep outline validators/deployment/expected_resources.go --items all --match 'rdmaFabricProbeCoverage|FindGpuNodes|PropagateOrWrap' || true
rg -n -C 8 'rdmaFabricProbeCoverage|FindGpuNodes\s*\(|PropagateOrWrap|listCtx\.Done\(\)' \
  validators/deployment/expected_resources.go

rg -n -C 6 'func\s+FindGpuNodes\b|FindGpuNodes\s*=|FindGpuNodes\b' \
  --glob '*.go' .

rg -n -C 5 'func\s+PropagateOrWrap\b|PropagateOrWrap\b' \
  --glob '*.go' pkg validators || true

rg -n -C 4 'TestRDMAFabricProbeCoverage_DisclosesCordoned|TestRDMAFabricProbeCoverage_CountsCordonedOnFailClosed|FailsClosedOnListError' \
  validators/deployment

Length of output: 211309


@njhensley Confirmed. I withdraw all three findings.

errors.PropagateOrWrap preserves the existing structured error code at expected_resources.go Line 1122. It also provides a valid fallback classification for an uncoded error.

The listCtx.Done() branch returns accumulated coverage and retains the scanned schedulable cohort. The cancellation-coverage finding was stale.

The public path does not provide a deterministic point to cancel after helper.FindGpuNodes returns and before the probe loop checks listCtx.Done(). Adding a production seam only for this defensive branch is not justified. The existing cordoned-node tests cover the relevant success and fail-closed disclosure behavior.

🐇 ✅


✏️ Learnings added
Learnt from: njhensley
URL: https://github.com/NVIDIA/aicr/pull/1981

Timestamp: 2026-07-31T23:07:50.391Z
Learning: In `validators/deployment/expected_resources.go`, `rdmaFabricProbeCoverage` passes the same `listCtx` to `helper.FindGpuNodes` and then checks `listCtx.Done()` while it walks the returned nodes. A deterministic public-path test of cancellation during this later loop would require an artificial production synchronization seam. Existing tests must instead validate the externally observable cordoned-node coverage contract on normal and fail-closed outcomes.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

You are interacting with an AI system.

@yuanchen8911
yuanchen8911 self-requested a review August 1, 2026 00:38

@yuanchen8911 yuanchen8911 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The fail-closed disclosure path has a gap: on a never-ready poll, the pod is killed before the terminal disclosure runs.

The catalog's 8m timeout feeds both the Job's activeDeadlineSeconds and AICR_CHECK_TIMEOUT with no margin (pkg/validator/v1/job_plan.go L229/L273/L303), and LoadContext starts its 8m context at process start (validators/context.go L100). Since GPUReadinessTimeout is also 8m, an exhausted poll returns at ~processStart+8m — after the Job deadline at jobStart+8m. With no SIGTERM handler, the process dies before printLines/EmitExtra execute, losing the enumeration and nodesValidated/nodesTotal exactly where a cordoned node narrowed coverage. Reachable in supported states: all RDMA nodes cordoned for maintenance, or a rollout slower than 8m.

Scope: the kill race predates this PR — every exhausted pollUntilStable gate already loses its timeout diagnostic this way, so the general no-margin defect should be a follow-up issue. But the every-terminal-outcome disclosure contract is new here, so the disclosure gap belongs in this PR.

Suggested fix: also emit the coverage disclosure once eagerly after the first probe observation, keeping the terminal emit. parseExtraSentinels keeps the last valid sentinel, so a clean exit's emit wins and a deadline kill still leaves the early one in the logs — no per-tick spam, no envelope changes.

@njhensley
njhensley force-pushed the fix/issue-1952-node-scope-disclosure branch from bb384ad to d57b74d Compare August 3, 2026 20:55
@njhensley

Copy link
Copy Markdown
Member Author

@yuanchen8911 thanks — good catch on the mid-poll kill race. Addressed in d57b74dc.

The gap: on a never-ready poll, the terminal emitExtraOrWarn never runs when the Job's activeDeadlineSeconds SIGKILLs the process at the same no-margin 8m as the poll budget — so the cordoned-node coverage (the whole point of this gate) never reaches the signed bundle. Reachable exactly as you describe: all RDMA nodes cordoned for maintenance, or a rollout slower than the budget.

Fix (your suggested approach): verifyRDMAFabricReady now emits the structured coverage eagerly, once, on the first poll observation that enumerates any RDMA-candidate node, with nodesValidated=0 (nothing is certified mid-poll), keeping the terminal emit. parseExtraSentinels keeps the last valid sentinel (verified in pkg/validator/job/result.go), so a clean exit's terminal emit wins and a deadline kill leaves the eager floor as the disclosure of record — no per-tick spam, no envelope changes.

Scoping notes:

  • Only the structured Extra is emitted eagerly, not the stdout enumeration — the Extra is the piece that survives redaction into the signed bundle (Validator evidence disclosure (e.g. node coverage lines) is stripped by default redaction #1951/Other node-scoped checks silently narrow to schedulable GPU nodes without disclosure #1952), and duplicating the stdout RESULT:/enumeration would produce divergent counts across the poll. Node names still appear only in the redacted stdout channel. Flag if you'd also like the stdout enumeration floored.
  • The eager emit is wired through an injected seam (verifyRDMAFabricReadyEmit) so it's unit-testable without capturing the validators-package stdout transport — no mutable global.
  • The broader no-margin kill race (every exhausted pollUntilStable gate loses its timeout diagnostic this way) is pre-existing and out of scope here; agree it belongs in a follow-up issue for the general activeDeadlineSeconds vs AICR_CHECK_TIMEOUT margin.

Tests: added TestVerifyRDMAFabricReady_EagerDisclosureFloor (table-driven, -race) — asserts the eager floor emits {validated:0, total:2} (cordoned node disclosed) before the terminal emit on both the fail-closed timeout and the ready-certifies paths. docs/contributor/validator.md updated to describe the eager-floor/last-wins semantics. golangci-lint clean, full validators/deployment package green under -race.

@github-actions github-actions Bot added size/XL and removed size/L labels Aug 3, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@validators/deployment/expected_resources.go`:
- Around line 1127-1134: Remove the unused rdmaFabricProbe wrapper and update
all test callers to use rdmaFabricProbeCoverage directly, adapting assertions to
its coverage result as needed. Keep production polling unchanged and do not
retain a duplicate test-only wrapper.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 8ab04663-9d4d-4cc1-9e96-bd83465c2153

📥 Commits

Reviewing files that changed from the base of the PR and between bb384ad and d57b74d.

📒 Files selected for processing (5)
  • docs/contributor/validator.md
  • pkg/evidence/redact/redact_test.go
  • validators/deployment/expected_resources.go
  • validators/deployment/expected_resources_rdma_test.go
  • validators/deployment/expected_resources_test.go

Comment thread validators/deployment/expected_resources.go Outdated
@njhensley
njhensley force-pushed the fix/issue-1952-node-scope-disclosure branch from d57b74d to 03cab57 Compare August 3, 2026 21:10
@njhensley

Copy link
Copy Markdown
Member Author

Follow-up in 03cab57e (amended, still one commit):

Removed the now-dead rdmaFabricProbe wrapper (CodeRabbit nitpick, valid). This PR orphaned it: the poll switched from rdmaFabricProbe (int-returning) to rdmaFabricProbeCoverage (full partition), so the wrapper had no production callers left. Deleted it and migrated its 5 unit-test call sites to rdmaFabricProbeCoverage(ctx) reading cov.schedulable — matching the pattern TestRDMAFabricProbeCoverage_DisclosesCordoned/_CountsCordonedOnFailClosed already use. golangci-lint clean, validators/deployment green under -race.

The other three CodeRabbit comments on this commit are re-anchored duplicates of the first-round items already resolved/explained:

  • "return the existing structured error" (PropagateOrWrap)FindGpuNodes always returns a *StructuredError, so PropagateOrWrap preserves ErrCodeTimeout identically while defensively wrapping any future plain error with gate context. Deliberate; keeping it.
  • "cancellation discards accumulated coverage" — already returns the accumulated coverage (CodeRabbit's own status now shows ✅ Addressed).
  • "add a cancellation mid-scan test" — the listCtx.Done() branch isn't deterministically reachable through rdmaFabricProbeCoverage: FindGpuNodes shares the same listCtx and short-circuits first with an identical guard. Testing it would need a production cancellation seam, unwarranted for a defensive select.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/contributor/validator.md`:
- Around line 532-534: Update the stale rdmaFabricProbe reference in the
expected-resources documentation to rdmaFabricProbeCoverage, matching the
current production function name and preserving the surrounding explanation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 658910a2-7af4-4fc9-a1ac-bb558ee58f6b

📥 Commits

Reviewing files that changed from the base of the PR and between d57b74d and 03cab57.

📒 Files selected for processing (5)
  • docs/contributor/validator.md
  • pkg/evidence/redact/redact_test.go
  • validators/deployment/expected_resources.go
  • validators/deployment/expected_resources_rdma_test.go
  • validators/deployment/expected_resources_test.go

Comment thread docs/contributor/validator.md Outdated
rdmaFabricProbe now enumerates nodes via FindGpuNodes and discloses
cordoned Mellanox RDMA nodes: they are reported as skipped (cordoned)
and counted in nodesTotal, and the coverage is surfaced through
EmitExtra so it survives redaction (following the NVIDIA#1951 pattern).

Reuses the existing nodesValidated/nodesTotal allowlist keys, so
PolicyVersion stays at v2.

The two performance call sites (nccl_all_reduce_bw, inference_perf)
were deliberately audited and left unchanged: they assert workload
placement/sizing, not node-coverage, so cordoned-node disclosure does
not apply.

Fixes NVIDIA#1952

Signed-off-by: Nathan Hensley <nhensley@nvidia.com>
@njhensley
njhensley force-pushed the fix/issue-1952-node-scope-disclosure branch from 03cab57 to 60280cc Compare August 3, 2026 21:24
@njhensley

Copy link
Copy Markdown
Member Author

Fixed the stale rdmaFabricProbe doc reference in docs/contributor/validator.md (now rdmaFabricProbeCoverage, matching the wrapper removal) in 60280cc2 — doc-only, still one commit. The other three CodeRabbit comments on the prior commit are the recurring first-round items (two now show ✅ Addressed; the cancellation-test nitpick remains not deterministically testable, as explained above).

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

Labels

area/docs size/XL theme/validation Constraint evaluation, health checks, and conformance evidence

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Other node-scoped checks silently narrow to schedulable GPU nodes without disclosure

2 participants