voltry-probe 0.2.0__tar.gz → 0.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. {voltry_probe-0.2.0 → voltry_probe-0.2.2}/.gitignore +13 -0
  2. {voltry_probe-0.2.0 → voltry_probe-0.2.2}/CHANGELOG.md +54 -0
  3. {voltry_probe-0.2.0 → voltry_probe-0.2.2}/PKG-INFO +1 -1
  4. {voltry_probe-0.2.0 → voltry_probe-0.2.2}/pyproject.toml +1 -1
  5. {voltry_probe-0.2.0 → voltry_probe-0.2.2}/tests/test_cli.py +19 -0
  6. voltry_probe-0.2.2/tests/test_live_source.py +573 -0
  7. {voltry_probe-0.2.0 → voltry_probe-0.2.2}/voltry_probe/__init__.py +3 -2
  8. {voltry_probe-0.2.0 → voltry_probe-0.2.2}/voltry_probe/cli.py +8 -2
  9. {voltry_probe-0.2.0 → voltry_probe-0.2.2}/voltry_probe/sources/__init__.py +2 -2
  10. {voltry_probe-0.2.0 → voltry_probe-0.2.2}/voltry_probe/sources/base.py +9 -0
  11. voltry_probe-0.2.2/voltry_probe/sources/live.py +526 -0
  12. voltry_probe-0.2.0/tests/test_live_source.py +0 -205
  13. voltry_probe-0.2.0/voltry_probe/sources/live.py +0 -255
  14. {voltry_probe-0.2.0 → voltry_probe-0.2.2}/CONTRIBUTING.md +0 -0
  15. {voltry_probe-0.2.0 → voltry_probe-0.2.2}/Dockerfile +0 -0
  16. {voltry_probe-0.2.0 → voltry_probe-0.2.2}/LICENSE +0 -0
  17. {voltry_probe-0.2.0 → voltry_probe-0.2.2}/README.md +0 -0
  18. {voltry_probe-0.2.0 → voltry_probe-0.2.2}/tests/conftest.py +0 -0
  19. {voltry_probe-0.2.0 → voltry_probe-0.2.2}/tests/fixtures/h100_read.json +0 -0
  20. {voltry_probe-0.2.0 → voltry_probe-0.2.2}/tests/test_attestation.py +0 -0
  21. {voltry_probe-0.2.0 → voltry_probe-0.2.2}/tests/test_builder.py +0 -0
  22. {voltry_probe-0.2.0 → voltry_probe-0.2.2}/tests/test_dcgm_reader.py +0 -0
  23. {voltry_probe-0.2.0 → voltry_probe-0.2.2}/tests/test_functional.py +0 -0
  24. {voltry_probe-0.2.0 → voltry_probe-0.2.2}/tests/test_monitor.py +0 -0
  25. {voltry_probe-0.2.0 → voltry_probe-0.2.2}/tests/test_nvml_reader.py +0 -0
  26. {voltry_probe-0.2.0 → voltry_probe-0.2.2}/tests/test_read_only.py +0 -0
  27. {voltry_probe-0.2.0 → voltry_probe-0.2.2}/tests/test_render.py +0 -0
  28. {voltry_probe-0.2.0 → voltry_probe-0.2.2}/tests/test_sources.py +0 -0
  29. {voltry_probe-0.2.0 → voltry_probe-0.2.2}/tests/test_tokens_asset.py +0 -0
  30. {voltry_probe-0.2.0 → voltry_probe-0.2.2}/voltry_probe/attestation/__init__.py +0 -0
  31. {voltry_probe-0.2.0 → voltry_probe-0.2.2}/voltry_probe/attestation/model.py +0 -0
  32. {voltry_probe-0.2.0 → voltry_probe-0.2.2}/voltry_probe/attestation/verify.py +0 -0
  33. {voltry_probe-0.2.0 → voltry_probe-0.2.2}/voltry_probe/evidence/__init__.py +0 -0
  34. {voltry_probe-0.2.0 → voltry_probe-0.2.2}/voltry_probe/evidence/builder.py +0 -0
  35. {voltry_probe-0.2.0 → voltry_probe-0.2.2}/voltry_probe/functional/__init__.py +0 -0
  36. {voltry_probe-0.2.0 → voltry_probe-0.2.2}/voltry_probe/functional/runner.py +0 -0
  37. {voltry_probe-0.2.0 → voltry_probe-0.2.2}/voltry_probe/monitor/__init__.py +0 -0
  38. {voltry_probe-0.2.0 → voltry_probe-0.2.2}/voltry_probe/monitor/monitor.py +0 -0
  39. {voltry_probe-0.2.0 → voltry_probe-0.2.2}/voltry_probe/py.typed +0 -0
  40. {voltry_probe-0.2.0 → voltry_probe-0.2.2}/voltry_probe/readers/__init__.py +0 -0
  41. {voltry_probe-0.2.0 → voltry_probe-0.2.2}/voltry_probe/readers/dcgm.py +0 -0
  42. {voltry_probe-0.2.0 → voltry_probe-0.2.2}/voltry_probe/readers/nvml.py +0 -0
  43. {voltry_probe-0.2.0 → voltry_probe-0.2.2}/voltry_probe/readers/redfish.py +0 -0
  44. {voltry_probe-0.2.0 → voltry_probe-0.2.2}/voltry_probe/readers/taxonomy.py +0 -0
  45. {voltry_probe-0.2.0 → voltry_probe-0.2.2}/voltry_probe/render/__init__.py +0 -0
  46. {voltry_probe-0.2.0 → voltry_probe-0.2.2}/voltry_probe/render/assets/certificate.css +0 -0
  47. {voltry_probe-0.2.0 → voltry_probe-0.2.2}/voltry_probe/render/assets/tokens.css +0 -0
  48. {voltry_probe-0.2.0 → voltry_probe-0.2.2}/voltry_probe/render/certificate.py +0 -0
  49. {voltry_probe-0.2.0 → voltry_probe-0.2.2}/voltry_probe/sources/fixture.py +0 -0
