Skip to content

fix(libvirt): record suppressed native failures on the operator side - #1163

Open
doublewhy wants to merge 4 commits into
devfrom
fix/libvirt-failure-observability
Open

fix(libvirt): record suppressed native failures on the operator side#1163
doublewhy wants to merge 4 commits into
devfrom
fix/libvirt-failure-observability

Conversation

@doublewhy

Copy link
Copy Markdown

Plain-language summary

  • Problem: The libvirt backend collapses native errors into value-free portable diagnostics by design — but 25 broad except Exception/BaseException sites discarded the native failure entirely. Only 3 of 787 production files import logging, and raes_backend_libvirt had zero logging calls, so a failing real-libvirt run in the field could not be explained.
  • Fix: A backend-local observability seam that records the collapsed detail on the operator's side of the portable boundary, leaving the boundary itself untouched.

Summary

  • New raes_backend_libvirt/_observability.py: one package logger (logging.getLogger("raes_backend_libvirt"), no handlers — silent unless the embedding application configures it) and a shared message format.
  • Every broad suppression site (deployment.py ×6, techvault_native/_driver.py ×6, _define.py ×4, drivers/libvirt/_native.py ×3, techvault_lifecycle.py ×2, _initramfs.py ×2, guest_certified_driver.py ×1, _preflight.py ×1) now binds the exception and emits DEBUG with the enclosing operation and exc_info before collapsing to its portable diagnostic. Narrow control-flow excepts (KeyError, ValueError, …) are deliberately untouched.
  • _verify_and_finalize moves to techvault_native/_finalize.py as a driver-taking function (the established _define.py pattern) so _driver.py stays under the 500-line cap after instrumentation.

Design note

The value-free posture (native errno/strerror never crossing the portable boundary) is preserved exactly: diagnostics are unchanged, and DEBUG logs are operator-local. If a different level or logger topology is preferred, it's a two-line change in _observability.py.

Verification

  • Full hermetic suite green with the branch-aware coverage gate; the 366 libvirt/techvault-focused tests pass.
  • Ruff format and lint clean; tools/check_repo_policy.py pass (including the 500-line cap).

🤖 Generated with Claude Code

doublewhy and others added 4 commits August 14, 2026 17:12
The libvirt backend deliberately collapses native errors into value-free
portable diagnostics, but that left zero field observability: 25 broad
except-Exception/BaseException sites discarded the native failure
entirely, so a failing real-libvirt run could not be explained.

Add a backend-local logger (raes_backend_libvirt._observability, named
'raes_backend_libvirt', no handlers -- silent unless the embedding
application configures logging) and record every suppressed native
failure at DEBUG with the enclosing operation and full exc_info before
it collapses. The portable boundary is unchanged: diagnostics stay
value-free and no native detail crosses it.

_verify_and_finalize moves to techvault_native/_finalize.py (the
existing _define.py driver-function pattern) to keep _driver.py under
the 500-line cap after instrumentation.

Verification: full hermetic suite green with the coverage gate;
libvirt/techvault-focused tests (366) pass; ruff clean;
check_repo_policy pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The PR quality gate counted the new debug lines in never-exercised
exception arms as uncovered new code (74% vs the 80% floor) and flagged
_verify_and_finalize's eighth parameter after the relocation.

test_libvirt_failure_observability.py now forces all 25 instrumented
collapse sites with raising fakes, pinning both halves of the contract
per arm: the portable behavior is unchanged (value-free diagnostic,
None, or empty result) and the suppressed native failure is recorded on
the raes_backend_libvirt logger at DEBUG with the operation named and
exc_info attached. Every instrumented line is now covered by the
hermetic suite.

_verify_and_finalize takes the envelope/configuration digests as one
binding_digests tuple, returning the signature to seven parameters.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A ruff --fix pass ran without the project working directory, so its
first-party classification differed from the verify static lane and it
rewrote import blocks in sixteen unrelated test modules. Those edits
rode along in the coverage commit and the static lane rejects them.
Restore every unrelated test module to its dev content; the only test
change this branch carries is the new observability suite.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sonar S9073 requires one condition per assert so a failure names the
exact clause; split the eight and-joined assertions accordingly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant