Skip to content

feat(validator): carry redaction-safe CTRF extra outcome data - #1973

Merged
njhensley merged 1 commit into
NVIDIA:mainfrom
njhensley:feat/issue-1951-ctrf-extras
Jul 31, 2026
Merged

feat(validator): carry redaction-safe CTRF extra outcome data#1973
njhensley merged 1 commit into
NVIDIA:mainfrom
njhensley:feat/issue-1951-ctrf-extras

Conversation

@njhensley

Copy link
Copy Markdown
Member

Summary

Carry validator coverage disclosure (per-check node counts, skip-reason codes) in a structured CTRF extra field that survives the default "minimal" redaction policy, instead of overloading Stdout/Message (which are correctly stripped from signed evidence bundles by default).

Motivation / Context

#1936 added nodesValidated: <schedulable>/<total> (plus per-node cordoned-skip disclosure) to check-nvidia-smi stdout, so a pass on a reduced node set is visible rather than silent (#1668). But that disclosure only reaches TestResult.Stdout/.Message, which pkg/evidence/redact's default "minimal" policy omits from a published, signed bundle. So in the default signed-bundle path a 1-of-2-node pass was indistinguishable from a 2-of-2 pass, and an all-cordoned skip lost its reason — for the very artifact a downstream consumer verifies by default.

Fixes: #1951
Related: #1936, #1668

Type of Change

  • New feature (non-breaking change that adds functionality)

Component(s) Affected

  • Validator (pkg/validator)
  • Docs/examples (docs/, examples/)
  • Other: pkg/evidence/redact, validators/

Implementation Notes

  • ctrf.TestResult.Extra (map[string]string) mirrors the CTRF spec's extra object. ctrf.ExtraLinePrefix (##AICR-EXTRA## ) is the shared transport contract between the in-pod producer and the orchestrator consumer; it lives in pkg/validator/ctrf because both sides import it.
  • Producervalidators.EmitExtra(map[string]string) marshals a check's outcome data to one prefixed stdout line. Consumerpkg/validator/job.ExtractResult parses the last such line into TestResult.Extra and strips all prefixed lines from human-readable Stdout.
  • Redactionredact.CTRF rebuilds Extra against a fail-closed allowlist (ctrfExtraAllowlist: nodesValidated, nodesTotal, skipReason). Unknown keys (including any a future check adds) are dropped; an empty result ships no extra: {}. PolicyVersion bumps v1v2.
  • checkNvidiaSMI now populates Extra with its coverage counts / skip code; validators/helper.FindAllGpuNodes computes the candidate-node total.
  • Contract (enforced by godoc + the allowlist): Extra values must be low-cardinality counts or enum codes only — never node names, IPs, or hostnames. The default treatment of free-form Stdout/Message is unchanged (a stated non-goal of the issue).

Testing

make qualify
  • golangci-lint on all changed packages — 0 issues
  • go test -race ./pkg/validator/... ./pkg/evidence/... ./validators/...PASS
  • make test-coverage80.8% (threshold 80%) PASS
  • make qualify — PASS end-to-end (chainsaw e2e 23/0/0; grype: only pre-existing Low/Unknown transitive advisories, non-gating)

New exported funcs covered: EmitExtra 75.0%, FindAllGpuNodes 87.5%.

Risk Assessment

  • Low — Additive, fail-closed, well-tested, easy to revert.

Rollout notes: Backwards compatible. Extra is omitempty; existing checks that don't emit it are unaffected. Redaction fails closed, so no new data leaks into signed bundles. PolicyVersion bump (v2) lets verifiers tell which rules ran.

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 30, 2026 22:12
@njhensley njhensley added the theme/validation Constraint evaluation, health checks, and conformance evidence label Jul 30, 2026
@njhensley njhensley self-assigned this Jul 30, 2026
@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.

@coderabbitai

coderabbitai Bot commented Jul 30, 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

The PR adds structured Extra metadata to CTRF results, transports it through stdout sentinel lines, and extracts it during validator job handling. Minimal redaction validates and allowlists the data under policy version v2. NVIDIA SMI reports skip reasons and GPU coverage using the new mechanism, with GPU discovery helpers and tests. Documentation defines the sentinel and evidence lifecycle.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Suggested reviewers: yuanchen8911

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes carrying redaction-safe CTRF extra outcome data.
Description check ✅ Passed The description directly explains the structured CTRF extra data, redaction behavior, validator wiring, testing, and motivation.
Linked Issues check ✅ Passed The changes satisfy issue #1951 by preserving validated coverage and skip metadata while retaining conservative stdout and message redaction.
Out of Scope Changes check ✅ Passed All code, documentation, and tests are directly related to the structured validator evidence and redaction objectives.
✨ 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: 9