@@ -48,3 +48,16 @@ next-env.d.ts
48
48
 
49
49
  # hypothesis example database
50
50
  .hypothesis/
51
+
52
+ # --- Field-capture run artifacts (tools/capture) ---
53
+ # Raw rental artifacts (logs, sidecars, ssh session records) stay local; verified
54
+ # bundles are published deliberately into apps/console/fixtures/captures/.
55
+ captures/runs/
56
+
57
+ # --- Census index + snapshots (tools/census) ---
58
+ # Marketplace inventory is large, host-identifying, and regenerated; keep it local.
59
+ captures/census/
60
+
61
+ # --- Local Phase-0 registry (tools/registry/seed_local.py) ---
62
+ # Seeded internal registry (SQLite DB + object store); regenerated from the field bundles.
63
+ captures/registry/
@@ -3,6 +3,60 @@
3
3
  All notable changes to `voltry-probe` are documented here. The format follows
4
4
  Keep a Changelog.
5
5
 
6
+ ## [0.2.2] - 2026-07-05
7
+
8
+ Measured-honesty fix in the NVLink reader, found by independent audit.
9
+
10
+ - NVLink error counters are now banked per counter, and only when actually
11
+ measured: the counter's NVML_NVLINK_ERROR_DL_* constant must resolve and at
12
+ least one per-link read must succeed. 0.2.1 guarded only the all-or-nothing
13
+ case; on current nvidia-ml-py (12.575.51, 13.610.43), which dropped
14
+ NVML_NVLINK_ERROR_DL_CRC but kept REPLAY/RECOVERY and the counter API, it
15
+ banked a fabricated crc_errors: 0 even though CRC was never read. The key is
16
+ now omitted (renders Not Assessed), matching how the probe treats all other
17
+ absent telemetry. Same treatment when the constants resolve but the device
18
+ refuses every per-link counter read.
19
+ - A genuinely-measured zero still banks as 0: a clean read is a valid fact.
20
+
21
+ ## [0.2.1] - 2026-07-05
22
+
23
+ Hardening for the live path on the full range of rental and consumer hardware,
24
+ based on the NVML per-device-class support matrix. Previously a live scan of a
25
+ device outside the certifiable envelope died with a raw driver traceback.
26
+
27
+ - Honest refusals: a live scan of a device that cannot expose the required
28
+ memory-health reads now raises `UnsupportedGpuError` (exported at package
29
+ level) with a diagnosis naming the device and the reason, and `voltry scan`
30
+ exits with a clean, specific error instead of a traceback. The cases:
31
+ - consumer GPUs with no ECC memory (GeForce class), disambiguated from
32
+ - ECC-capable devices with ECC currently disabled (common on workstation
33
+ cards such as the RTX A6000, which ship ECC-off), with the fix named;
34
+ - pre-Ampere devices with no row remapping (Tesla V100, T4, P100 class);
35
+ - MIG-enabled devices, where row remapping is unreadable even on the parent
36
+ handle.
37
+ - Board serial is now an optional read: on devices with no inforom serial
38
+ (GeForce class, some virtualized environments) the reader omits the field
39
+ instead of crashing; identity binds to the GPU UUID and the bundle records
40
+ serial "UNKNOWN". Never fabricated.
41
+ - A generic NVML error on a required read still propagates unchanged: a driver
42
+ fault stays a driver fault, distinct from an honest device-class refusal.
43
+ - More ratchet fields sealed on every successful scan: MIG state (closing the
44
+ gap where MIG appeared only in refusal messages), GPU core count, HBM memory
45
+ temperature via the field-values API (banked only when the per-field status
46
+ is clean; GDDR cards without the sensor omit it), and the per-bank
47
+ row-remapper histogram where the binding exposes it.
48
+ - NVLink capture survives current nvidia-ml-py builds, which dropped the legacy
49
+ error-counter API (the NVML_NVLINK_ERROR_DL_* constants and
50
+ nvmlDeviceGetNvLinkErrorCounter). Link state is still captured; the error
51
+ counters are omitted when the binding lacks the API, rendering Not Assessed
52
+ instead of crashing the scan. Found live on a rented A100 SXM4, where 0.2.0
53
+ dies with an AttributeError on any NVLink-bearing card.
54
+ - Refusal diagnoses only assert what was actually read: the pre-Ampere claim is
55
+ made only when the device architecture was read and predates Ampere, and the
56
+ no-ECC claim only on a clean NotSupported from the ECC-mode read (virtualized
57
+ guests that hide board-level reads get an honest "not readable here" message
58
+ instead of a fabricated device-class fact).
59
+
6
60
  ## [0.2.0] - 2026-07-04
