refactor(telemetry): extract scan artifact I/O into `strix.io.scan_artifacts`

The 150-line ``Tracer.save_run_data`` mashed three concerns together:
opening file handles, formatting Markdown for vulnerabilities, and
writing the executive penetration-test report. None of that is
telemetry — it's pure on-disk artifact emission.

Extract to :class:`ScanArtifactWriter` in ``strix/io/scan_artifacts.py``:

- One writer per ``run_dir``, owns its own ``_saved_vuln_ids`` dedupe
  set so re-saves only emit new files.
- ``writer.save(vulnerability_reports=, final_scan_result=)`` is the
  only public entry point.
- ``_render_vulnerability_md`` is module-private and unit-testable in
  isolation.

``Tracer`` now lazily creates a single ``ScanArtifactWriter`` per
``run_dir`` and delegates ``save_run_data`` to it (~150 LoC body
collapses to ~10).

Net: tracer.py 422 → 327 LoC; new scan_artifacts.py 196 LoC. About
−95 LoC of mixed concerns, plus telemetry no longer carries file-I/O
responsibilities.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
0xallam
2026-04-25 16:28:07 -07:00
parent 494e6fab0d
commit 8f1f473eb8
4 changed files with 225 additions and 149 deletions
+3
View File
@@ -209,6 +209,9 @@ ignore = [
# Backend factories import their backend's deps lazily so deployments
# that pick a different backend don't need every backend's libs installed.
"strix/runtime/backends.py" = ["PLC0415"]
# The vulnerability MD renderer is a long monolithic format function;
# splitting per-section would obscure the structure without simplifying.
"strix/io/scan_artifacts.py" = ["PLR0912", "PLR0915", "PERF401"]
"strix/runtime/docker_client.py" = [
"TC002", # Manifest, Container imported for annotations
"TC003", # uuid imported for annotation