Skip to content

feat: surface the swallowed errors across the fleet#3469

Open
chet wants to merge 1 commit into
NVIDIA:mainfrom
chet:gh-issue-3179
Open

feat: surface the swallowed errors across the fleet#3469
chet wants to merge 1 commit into
NVIDIA:mainfrom
chet:gh-issue-3179

Conversation

@chet

@chet chet commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

A handful of fallible operations were silent in both pipelines -- no log, no metric -- so observability couldn't see them at all, including a rack health-report persist failure discarded with Err(_) => { /* will retry */ }. Every one now logs, and the one whose failure rate is worth watching also counts.

  • carbide_dsx_exchange_consumer_health_report_persist_failures_total -- the dsx health-report persist failure now emits a single framework event that both warns and counts (rack id and error ride as log context). It's the one failure here worth a rate.
  • The silently-dropped failures now warn: the read-only and error-unwinding txn.rollback() drops in the instance / power-shelf / expected-machine handlers; the DHCP TLS-material reads that fall back to a built-in default cert; the empty product serial fed into DPF; the preingestion subprocess kill/wait drops; and the health log-file flush, rotate, and prune drops. Each keeps its crate's logging convention, and the error-unwinding rollbacks log without clobbering the original error being returned.
  • The site-explorer bmc_reset_count counted attempts, not successes: the ipmitool reset dropped its result with .ok() while the counter ticked regardless. A shared record_bmc_reset_outcome now increments only on success and logs the failure, and both the ipmitool and Redfish paths route through it -- so the counter finally means what its name says, with no rename.

The MQTT state-change send drop the issue pointed at turned out to be a test mock; the production send path already logs and meters every outcome, so nothing was left silent there.

Note: this and #3465 both touch dsx-exchange-consumer/tests/metric_exposition.rs. Whichever merges second wants #3465's de-doubled message-events assertions plus this PR's health_report_persist_failed test -- I'll reconcile at rebase time.

This supports #3179

@chet chet requested a review from a team as a code owner July 14, 2026 02:05
@chet

chet commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai full_review, thanks!

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

@chet Sounds good — I'll redo the full review of all changes in this PR.

(ᵘ‿ᵘ)♡

✅ Action performed

Full review finished.

@coderabbitai

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

Summary by CodeRabbit

  • Observability

    • Added warnings for transaction rollback, log rotation, subprocess cleanup, missing DPU serial numbers, and configuration fallback issues.
    • Added health-report persistence failure metrics with rack and error details.
    • BMC reset metrics now count successful resets only and include clearer failure reporting.
  • Documentation

    • Updated core metrics documentation to reflect the revised BMC reset counting behavior.
  • Reliability

    • Improved error handling while preserving non-fatal behavior during cleanup, log rotation, and remediation operations.

Walkthrough

The changes make previously ignored rollback, cleanup, filesystem, configuration, and subprocess failures observable. They add health-report persistence telemetry, warn on empty DPU serial numbers, and ensure BMC reset metrics count only successful resets.

Changes

API transaction rollback observability

Layer / File(s) Summary
Rollback logging helper
crates/api-db/src/lib.rs
Adds a transaction helper that logs rollback failures with caller location and operation context.
Handler rollback error reporting
crates/api-core/src/handlers/*.rs
API handlers now use the helper for read-only lookups and expected-machine batch-operation failures.

Runtime failure diagnostics

Layer / File(s) Summary
Configuration and filesystem failure reporting
crates/dhcp/src/lib.rs, crates/health/src/sink/log_file.rs
TLS environment-variable fallbacks and log rotation operations now warn when underlying operations fail.
Subprocess and registration cleanup
crates/preingestion-manager/src/lib.rs, crates/machine-controller/src/handler/dpf.rs
Subprocess cleanup now checks kill, wait, and stderr-task completion; DPU registration warns when the serial number is empty.

Health report persistence telemetry

Layer / File(s) Summary
Persistence failure metric
crates/dsx-exchange-consumer/{src,tests}/*, crates/dsx-exchange-consumer/Cargo.toml
Health-report persistence failures emit a warning-level labeled metric event, with tests covering logs and counter exposition.

BMC reset outcome accounting

Layer / File(s) Summary
Reset outcome recording
crates/site-explorer/src/*, docs/observability/core_metrics.md
IPMI and Redfish reset handling now increments the reset counter only for successful operations, with unit coverage and updated metric descriptions.

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

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: previously swallowed errors are now surfaced through logging and metrics.
Description check ✅ Passed The description is clearly aligned with the changeset and describes the new logging, metrics, and BMC reset behavior.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (2)
crates/site-explorer/src/lib.rs (1)

3938-3969: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Make the outcome test table-driven and cover both transports.

This test only exercises Ipmitool, manually repeats the two cases, and does not verify the new failure log fields. Add table-driven cases for both Ipmitool and Redfish, asserting the returned boolean, metric count, and logged method/address/error.

As per coding guidelines, Rust tests for functions mapping inputs to observable results should use table-driven cases.

🤖 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 `@crates/site-explorer/src/lib.rs` around lines 3938 - 3969, Update
bmc_reset_counter_counts_successes_not_attempts to use table-driven cases
covering successful and failed outcomes for both BmcResetMethod::Ipmitool and
BmcResetMethod::Redfish. For each case, assert the returned boolean,
bmc_reset_count, and failure log fields including method, address, and error;
retain the success behavior and isolate or reset metrics/log capture per case.

Source: Coding guidelines

crates/preingestion-manager/src/lib.rs (1)

2160-2191: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the repeated kill/wait/warn cleanup sequence.

The stdout-missing and stderr-missing branches duplicate the same 4-statement kill → wait → warn cleanup verbatim. Consider a small helper to avoid drift if this cleanup logic changes later.

♻️ Proposed helper extraction
+async fn kill_and_reap(cmd: &mut tokio::process::Child, address: &str, context: &str) {
+    if let Err(e) = cmd.kill().await {
+        tracing::warn!("Upgrade script {address} cleanup: failed to kill subprocess after {context}: {e}");
+    }
+    if let Err(e) = cmd.wait().await {
+        tracing::warn!("Upgrade script {address} cleanup: failed to reap subprocess after {context}: {e}");
+    }
+}

Then call kill_and_reap(&mut cmd, &address, "STDOUT capture failure").await; and kill_and_reap(&mut cmd, &address, "STDERR capture failure").await; at the two call sites.

🤖 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 `@crates/preingestion-manager/src/lib.rs` around lines 2160 - 2191, Extract the
duplicated subprocess cleanup from the stdout and stderr failure branches into a
small async helper, such as kill_and_reap, accepting the command, address, and
failure context. Preserve the existing kill-then-wait order and warning
messages, then call the helper from both branches with their respective
capture-failure descriptions before marking completion and returning.
🤖 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 `@crates/dsx-exchange-consumer/tests/metric_exposition.rs`:
- Around line 79-84: Update the test documentation comment above the
persist-failure metric test to use the complete metric name
carbide_dsx_exchange_consumer_health_report_persist_failures_total, matching the
event definition and assertion; leave the test behavior unchanged.

In `@crates/site-explorer/src/lib.rs`:
- Around line 2565-2567: Update the documentation for
carbide_site_explorer_bmc_reset_count and all dependent dashboards or alerts to
define and interpret the metric as the number of successfully completed BMC
resets, not initiated attempts. Locate references using the metric name and
revise labels, descriptions, queries, and thresholds consistently without
changing the implementation.
- Around line 2568-2581: Separate the remote BMC reset result from timestamp
persistence errors in redfish_reset_bmc and ipmitool_reset_bmc, and update
record_bmc_reset_outcome and their callers accordingly. Treat a successful
physical reset as success for metrics and control flow even when timestamp
persistence fails, while still surfacing the bookkeeping error; ensure the
Redfish path does not fall through to a second IPMI reset after the hardware
reset succeeds.

---

Nitpick comments:
In `@crates/preingestion-manager/src/lib.rs`:
- Around line 2160-2191: Extract the duplicated subprocess cleanup from the
stdout and stderr failure branches into a small async helper, such as
kill_and_reap, accepting the command, address, and failure context. Preserve the
existing kill-then-wait order and warning messages, then call the helper from
both branches with their respective capture-failure descriptions before marking
completion and returning.

In `@crates/site-explorer/src/lib.rs`:
- Around line 3938-3969: Update bmc_reset_counter_counts_successes_not_attempts
to use table-driven cases covering successful and failed outcomes for both
BmcResetMethod::Ipmitool and BmcResetMethod::Redfish. For each case, assert the
returned boolean, bmc_reset_count, and failure log fields including method,
address, and error; retain the success behavior and isolate or reset metrics/log
capture per case.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: dba974ad-5c4a-49e9-bfb5-3acecfab5634

📥 Commits

Reviewing files that changed from the base of the PR and between cdcb7d5 and 1be10a0.

📒 Files selected for processing (15)
  • crates/api-core/src/handlers/credential.rs
  • crates/api-core/src/handlers/expected_machine.rs
  • crates/api-core/src/handlers/instance.rs
  • crates/api-core/src/handlers/power_shelf.rs
  • crates/api-core/src/handlers/rack.rs
  • crates/api-core/src/handlers/switch.rs
  • crates/dhcp/src/lib.rs
  • crates/dsx-exchange-consumer/Cargo.toml
  • crates/dsx-exchange-consumer/src/health_updater.rs
  • crates/dsx-exchange-consumer/src/metrics.rs
  • crates/dsx-exchange-consumer/tests/metric_exposition.rs
  • crates/health/src/sink/log_file.rs
  • crates/machine-controller/src/handler/dpf.rs
  • crates/preingestion-manager/src/lib.rs
  • crates/site-explorer/src/lib.rs

Comment thread crates/dsx-exchange-consumer/tests/metric_exposition.rs
Comment thread crates/site-explorer/src/lib.rs
Comment thread crates/site-explorer/src/lib.rs

@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

🧹 Nitpick comments (4)
crates/dhcp/src/lib.rs (1)

78-98: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use the repository’s structured logging contract for the new warnings.

These changes introduce free-form interpolation of errors, paths, and endpoint identifiers. Use structured tracing::warn! fields consistently.

  • crates/dhcp/src/lib.rs#L78-L98: replace the three log::warn! calls with structured fields and describe VarError accurately.
  • crates/preingestion-manager/src/lib.rs#L2162-L2187: emit address and error fields for cleanup warnings.
  • crates/preingestion-manager/src/lib.rs#L2206-L2210: emit address and error fields for the stderr-task warning.

As per coding guidelines and path instructions, ordinary Rust logging should use structured tracing:: macros with common values passed as fields.

🤖 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 `@crates/dhcp/src/lib.rs` around lines 78 - 98, Replace the three log::warn!
calls in crates/dhcp/src/lib.rs lines 78-98 with structured tracing::warn!
fields, recording the VarError accurately alongside the relevant fallback path.
In crates/preingestion-manager/src/lib.rs lines 2162-2187, update cleanup
warnings to emit structured address and error fields; likewise update the
stderr-task warning at lines 2206-2210 to emit address and error fields. No
direct changes are needed outside these three sites.

Sources: Coding guidelines, Path instructions

crates/site-explorer/src/lib.rs (2)

3938-3967: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Cover the physical-success/bookkeeping-failure path.

This test only feeds record_bmc_reset_outcome pre-composed results; it cannot catch a successful remote reset followed by timestamp persistence failure. Add coverage ensuring that success is counted and the IPMI fallback is not triggered.

🤖 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 `@crates/site-explorer/src/lib.rs` around lines 3938 - 3967, Add a test
covering the real BMC reset flow where the remote reset succeeds but timestamp
persistence fails: verify the successful reset increments bmc_reset_count and
does not trigger the IPMI fallback. Exercise the higher-level reset method
rather than only record_bmc_reset_outcome, and use the existing
persistence-failure setup and fallback-observable symbols.

3943-3967: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use the repository’s table-driven test pattern.

The test maps outcomes to a boolean and counter state but encodes cases sequentially. Convert these cases to the existing check_cases/scenario style. As per coding guidelines, tests mapping inputs to outputs or observable results must be table-driven.

🤖 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 `@crates/site-explorer/src/lib.rs` around lines 3943 - 3967, Convert
bmc_reset_counter_counts_successes_not_attempts into the repository’s
table-driven check_cases/scenario pattern, defining success and failure inputs
with their expected boolean result and bmc_reset_count. Iterate through the
cases while preserving the existing record_bmc_reset_outcome assertions and
expected counter behavior.

Source: Coding guidelines

crates/dsx-exchange-consumer/src/health_updater.rs (1)

215-222: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a handler-level assertion for the new emission.

test_api_failure_does_not_cache_state verifies only cache behavior, while metric_exposition.rs emits HealthReportPersistFailed directly. Capture the failure-path log around handle_value_message and assert the rack ID and error; otherwise this test suite can pass even if this call site stops emitting the event.

🤖 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 `@crates/dsx-exchange-consumer/src/health_updater.rs` around lines 215 - 222,
Update test_api_failure_does_not_cache_state around handle_value_message to
capture the handler-level HealthReportPersistFailed emission, then assert that
the captured event contains the expected rack ID and persistence error. Keep the
existing cache-behavior assertions and ensure the test specifically covers this
call site rather than only metric_exposition.rs.
🤖 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 `@crates/health/src/sink/log_file.rs`:
- Around line 236-254: Update shift_backups to avoid using Path::exists() before
rotating or pruning files. Call fs::rename and fs::remove_file directly,
treating only ErrorKind::NotFound as an ignorable absence and logging other
filesystem errors through the existing tracing warnings.
- Around line 211-215: The rotate_if_needed() flush path must stop rotation when
file.flush() fails instead of dropping the handle and continuing. Preserve
self.current_file on failure, propagate the I/O error immediately, and only
allow the existing ownership transfer/drop behavior after a successful flush.

---

Nitpick comments:
In `@crates/dhcp/src/lib.rs`:
- Around line 78-98: Replace the three log::warn! calls in
crates/dhcp/src/lib.rs lines 78-98 with structured tracing::warn! fields,
recording the VarError accurately alongside the relevant fallback path. In
crates/preingestion-manager/src/lib.rs lines 2162-2187, update cleanup warnings
to emit structured address and error fields; likewise update the stderr-task
warning at lines 2206-2210 to emit address and error fields. No direct changes
are needed outside these three sites.

In `@crates/dsx-exchange-consumer/src/health_updater.rs`:
- Around line 215-222: Update test_api_failure_does_not_cache_state around
handle_value_message to capture the handler-level HealthReportPersistFailed
emission, then assert that the captured event contains the expected rack ID and
persistence error. Keep the existing cache-behavior assertions and ensure the
test specifically covers this call site rather than only metric_exposition.rs.

In `@crates/site-explorer/src/lib.rs`:
- Around line 3938-3967: Add a test covering the real BMC reset flow where the
remote reset succeeds but timestamp persistence fails: verify the successful
reset increments bmc_reset_count and does not trigger the IPMI fallback.
Exercise the higher-level reset method rather than only
record_bmc_reset_outcome, and use the existing persistence-failure setup and
fallback-observable symbols.
- Around line 3943-3967: Convert bmc_reset_counter_counts_successes_not_attempts
into the repository’s table-driven check_cases/scenario pattern, defining
success and failure inputs with their expected boolean result and
bmc_reset_count. Iterate through the cases while preserving the existing
record_bmc_reset_outcome assertions and expected counter 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: CHILL

Plan: Enterprise

Run ID: 6e1d2c76-325f-4155-ab95-ce22450149af

📥 Commits

Reviewing files that changed from the base of the PR and between cdcb7d5 and 1be10a0.

📒 Files selected for processing (15)
  • crates/api-core/src/handlers/credential.rs
  • crates/api-core/src/handlers/expected_machine.rs
  • crates/api-core/src/handlers/instance.rs
  • crates/api-core/src/handlers/power_shelf.rs
  • crates/api-core/src/handlers/rack.rs
  • crates/api-core/src/handlers/switch.rs
  • crates/dhcp/src/lib.rs
  • crates/dsx-exchange-consumer/Cargo.toml
  • crates/dsx-exchange-consumer/src/health_updater.rs
  • crates/dsx-exchange-consumer/src/metrics.rs
  • crates/dsx-exchange-consumer/tests/metric_exposition.rs
  • crates/health/src/sink/log_file.rs
  • crates/machine-controller/src/handler/dpf.rs
  • crates/preingestion-manager/src/lib.rs
  • crates/site-explorer/src/lib.rs

Comment on lines +211 to 215
if let Some(mut file) = self.current_file.take()
&& let Err(e) = file.flush()
{
tracing::warn!(error = %e, "failed to flush log file before rotation");
}

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.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

set -euo pipefail

# Map the file shape first, then inspect the relevant section.
ast-grep outline crates/health/src/sink/log_file.rs --view expanded || true

echo
wc -l crates/health/src/sink/log_file.rs
echo

sed -n '170,290p' crates/health/src/sink/log_file.rs | cat -n

Repository: NVIDIA/infra-controller

Length of output: 7202


🏁 Script executed:

set -euo pipefail

# Inspect the call path and any rotation tests.
sed -n '1,170p' crates/health/src/sink/log_file.rs | cat -n
echo
sed -n '300,560p' crates/health/src/sink/log_file.rs | cat -n
echo
rg -n "rotate_if_needed|write_line\\(" crates/health/src/sink/log_file.rs crates/health/src -g '!target'

Repository: NVIDIA/infra-controller

Length of output: 18896


Do not continue rotation after a flush error.
rotate_if_needed() takes ownership of self.current_file before checking flush(). If that flush fails, the handle is dropped and rotation still proceeds, which can strand any pending bytes and mask the underlying I/O failure. Keep the writer alive, return the error, and only drop it after a successful flush.

🤖 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 `@crates/health/src/sink/log_file.rs` around lines 211 - 215, The
rotate_if_needed() flush path must stop rotation when file.flush() fails instead
of dropping the handle and continuing. Preserve self.current_file on failure,
propagate the I/O error immediately, and only allow the existing ownership
transfer/drop behavior after a successful flush.

Comment on lines +236 to +254
if from.exists()
&& let Err(e) = fs::rename(&from, &to)
{
tracing::warn!(error = %e, from = %from.display(), to = %to.display(), "failed to shift rotated log file");
}
}

// current -> .1
let backup = self.rotated_path(1);
let _ = fs::rename(current_path, &backup);
if let Err(e) = fs::rename(current_path, &backup) {
tracing::warn!(error = %e, from = %current_path.display(), to = %backup.display(), "failed to rotate current log file to backup");
}

// prune the oldest backup beyond the limit
let oldest = self.rotated_path(self.max_backups + 1);
if oldest.exists() {
let _ = fs::remove_file(&oldest);
if oldest.exists()
&& let Err(e) = fs::remove_file(&oldest)
{
tracing::warn!(error = %e, path = %oldest.display(), "failed to prune oldest rotated log file");

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.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

file='crates/health/src/sink/log_file.rs'
echo "== file size =="
wc -l "$file"
echo "== relevant slice =="
sed -n '200,290p' "$file"
echo "== search exists/try_exists in file =="
rg -n 'try_exists|\.exists\(\)|rename\(|remove_file\(' "$file"

Repository: NVIDIA/infra-controller

Length of output: 4000


Avoid gating backup rotation on Path::exists()

exists() turns permission and other metadata failures into false, so shift_backups() can skip a real error without logging it. Call rename/remove_file directly and ignore only NotFound, or switch to try_exists() with explicit Err handling.

🤖 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 `@crates/health/src/sink/log_file.rs` around lines 236 - 254, Update
shift_backups to avoid using Path::exists() before rotating or pruning files.
Call fs::rename and fs::remove_file directly, treating only ErrorKind::NotFound
as an ignorable absence and logging other filesystem errors through the existing
tracing warnings.

@github-actions

github-actions Bot commented Jul 14, 2026

Copy link
Copy Markdown

🔍 Container Scan Summary

Service Total Critical High Medium Low Other
boot-artifacts-aarch64 3 0 0 3 0 0
boot-artifacts-x86_64 3 0 0 3 0 0
forge-admin-cli-x86_64 259 13 30 78 7 131
machine-validation-runner 807 40 234 288 36 209
machine_validation 807 40 234 288 36 209
machine_validation-aarch64 807 40 234 288 36 209
nvmetal-carbide 807 40 234 288 36 209
TOTAL 3493 173 966 1236 151 967

Per-CVE detail lives in the per-service grype-* artifacts (JSON + SARIF). Severity counts only — no CVE IDs published here.

@github-actions

Copy link
Copy Markdown

@chet

chet commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @coderabbitai! Addressed all four actionable items in the latest push:

  • Doc comment metric name (metric_exposition.rs): now uses the full carbide_dsx_exchange_consumer_health_report_persist_failures_total.
  • bmc_reset_count describe: now reads "…resets that succeeded…" (the counter increments only on success as of this PR), and core_metrics.md is regenerated to match.
  • Preingestion cleanup: extracted the duplicated kill→wait→warn sequence into a kill_and_reap helper (order, wording, and level preserved).
  • bmc_reset test: rewritten table-driven over both transports × both outcomes, asserting the returned bool, the bmc_reset_count delta, and the failure log's method/address/error fields.

On the reset-outcome finding — a physical reset that succeeds but whose timestamp write fails is counted as a failure and, on the Redfish path, falls through to a second IPMI reset — that's a real issue, but it's pre-existing: this PR only swapped the counting mechanism, not the reset methods' ?-propagation of the timestamp write or the Redfish→IPMI fall-through. Filed #3471 to fix it properly (separating the physical-reset outcome from the timestamp bookkeeping).

@chet

chet commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai full_review, thanks!

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

@chet Understood — thanks for the detailed rundown of the fixes. I'll redo the full review now.

Confirmed my understanding of the addressed items:

  • Full metric name restored in the metric_exposition.rs doc comment.
  • bmc_reset_count gauge description now accurately reflects success-only semantics, with core_metrics.md in sync.
  • kill_and_reap helper cleanly de-duplicates the preingestion cleanup sequence.
  • Table-driven test for record_bmc_reset_outcome covering both transports and outcomes.

Also, good call filing #3471 to separate the physical-reset outcome from timestamp-write propagation and the Redfish→IPMI fall-through — agreed that's out of scope here and worth tracking separately.

ᕕ( ᐛ )ᕗ

✅ Action performed

Full review finished.

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Tip

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

@chet Thank you for the thorough follow-through — all four items look properly addressed:

  • Doc comment now matches the full metric name carbide_dsx_exchange_consumer_health_report_persist_failures_total.
  • bmc_reset_count description accurately reflects success-only counting, with docs regenerated to match.
  • The kill_and_reap helper is a clean way to deduplicate the kill→wait→warn sequence without altering behavior.
  • The table-driven test covering both transports × both outcomes gives good coverage of the returned bool, counter delta, and log fields.

Your call on the reset-outcome issue (timestamp-write failure being counted as a reset failure, and the Redfish→IPMI fall-through) makes sense — since this PR only changed how successes/failures are counted rather than the underlying ?-propagation and fallback logic, keeping that fix scoped to #3471 is reasonable. I'll keep an eye out for that follow-up.

(๑˃̵ᴗ˂̵)و

@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

🧹 Nitpick comments (1)
crates/dhcp/src/lib.rs (1)

78-98: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use structured tracing fields for the new operational diagnostics.

These messages interpolate errors, paths, addresses, and cleanup context into free-form text; DHCP also uses log::warn!. Keep messages static and pass values as fields such as error = %e, fallback = %path, address = %address, and context.

  • crates/dhcp/src/lib.rs#L78-L98: replace each log::warn! with structured tracing::warn!.
  • crates/preingestion-manager/src/lib.rs#L2168-L2176: structure address, context, and cleanup errors.
  • crates/preingestion-manager/src/lib.rs#L2208-L2211: structure the endpoint and join error.

As per coding guidelines and path instructions, ordinary Rust logging should use structured tracing:: macros with common values passed as fields.

🤖 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 `@crates/dhcp/src/lib.rs` around lines 78 - 98, Replace the three log::warn!
calls in crates/dhcp/src/lib.rs lines 78-98 with tracing::warn! calls using
static messages and structured fields for the environment-variable errors and
fallback paths. In crates/preingestion-manager/src/lib.rs lines 2168-2176,
structure address, context, and cleanup errors; in lines 2208-2211, structure
the endpoint and join error. Preserve the existing warning content and fallback
behavior while using fields such as error = %e, fallback = %path, address =
%address, and context.

Sources: Coding guidelines, Path instructions

🤖 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 `@crates/dsx-exchange-consumer/src/health_updater.rs`:
- Around line 215-222: Extend test_api_failure_does_not_cache_state to capture
the HealthReportPersistFailed telemetry emitted by the Err branch, then assert
the WARN message, matching rack_id, persist error text, and expected counter
increment. Keep the existing assertion that failed state is not cached.

---

Nitpick comments:
In `@crates/dhcp/src/lib.rs`:
- Around line 78-98: Replace the three log::warn! calls in
crates/dhcp/src/lib.rs lines 78-98 with tracing::warn! calls using static
messages and structured fields for the environment-variable errors and fallback
paths. In crates/preingestion-manager/src/lib.rs lines 2168-2176, structure
address, context, and cleanup errors; in lines 2208-2211, structure the endpoint
and join error. Preserve the existing warning content and fallback behavior
while using fields such as error = %e, fallback = %path, address = %address, and
context.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 2475c6ec-8ada-491c-9f8f-42b7e4b781aa

📥 Commits

Reviewing files that changed from the base of the PR and between da30d63 and fb2914e.

📒 Files selected for processing (17)
  • crates/api-core/src/handlers/credential.rs
  • crates/api-core/src/handlers/expected_machine.rs
  • crates/api-core/src/handlers/instance.rs
  • crates/api-core/src/handlers/power_shelf.rs
  • crates/api-core/src/handlers/rack.rs
  • crates/api-core/src/handlers/switch.rs
  • crates/dhcp/src/lib.rs
  • crates/dsx-exchange-consumer/Cargo.toml
  • crates/dsx-exchange-consumer/src/health_updater.rs
  • crates/dsx-exchange-consumer/src/metrics.rs
  • crates/dsx-exchange-consumer/tests/metric_exposition.rs
  • crates/health/src/sink/log_file.rs
  • crates/machine-controller/src/handler/dpf.rs
  • crates/preingestion-manager/src/lib.rs
  • crates/site-explorer/src/lib.rs
  • crates/site-explorer/src/metrics.rs
  • docs/observability/core_metrics.md

Comment thread crates/dsx-exchange-consumer/src/health_updater.rs
@chet

chet commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @coderabbitai — on the structured-logging finding:

  • Preingestion (the kill_and_reap cleanup warnings + the STDERR-join warning): addressed. They now pass address, context, and error as structured tracing fields instead of interpolating them into the message.
  • dhcp log::warn!: deferred to a follow-up (Migrate the dhcp crate from the log facade to structured tracing #3475). dhcp is the codebase's lone log:: facade island — 47 of the workspace's 50 log:: calls, against ~3,380 tracing:: calls elsewhere — so converting just these three lines would leave them the only tracing:: calls among ~50 log:: ones. It's better done as a crate-wide migration (including confirming whether dhcp-server bridges logtracing today), which Migrate the dhcp crate from the log facade to structured tracing #3475 tracks.

@chet

chet commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai full_review, thanks!

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

@chet Understood — I'll redo the full review of all changes in this PR now.

ヽ(´▽`)/

✅ Action performed

Full review finished.

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Tip

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

@chet Thank you for the update — both points make sense.

  • Preingestion structured logging: confirmed — passing address, context, and error as structured tracing fields (rather than string-interpolating them into the message) is the correct approach and keeps these warnings queryable/filterable in log aggregation, consistent with the rest of the codebase.
  • dhcp log::warn! deferral to #3475: agreed, this is the right call. Converting just those three call sites would create a worse inconsistency (3 lone tracing:: calls among ~50 log:: calls) than leaving them as-is temporarily. A crate-wide migration — with the added step of confirming whether dhcp-server already bridges logtracing — is the more disciplined approach and keeps this PR properly scoped.

Nothing further from me on this thread.

ᕦ(ò_óˇ)ᕤ

@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 `@crates/dhcp/src/lib.rs`:
- Around line 78-98: Update the TLS path configuration fields
forge_root_ca_path, forge_client_cert_path, and forge_client_key_path to reject
empty values returned by std::env::var, accepting only non-empty paths and
falling back to the corresponding tls_default constants with the existing
warnings. Apply identical validation to all three paths before they reach
build_forge_client_config.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 64b1080c-9343-4c87-95a2-995f6e47aab9

📥 Commits

Reviewing files that changed from the base of the PR and between fb2914e and 8dd85cc.

📒 Files selected for processing (17)
  • crates/api-core/src/handlers/credential.rs
  • crates/api-core/src/handlers/expected_machine.rs
  • crates/api-core/src/handlers/instance.rs
  • crates/api-core/src/handlers/power_shelf.rs
  • crates/api-core/src/handlers/rack.rs
  • crates/api-core/src/handlers/switch.rs
  • crates/dhcp/src/lib.rs
  • crates/dsx-exchange-consumer/Cargo.toml
  • crates/dsx-exchange-consumer/src/health_updater.rs
  • crates/dsx-exchange-consumer/src/metrics.rs
  • crates/dsx-exchange-consumer/tests/metric_exposition.rs
  • crates/health/src/sink/log_file.rs
  • crates/machine-controller/src/handler/dpf.rs
  • crates/preingestion-manager/src/lib.rs
  • crates/site-explorer/src/lib.rs
  • crates/site-explorer/src/metrics.rs
  • docs/observability/core_metrics.md
🚧 Files skipped from review as they are similar to previous changes (13)
  • crates/api-core/src/handlers/credential.rs
  • crates/site-explorer/src/metrics.rs
  • crates/api-core/src/handlers/expected_machine.rs
  • crates/api-core/src/handlers/rack.rs
  • crates/machine-controller/src/handler/dpf.rs
  • crates/dsx-exchange-consumer/src/health_updater.rs
  • crates/health/src/sink/log_file.rs
  • crates/preingestion-manager/src/lib.rs
  • crates/api-core/src/handlers/switch.rs
  • crates/dsx-exchange-consumer/src/metrics.rs
  • crates/api-core/src/handlers/instance.rs
  • crates/dsx-exchange-consumer/Cargo.toml
  • crates/site-explorer/src/lib.rs

Comment thread crates/dhcp/src/lib.rs
Comment on lines +78 to +98
forge_root_ca_path: std::env::var("FORGE_ROOT_CAFILE_PATH").unwrap_or_else(|e| {
log::warn!(
"FORGE_ROOT_CAFILE_PATH unset or unreadable ({e}); falling back to the built-in default root CA at {}",
tls_default::ROOT_CA
);
tls_default::ROOT_CA.to_string()
}),
forge_client_cert_path: std::env::var("FORGE_CLIENT_CERT_PATH").unwrap_or_else(|e| {
log::warn!(
"FORGE_CLIENT_CERT_PATH unset or unreadable ({e}); falling back to the built-in default client certificate at {}",
tls_default::CLIENT_CERT
);
tls_default::CLIENT_CERT.to_string()
}),
forge_client_key_path: std::env::var("FORGE_CLIENT_KEY_PATH").unwrap_or_else(|e| {
log::warn!(
"FORGE_CLIENT_KEY_PATH unset or unreadable ({e}); falling back to the built-in default client key at {}",
tls_default::CLIENT_KEY
);
tls_default::CLIENT_KEY.to_string()
}),

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.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Treat empty TLS-path environment variables as invalid.

std::env::var returns Ok("") for an explicitly empty variable, so these branches skip the fallback and pass empty certificate/key paths to build_forge_client_config. Validate the returned paths before accepting them, and use the built-in defaults when they are empty.

Proposed approach
-            forge_root_ca_path: std::env::var("FORGE_ROOT_CAFILE_PATH").unwrap_or_else(|e| {
+            forge_root_ca_path: match std::env::var("FORGE_ROOT_CAFILE_PATH") {
+                Ok(path) if !path.is_empty() => path,
+                Ok(_) | Err(_) => {
+                    // log the appropriate reason, then:
+                    tls_default::ROOT_CA.to_string()
+                }
+            },

Apply the same validation to the client certificate and key paths.

As per path instructions, DHCP TLS-path fallbacks must be reviewed for configuration correctness and runtime 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 `@crates/dhcp/src/lib.rs` around lines 78 - 98, Update the TLS path
configuration fields forge_root_ca_path, forge_client_cert_path, and
forge_client_key_path to reject empty values returned by std::env::var,
accepting only non-empty paths and falling back to the corresponding tls_default
constants with the existing warnings. Apply identical validation to all three
paths before they reach build_forge_client_config.

Source: Path instructions

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

🧹 Nitpick comments (1)
crates/preingestion-manager/src/lib.rs (1)

2160-2196: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use start_kill() for separate kill/reap diagnostics.

tokio::process::Child::kill().await already performs the kill and waits, so the following wait().await is not an independent reap step. Use start_kill() followed by wait().await, or keep only kill().await and log one cleanup failure. Tokio documents kill as SIGKILL followed by wait. (docs.rs)

🤖 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 `@crates/preingestion-manager/src/lib.rs` around lines 2160 - 2196, Update the
local kill_and_reap helper to call Child::start_kill() before
Child::wait().await, preserving the separate warning diagnostics for kill
initiation and process reaping. Keep the existing address, context, and error
fields and cleanup call sites unchanged.
🤖 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.

Nitpick comments:
In `@crates/preingestion-manager/src/lib.rs`:
- Around line 2160-2196: Update the local kill_and_reap helper to call
Child::start_kill() before Child::wait().await, preserving the separate warning
diagnostics for kill initiation and process reaping. Keep the existing address,
context, and error fields and cleanup call sites unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 30d872a5-7572-4f7e-83f9-01cda9c528b0

📥 Commits

Reviewing files that changed from the base of the PR and between 5d8e008 and 8dd85cc.

📒 Files selected for processing (17)
  • crates/api-core/src/handlers/credential.rs
  • crates/api-core/src/handlers/expected_machine.rs
  • crates/api-core/src/handlers/instance.rs
  • crates/api-core/src/handlers/power_shelf.rs
  • crates/api-core/src/handlers/rack.rs
  • crates/api-core/src/handlers/switch.rs
  • crates/dhcp/src/lib.rs
  • crates/dsx-exchange-consumer/Cargo.toml
  • crates/dsx-exchange-consumer/src/health_updater.rs
  • crates/dsx-exchange-consumer/src/metrics.rs
  • crates/dsx-exchange-consumer/tests/metric_exposition.rs
  • crates/health/src/sink/log_file.rs
  • crates/machine-controller/src/handler/dpf.rs
  • crates/preingestion-manager/src/lib.rs
  • crates/site-explorer/src/lib.rs
  • crates/site-explorer/src/metrics.rs
  • docs/observability/core_metrics.md

@chet

chet commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai full_review, thanks!

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

@chet Understood — I'll redo the full review of all changes in this PR now.

(๑˃̵ᴗ˂̵)و

✅ Action performed

Full review finished.


Your plan includes PR reviews subject to rate limits. More reviews will be available in 20 minutes.

.await?;

let _ = txn.rollback().await;
if let Err(e) = txn.rollback().await {

@poroh poroh Jul 14, 2026

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.

Proably better to add some function to txn that does this (infallible rollback with some additional &str to add to logs.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Great call -- done. Added a Transaction::rollback_or_log(context) method (matching the existing rollback/commit style) so the roll-back-and-warn-but-don't-fail logic lives in one place, and collapsed all 7 sites to txn.rollback_or_log("…").await. The expected_machine error-unwind paths still surface the original operation error afterward. Thanks for the suggestion!

@chet

chet commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai full_review, thanks!

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

@chet Understood — I'll redo the full review of all changes in this PR now.

٩(◕‿◕)۶

✅ Action performed

Full review finished.

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

♻️ Duplicate comments (1)
crates/dhcp/src/lib.rs (1)

78-98: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Reject explicitly empty TLS paths before accepting environment overrides.

std::env::var returns Ok("") for an explicitly empty variable, so these branches skip the fallback and pass empty certificate/key paths downstream. Accept only non-empty values; otherwise log the reason and use the corresponding tls_default path. This is the same unresolved issue reported in the previous review.

🤖 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 `@crates/dhcp/src/lib.rs` around lines 78 - 98, Update the environment-variable
handling for forge_root_ca_path, forge_client_cert_path, and
forge_client_key_path to accept overrides only when their values are non-empty.
Treat Ok("") like an unset or unreadable variable, log the fallback reason, and
use the corresponding tls_default path.

Source: Path instructions

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

Duplicate comments:
In `@crates/dhcp/src/lib.rs`:
- Around line 78-98: Update the environment-variable handling for
forge_root_ca_path, forge_client_cert_path, and forge_client_key_path to accept
overrides only when their values are non-empty. Treat Ok("") like an unset or
unreadable variable, log the fallback reason, and use the corresponding
tls_default path.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 54a7f7c6-a0b3-42f5-a871-faf36889695e

📥 Commits

Reviewing files that changed from the base of the PR and between 8dd85cc and 13a6e36.

📒 Files selected for processing (18)
  • crates/api-core/src/handlers/credential.rs
  • crates/api-core/src/handlers/expected_machine.rs
  • crates/api-core/src/handlers/instance.rs
  • crates/api-core/src/handlers/power_shelf.rs
  • crates/api-core/src/handlers/rack.rs
  • crates/api-core/src/handlers/switch.rs
  • crates/api-db/src/lib.rs
  • crates/dhcp/src/lib.rs
  • crates/dsx-exchange-consumer/Cargo.toml
  • crates/dsx-exchange-consumer/src/health_updater.rs
  • crates/dsx-exchange-consumer/src/metrics.rs
  • crates/dsx-exchange-consumer/tests/metric_exposition.rs
  • crates/health/src/sink/log_file.rs
  • crates/machine-controller/src/handler/dpf.rs
  • crates/preingestion-manager/src/lib.rs
  • crates/site-explorer/src/lib.rs
  • crates/site-explorer/src/metrics.rs
  • docs/observability/core_metrics.md
🚧 Files skipped from review as they are similar to previous changes (13)
  • crates/dsx-exchange-consumer/Cargo.toml
  • crates/api-core/src/handlers/power_shelf.rs
  • crates/api-core/src/handlers/expected_machine.rs
  • crates/dsx-exchange-consumer/tests/metric_exposition.rs
  • crates/site-explorer/src/metrics.rs
  • crates/api-core/src/handlers/instance.rs
  • crates/api-core/src/handlers/rack.rs
  • crates/dsx-exchange-consumer/src/metrics.rs
  • crates/health/src/sink/log_file.rs
  • crates/dsx-exchange-consumer/src/health_updater.rs
  • docs/observability/core_metrics.md
  • crates/preingestion-manager/src/lib.rs
  • crates/site-explorer/src/lib.rs

@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

🧹 Nitpick comments (1)
crates/preingestion-manager/src/lib.rs (1)

2163-2167: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Model the capture source as an enum.

context has only two valid values, stdout and stderr capture failure. Accept an enum with Display instead of &str so future call sites cannot introduce arbitrary context values.

🤖 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 `@crates/preingestion-manager/src/lib.rs` around lines 2163 - 2167, Update
kill_and_reap to accept a dedicated enum representing the two capture-failure
sources, stdout and stderr, instead of an arbitrary &str context. Define or
reuse an enum implementing Display, update all call sites to pass the
appropriate variant, and preserve the existing context text in error reporting.

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 `@crates/api-core/src/handlers/instance.rs`:
- Around line 281-282: Move each handler’s rollback_or_log call before its
fallible query and conversion work so rollback failures are reported on all
early-return paths. Apply this pattern in
crates/api-core/src/handlers/instance.rs:281-282, credential.rs:529-530,
power_shelf.rs:141-142, rack.rs:128, and switch.rs:179-180, preserving each
existing rollback context message.

---

Nitpick comments:
In `@crates/preingestion-manager/src/lib.rs`:
- Around line 2163-2167: Update kill_and_reap to accept a dedicated enum
representing the two capture-failure sources, stdout and stderr, instead of an
arbitrary &str context. Define or reuse an enum implementing Display, update all
call sites to pass the appropriate variant, and preserve the existing context
text in error reporting.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 47f215b2-7846-4147-8559-a5966798b7d9

📥 Commits

Reviewing files that changed from the base of the PR and between 21a6430 and 13a6e36.

📒 Files selected for processing (18)
  • crates/api-core/src/handlers/credential.rs
  • crates/api-core/src/handlers/expected_machine.rs
  • crates/api-core/src/handlers/instance.rs
  • crates/api-core/src/handlers/power_shelf.rs
  • crates/api-core/src/handlers/rack.rs
  • crates/api-core/src/handlers/switch.rs
  • crates/api-db/src/lib.rs
  • crates/dhcp/src/lib.rs
  • crates/dsx-exchange-consumer/Cargo.toml
  • crates/dsx-exchange-consumer/src/health_updater.rs
  • crates/dsx-exchange-consumer/src/metrics.rs
  • crates/dsx-exchange-consumer/tests/metric_exposition.rs
  • crates/health/src/sink/log_file.rs
  • crates/machine-controller/src/handler/dpf.rs
  • crates/preingestion-manager/src/lib.rs
  • crates/site-explorer/src/lib.rs
  • crates/site-explorer/src/metrics.rs
  • docs/observability/core_metrics.md

Comment on lines +281 to +282
txn.rollback_or_log("read-only load of instances by id")
.await;

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.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the relevant handler sections with line numbers.
for f in \
  crates/api-core/src/handlers/instance.rs \
  crates/api-core/src/handlers/credential.rs \
  crates/api-core/src/handlers/power_shelf.rs \
  crates/api-core/src/handlers/rack.rs \
  crates/api-core/src/handlers/switch.rs
do
  echo "===== $f ====="
  sed -n '1,260p' "$f" | nl -ba | sed -n '1,260p' | tail -n +1
  echo
done

Repository: NVIDIA/infra-controller

Length of output: 255


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Focus on the exact ranges around the cited lines.
sed -n '240,310p' crates/api-core/src/handlers/instance.rs | nl -ba
echo "-----"
sed -n '500,560p' crates/api-core/src/handlers/credential.rs | nl -ba
echo "-----"
sed -n '110,170p' crates/api-core/src/handlers/power_shelf.rs | nl -ba
echo "-----"
sed -n '100,150p' crates/api-core/src/handlers/rack.rs | nl -ba
echo "-----"
sed -n '150,220p' crates/api-core/src/handlers/switch.rs | nl -ba

Repository: NVIDIA/infra-controller

Length of output: 201


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "rollback_or_log|fn rollback_or_log|impl .*rollback_or_log" crates/api-core/src crates -g '*.rs'

Repository: NVIDIA/infra-controller

Length of output: 1878


🏁 Script executed:

#!/bin/bash
set -euo pipefail

show() {
  f="$1"
  start="$2"
  end="$3"
  echo "===== $f:$start-$end ====="
  awk -v s="$start" -v e="$end" 'NR>=s && NR<=e { printf "%d:%s\n", NR, $0 }' "$f"
  echo
}

show crates/api-core/src/handlers/instance.rs 250 295
show crates/api-core/src/handlers/credential.rs 500 545
show crates/api-core/src/handlers/power_shelf.rs 120 155
show crates/api-core/src/handlers/rack.rs 110 140
show crates/api-core/src/handlers/switch.rs 160 205
show crates/api-db/src/lib.rs 680 735

Repository: NVIDIA/infra-controller

Length of output: 9537


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect Transaction drop/commit behavior and any auto-rollback path.
rg -n "impl Drop for Transaction|rollback\(\)|commit\(\)|auto-rollback|Drop for .*Transaction" crates/api-db/src/lib.rs crates/api-db/src -g '*.rs'

Repository: NVIDIA/infra-controller

Length of output: 9308


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the transaction type around the drop/commit implementation.
awk 'NR>=735 && NR<=860 { printf "%d:%s\n", NR, $0 }' crates/api-db/src/lib.rs

Repository: NVIDIA/infra-controller

Length of output: 4879


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find the Drop implementation and any references to implicit rollback.
rg -n "Drop for Transaction|drop\(self\)|rollback on drop|auto.*rollback|transaction.*drop" crates/api-db/src/lib.rs crates/api-db/src -g '*.rs'

Repository: NVIDIA/infra-controller

Length of output: 161


🌐 Web query:

sqlx Transaction drop automatically rolls back rollback errors ignored documentation

💡 Result:

In sqlx, when a Transaction object goes out of scope without being explicitly committed or rolled back, its Drop implementation triggers an automatic rollback [1][2]. Key points regarding this behavior include: 1. Automatic Rollback: If neither commit nor rollback is called before the transaction goes out of scope, the Transaction destructor (Drop) queues a rollback operation [1][2]. This is designed to ensure that pending transactions do not persist if an error occurs or the code path exits prematurely [1][2]. 2. Error Handling Limitations: Because the Drop trait in Rust is synchronous and cannot be async, the rollback operation triggered by dropping a transaction cannot be awaited [2][3]. Consequently, any errors that might occur during this automatic rollback cannot be captured or handled by your application code [3]. The rollback is effectively "fire-and-forget," and its success or failure remains opaque to the caller [2][3]. 3. Recommended Practice: Relying on the Drop implementation for rollback is considered a fallback mechanism rather than a best practice [3][4]. Since the automatic rollback relies on the underlying connection's state and future I/O, developers are encouraged to explicitly call.rollback.await or use helper functions like Connection::transaction (which handles commit/rollback automatically based on the result of the provided closure) to ensure predictable and reliable transaction lifecycle management [5][3]. 4. Performance Implications: Relying on Drop can lead to delayed rollbacks [3][4]. The rollback command is queued to be executed during the next asynchronous operation on that connection (or when the connection is returned to the pool and validated), which may result in database locks being held longer than necessary [3][4].

Citations:


🌐 Web query:

sqlx Transaction on Drop rollback silently ignore errors docs

💡 Result:

In sqlx, if a Transaction goes out of scope without an explicit call to commit or rollback, the transaction is automatically rolled back via its Drop implementation [1][2][3]. This automatic rollback occurs silently in the sense that the Drop trait cannot return a Result, and therefore it cannot propagate errors to the caller [2][3]. Because the rollback is initiated during the synchronous Drop method, sqlx queues a rollback operation using start_rollback, which is intended to be executed upon the next asynchronous invocation of the underlying connection (such as when the connection is returned to a connection pool) [2][3]. Key points regarding this behavior: - Automatic Rollback: If you do not explicitly commit or rollback, sqlx will ensure the transaction is aborted when the Transaction object is dropped [1][3]. - Error Handling: Because the automatic process happens in the drop implementation, any errors that occur during this background rollback cannot be caught or handled by your application code [2][3]. - Implementation: The library performs this by setting a flag in the connection state to trigger a rollback at the next available opportunity [2][3]. It is considered best practice to explicitly call commit on your transactions to ensure they are finalized successfully and to handle potential errors resulting from the commit operation [2][3].

Citations:


Move rollback_or_log ahead of the fallible work.

These read-only handlers return early on query/conversion errors, so the explicit rollback/log path is skipped and the transaction falls back to Drop’s silent rollback. That leaves rollback failures unreported on the error path. Apply the same pattern in the other listed handlers.

📍 Affects 5 files
  • crates/api-core/src/handlers/instance.rs#L281-L282 (this comment)
  • crates/api-core/src/handlers/credential.rs#L529-L530
  • crates/api-core/src/handlers/power_shelf.rs#L141-L142
  • crates/api-core/src/handlers/rack.rs#L128-L128
  • crates/api-core/src/handlers/switch.rs#L179-L180
🤖 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 `@crates/api-core/src/handlers/instance.rs` around lines 281 - 282, Move each
handler’s rollback_or_log call before its fallible query and conversion work so
rollback failures are reported on all early-return paths. Apply this pattern in
crates/api-core/src/handlers/instance.rs:281-282, credential.rs:529-530,
power_shelf.rs:141-142, rack.rs:128, and switch.rs:179-180, preserving each
existing rollback context message.

@chet

chet commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai full_review, thanks!

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

@chet Understood — I'll redo the full review of all changes in this PR now.

(づ。◕‿‿◕。)づ

✅ Action performed

Full review finished.

A handful of fallible operations were silent in both pipelines -- no log, no metric -- so observability couldn't see them at all, including a rack health-report persist failure discarded with `Err(_) => { /* will retry */ }`. Every one now logs, and the one whose failure rate is worth watching also counts.

Primary callouts are:

- `carbide_dsx_exchange_consumer_health_report_persist_failures_total` -- the dsx health-report persist failure now emits a single framework event that both warns and counts (the rack id and error ride as log context). It's the one failure here worth a rate, so it's the one that gets a counter.
- The silently-dropped failures now warn: the read-only and error-unwinding `txn.rollback()` drops across the handlers, now funneled through a shared `Transaction::rollback_or_log` helper; the DHCP TLS-material reads that fall back to a built-in default cert; the empty product serial fed into DPF; the preingestion subprocess kill/wait drops; and the health log-file flush, rotate, and prune drops. Each keeps its crate's logging convention, and the error-unwinding rollbacks log without clobbering the original error being returned.
- The site-explorer `bmc_reset_count` counted attempts, not successes: the ipmitool reset dropped its result with `.ok()` while the counter ticked regardless. A shared `record_bmc_reset_outcome` now increments only on success and logs the failure, and both the ipmitool and Redfish paths route through it -- so the counter finally means what its name says, with no rename.

The MQTT state-change send drop the issue pointed at turned out to be a test mock; the production send path already logs and meters every outcome, so nothing was left silent there.

Tests added!

This supports NVIDIA#3179
@chet

chet commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @coderabbitai -- done. kill_and_reap now takes a two-variant CaptureFailure enum (Stdout/Stderr) with a Display that renders the same strings, so the log output is unchanged and callers can't pass arbitrary values.

@chet

chet commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai full_review, thanks!

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

@chet Understood — I'll redo the full review of all changes in this PR now.

(⌐■_■)

✅ Action performed

Full review finished.

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Tip

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

@chet Thank you for tightening this up — using a two-variant CaptureFailure enum with a Display impl is a clean, type-safe way to constrain the possible log field values while keeping the rendered output identical. This closes the loop nicely on structured logging for the preingestion cleanup path.

Nothing further from me on this thread.

(づ。◕‿‿◕。)づ

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

♻️ Duplicate comments (3)
crates/health/src/sink/log_file.rs (2)

211-222: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Rotation still proceeds after a flush failure.

self.current_file.take() drops the writer before checking flush(); on failure the handle is gone and rotation continues, which can strand pending bytes while only logging a warning. This was flagged previously and remains unresolved.

🐛 Proposed fix
-        if let Some(mut file) = self.current_file.take()
-            && let Err(e) = file.flush()
-        {
-            tracing::warn!(error = %e, "failed to flush log file before rotation");
-        }
+        if let Some(file) = self.current_file.as_mut() {
+            if let Err(e) = file.flush() {
+                tracing::warn!(error = %e, "failed to flush log file before rotation");
+                return Err(format!("failed to flush log file before rotation: {e}"));
+            }
+        }
+        self.current_file = None;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/health/src/sink/log_file.rs` around lines 211 - 222, Update the
rotation logic around current_file.take() so a failed file.flush() returns or
otherwise aborts rotation immediately instead of only logging a warning.
Preserve the writer handle until the flush succeeds, ensuring pending bytes are
not stranded and the existing rotation steps run only after a successful flush.