🤖 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 213-214: Update the inline code spans in the validator
documentation table to remove trailing spaces from the code content: represent
the delimiters as `RESULT:` and `##AICR-EXTRA##`, then describe the following
single space separately in prose. Apply the same MD038 fix to the additional
occurrence at the referenced later location.

In `@pkg/evidence/redact/redact_test.go`:
- Around line 411-427: Refactor TestCTRFAllowlistsExtra into named table-driven
cases that define each input Extra map and its expected output, including
invalid values for otherwise allowed keys. Execute each case independently and
assert preserved allowlisted entries, removed unknown entries, and nil Extra
when all values are discarded, without relying on hard-coded result indexes.

In `@pkg/evidence/redact/redact.go`:
- Around line 301-308: Update the allowlisted Extra-value handling in the
surrounding redaction function to validate values as well as keys: accept only
non-negative decimal counts for numeric fields such as nodesTotal, and only the
defined closed enum values for skipReason; drop invalid entries while preserving
valid ones. Add adversarial tests covering hostname/IP and other invalid values
under allowed keys.

In `@pkg/validator/ctrf/builder.go`:
- Around line 169-171: Update the result-building logic around tr.Extra to
retain a defensive clone of r.Extra rather than the caller-owned map, while
preserving the existing empty-map behavior. Add a regression test that mutates
ValidatorResult.Extra after insertion and verifies the stored CTRF report
remains unchanged.

In `@pkg/validator/ctrf/types.go`:
- Around line 155-166: Enforce Extra value validation at the publication
boundary in the pkg/evidence/redact flow, not only at emission sites. For
allowlisted keys such as nodesTotal and skipReason, retain only canonical
non-negative count strings and values from the finite skip-reason enum; drop
invalid entries and omit the Extra object when no valid entries remain. Ensure
this also protects data parsed from raw prefixed stdout.

In `@pkg/validator/job/result.go`:
- Around line 130-152: The sentinel loop should retain the last valid non-empty
payload instead of storing only the final sentinel before parsing. Update the
logic around sawSentinel and parsed to unmarshal each sentinel as encountered,
warn for malformed entries, and replace the accumulated extra map only after a
valid non-empty object; preserve the earlier valid map when a later sentinel is
malformed, including the valid-then-malformed case.

In `@validators/deployment/nvidia_smi.go`:
- Line 121: Replace the success message’s fmt.Printf call with structured
slog.Info logging, preserving the verification message and passing len(gpuNodes)
as a named node-count attribute. Remove the production logging use of fmt.Printf
in this success path.
- Around line 110-119: Update the NVIDIA SMI validation flow around
nvidiaSMICoverageExtra and the failed-node return to determine the GPU-node
denominator before verification and emit coverage for both success and failure
outcomes. Track FindAllGpuNodes discovery failure separately so failed discovery
does not fall back to len(gpuNodes) or emit nodesTotal; preserve coverage
emission on validation failures. Add check-level tests covering successful and
failed verification paths, including denominator-discovery failure.

In `@validators/helper/gpu.go`:
- Around line 80-83: Update the node-filtering loop in the GPU validation flow
to check the request context via ctx.Err() or ctx.Done() on each iteration. When
cancellation is detected, stop processing and return the established wrapped
structured cancellation error; otherwise preserve the existing allocatable-GPU
filtering 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: 97d8a0be-b858-4a76-bc31-c6e1ecca78f6

📥 Commits

Reviewing files that changed from the base of the PR and between aa8733f and cdb9f21.

📒 Files selected for processing (15)
  • docs/contributor/validator.md
  • pkg/evidence/attestation/emit_test.go
  • pkg/evidence/redact/redact.go
  • pkg/evidence/redact/redact_test.go
  • pkg/validator/ctrf/builder.go
  • pkg/validator/ctrf/ctrf_test.go
  • pkg/validator/ctrf/types.go
  • pkg/validator/job/result.go
  • pkg/validator/job/result_test.go
  • validators/deployment/nvidia_smi.go
  • validators/deployment/nvidia_smi_test.go
  • validators/extras.go
  • validators/extras_test.go
  • validators/helper/gpu.go
  • validators/helper/gpu_test.go