7
61
 
8
62
  - Lifetime duty (the odometer) on the certificate: GPU-hours, thermal cycles,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: voltry-probe
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Voltry Probe — the open, read-only agent. Reads NVML/DCGM/Redfish/attestation into a signed evidence bundle and renders the offline certificate.
5
5
  License-Expression: Apache-2.0
6
6
  License-File: LICENSE
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "voltry-probe"
3
- version = "0.2.0"
3
+ version = "0.2.2"
4
4
  description = "Voltry Probe — the open, read-only agent. Reads NVML/DCGM/Redfish/attestation into a signed evidence bundle and renders the offline certificate."
5
5
  requires-python = ">=3.10"
6
6
  license = "Apache-2.0"
@@ -89,6 +89,25 @@ def test_scan_then_cert_offline(tmp_path):
89
89
  assert "UNVERIFIED" not in html # a signed, valid bundle renders as verified
90
90
 
91
91
 
92
+ def test_scan_live_refusal_is_clean_and_specific(monkeypatch):
93
+ # A live scan of a device outside the certifiable envelope (no ECC, pre-Ampere, MIG)
94
+ # must exit nonzero with the diagnostic reason, never a raw driver traceback.
95
+ from voltry_probe.sources import UnsupportedGpuError
96
+ from voltry_probe.sources.live import LiveSource
97
+
98
+ def _refuse(self):
99
+ raise UnsupportedGpuError(
100
+ "NVIDIA GeForce RTX 4090: this is a consumer-class GPU with no ECC memory."
101
+ )
102
+
103
+ monkeypatch.setattr(LiveSource, "capture", _refuse)
104
+ result = runner.invoke(app, ["scan", "--ephemeral-key"])
105
+ assert result.exit_code == 2
106
+ text = _text(result)
107
+ assert "no ECC" in text
108
+ assert "Traceback" not in text
109
+
110
+
92
111
  def test_scan_requires_a_signing_key():
93
112
  # No --signing-key and no --ephemeral-key → refuse (don't silently sign).
94
113
  result = runner.invoke(app, ["scan", "--fixture", str(FIXTURE)])