voltry-probe 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,40 @@
1
+ """Voltry Probe — the open, read-only agent.
2
+
3
+ Read mode (Stage 3): read-only NVML/DCGM/Redfish mappers + real attestation verification
4
+ → a signed evidence bundle. Render (Stage 4), CLI + container (Stage 5), and the
5
+ functional/monitor modes (Stage 9) build on this.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from .attestation import AttestationOutcome, AttestationReport, verify_attestation
11
+ from .evidence import build_functional_bundle, build_monitor_bundle, build_read_bundle
12
+ from .functional import DrainConsentError, FunctionalRunner
13
+ from .monitor import MonitorSession, TelemetrySample
14
+ from .readers import map_dcgm, map_nvml, map_redfish, normalize_xid
15
+ from .render import render_certificate
16
+ from .sources import FixtureSource, RawCapture, RawSource
17
+
18
+ __version__ = "0.1.0"
19
+
20
+ __all__ = [
21
+ "__version__",
22
+ "build_read_bundle",
23
+ "build_functional_bundle",
24
+ "build_monitor_bundle",
25
+ "FunctionalRunner",
26
+ "DrainConsentError",
27
+ "MonitorSession",
28
+ "TelemetrySample",
29
+ "render_certificate",
30
+ "map_nvml",
31
+ "map_dcgm",
32
+ "map_redfish",
33
+ "normalize_xid",
34
+ "verify_attestation",
35
+ "AttestationReport",
36
+ "AttestationOutcome",
37
+ "FixtureSource",
38
+ "RawCapture",
39
+ "RawSource",
40
+ ]
@@ -0,0 +1,14 @@
1
+ """Attestation: real cryptographic verification of the device identity chain.
2
+
3
+ The verdict reflects the **real** chain state and is never stubbed as PASS (crypto-standards
4
+ skill, §security). ``UNVERIFIED`` when the root/PKI is unreachable; ``FAILED`` on a broken
5
+ chain or measurement; ``FALLBACK`` (lower confidence, marked) for parts without a hardware
6
+ root; ``VERIFIED`` only when the full chain checks out.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from .model import AttestationReport
12
+ from .verify import AttestationOutcome, verify_attestation
13
+
14
+ __all__ = ["AttestationReport", "AttestationOutcome", "verify_attestation"]
@@ -0,0 +1,33 @@
1
+ """The attestation report payload.
2
+
3
+ A deliberately small but faithful model of the NVIDIA Device Identity (over SPDM) chain:
4
+ a device identity key whose binding is signed by the NVIDIA root, plus a freshness-bound
5
+ measurement (VBIOS hash + nonce) signed by the device key. Verification (``verify.py``)
6
+ checks both signatures and the VBIOS hash — the same structure as the real chain
7
+ (root → device cert → signed measurement), without re-implementing X.509/SPDM transport.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from evidence_schema import IdentityScheme
13
+ from pydantic import BaseModel, ConfigDict, Field
14
+
15
+
16
+ class AttestationReport(BaseModel):
17
+ """Verbatim attestation report to be verified (never trusted blindly)."""
18
+
19
+ model_config = ConfigDict(extra="forbid")
20
+
21
+ scheme: IdentityScheme = Field(description="HARDWARE_ROOT or SECONDARY_FALLBACK.")
22
+ device_id: str = Field(description="Device identity id (the permanent ECC-384 id).")
23
+ device_public_key_b64: str = Field(
24
+ description="Device identity public key (SPKI DER, base64), P-384."
25
+ )
26
+ root_signature_b64: str = Field(
27
+ description="Root CA signature over the canonical (device_id, device_public_key) binding."
28
+ )
29
+ vbios_hash: str = Field(description="VBIOS measurement hash attested by this report.")
30
+ nonce: str = Field(description="Freshness nonce bound into the measurement signature.")
31
+ measurement_signature_b64: str = Field(
32
+ description="Device-key signature over the canonical (nonce, vbios_hash) measurement."
33
+ )
@@ -0,0 +1,190 @@
1
+ """Verify an attestation report against a trusted root — real ECDSA P-384 checks.
2
+
3
+ Outcome rules (never a stubbed PASS):
4
+ - no report → UNVERIFIED (authenticity NOT_ASSESSED)
5
+ - no trusted root provided → UNVERIFIED (chain unevaluable; authenticity NOT_ASSESSED)
6
+ - root signature invalid → FAILED (authenticity FAIL — disqualifying)
7
+ - measurement signature invalid → FAILED (authenticity FAIL — disqualifying)
8
+ - VBIOS hash != expected → reflash_detected=True, firmware FAIL (disqualifying)
9
+ - all checks pass, HARDWARE_ROOT→ VERIFIED (authenticity PASS, high confidence)
10
+ - all checks pass, FALLBACK → FALLBACK (authenticity PASS, lower confidence, marked)
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import base64
16
+ import binascii
17
+
18
+ import rfc8785
19
+ from cryptography.exceptions import InvalidSignature
20
+ from cryptography.hazmat.primitives import hashes
21
+ from cryptography.hazmat.primitives.asymmetric import ec
22
+ from evidence_schema import (
23
+ Attestation,
24
+ AttestationVerdict,
25
+ GateResult,
26
+ IdentityScheme,
27
+ )
28
+ from evidence_schema.sign import load_public_key_spki_b64
29
+ from evidence_schema.types import UtcDateTime, utcnow
30
+ from pydantic import BaseModel, ConfigDict
31
+
32
+ from .model import AttestationReport
33
+
34
+ _HASH = hashes.SHA384
35
+ # Confidence levels by outcome (documented, not magic): a hardware-rooted verified chain
36
+ # is near-certain; a secondary fallback is materially lower and explicitly marked.
37
+ _CONFIDENCE_VERIFIED = 0.99
38
+ _CONFIDENCE_FALLBACK = 0.60
39
+ _CONFIDENCE_UNVERIFIED = 0.0
40
+ _CONFIDENCE_FAILED = 0.0
41
+
42
+
43
+ class AttestationOutcome(BaseModel):
44
+ """The verified result: the certificate's Attestation block + derived gate inputs."""
45
+
46
+ model_config = ConfigDict(extra="forbid")
47
+
48
+ attestation: Attestation
49
+ reflash_detected: bool
50
+ authenticity_confidence: float
51
+ identity_scheme: IdentityScheme
52
+ vbios_hash: str | None
53
+ authenticity_gate: GateResult
54
+ firmware_gate: GateResult
55
+
56
+
57
+ def _root_statement_bytes(report: AttestationReport) -> bytes:
58
+ return rfc8785.dumps(
59
+ {"device_id": report.device_id, "device_public_key_b64": report.device_public_key_b64}
60
+ )
61
+
62
+
63
+ def _measurement_bytes(report: AttestationReport) -> bytes:
64
+ return rfc8785.dumps({"nonce": report.nonce, "vbios_hash": report.vbios_hash})
65
+
66
+
67
+ def _ecdsa_ok(public_key: ec.EllipticCurvePublicKey, signature_b64: str, payload: bytes) -> bool:
68
+ try:
69
+ public_key.verify(
70
+ base64.b64decode(signature_b64, validate=True), payload, ec.ECDSA(_HASH())
71
+ )
72
+ return True
73
+ except (InvalidSignature, ValueError, binascii.Error):
74
+ return False
75
+
76
+
77
+ def _unverified(
78
+ scheme: IdentityScheme,
79
+ detail: str,
80
+ *,
81
+ root_reachability: bool,
82
+ measured_at: UtcDateTime | None,
83
+ ) -> AttestationOutcome:
84
+ return AttestationOutcome(
85
+ attestation=Attestation(
86
+ scheme=scheme,
87
+ verdict=AttestationVerdict.UNVERIFIED,
88
+ root_reachability=root_reachability,
89
+ detail=detail,
90
+ measured_at=measured_at,
91
+ ),
92
+ reflash_detected=False,
93
+ authenticity_confidence=_CONFIDENCE_UNVERIFIED,
94
+ identity_scheme=scheme,
95
+ vbios_hash=None,
96
+ authenticity_gate=GateResult.NOT_ASSESSED,
97
+ firmware_gate=GateResult.NOT_ASSESSED,
98
+ )
99
+
100
+
101
+ def verify_attestation(
102
+ report: AttestationReport | None,
103
+ *,
104
+ trusted_root_public_key: ec.EllipticCurvePublicKey | None = None,
105
+ expected_vbios_hash: str | None = None,
106
+ measured_at: UtcDateTime | None = None,
107
+ ) -> AttestationOutcome:
108
+ """Verify ``report`` against ``trusted_root_public_key`` and return the real outcome."""
109
+ when = measured_at if measured_at is not None else utcnow()
110
+
111
+ if report is None:
112
+ return _unverified(
113
+ IdentityScheme.SECONDARY_FALLBACK,
114
+ "No attestation report available.",
115
+ root_reachability=False,
116
+ measured_at=when,
117
+ )
118
+ if trusted_root_public_key is None:
119
+ return _unverified(
120
+ report.scheme,
121
+ "Trusted root unreachable; attestation chain not evaluated.",
122
+ root_reachability=False,
123
+ measured_at=when,
124
+ )
125
+
126
+ # 1) Chain: the root must have signed the device-key binding.
127
+ root_ok = _ecdsa_ok(
128
+ trusted_root_public_key, report.root_signature_b64, _root_statement_bytes(report)
129
+ )
130
+ # 2) Measurement: the device key must have signed (nonce, vbios_hash).
131
+ measurement_ok = False
132
+ if root_ok:
133
+ try:
134
+ device_key = load_public_key_spki_b64(report.device_public_key_b64)
135
+ measurement_ok = _ecdsa_ok(
136
+ device_key, report.measurement_signature_b64, _measurement_bytes(report)
137
+ )
138
+ except (ValueError, binascii.Error):
139
+ measurement_ok = False
140
+
141
+ if not (root_ok and measurement_ok):
142
+ detail = "Root signature invalid." if not root_ok else "Measurement signature invalid."
143
+ return AttestationOutcome(
144
+ attestation=Attestation(
145
+ scheme=report.scheme,
146
+ verdict=AttestationVerdict.FAILED,
147
+ root_reachability=True,
148
+ detail=detail,
149
+ measured_at=when,
150
+ ),
151
+ reflash_detected=False,
152
+ authenticity_confidence=_CONFIDENCE_FAILED,
153
+ identity_scheme=report.scheme,
154
+ vbios_hash=report.vbios_hash,
155
+ authenticity_gate=GateResult.FAIL,
156
+ firmware_gate=GateResult.NOT_ASSESSED,
157
+ )
158
+
159
+ # 3) Re-flash detection: compare attested VBIOS hash to the known-good expectation.
160
+ reflash = expected_vbios_hash is not None and report.vbios_hash != expected_vbios_hash
161
+ firmware_gate = (
162
+ GateResult.FAIL
163
+ if reflash
164
+ else (GateResult.PASS if expected_vbios_hash is not None else GateResult.NOT_ASSESSED)
165
+ )
166
+
167
+ if report.scheme is IdentityScheme.HARDWARE_ROOT:
168
+ verdict, confidence = AttestationVerdict.VERIFIED, _CONFIDENCE_VERIFIED
169
+ detail = "Device Identity chain verified; measurement signature valid."
170
+ else:
171
+ verdict, confidence = AttestationVerdict.FALLBACK, _CONFIDENCE_FALLBACK
172
+ detail = "Secondary (no hardware root) scheme verified at lower confidence."
173
+
174
+ return AttestationOutcome(
175
+ attestation=Attestation(
176
+ scheme=report.scheme,
177
+ verdict=verdict,
178
+ root_reachability=True,
179
+ detail=detail + (" VBIOS re-flash detected." if reflash else ""),
180
+ measured_at=when,
181
+ ),
182
+ reflash_detected=reflash,
183
+ authenticity_confidence=confidence,
184
+ identity_scheme=report.scheme,
185
+ vbios_hash=report.vbios_hash,
186
+ # A verified-genuine chain passes authenticity even on the marked fallback scheme;
187
+ # a detected re-flash fails the firmware gate (disqualifying) but the part is still genuine.
188
+ authenticity_gate=GateResult.PASS,
189
+ firmware_gate=firmware_gate,
190
+ )
voltry_probe/cli.py ADDED
@@ -0,0 +1,214 @@
1
+ """``voltry`` — the probe CLI.
2
+
3
+ Three commands:
4
+ - ``voltry scan`` read-only capture → signed evidence bundle (works fully OFFLINE).
5
+ - ``voltry cert`` bundle → self-contained offline HTML certificate (works OFFLINE).
6
+ - ``voltry submit`` upload a signed bundle to the platform — **opt-in and separate**: it
7
+ is the only networked command, requires an explicit consent flag, and its HTTP client
8
+ is an optional dependency so scan/cert stay offline and dependency-light (§1.7).
9
+
10
+ No PII is collected: identity is device-level only; account linkage is server-side (NGC
11
+ GDPR posture).
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import platform
17
+ import sys
18
+ from pathlib import Path
19
+
20
+ import typer
21
+ from cryptography.hazmat.primitives import serialization
22
+ from cryptography.hazmat.primitives.asymmetric import ec
23
+ from evidence_schema import AgentInfo, EvidenceBundle, RunMode, generate_keypair, verify_bundle
24
+
25
+ from . import __version__
26
+ from .evidence import build_read_bundle
27
+ from .render import render_certificate
28
+ from .sources import FixtureSource
29
+
30
+ app = typer.Typer(
31
+ no_args_is_help=True,
32
+ add_completion=False,
33
+ help="Voltry Probe — read-only GPU condition & provenance (scan/cert offline; submit opt-in).",
34
+ )
35
+
36
+
37
+ def _version_callback(value: bool) -> None:
38
+ if value:
39
+ typer.echo(f"voltry-probe {__version__}")
40
+ raise typer.Exit()
41
+
42
+
43
+ @app.callback()
44
+ def _root(
45
+ version: bool = typer.Option(
46
+ False,
47
+ "--version",
48
+ callback=_version_callback,
49
+ is_eager=True,
50
+ help="Show the version and exit.",
51
+ ),
52
+ ) -> None:
53
+ """Voltry Probe — read-only GPU condition & provenance."""
54
+
55
+
56
+ def _load_signing_key(signing_key: Path | None, ephemeral: bool) -> ec.EllipticCurvePrivateKey:
57
+ """Load the operator signing key from PEM, or generate an ephemeral one if opted in."""
58
+ if signing_key is not None:
59
+ key = serialization.load_pem_private_key(signing_key.read_bytes(), password=None)
60
+ if not isinstance(key, ec.EllipticCurvePrivateKey):
61
+ raise typer.BadParameter("signing key must be an EC (P-384) private key")
62
+ return key
63
+ if ephemeral:
64
+ typer.echo(
65
+ "WARNING: using an ephemeral signing key — not a persistent operator identity; "
66
+ "for production pass --signing-key.",
67
+ err=True,
68
+ )
69
+ return generate_keypair()
70
+ raise typer.BadParameter("provide --signing-key PATH (PEM) or pass --ephemeral-key")
71
+
72
+
73
+ def _load_root_pubkey(path: Path | None) -> ec.EllipticCurvePublicKey | None:
74
+ if path is None:
75
+ return None
76
+ key = serialization.load_pem_public_key(path.read_bytes())
77
+ if not isinstance(key, ec.EllipticCurvePublicKey):
78
+ raise typer.BadParameter("trusted root must be an EC public key")
79
+ return key
80
+
81
+
82
+ @app.command()
83
+ def scan(
84
+ fixture: Path = typer.Option(
85
+ None, help="Captured RawCapture JSON (dev/sim). Omit to read live hardware."
86
+ ),
87
+ out: Path = typer.Option(
88
+ None, "--out", "-o", help="Write the signed bundle JSON here (default: stdout)."
89
+ ),
90
+ methodology: str = typer.Option(
91
+ "read-v0", help="Methodology version hash stamped into the bundle."
92
+ ),
93
+ signing_key: Path = typer.Option(
94
+ None, help="Operator EC P-384 private key (PEM) to sign the bundle."
95
+ ),
96
+ ephemeral_key: bool = typer.Option(
97
+ False, "--ephemeral-key", help="Sign with a throwaway key (dev only)."
98
+ ),
99
+ trusted_root: Path = typer.Option(
100
+ None, help="NVIDIA root EC public key (PEM) for attestation."
101
+ ),
102
+ expected_vbios: str = typer.Option(None, help="Known-good VBIOS hash for re-flash detection."),
103
+ ) -> None:
104
+ """Read the device (read-only) and emit a signed evidence bundle. Offline."""
105
+ key = _load_signing_key(signing_key, ephemeral_key)
106
+ root_pub = _load_root_pubkey(trusted_root)
107
+ if fixture is not None:
108
+ source = FixtureSource(fixture)
109
+ else: # pragma: no cover - requires a GPU + the [hardware] extra
110
+ from .sources.live import LiveSource
111
+
112
+ source = LiveSource()
113
+ capture = source.capture()
114
+ bundle = build_read_bundle(
115
+ capture,
116
+ signer_key=key,
117
+ agent=AgentInfo(
118
+ name="voltry-probe",
119
+ version=__version__,
120
+ run_mode=RunMode.READ,
121
+ host_arch=platform.machine(),
122
+ ),
123
+ methodology_version_hash=methodology,
124
+ trusted_root_public_key=root_pub,
125
+ expected_vbios_hash=expected_vbios,
126
+ signer_label="operator",
127
+ )
128
+ payload = bundle.model_dump_json(indent=2)
129
+ if out is None:
130
+ sys.stdout.write(payload + "\n")
131
+ else:
132
+ out.write_text(payload, encoding="utf-8")
133
+ typer.echo(f"wrote signed bundle: {out}", err=True)
134
+
135
+
136
+ @app.command()
137
+ def cert(
138
+ bundle: Path = typer.Argument(..., help="A signed evidence bundle JSON (from `voltry scan`)."),
139
+ out: Path = typer.Option(
140
+ None, "--out", "-o", help="Write the HTML certificate here (default: stdout)."
141
+ ),
142
+ ) -> None:
143
+ """Render a bundle to a self-contained, offline HTML certificate. Offline."""
144
+ try:
145
+ text = bundle.read_text(encoding="utf-8")
146
+ except OSError as exc:
147
+ typer.echo(f"ERROR: bundle file not found: {bundle}", err=True)
148
+ raise typer.Exit(2) from exc
149
+ try:
150
+ model = EvidenceBundle.model_validate_json(text)
151
+ except ValueError as exc:
152
+ typer.echo(f"ERROR: not a valid evidence bundle ({bundle}): {exc}", err=True)
153
+ raise typer.Exit(2) from exc
154
+ # A certificate is authoritative only if the signature actually verifies. Render an
155
+ # unverified bundle with a prominent watermark rather than refusing outright, but never
156
+ # let it look like proof (security §trust).
157
+ verified = model.signature is not None and verify_bundle(model)
158
+ if not verified:
159
+ typer.echo(
160
+ "WARNING: bundle signature is UNVERIFIED — the certificate is a rendered view, "
161
+ "not cryptographic proof.",
162
+ err=True,
163
+ )
164
+ html = render_certificate(model, verified=verified)
165
+ if out is None:
166
+ sys.stdout.write(html + "\n")
167
+ else:
168
+ out.write_text(html, encoding="utf-8")
169
+ typer.echo(f"wrote certificate: {out}", err=True)
170
+
171
+
172
+ @app.command()
173
+ def submit(
174
+ bundle: Path = typer.Argument(..., help="A signed evidence bundle JSON to upload."),
175
+ url: str = typer.Option(..., help="Platform ingest URL."),
176
+ i_consent_to_submit: bool = typer.Option(
177
+ False,
178
+ "--i-consent-to-submit",
179
+ help="Required. Uploading the signed bundle (including raw reads) leaves your premises.",
180
+ ),
181
+ ) -> None:
182
+ """Upload a signed bundle to the platform. OPT-IN and SEPARATE from scan/cert (§1.7)."""
183
+ if not i_consent_to_submit:
184
+ typer.echo(
185
+ "ERROR: submission is opt-in and separate from scan/cert. Nothing leaves your "
186
+ "premises without consent. Re-run with --i-consent-to-submit to upload the signed "
187
+ "bundle (which includes its raw reads) to the platform.",
188
+ err=True,
189
+ )
190
+ raise typer.Exit(2)
191
+ try:
192
+ import httpx
193
+ except ImportError as exc: # pragma: no cover - exercised only without the extra
194
+ typer.echo(
195
+ "submit requires the network extra: pip install 'voltry-probe[submit]'", err=True
196
+ )
197
+ raise typer.Exit(3) from exc
198
+ data = bundle.read_text(encoding="utf-8")
199
+ response = httpx.post( # pragma: no cover
200
+ url,
201
+ content=data,
202
+ headers={"content-type": "application/json"},
203
+ timeout=30.0, # bound the consent-gated upload; never hang on a slow endpoint
204
+ )
205
+ typer.echo(f"submitted to {url}: HTTP {response.status_code}") # pragma: no cover
206
+
207
+
208
+ def main() -> None:
209
+ """Console-script entry point (`voltry`)."""
210
+ app()
211
+
212
+
213
+ if __name__ == "__main__": # pragma: no cover - module run
214
+ main()
@@ -0,0 +1,11 @@
1
+ """Thin adapter over evidence-schema — assembles reader outputs into a signed bundle.
2
+
3
+ This never re-implements the contract; it imports the frozen models + signing from
4
+ evidence-schema and only orchestrates assembly.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from .builder import build_functional_bundle, build_monitor_bundle, build_read_bundle
10
+
11
+ __all__ = ["build_read_bundle", "build_functional_bundle", "build_monitor_bundle"]
@@ -0,0 +1,165 @@
1
+ """Assemble a Read-mode :class:`EvidenceBundle` from a capture, then sign it."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import json
7
+
8
+ from cryptography.hazmat.primitives.asymmetric import ec
9
+ from evidence_schema import (
10
+ AgentInfo,
11
+ DeterministicGates,
12
+ EvidenceBundle,
13
+ FunctionalBlock,
14
+ GateResult,
15
+ History,
16
+ IdentityBlock,
17
+ MeasuredBlock,
18
+ ProvenanceBlock,
19
+ RawPayload,
20
+ RawReads,
21
+ Tier,
22
+ sign_bundle,
23
+ )
24
+ from evidence_schema.types import UtcDateTime, utcnow
25
+
26
+ from ..attestation import AttestationReport, verify_attestation
27
+ from ..readers import map_dcgm, map_nvml, map_redfish
28
+ from ..sources.base import RawCapture
29
+
30
+
31
+ def _raw_payload(source: str, key: str, payload: object, captured_at: UtcDateTime) -> RawPayload:
32
+ """Wrap a verbatim source payload as a RawPayload with an integrity hash."""
33
+ content = json.dumps(payload, sort_keys=True, ensure_ascii=False) # not the signing path
34
+ return RawPayload(
35
+ source=source,
36
+ key=key,
37
+ format="json",
38
+ content=content,
39
+ sha256=hashlib.sha256(content.encode("utf-8")).hexdigest(),
40
+ captured_at=captured_at,
41
+ )
42
+
43
+
44
+ def build_read_bundle(
45
+ capture: RawCapture,
46
+ *,
47
+ signer_key: ec.EllipticCurvePrivateKey,
48
+ agent: AgentInfo,
49
+ methodology_version_hash: str,
50
+ trusted_root_public_key: ec.EllipticCurvePublicKey | None = None,
51
+ expected_vbios_hash: str | None = None,
52
+ signer_label: str | None = "operator",
53
+ created_at: UtcDateTime | None = None,
54
+ functional: FunctionalBlock | None = None,
55
+ tier: Tier = Tier.BRONZE,
56
+ history: History = History.RECONSTRUCTED,
57
+ born_on: UtcDateTime | None = None,
58
+ ) -> EvidenceBundle:
59
+ """Map a capture into a schema-valid, signed bundle.
60
+
61
+ Read mode (defaults) ⇒ Bronze, reconstructed history. Functional mode passes a
62
+ ``functional`` block (the functional gates derive from it) with ``tier=SILVER``;
63
+ Monitor mode passes ``tier=GOLD``, ``history=BORN_ON`` and a ``born_on`` timestamp.
64
+ Attestation is verified for real (never stubbed) regardless of mode.
65
+ """
66
+ when = created_at if created_at is not None else utcnow()
67
+
68
+ nvml_out = map_nvml(capture.nvml)
69
+ dcgm_out = map_dcgm(capture.dcgm)
70
+ redfish_out = map_redfish(capture.redfish)
71
+
72
+ report = AttestationReport.model_validate(capture.attestation) if capture.attestation else None
73
+ att = verify_attestation(
74
+ report,
75
+ trusted_root_public_key=trusted_root_public_key,
76
+ expected_vbios_hash=expected_vbios_hash,
77
+ measured_at=capture.captured_at,
78
+ )
79
+
80
+ ecc384_id = (report.device_id if report else None) or nvml_out.gpu_uuid or "UNKNOWN-IDENTITY"
81
+
82
+ identity = IdentityBlock(
83
+ device_part=nvml_out.device_part or nvml_out.device_model or "UNKNOWN",
84
+ device_model=nvml_out.device_model,
85
+ serial=nvml_out.serial or "UNKNOWN",
86
+ ecc384_id=ecc384_id,
87
+ gpu_uuid=nvml_out.gpu_uuid,
88
+ board_id=redfish_out.board_id,
89
+ attestation=att.attestation,
90
+ vbios_version=nvml_out.vbios_version,
91
+ vbios_hash=att.vbios_hash,
92
+ reflash_detected=att.reflash_detected,
93
+ identity_scheme=att.identity_scheme,
94
+ authenticity_confidence=att.authenticity_confidence,
95
+ gates=DeterministicGates(
96
+ authenticity=att.authenticity_gate,
97
+ firmware_vbios=att.firmware_gate,
98
+ # Functional gates derive from the functional block (functional mode); else
99
+ # NOT_ASSESSED (Read/Monitor mode is non-disruptive).
100
+ functional_burnin=functional.burnin_result if functional else GateResult.NOT_ASSESSED,
101
+ sdc_functional=functional.sdc_functional if functional else GateResult.NOT_ASSESSED,
102
+ data_sanitization=(
103
+ functional.sanitization.result if functional else GateResult.NOT_ASSESSED
104
+ ),
105
+ ),
106
+ )
107
+
108
+ measured = MeasuredBlock(
109
+ ecc=nvml_out.ecc,
110
+ xid=nvml_out.xid,
111
+ pages=nvml_out.pages,
112
+ spare_rows=nvml_out.spare_rows,
113
+ stability=nvml_out.stability,
114
+ thermals=nvml_out.thermals,
115
+ clock_power=nvml_out.clock_power,
116
+ nvlink=dcgm_out.nvlink,
117
+ pcie=dcgm_out.pcie,
118
+ )
119
+
120
+ payloads = [_raw_payload("nvml", "readout", capture.nvml, capture.captured_at)]
121
+ if capture.dcgm is not None:
122
+ payloads.append(_raw_payload("dcgm", "telemetry", capture.dcgm, capture.captured_at))
123
+ if capture.redfish is not None:
124
+ payloads.append(_raw_payload("redfish", "system", capture.redfish, capture.captured_at))
125
+ if capture.attestation is not None:
126
+ payloads.append(
127
+ _raw_payload("attestation", "report", capture.attestation, capture.captured_at)
128
+ )
129
+
130
+ bundle = EvidenceBundle(
131
+ created_at=when,
132
+ agent=agent,
133
+ methodology_version_hash=methodology_version_hash,
134
+ calibration_snapshot_id=None, # Phase 0: no modeled fields.
135
+ identity=identity,
136
+ measured=measured,
137
+ functional=functional,
138
+ raw_reads=RawReads(payloads=payloads),
139
+ environment=None, # No facility instrumentation ⇒ exposure Not Assessed.
140
+ provenance=ProvenanceBlock(
141
+ tier=tier,
142
+ history=history,
143
+ # Born-on (Monitor) implies a continuous chain; reconstructed implies gaps.
144
+ chain_gaps=history is not History.BORN_ON,
145
+ exposure_assessed=False,
146
+ born_on=born_on,
147
+ ),
148
+ )
149
+ return sign_bundle(bundle, signer_key, signer=signer_label)
150
+
151
+
152
+ def build_functional_bundle(
153
+ capture: RawCapture, *, functional: FunctionalBlock, **kwargs: object
154
+ ) -> EvidenceBundle:
155
+ """Functional mode (Silver): a bundle carrying the drained-unit functional results."""
156
+ return build_read_bundle(capture, functional=functional, tier=Tier.SILVER, **kwargs) # type: ignore[arg-type]
157
+
158
+
159
+ def build_monitor_bundle(
160
+ capture: RawCapture, *, born_on: UtcDateTime, **kwargs: object
161
+ ) -> EvidenceBundle:
162
+ """Monitor mode (Gold): a born-on bundle from continuous read-only capture."""
163
+ return build_read_bundle(
164
+ capture, tier=Tier.GOLD, history=History.BORN_ON, born_on=born_on, **kwargs # type: ignore[arg-type]
165
+ )
@@ -0,0 +1,13 @@
1
+ """Functional mode (Silver) — the ONLY package where device-mutating calls may live.
2
+
3
+ DCGM r4 diagnostics + burn-in + the SDC functional test stress and drain the GPU, so they
4
+ run exclusively here and only behind an explicit drain-consent flag. Everything outside
5
+ ``voltry_probe/functional/`` is read-only (invariant §1.1), enforced by the read-only
6
+ invariant test (which excludes this package) and the forbidden-patterns hook.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from .runner import DrainConsentError, FunctionalRunner
12
+
13
+ __all__ = ["FunctionalRunner", "DrainConsentError"]