Comment thread docs/contributor/validator.md Outdated
Comment thread pkg/evidence/redact/redact_test.go
Comment thread pkg/evidence/redact/redact.go
Comment thread pkg/validator/ctrf/builder.go
Comment on lines +155 to +166
// Extra carries structured, low-cardinality outcome data that survives the
// default "minimal" redaction policy (unlike Stdout and Message, which are
// free-form log text stripped by default). It mirrors the CTRF spec's
// `extra` object.
//
// CONTRACT: values MUST be low-cardinality counts or enum codes only —
// e.g. "1", "2", "all-cordoned", "no-gpu-nodes". NEVER node names, IPs,
// hostnames, or any operator-identifying free text; those belong in Stdout,
// which is redacted by default. Only keys on the redact package's explicit
// allowlist survive publication; unknown keys are dropped fail-closed
// (see pkg/evidence/redact).
Extra map[string]string `json:"extra,omitempty"`

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.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Enforce allowed value formats at the publication boundary.

The documented contract is not enforced: downstream redaction allowlists keys, but preserves their values. A validator can emit {"skipReason":"node-a.example"} or {"nodesTotal":"10.0.0.5"} and that identifier survives minimal redaction under an allowed key. Validate canonical non-negative counts and a finite skip-reason enum in pkg/evidence/redact; drop invalid values and empty maps. Emission-side validation alone is bypassable through raw prefixed stdout.

🤖 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 `@pkg/validator/ctrf/types.go` around lines 155 - 166, Enforce Extra value
validation at the publication boundary in the pkg/evidence/redact flow, not only
at emission sites. For allowlisted keys such as nodesTotal and skipReason,
retain only canonical non-negative count strings and values from the finite
skip-reason enum; drop invalid entries and omit the Extra object when no valid
entries remain. Ensure this also protects data parsed from raw prefixed stdout.

Comment thread pkg/validator/job/result.go Outdated
Comment thread validators/deployment/nvidia_smi.go Outdated
Comment thread validators/deployment/nvidia_smi.go Outdated
}
emitExtraOrWarn(nvidiaSMICoverageExtra(len(gpuNodes), total))

fmt.Printf("Successfully verified GPU on all %d nodes\n", len(gpuNodes))

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.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Use structured logging for the added success message.

Route this through slog.Info with the node count as an attribute rather than adding a fmt.Printf production log.

As per coding guidelines, “Use slog for production logging; do not use fmt.Printf for logging.”

