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.
- {voltry_probe-0.2.0 → voltry_probe-0.2.2}/.gitignore +13 -0
- {voltry_probe-0.2.0 → voltry_probe-0.2.2}/CHANGELOG.md +54 -0
- {voltry_probe-0.2.0 → voltry_probe-0.2.2}/PKG-INFO +1 -1
- {voltry_probe-0.2.0 → voltry_probe-0.2.2}/pyproject.toml +1 -1
- {voltry_probe-0.2.0 → voltry_probe-0.2.2}/tests/test_cli.py +19 -0
- voltry_probe-0.2.2/tests/test_live_source.py +573 -0
- {voltry_probe-0.2.0 → voltry_probe-0.2.2}/voltry_probe/__init__.py +3 -2
- {voltry_probe-0.2.0 → voltry_probe-0.2.2}/voltry_probe/cli.py +8 -2
- {voltry_probe-0.2.0 → voltry_probe-0.2.2}/voltry_probe/sources/__init__.py +2 -2
- {voltry_probe-0.2.0 → voltry_probe-0.2.2}/voltry_probe/sources/base.py +9 -0
- voltry_probe-0.2.2/voltry_probe/sources/live.py +526 -0
- voltry_probe-0.2.0/tests/test_live_source.py +0 -205
- voltry_probe-0.2.0/voltry_probe/sources/live.py +0 -255
- {voltry_probe-0.2.0 → voltry_probe-0.2.2}/CONTRIBUTING.md +0 -0
- {voltry_probe-0.2.0 → voltry_probe-0.2.2}/Dockerfile +0 -0
- {voltry_probe-0.2.0 → voltry_probe-0.2.2}/LICENSE +0 -0
- {voltry_probe-0.2.0 → voltry_probe-0.2.2}/README.md +0 -0
- {voltry_probe-0.2.0 → voltry_probe-0.2.2}/tests/conftest.py +0 -0
- {voltry_probe-0.2.0 → voltry_probe-0.2.2}/tests/fixtures/h100_read.json +0 -0
- {voltry_probe-0.2.0 → voltry_probe-0.2.2}/tests/test_attestation.py +0 -0
- {voltry_probe-0.2.0 → voltry_probe-0.2.2}/tests/test_builder.py +0 -0
- {voltry_probe-0.2.0 → voltry_probe-0.2.2}/tests/test_dcgm_reader.py +0 -0
- {voltry_probe-0.2.0 → voltry_probe-0.2.2}/tests/test_functional.py +0 -0
- {voltry_probe-0.2.0 → voltry_probe-0.2.2}/tests/test_monitor.py +0 -0
- {voltry_probe-0.2.0 → voltry_probe-0.2.2}/tests/test_nvml_reader.py +0 -0
- {voltry_probe-0.2.0 → voltry_probe-0.2.2}/tests/test_read_only.py +0 -0
- {voltry_probe-0.2.0 → voltry_probe-0.2.2}/tests/test_render.py +0 -0
- {voltry_probe-0.2.0 → voltry_probe-0.2.2}/tests/test_sources.py +0 -0
- {voltry_probe-0.2.0 → voltry_probe-0.2.2}/tests/test_tokens_asset.py +0 -0
- {voltry_probe-0.2.0 → voltry_probe-0.2.2}/voltry_probe/attestation/__init__.py +0 -0
- {voltry_probe-0.2.0 → voltry_probe-0.2.2}/voltry_probe/attestation/model.py +0 -0
- {voltry_probe-0.2.0 → voltry_probe-0.2.2}/voltry_probe/attestation/verify.py +0 -0
- {voltry_probe-0.2.0 → voltry_probe-0.2.2}/voltry_probe/evidence/__init__.py +0 -0
- {voltry_probe-0.2.0 → voltry_probe-0.2.2}/voltry_probe/evidence/builder.py +0 -0
- {voltry_probe-0.2.0 → voltry_probe-0.2.2}/voltry_probe/functional/__init__.py +0 -0
- {voltry_probe-0.2.0 → voltry_probe-0.2.2}/voltry_probe/functional/runner.py +0 -0
- {voltry_probe-0.2.0 → voltry_probe-0.2.2}/voltry_probe/monitor/__init__.py +0 -0
- {voltry_probe-0.2.0 → voltry_probe-0.2.2}/voltry_probe/monitor/monitor.py +0 -0
- {voltry_probe-0.2.0 → voltry_probe-0.2.2}/voltry_probe/py.typed +0 -0
- {voltry_probe-0.2.0 → voltry_probe-0.2.2}/voltry_probe/readers/__init__.py +0 -0
- {voltry_probe-0.2.0 → voltry_probe-0.2.2}/voltry_probe/readers/dcgm.py +0 -0
- {voltry_probe-0.2.0 → voltry_probe-0.2.2}/voltry_probe/readers/nvml.py +0 -0
- {voltry_probe-0.2.0 → voltry_probe-0.2.2}/voltry_probe/readers/redfish.py +0 -0
- {voltry_probe-0.2.0 → voltry_probe-0.2.2}/voltry_probe/readers/taxonomy.py +0 -0
- {voltry_probe-0.2.0 → voltry_probe-0.2.2}/voltry_probe/render/__init__.py +0 -0
- {voltry_probe-0.2.0 → voltry_probe-0.2.2}/voltry_probe/render/assets/certificate.css +0 -0
- {voltry_probe-0.2.0 → voltry_probe-0.2.2}/voltry_probe/render/assets/tokens.css +0 -0
- {voltry_probe-0.2.0 → voltry_probe-0.2.2}/voltry_probe/render/certificate.py +0 -0
- {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.
|
|
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.
|
|
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)])
|