236-254: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Still gating rename/remove on Path::exists() for two of three operations.

from.exists() (line 236) and oldest.exists() (line 251) mask real I/O errors (permissions, etc.) as silent absence, and are TOCTOU-prone. This was flagged previously; the current→.1 rename was fixed to call fs::rename directly, but the other two sites weren't.

🛡️ Proposed fix
-            if from.exists()
-                && let Err(e) = fs::rename(&from, &to)
-            {
-                tracing::warn!(error = %e, from = %from.display(), to = %to.display(), "failed to shift rotated log file");
-            }
+            if let Err(e) = fs::rename(&from, &to)
+                && e.kind() != std::io::ErrorKind::NotFound
+            {
+                tracing::warn!(error = %e, from = %from.display(), to = %to.display(), "failed to shift rotated log file");
+            }
...
-        if oldest.exists()
-            && let Err(e) = fs::remove_file(&oldest)
-        {
-            tracing::warn!(error = %e, path = %oldest.display(), "failed to prune oldest rotated log file");
+        if let Err(e) = fs::remove_file(&oldest)
+            && e.kind() != std::io::ErrorKind::NotFound
+        {
+            tracing::warn!(error = %e, path = %oldest.display(), "failed to prune oldest rotated log file");
         }
🤖 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 `@crates/health/src/sink/log_file.rs` around lines 236 - 254, Remove the
exists() guards around the rotation shift and oldest-backup pruning in the log
rotation logic, and call fs::rename(&from, &to) and fs::remove_file(&oldest)
unconditionally. Preserve the existing error warnings and path context, allowing
missing files and other filesystem errors to be handled through those results
consistently with the current_path-to-backup rename.
crates/dhcp/src/lib.rs (1)

78-98: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Empty FORGE_*_PATH values still bypass the fallback.

std::env::var returns Ok("") for an explicitly-empty variable, so all three branches (FORGE_ROOT_CAFILE_PATH, FORGE_CLIENT_CERT_PATH, FORGE_CLIENT_KEY_PATH) still skip the warning/fallback path and pass an empty path straight to build_forge_client_config. This was flagged on the prior commit and remains unresolved.

🛡️ Proposed fix
-            forge_root_ca_path: std::env::var("FORGE_ROOT_CAFILE_PATH").unwrap_or_else(|e| {
-                log::warn!(
-                    "FORGE_ROOT_CAFILE_PATH unset or unreadable ({e}); falling back to the built-in default root CA at {}",
-                    tls_default::ROOT_CA
-                );
-                tls_default::ROOT_CA.to_string()
-            }),
+            forge_root_ca_path: match std::env::var("FORGE_ROOT_CAFILE_PATH") {
+                Ok(path) if !path.is_empty() => path,
+                Ok(_) => {
+                    log::warn!(
+                        "FORGE_ROOT_CAFILE_PATH is empty; falling back to the built-in default root CA at {}",
+                        tls_default::ROOT_CA
+                    );
+                    tls_default::ROOT_CA.to_string()
+                }
+                Err(e) => {
+                    log::warn!(
+                        "FORGE_ROOT_CAFILE_PATH unset or unreadable ({e}); falling back to the built-in default root CA at {}",
+                        tls_default::ROOT_CA
+                    );
+                    tls_default::ROOT_CA.to_string()
+                }
+            },

Apply the same pattern to forge_client_cert_path and forge_client_key_path.

As per path instructions, "DHCP and networking code" configuration correctness should be reviewed for runtime 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 `@crates/dhcp/src/lib.rs` around lines 78 - 98, Update the three
environment-variable initializers for forge_root_ca_path,
forge_client_cert_path, and forge_client_key_path to treat an explicitly empty
FORGE_*_PATH value like an unset or unreadable value. Preserve the warning and
tls_default fallback behavior for empty values, while retaining non-empty
configured paths.

Source: Path instructions

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

Duplicate comments:
In `@crates/dhcp/src/lib.rs`:
- Around line 78-98: Update the three environment-variable initializers for
forge_root_ca_path, forge_client_cert_path, and forge_client_key_path to treat
an explicitly empty FORGE_*_PATH value like an unset or unreadable value.
Preserve the warning and tls_default fallback behavior for empty values, while
retaining non-empty configured paths.

In `@crates/health/src/sink/log_file.rs`:
- Around line 211-222: Update the rotation logic around current_file.take() so a
failed file.flush() returns or otherwise aborts rotation immediately instead of
only logging a warning. Preserve the writer handle until the flush succeeds,
ensuring pending bytes are not stranded and the existing rotation steps run only
after a successful flush.
- Around line 236-254: Remove the exists() guards around the rotation shift and
oldest-backup pruning in the log rotation logic, and call fs::rename(&from, &to)
and fs::remove_file(&oldest) unconditionally. Preserve the existing error
warnings and path context, allowing missing files and other filesystem errors to
be handled through those results consistently with the current_path-to-backup
rename.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 5ec51933-e5bc-4688-b6f8-f595669873c3

📥 Commits

Reviewing files that changed from the base of the PR and between b2ab553 and 0e283a9.

📒 Files selected for processing (18)
  • crates/api-core/src/handlers/credential.rs
  • crates/api-core/src/handlers/expected_machine.rs
  • crates/api-core/src/handlers/instance.rs
  • crates/api-core/src/handlers/power_shelf.rs
  • crates/api-core/src/handlers/rack.rs
  • crates/api-core/src/handlers/switch.rs
  • crates/api-db/src/lib.rs
  • crates/dhcp/src/lib.rs
  • crates/dsx-exchange-consumer/Cargo.toml
  • crates/dsx-exchange-consumer/src/health_updater.rs
  • crates/dsx-exchange-consumer/src/metrics.rs
  • crates/dsx-exchange-consumer/tests/metric_exposition.rs
  • crates/health/src/sink/log_file.rs
  • crates/machine-controller/src/handler/dpf.rs
  • crates/preingestion-manager/src/lib.rs
  • crates/site-explorer/src/lib.rs
  • crates/site-explorer/src/metrics.rs
  • docs/observability/core_metrics.md

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.

3 participants