🤖 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/nvidia_smi.go` at line 121, Replace the success
message’s fmt.Printf call with structured slog.Info logging, preserving the
verification message and passing len(gpuNodes) as a named node-count attribute.
Remove the production logging use of fmt.Printf in this success path.

Source: Coding guidelines

Comment thread validators/helper/gpu.go Outdated
@njhensley
njhensley force-pushed the feat/issue-1951-ctrf-extras branch from cdb9f21 to 3a846ba Compare July 30, 2026 22:44
@njhensley

Copy link
Copy Markdown
Member Author

Addressed CodeRabbit's review in the amended commit. Disposition:

Applied (7):

  • Validate Extra values, not just keys (redact.go, types.go) — the allowlist is now key and value: nodesValidated/nodesTotal must be a bare non-negative decimal, skipReason a lowercase kebab-case enum code. An identifier smuggled under an allowed key (an IP in nodesTotal, a hostname in skipReason) is dropped at the publication boundary. Added adversarial-value test cases.
  • Keep the last valid sentinel (result.go) — parseExtraSentinels now parses each sentinel as encountered and retains the last valid non-empty payload, so a valid coverage line followed by a malformed one no longer clears the evidence. Added a valid → malformed case.
  • Defensively copy Extra (builder.go) — AddResult clones the map; strengthened the test to mutate the source after insertion.
  • No fabricated / omitted coverage (nvidia_smi.go) — the denominator is computed before verification, coverage is emitted on both pass and fail paths (counting only nodes that passed), and nodesTotal is omitted rather than fabricated when node discovery fails. Added totalKnown=false and partial-failure cases.
  • Table-driven allowlist test (redact_test.go) — refactored into named cases covering surviving keys, dropped unknowns, invalid values under allowed keys, and full-drop-to-nil.
  • MD038 (validator.md) — removed trailing spaces from inline code spans.

Not applied (2), with reasoning:

  • fmt.Printfslog on the success line — that line is pre-existing (not introduced here), and this check deliberately uses fmt.Printf as its stdout evidence channel (the whole function does). Routing one line through slog would move it off the evidence transport and be inconsistent with the surrounding code.
  • ctx.Done() in FindAllGpuNodes’ filter loop — it iterates an already-fetched, in-memory node list (the List call is already ctx-bounded), and its sibling FindSchedulableGpuNodes intentionally does the same without a per-iteration ctx check. Adding it to only one of the two is inconsistent for negligible benefit.

make qualify passes (lint 0 issues, -race tests, coverage 80.9% ≥ 80%).

@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 239-244: Update the documented EmitExtra examples to capture and
log returned errors using the warning pattern from
validators/deployment/nvidia_smi.go, while preserving the validator verdict and
existing success/skip payloads.
🪄 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: 8528dd0f-e16d-4b5c-8d05-a2118b05b9aa

📥 Commits

Reviewing files that changed from the base of the PR and between cdb9f21 and 3a846ba.

📒 Files selected for processing (15)
  • docs/contributor/validator.md
  • pkg/evidence/attestation/emit_test.go
  • pkg/evidence/redact/redact.go
  • pkg/evidence/redact/redact_test.go
  • pkg/validator/ctrf/builder.go
  • pkg/validator/ctrf/ctrf_test.go
  • pkg/validator/ctrf/types.go
  • pkg/validator/job/result.go
  • pkg/validator/job/result_test.go
  • validators/deployment/nvidia_smi.go
  • validators/deployment/nvidia_smi_test.go
  • validators/extras.go
  • validators/extras_test.go
  • validators/helper/gpu.go
  • validators/helper/gpu_test.go

Comment thread docs/contributor/validator.md
@njhensley
njhensley force-pushed the feat/issue-1951-ctrf-extras branch from 3a846ba to f958530 Compare July 30, 2026 22:58
@njhensley

Copy link
Copy Markdown
Member Author

Follow-up: fixed the doc EmitExtra examples to model the warn-but-preserve-verdict error handling (matching emitExtraOrWarn in nvidia_smi.go) instead of _ = .... Doc-only change; amended into the commit.

@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: 4

🤖 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 195-197: Update the stdout row in the contributor validator
documentation to identify pod logs as CTRF stdout only; remove the message
mapping, while leaving the stderr and /dev/termination-log mappings unchanged.

In `@pkg/evidence/redact/redact.go`:
- Around line 177-199: Replace the regex-only validator used by the skipReason
entry in ctrfExtraAllowlist with an explicit allowlist of supported skip codes,
while retaining the existing validation for other enum values. In
pkg/evidence/redact/redact_test.go lines 411-472, add a lowercase kebab-case
value that is not a supported skip code and assert that minimal redaction drops
it.

In `@validators/deployment/nvidia_smi.go`:
- Line 53: Update validators/deployment/nvidia_smi.go at lines 53-53 so
FindSchedulableGpuNodes returning zero emits a distinct “no schedulable GPU
nodes” code rather than skipReasonNoGPUNodes, unless actual GPU-node absence has
been established. At lines 77-78, handle busy-status lookup failures with a
separate status-unavailable reason or propagate the lookup error; do not emit
nodes-busy for an unknown status.

In `@validators/helper/gpu_test.go`:
- Around line 54-65: Update the test assertions for FindAllGpuNodes and
FindSchedulableGpuNodes to verify node identities, not just counts: require the
expected GPU node names, ensure gpu-cordoned is included only in the all-nodes
result as appropriate, and explicitly exclude the cpu-only node from both
results.
🪄 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: 798b092a-f29f-4804-8860-d035224be5ff

📥 Commits

Reviewing files that changed from the base of the PR and between 3a846ba and f958530.

📒 Files selected for processing (15)
  • docs/contributor/validator.md
  • pkg/evidence/attestation/emit_test.go
  • pkg/evidence/redact/redact.go
  • pkg/evidence/redact/redact_test.go
  • pkg/validator/ctrf/builder.go
  • pkg/validator/ctrf/ctrf_test.go
  • pkg/validator/ctrf/types.go
  • pkg/validator/job/result.go
  • pkg/validator/job/result_test.go
  • validators/deployment/nvidia_smi.go
  • validators/deployment/nvidia_smi_test.go
  • validators/extras.go
  • validators/extras_test.go
  • validators/helper/gpu.go
  • validators/helper/gpu_test.go

Comment thread docs/contributor/validator.md Outdated
Comment thread pkg/evidence/redact/redact.go Outdated
Comment thread validators/deployment/nvidia_smi.go
Comment thread validators/helper/gpu_test.go Outdated
@njhensley
njhensley force-pushed the feat/issue-1951-ctrf-extras branch from f958530 to dc3acec Compare July 30, 2026 23:20
@njhensley

Copy link
Copy Markdown
Member Author

Second review round addressed (all 4 applied):

  • Closed skipReason allowlist (redact.go) — replaced the kebab-case regex with a closed set ctrfSkipReasons (no-gpu-nodes, no-schedulable-gpu-nodes, nodes-busy). You're right that a shape regex would still pass an arbitrary low-cardinality identifier like customer-prod-cluster; a well-formed but unlisted code is now dropped. Added test cases for a listed-code survivor and an unlisted-code drop. (This reverses my earlier "structural regex is enough" call — the closed set is the correct fail-closed boundary for signed evidence.)
  • Accurate skip codes (nvidia_smi.go) — zero schedulable GPU nodes no longer misreports as no-gpu-nodes when the cluster actually has cordoned GPU nodes: it now emits no-schedulable-gpu-nodes (checked via FindAllGpuNodes) with a matching human message. On the busy path I kept nodes-busy: a busy-check lookup error is already folded into "treat as busy" as pre-existing fail-safe behavior, and the code accurately reflects the "unavailable, treated as busy" verification decision (the underlying error is logged).
  • Assert node identities (gpu_test.go) — FindAllGpuNodes/FindSchedulableGpuNodes tests now assert the exact node-name sets (cordoned included only in the all-nodes result; cpu-only excluded from both), not just counts.
  • Doc stdout mapping (validator.md) — corrected the channel table: pod stdout → CTRF stdout only (message comes from /dev/termination-log).

make qualify passes (lint 0 issues, -race tests, coverage 80.9% ≥ 80%).

@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 `@pkg/validator/ctrf/types.go`:
- Around line 154-168: Update the Extra contract documentation near the Extra
field to remove the stale "all-cordoned" example and use only skip-reason values
accepted by ctrfSkipReasons, such as "no-gpu-nodes", "no-schedulable-gpu-nodes",
and "nodes-busy". Do not change the Extra field or redaction implementation.
🪄 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: 7fe33d31-4a26-484a-a15d-525173e960b8

📥 Commits

Reviewing files that changed from the base of the PR and between f958530 and dc3acec.

📒 Files selected for processing (15)
  • docs/contributor/validator.md
  • pkg/evidence/attestation/emit_test.go
  • pkg/evidence/redact/redact.go
  • pkg/evidence/redact/redact_test.go
  • pkg/validator/ctrf/builder.go
  • pkg/validator/ctrf/ctrf_test.go
  • pkg/validator/ctrf/types.go
  • pkg/validator/job/result.go
  • pkg/validator/job/result_test.go
  • validators/deployment/nvidia_smi.go
  • validators/deployment/nvidia_smi_test.go
  • validators/extras.go
  • validators/extras_test.go
  • validators/helper/gpu.go
  • validators/helper/gpu_test.go

Comment thread pkg/validator/ctrf/types.go
@njhensley
njhensley force-pushed the feat/issue-1951-ctrf-extras branch 2 times, most recently from 294ea4f to f398ea7 Compare July 31, 2026 01:54
@njhensley

Copy link
Copy Markdown
Member Author

Applied the multi-persona review. Disposition of all 9 findings:

Applied (8):

  • F1 (false no-gpu-nodes on transient List error) — the zero-schedulable branch now fails closed: on a FindAllGpuNodes error it emits no skipReason rather than a fabricated one (an absent reason is honest; a wrong one is signed misinformation). Logic extracted into a pure nvidiaSMIZeroScheduleOutcome helper.
  • F4 (headline branch logic untested) — new table-driven test on that helper covers all three outcomes incl. the F1 fail-closed path (100% covered). Note: the full checkNvidiaSMI verify path stays cluster-dependent/pre-existing 0%, but the fragile decision logic is now locked down.
  • F5 (parse-before-truncate wiring untested) — extracted processValidatorLogs (used by both ExtractResult and HandleTimeout, de-duplicating them) with a test that emits the sentinel after >ValidatorMaxStdoutLines of output and asserts Extra survives (100% covered).
  • F3 (user docs silent on surviving keys) — added the preserved-extra disclosure to docs/user/artifact-verification.md and docs/user/validation.md, linking the contributor contract.
  • F2 + F9 (count regex too wide / magic literal) — tightened to a named maxCountDigits = 5 const (≤ 99,999) with a drop-test for over-long counts.
  • F6 (prefix-boundary edges) — added parseExtraSentinels cases for a mid-line prefix (kept) and a prefix missing its trailing space (not a sentinel).
  • F7 (EmitExtra error branch) — test points extrasOut at an always-erroring writer, asserting a wrapped ErrCodeInternal (EmitExtra 75% → 87.5%).

Not applied (1):

  • F8 (two Nodes().List calls on the happy path) — left as-is. It's explicitly optional; a combined helper would churn the public FindSchedulableGpuNodes/FindAllGpuNodes API for one saved List per run, and correctness is already guarded by the len(allNodes) > total between-calls check. Noted for a possible follow-up.

make qualify passes (lint 0 issues, -race tests, coverage 80.9% ≥ 80%).

@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.

Three issues in validators/deployment/nvidia_smi.go. Two block merge (#1, #2); #3 is resolved by the #2 fix or by correcting the wording.

Comment thread validators/deployment/nvidia_smi.go Outdated
Comment thread validators/deployment/nvidia_smi.go Outdated
Comment thread validators/deployment/nvidia_smi.go Outdated
@yuanchen8911
yuanchen8911 self-requested a review July 31, 2026 02:30
@njhensley
njhensley force-pushed the feat/issue-1951-ctrf-extras branch from f398ea7 to 00c6f65 Compare July 31, 2026 04:22
@njhensley

Copy link
Copy Markdown
Member Author

Thanks @yuanchen8911 — all three addressed in the latest push (00c6f650). These caught real evidence-integrity bugs the automated passes missed.

#1 — TOCTOU between two Node LISTs (blocking). Fixed as you prescribed: added helper.FindGpuNodes, which lists nodes once and partitions into Schedulable/All from a single snapshot. checkNvidiaSMI now drives node selection, the zero-schedule classification, and the coverage denominator from that one view — no second LIST, so the cordon/uncordon and device-plugin-rollout races are gone. FindAllGpuNodes (the second-LIST helper this PR had added) is removed; FindSchedulableGpuNodes stays for its other callers and is asserted to agree with the partition.

#2 — probe errors masked as nodes-busy (blocking). Now tracked separately: a IsNodeGpuBusy error still makes the node count as busy (fail-safe skip), but skipReason: nodes-busy is emitted only when at least one node was confirmed occupied (busy=true). A skip driven solely by probe errors signs no reason (the error is logged), consistent with the fail-closed treatment of the other skip paths.

#3 — inaccurate "no GPU nodes found" message. Eliminated by #1: with a single snapshot there is no second-LIST-failure branch, so the misleading message can no longer be produced. The cordoned-only case accurately reads no schedulable GPU nodes: N GPU node(s) present but unschedulable (cordoned).

Side effects: nvidiaSMIZeroScheduleOutcome simplified (no discovery-error arg) and nvidiaSMICoverageExtra dropped its totalKnown param since the denominator is now always known from the snapshot — both remain pure and 100%-covered. make qualify passes (lint 0, -race, coverage 80.9%).

yuanchen8911
yuanchen8911 previously approved these changes Jul 31, 2026

@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.

00c6f65 verified — both blockers fixed: confirmedBusy gates the nodes-busy evidence signing so probe errors no longer masquerade as occupancy, and the single-snapshot GpuNodePartition drives node selection, zero-schedule classification, and the coverage denominator, eliminating the TOCTOU window. Finding 3 dissolved with the second LIST as anticipated, and TestNvidiaSMIZeroScheduleOutcome asserts both accurate outcomes. Approving.

@njhensley

Copy link
Copy Markdown
Member Author

Rebased onto main now that #1936 has merged — the overlapping PR I flagged earlier. This significantly slims this PR and resolves the overlap cleanly:

Net: validators/helper/gpu.go reverts entirely to #1936's version; checkNvidiaSMI is #1936's structure plus the Extra emission layer. make qualify passes (lint 0, -race, coverage 80.9%). Single signed commit, now current on main.

@github-actions

Copy link
Copy Markdown
Contributor

@njhensley this PR now has merge conflicts with main. Please rebase to resolve them.

…#1951)

Validator coverage disclosure (e.g. check-nvidia-smi's
nodesValidated/nodesTotal and skip reasons from NVIDIA#1936) only reached
TestResult.Stdout/.Message, which the default "minimal" redaction policy
strips from a signed evidence bundle. A downstream consumer verifying
the default artifact could not distinguish a 1-of-2-node pass from a
2-of-2 pass, and an all-cordoned skip lost its reason.

Carry that signal in a structured, low-cardinality CTRF field instead of
overloading free-form log text:

- ctrf.TestResult gains an Extra map[string]string mirroring the CTRF
  spec's `extra` object, plus an ExtraLinePrefix contract shared between
  the in-pod producer and the orchestrator consumer.
- validators.EmitExtra marshals a check's Extra map to a single
  prefixed stdout line; job.ExtractResult parses each sentinel line and
  keeps the last VALID non-empty payload (a later malformed line no
  longer discards an earlier valid one), stripping all prefixed lines
  from human-readable Stdout.
- redact.CTRF rebuilds Extra against a fail-closed key AND value
  allowlist (ctrfExtraAllowlist): a surviving key's value must also match
  its canonical shape (decimal count / kebab-case enum code), so an
  identifier smuggled under an allowed key is dropped at the publication
  boundary — emission-side validation alone is bypassable. Unknown keys
  and empty results ship nothing; PolicyVersion bumps to v2.
- checkNvidiaSMI computes the coverage denominator before verification
  and emits coverage on BOTH pass and fail paths, counting only nodes
  that passed and omitting nodesTotal when node discovery failed rather
  than fabricating a full-coverage claim; helper adds FindAllGpuNodes.
- builder.AddResult defensively copies Extra so later caller mutation
  cannot alter the built report.
- docs/contributor/validator.md documents the field, the contract, the
  value-shape enforcement, and the allowlist.

The contract is enforced by godoc and the redact allowlist: values must
be counts or enum codes only, never node names, IPs, or hostnames.

Signed-off-by: Nathan Hensley <nhensley@nvidia.com>
@njhensley
njhensley force-pushed the feat/issue-1951-ctrf-extras branch from b371989 to 88ee46f Compare July 31, 2026 18:54

@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.

Re-approving after the rebase onto merged #1936 (the earlier approval was auto-dismissed by the force-push). Verified at 88ee46f:

  • The plumbing files (ctrf types/builder, redact, extras) are byte-identical to the previously approved head, and the nvidia_smi adoption is rebuilt cleanly on #1936's partition with all three earlier fixes preserved (single-snapshot classification, confirmedBusy gating, accurate skip messages).
  • The only new delta is the result.go sentinel transport, which is sound: Extra is parsed from the full logs before tail-truncation, malformed sentinel lines are skipped without discarding an earlier valid payload, sentinels are stripped from human stdout, and both extraction paths are covered.

On the two open CodeRabbit threads: the value-format enforcement one is already implemented — redact.go's ctrfExtraAllowlist is fail-closed on key AND value (bounded counts, closed skip-code enum) at the publication boundary, so the thread is stale against current code. The fmt.Printf one misapplies the logging rule: validator-check stdout is the CTRF evidence transport; slog would remove the line from evidence. Neither blocks.

@njhensley
njhensley merged commit f2b54e5 into NVIDIA:main Jul 31, 2026
40 checks passed
@njhensley
njhensley deleted the feat/issue-1951-ctrf-extras branch July 31, 2026 19:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/docs area/validator needs-rebase size/XL theme/validation Constraint evaluation, health checks, and conformance evidence

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Validator evidence disclosure (e.g. node coverage lines) is stripped by default redaction

2 participants