diff --git a/Makefile b/Makefile index 5e914cc2a..37285c614 100644 --- a/Makefile +++ b/Makefile @@ -655,6 +655,9 @@ install-checks: .installed @echo "=== Checking sandbox probe contract ===" @$(MAKE) check-sandbox-probe-contract @echo "" + @echo "=== Checking nvattest authority ===" + @$(MAKE) check-nvattest-authority + @echo "" @echo "=== Running rust format check ===" @$(MAKE) check-rust-fmt @echo "" @@ -834,6 +837,12 @@ sandbox-probe-contract: check-sandbox-probe-contract: .installed $(VENV_BIN)/python scripts/build_sandbox_probe_contract.py --check +nvattest-authority: + $(VENV_BIN)/python scripts/build_nvattest_authority.py + +check-nvattest-authority: .installed + $(VENV_BIN)/python scripts/build_nvattest_authority.py --check + build-native-sol-grammar-oracle: .installed $(VENV_BIN)/python scripts/build_native_sol_authority_grammar.py diff --git a/scripts/build_nvattest_authority.py b/scripts/build_nvattest_authority.py new file mode 100644 index 000000000..e4e256b2c --- /dev/null +++ b/scripts/build_nvattest_authority.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: AGPL-3.0-only +# Copyright (c) 2026 sol pbc + +"""Build the committed nvattest authority v1 JSON mirror.""" + +from __future__ import annotations + +import argparse +import json +import sys +from pathlib import Path + +from solstone.think.providers import nvattest_authority + +ROOT = Path(__file__).resolve().parent.parent +ARTIFACT_PATH = ROOT / "solstone" / "think" / "providers" / "nvattest_authority_v1.json" + + +def render_nvattest_authority_json() -> str: + return ( + json.dumps(nvattest_authority.authority_payload(), indent=2, sort_keys=True) + + "\n" + ) + + +def write_outputs() -> None: + ARTIFACT_PATH.parent.mkdir(parents=True, exist_ok=True) + ARTIFACT_PATH.write_text(render_nvattest_authority_json(), encoding="utf-8") + print(f"wrote {ARTIFACT_PATH.relative_to(ROOT)}") + + +def check_outputs() -> int: + expected = render_nvattest_authority_json() + try: + actual = ARTIFACT_PATH.read_text(encoding="utf-8") + except FileNotFoundError: + actual = None + if actual != expected: + print( + "nvattest authority is stale: " + f"{ARTIFACT_PATH.relative_to(ROOT)}. Run: make nvattest-authority", + file=sys.stderr, + ) + return 1 + return 0 + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--check", action="store_true") + args = parser.parse_args() + if args.check: + return check_outputs() + write_outputs() + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/solstone/think/providers/nvattest_authority.py b/solstone/think/providers/nvattest_authority.py new file mode 100644 index 000000000..43e7523d8 --- /dev/null +++ b/solstone/think/providers/nvattest_authority.py @@ -0,0 +1,462 @@ +# SPDX-License-Identifier: AGPL-3.0-only +# Copyright (c) 2026 sol pbc + +"""Canonical nvattest release authority and platform resolution.""" + +from __future__ import annotations + +import platform +import re +import sys +from dataclasses import dataclass +from pathlib import PurePosixPath +from typing import Any, Literal, Mapping, cast + +SCHEMA_VERSION = 1 +NVATTEST_VERSION = "1.2.2-sol.2" +NVATTEST_FORK_COMMIT = "1c95b1f5ae72b5f4282f7b6b96722f7c8d69f744" +NVATTEST_UPSTREAM_BASE = "73c032ebff680ca6d2ba06f4006b511491b71ce9" +NVATTEST_URL_PREFIX = "https://updates.solstone.app/providers/nvattest/" + +NvattestTargetKey = Literal["linux-x86_64", "linux-aarch64", "macos-arm64"] +NvattestMemberKind = Literal["regular", "symlink"] + +TARGET_KEYS: tuple[NvattestTargetKey, ...] = ( + "linux-x86_64", + "linux-aarch64", + "macos-arm64", +) +_HASH_RE = re.compile(r"^[0-9a-f]{64}$") + + +@dataclass(frozen=True, slots=True) +class NvattestSourceIdentity: + version: str + fork_commit: str + upstream_base: str + url_prefix: str + + def to_payload(self) -> dict[str, str]: + return { + "fork_commit": self.fork_commit, + "upstream_base": self.upstream_base, + "url_prefix": self.url_prefix, + "version": self.version, + } + + +@dataclass(frozen=True, slots=True) +class NvattestArtifactIdentity: + name: str + url: str + size_bytes: int + sha256: str + + def to_payload(self) -> dict[str, object]: + return { + "name": self.name, + "sha256": self.sha256, + "size_bytes": self.size_bytes, + "url": self.url, + } + + +@dataclass(frozen=True, slots=True) +class NvattestManifestIdentity: + name: str + url: str + sha256: str + + def to_payload(self) -> dict[str, str]: + return { + "name": self.name, + "sha256": self.sha256, + "url": self.url, + } + + +@dataclass(frozen=True, slots=True) +class NvattestInventoryMember: + kind: NvattestMemberKind + relpath: str + symlink_target: str | None + executable: bool + + def to_payload(self) -> dict[str, object]: + return { + "executable": self.executable, + "kind": self.kind, + "relpath": self.relpath, + "symlink_target": self.symlink_target, + } + + +@dataclass(frozen=True, slots=True) +class NvattestTargetEntry: + key: NvattestTargetKey + source: NvattestSourceIdentity + artifact: NvattestArtifactIdentity + companion_manifest: NvattestManifestIdentity + inventory: tuple[NvattestInventoryMember, ...] + + def to_payload(self) -> dict[str, object]: + return { + "artifact": self.artifact.to_payload(), + "companion_manifest": self.companion_manifest.to_payload(), + "inventory": [member.to_payload() for member in self.inventory], + "source": self.source.to_payload(), + } + + +SOURCE_IDENTITY = NvattestSourceIdentity( + version=NVATTEST_VERSION, + fork_commit=NVATTEST_FORK_COMMIT, + upstream_base=NVATTEST_UPSTREAM_BASE, + url_prefix=NVATTEST_URL_PREFIX, +) + + +def _artifact(name: str, *, size_bytes: int, sha256: str) -> NvattestArtifactIdentity: + return NvattestArtifactIdentity( + name=name, + url=f"{NVATTEST_URL_PREFIX}{name}", + size_bytes=size_bytes, + sha256=sha256, + ) + + +def _manifest(name: str, *, sha256: str) -> NvattestManifestIdentity: + return NvattestManifestIdentity( + name=name, + url=f"{NVATTEST_URL_PREFIX}{name}", + sha256=sha256, + ) + + +def _regular(relpath: str, *, executable: bool = False) -> NvattestInventoryMember: + return NvattestInventoryMember( + kind="regular", + relpath=relpath, + symlink_target=None, + executable=executable, + ) + + +def _symlink(relpath: str, target: str) -> NvattestInventoryMember: + return NvattestInventoryMember( + kind="symlink", + relpath=relpath, + symlink_target=target, + executable=False, + ) + + +def _linux_inventory() -> tuple[NvattestInventoryMember, ...]: + return ( + _regular("bin/nvattest", executable=True), + _regular("lib/libnvat.so.1.2.2"), + _symlink("lib/libnvat.so.1", "libnvat.so.1.2.2"), + _symlink("lib/libnvat.so", "libnvat.so.1"), + _regular("LICENSE"), + _regular("share/ca/ca-bundle.pem"), + _regular("share/THIRD_PARTY_NOTICES.md"), + ) + + +MACOS_INVENTORY: tuple[NvattestInventoryMember, ...] = ( + _regular("bin/nvattest", executable=True), + _regular("lib/libnvat.1.2.2.dylib"), + _symlink("lib/libnvat.1.dylib", "libnvat.1.2.2.dylib"), + _symlink("lib/libnvat.dylib", "libnvat.1.dylib"), + _regular("LICENSE"), + _regular("share/ca/ca-bundle.pem"), + _regular("share/THIRD_PARTY_NOTICES.md"), +) + +LINUX_INVENTORY = _linux_inventory() + +NVATTEST_AUTHORITY: Mapping[NvattestTargetKey, NvattestTargetEntry] = { + "linux-x86_64": NvattestTargetEntry( + key="linux-x86_64", + source=SOURCE_IDENTITY, + artifact=_artifact( + "libnvat-linux-x86_64-1.2.2-sol.2-archive.tar.xz", + size_bytes=7655628, + sha256="3e2d207a3bb6eab9c47fc9cf65d7990f0d11541ff8691dce18e786e2ce9b26c7", + ), + companion_manifest=_manifest( + "libnvat-linux-x86_64-1.2.2-sol.2-archive.manifest.json", + sha256="a013d18a74d89f3ee99a0a3648c582df08446a30f5b8f5bbb48929a08fad4a8f", + ), + inventory=LINUX_INVENTORY, + ), + "linux-aarch64": NvattestTargetEntry( + key="linux-aarch64", + source=SOURCE_IDENTITY, + artifact=_artifact( + "libnvat-linux-aarch64-1.2.2-sol.2-archive.tar.xz", + size_bytes=7423268, + sha256="7a13a15192f5005a700dbe154da28cbc2a2b5f3f387113c8cd89a36083a2bd80", + ), + companion_manifest=_manifest( + "libnvat-linux-aarch64-1.2.2-sol.2-archive.manifest.json", + sha256="0d46ffdf21a9c5a61370fbf77aed0c275dae93f3609511f4573515a7260ddc39", + ), + inventory=LINUX_INVENTORY, + ), + "macos-arm64": NvattestTargetEntry( + key="macos-arm64", + source=SOURCE_IDENTITY, + artifact=_artifact( + "libnvat-macos-arm64-1.2.2-sol.2-archive.tar.xz", + size_bytes=5848264, + sha256="f2b01f60ee52f9c38c9e5e0f3cea6e8078a1efa165fad1f5c59994563120e365", + ), + companion_manifest=_manifest( + "libnvat-macos-arm64-1.2.2-sol.2-archive.manifest.json", + sha256="dca5342f87bba1244f8ee78848207410ae5ee8976df42fe10d804f1c99167094", + ), + inventory=MACOS_INVENTORY, + ), +} + + +def nvattest_target_key( + os_name: str | None = None, + arch: str | None = None, +) -> NvattestTargetKey | None: + if os_name is None: + os_name = "linux" if sys.platform.startswith("linux") else sys.platform + if arch is None: + arch = platform.machine() + normalized_arch = arch.lower() + if os_name == "linux" and normalized_arch in {"x86_64", "amd64", "x64"}: + return "linux-x86_64" + if os_name == "linux" and normalized_arch in {"aarch64", "arm64"}: + return "linux-aarch64" + if os_name == "darwin" and normalized_arch == "arm64": + return "macos-arm64" + return None + + +def authority_entry(target_key: NvattestTargetKey) -> NvattestTargetEntry: + return NVATTEST_AUTHORITY[target_key] + + +def authority_payload() -> dict[str, object]: + return { + "schema_version": SCHEMA_VERSION, + "targets": {key: NVATTEST_AUTHORITY[key].to_payload() for key in TARGET_KEYS}, + } + + +def validate_authority_payload(payload: Mapping[str, Any]) -> None: + if payload.get("schema_version") != SCHEMA_VERSION: + raise ValueError("nvattest authority schema_version must be 1") + targets = payload.get("targets") + if not isinstance(targets, Mapping): + raise ValueError("nvattest authority targets must be an object") + target_keys = set(targets) + expected_keys = set(TARGET_KEYS) + if target_keys != expected_keys: + raise ValueError("nvattest authority target set is not exact") + + artifact_names: set[str] = set() + artifact_urls: set[str] = set() + artifact_hashes: set[str] = set() + manifest_names: set[str] = set() + manifest_urls: set[str] = set() + manifest_hashes: set[str] = set() + sources: set[tuple[str, str, str, str]] = set() + for target_key in TARGET_KEYS: + raw_entry = targets[target_key] + if not isinstance(raw_entry, Mapping): + raise ValueError("nvattest authority target entry must be an object") + source = _mapping(raw_entry.get("source"), "source") + artifact = _mapping(raw_entry.get("artifact"), "artifact") + companion_manifest = _mapping( + raw_entry.get("companion_manifest"), + "companion_manifest", + ) + inventory = raw_entry.get("inventory") + if not isinstance(inventory, list): + raise ValueError("nvattest authority inventory must be a list") + + source_tuple = _validate_source(source) + sources.add(source_tuple) + _validate_artifact(target_key, source, artifact) + _validate_manifest(target_key, source, companion_manifest) + _validate_inventory(target_key, inventory) + + _add_unique(artifact_names, str(artifact["name"]), "artifact name") + _add_unique(artifact_urls, str(artifact["url"]), "artifact url") + _add_unique(artifact_hashes, str(artifact["sha256"]), "artifact sha256") + _add_unique(manifest_names, str(companion_manifest["name"]), "manifest name") + _add_unique(manifest_urls, str(companion_manifest["url"]), "manifest url") + _add_unique( + manifest_hashes, + str(companion_manifest["sha256"]), + "manifest sha256", + ) + if len(sources) != 1: + raise ValueError("nvattest authority source identity drifts across targets") + + +def _mapping(value: object, label: str) -> Mapping[str, Any]: + if not isinstance(value, Mapping): + raise ValueError(f"nvattest authority {label} must be an object") + return value + + +def _validate_source(source: Mapping[str, Any]) -> tuple[str, str, str, str]: + version = source.get("version") + fork_commit = source.get("fork_commit") + upstream_base = source.get("upstream_base") + url_prefix = source.get("url_prefix") + expected = ( + NVATTEST_VERSION, + NVATTEST_FORK_COMMIT, + NVATTEST_UPSTREAM_BASE, + NVATTEST_URL_PREFIX, + ) + observed = (version, fork_commit, upstream_base, url_prefix) + if observed != expected: + raise ValueError("nvattest authority source identity drifted") + return cast(tuple[str, str, str, str], observed) + + +def _validate_artifact( + target_key: NvattestTargetKey, + source: Mapping[str, Any], + artifact: Mapping[str, Any], +) -> None: + name = artifact.get("name") + url = artifact.get("url") + size_bytes = artifact.get("size_bytes") + sha256 = artifact.get("sha256") + if name != f"libnvat-{target_key}-{source['version']}-archive.tar.xz": + raise ValueError("nvattest artifact name does not match target/version") + if url != f"{source['url_prefix']}{name}" or not str(url).startswith("https://"): + raise ValueError("nvattest artifact url is not the pinned HTTPS url") + if ( + isinstance(size_bytes, bool) + or not isinstance(size_bytes, int) + or size_bytes <= 0 + ): + raise ValueError("nvattest artifact size_bytes must be positive") + _validate_hash(sha256, "artifact sha256") + + +def _validate_manifest( + target_key: NvattestTargetKey, + source: Mapping[str, Any], + companion_manifest: Mapping[str, Any], +) -> None: + name = companion_manifest.get("name") + url = companion_manifest.get("url") + sha256 = companion_manifest.get("sha256") + if name != f"libnvat-{target_key}-{source['version']}-archive.manifest.json": + raise ValueError("nvattest manifest name does not match target/version") + if url != f"{source['url_prefix']}{name}" or not str(url).startswith("https://"): + raise ValueError("nvattest manifest url is not the pinned HTTPS url") + _validate_hash(sha256, "manifest sha256") + + +def _validate_hash(value: object, label: str) -> None: + if not isinstance(value, str) or _HASH_RE.fullmatch(value) is None: + raise ValueError(f"nvattest {label} must be lowercase 64 hex") + + +def _validate_inventory(target_key: NvattestTargetKey, inventory: list[object]) -> None: + expected = _expected_inventory_payload(target_key) + observed: dict[str, Mapping[str, Any]] = {} + for raw_member in inventory: + member = _mapping(raw_member, "inventory member") + relpath = member.get("relpath") + kind = member.get("kind") + symlink_target = member.get("symlink_target") + executable = member.get("executable") + if kind not in {"regular", "symlink"}: + raise ValueError("nvattest inventory kind must be regular or symlink") + if not isinstance(relpath, str): + raise ValueError("nvattest inventory relpath must be a string") + _validate_relative_posix_path(relpath, "inventory relpath") + if kind == "regular": + if symlink_target is not None: + raise ValueError("nvattest regular member symlink_target must be null") + else: + if not isinstance(symlink_target, str): + raise ValueError("nvattest symlink member target must be a string") + _validate_relative_link_target(symlink_target) + if executable is not False: + raise ValueError("nvattest symlink members must not be executable") + if not isinstance(executable, bool): + raise ValueError("nvattest inventory executable must be a boolean") + if relpath in observed: + raise ValueError("nvattest inventory contains duplicate relpath") + observed[relpath] = member + if set(observed) != set(expected): + raise ValueError("nvattest inventory member set is not exact") + for relpath, expected_member in expected.items(): + actual = observed[relpath] + expected_payload = expected_member.to_payload() + if dict(actual) != expected_payload: + raise ValueError("nvattest inventory member does not match target platform") + + +def _expected_inventory_payload( + target_key: NvattestTargetKey, +) -> dict[str, NvattestInventoryMember]: + inventory = MACOS_INVENTORY if target_key == "macos-arm64" else LINUX_INVENTORY + return {member.relpath: member for member in inventory} + + +def _validate_relative_posix_path(value: str, label: str) -> None: + path = PurePosixPath(value) + if ( + not value + or path.is_absolute() + or ".." in path.parts + or path.as_posix() != value + ): + raise ValueError(f"nvattest {label} must be a safe relative POSIX path") + + +def _validate_relative_link_target(value: str) -> None: + path = PurePosixPath(value) + if ( + not value + or path.is_absolute() + or ".." in path.parts + or path.as_posix() != value + ): + raise ValueError("nvattest symlink target must stay inside the tree") + + +def _add_unique(seen: set[str], value: str, label: str) -> None: + if value in seen: + raise ValueError(f"nvattest authority duplicate {label}") + seen.add(value) + + +validate_authority_payload(authority_payload()) + +__all__ = [ + "NVATTEST_AUTHORITY", + "NVATTEST_FORK_COMMIT", + "NVATTEST_UPSTREAM_BASE", + "NVATTEST_URL_PREFIX", + "NVATTEST_VERSION", + "NvattestArtifactIdentity", + "NvattestInventoryMember", + "NvattestManifestIdentity", + "NvattestMemberKind", + "NvattestSourceIdentity", + "NvattestTargetEntry", + "NvattestTargetKey", + "TARGET_KEYS", + "authority_entry", + "authority_payload", + "nvattest_target_key", + "validate_authority_payload", +] diff --git a/solstone/think/providers/nvattest_authority_v1.json b/solstone/think/providers/nvattest_authority_v1.json new file mode 100644 index 000000000..d4b58c472 --- /dev/null +++ b/solstone/think/providers/nvattest_authority_v1.json @@ -0,0 +1,194 @@ +{ + "schema_version": 1, + "targets": { + "linux-aarch64": { + "artifact": { + "name": "libnvat-linux-aarch64-1.2.2-sol.2-archive.tar.xz", + "sha256": "7a13a15192f5005a700dbe154da28cbc2a2b5f3f387113c8cd89a36083a2bd80", + "size_bytes": 7423268, + "url": "https://updates.solstone.app/providers/nvattest/libnvat-linux-aarch64-1.2.2-sol.2-archive.tar.xz" + }, + "companion_manifest": { + "name": "libnvat-linux-aarch64-1.2.2-sol.2-archive.manifest.json", + "sha256": "0d46ffdf21a9c5a61370fbf77aed0c275dae93f3609511f4573515a7260ddc39", + "url": "https://updates.solstone.app/providers/nvattest/libnvat-linux-aarch64-1.2.2-sol.2-archive.manifest.json" + }, + "inventory": [ + { + "executable": true, + "kind": "regular", + "relpath": "bin/nvattest", + "symlink_target": null + }, + { + "executable": false, + "kind": "regular", + "relpath": "lib/libnvat.so.1.2.2", + "symlink_target": null + }, + { + "executable": false, + "kind": "symlink", + "relpath": "lib/libnvat.so.1", + "symlink_target": "libnvat.so.1.2.2" + }, + { + "executable": false, + "kind": "symlink", + "relpath": "lib/libnvat.so", + "symlink_target": "libnvat.so.1" + }, + { + "executable": false, + "kind": "regular", + "relpath": "LICENSE", + "symlink_target": null + }, + { + "executable": false, + "kind": "regular", + "relpath": "share/ca/ca-bundle.pem", + "symlink_target": null + }, + { + "executable": false, + "kind": "regular", + "relpath": "share/THIRD_PARTY_NOTICES.md", + "symlink_target": null + } + ], + "source": { + "fork_commit": "1c95b1f5ae72b5f4282f7b6b96722f7c8d69f744", + "upstream_base": "73c032ebff680ca6d2ba06f4006b511491b71ce9", + "url_prefix": "https://updates.solstone.app/providers/nvattest/", + "version": "1.2.2-sol.2" + } + }, + "linux-x86_64": { + "artifact": { + "name": "libnvat-linux-x86_64-1.2.2-sol.2-archive.tar.xz", + "sha256": "3e2d207a3bb6eab9c47fc9cf65d7990f0d11541ff8691dce18e786e2ce9b26c7", + "size_bytes": 7655628, + "url": "https://updates.solstone.app/providers/nvattest/libnvat-linux-x86_64-1.2.2-sol.2-archive.tar.xz" + }, + "companion_manifest": { + "name": "libnvat-linux-x86_64-1.2.2-sol.2-archive.manifest.json", + "sha256": "a013d18a74d89f3ee99a0a3648c582df08446a30f5b8f5bbb48929a08fad4a8f", + "url": "https://updates.solstone.app/providers/nvattest/libnvat-linux-x86_64-1.2.2-sol.2-archive.manifest.json" + }, + "inventory": [ + { + "executable": true, + "kind": "regular", + "relpath": "bin/nvattest", + "symlink_target": null + }, + { + "executable": false, + "kind": "regular", + "relpath": "lib/libnvat.so.1.2.2", + "symlink_target": null + }, + { + "executable": false, + "kind": "symlink", + "relpath": "lib/libnvat.so.1", + "symlink_target": "libnvat.so.1.2.2" + }, + { + "executable": false, + "kind": "symlink", + "relpath": "lib/libnvat.so", + "symlink_target": "libnvat.so.1" + }, + { + "executable": false, + "kind": "regular", + "relpath": "LICENSE", + "symlink_target": null + }, + { + "executable": false, + "kind": "regular", + "relpath": "share/ca/ca-bundle.pem", + "symlink_target": null + }, + { + "executable": false, + "kind": "regular", + "relpath": "share/THIRD_PARTY_NOTICES.md", + "symlink_target": null + } + ], + "source": { + "fork_commit": "1c95b1f5ae72b5f4282f7b6b96722f7c8d69f744", + "upstream_base": "73c032ebff680ca6d2ba06f4006b511491b71ce9", + "url_prefix": "https://updates.solstone.app/providers/nvattest/", + "version": "1.2.2-sol.2" + } + }, + "macos-arm64": { + "artifact": { + "name": "libnvat-macos-arm64-1.2.2-sol.2-archive.tar.xz", + "sha256": "f2b01f60ee52f9c38c9e5e0f3cea6e8078a1efa165fad1f5c59994563120e365", + "size_bytes": 5848264, + "url": "https://updates.solstone.app/providers/nvattest/libnvat-macos-arm64-1.2.2-sol.2-archive.tar.xz" + }, + "companion_manifest": { + "name": "libnvat-macos-arm64-1.2.2-sol.2-archive.manifest.json", + "sha256": "dca5342f87bba1244f8ee78848207410ae5ee8976df42fe10d804f1c99167094", + "url": "https://updates.solstone.app/providers/nvattest/libnvat-macos-arm64-1.2.2-sol.2-archive.manifest.json" + }, + "inventory": [ + { + "executable": true, + "kind": "regular", + "relpath": "bin/nvattest", + "symlink_target": null + }, + { + "executable": false, + "kind": "regular", + "relpath": "lib/libnvat.1.2.2.dylib", + "symlink_target": null + }, + { + "executable": false, + "kind": "symlink", + "relpath": "lib/libnvat.1.dylib", + "symlink_target": "libnvat.1.2.2.dylib" + }, + { + "executable": false, + "kind": "symlink", + "relpath": "lib/libnvat.dylib", + "symlink_target": "libnvat.1.dylib" + }, + { + "executable": false, + "kind": "regular", + "relpath": "LICENSE", + "symlink_target": null + }, + { + "executable": false, + "kind": "regular", + "relpath": "share/ca/ca-bundle.pem", + "symlink_target": null + }, + { + "executable": false, + "kind": "regular", + "relpath": "share/THIRD_PARTY_NOTICES.md", + "symlink_target": null + } + ], + "source": { + "fork_commit": "1c95b1f5ae72b5f4282f7b6b96722f7c8d69f744", + "upstream_base": "73c032ebff680ca6d2ba06f4006b511491b71ce9", + "url_prefix": "https://updates.solstone.app/providers/nvattest/", + "version": "1.2.2-sol.2" + } + } + } +} diff --git a/tests/test_nvattest_authority.py b/tests/test_nvattest_authority.py new file mode 100644 index 000000000..69cb25703 --- /dev/null +++ b/tests/test_nvattest_authority.py @@ -0,0 +1,410 @@ +# SPDX-License-Identifier: AGPL-3.0-only +# Copyright (c) 2026 sol pbc + +from __future__ import annotations + +import importlib.util +import json +import platform +import sys +from copy import deepcopy +from pathlib import Path + +import pytest + +from solstone.think.providers import nvattest_authority + +REPO_ROOT = Path(__file__).resolve().parents[1] +SCRIPT = REPO_ROOT / "scripts" / "build_nvattest_authority.py" +AUTHORITY_PATH = ( + REPO_ROOT / "solstone" / "think" / "providers" / "nvattest_authority.py" +) +MIRROR_PATH = ( + REPO_ROOT / "solstone" / "think" / "providers" / "nvattest_authority_v1.json" +) +INSTALL_PATH = REPO_ROOT / "solstone" / "think" / "providers" / "nvattest_install.py" +LEGACY_NVIDIA_SHA256 = ( + "3f10da6fca794b7e3025c6645447947ec8bc45bcfde5b5b1d23241c7115630db" +) +EXPECTED_TARGETS = { + "linux-x86_64": { + "artifact": { + "name": "libnvat-linux-x86_64-1.2.2-sol.2-archive.tar.xz", + "url": "https://updates.solstone.app/providers/nvattest/libnvat-linux-x86_64-1.2.2-sol.2-archive.tar.xz", + "size_bytes": 7655628, + "sha256": "3e2d207a3bb6eab9c47fc9cf65d7990f0d11541ff8691dce18e786e2ce9b26c7", + }, + "companion_manifest": { + "name": "libnvat-linux-x86_64-1.2.2-sol.2-archive.manifest.json", + "url": "https://updates.solstone.app/providers/nvattest/libnvat-linux-x86_64-1.2.2-sol.2-archive.manifest.json", + "sha256": "a013d18a74d89f3ee99a0a3648c582df08446a30f5b8f5bbb48929a08fad4a8f", + }, + }, + "linux-aarch64": { + "artifact": { + "name": "libnvat-linux-aarch64-1.2.2-sol.2-archive.tar.xz", + "url": "https://updates.solstone.app/providers/nvattest/libnvat-linux-aarch64-1.2.2-sol.2-archive.tar.xz", + "size_bytes": 7423268, + "sha256": "7a13a15192f5005a700dbe154da28cbc2a2b5f3f387113c8cd89a36083a2bd80", + }, + "companion_manifest": { + "name": "libnvat-linux-aarch64-1.2.2-sol.2-archive.manifest.json", + "url": "https://updates.solstone.app/providers/nvattest/libnvat-linux-aarch64-1.2.2-sol.2-archive.manifest.json", + "sha256": "0d46ffdf21a9c5a61370fbf77aed0c275dae93f3609511f4573515a7260ddc39", + }, + }, + "macos-arm64": { + "artifact": { + "name": "libnvat-macos-arm64-1.2.2-sol.2-archive.tar.xz", + "url": "https://updates.solstone.app/providers/nvattest/libnvat-macos-arm64-1.2.2-sol.2-archive.tar.xz", + "size_bytes": 5848264, + "sha256": "f2b01f60ee52f9c38c9e5e0f3cea6e8078a1efa165fad1f5c59994563120e365", + }, + "companion_manifest": { + "name": "libnvat-macos-arm64-1.2.2-sol.2-archive.manifest.json", + "url": "https://updates.solstone.app/providers/nvattest/libnvat-macos-arm64-1.2.2-sol.2-archive.manifest.json", + "sha256": "dca5342f87bba1244f8ee78848207410ae5ee8976df42fe10d804f1c99167094", + }, + }, +} + + +def _load_builder(): + spec = importlib.util.spec_from_file_location("build_nvattest_authority", SCRIPT) + assert spec and spec.loader + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def _payload() -> dict: + return deepcopy(nvattest_authority.authority_payload()) + + +def _assert_rejected(payload: dict) -> None: + with pytest.raises(ValueError): + nvattest_authority.validate_authority_payload(payload) + + +def test_authority_payload_contains_exact_operator_literals() -> None: + payload = nvattest_authority.authority_payload() + assert payload["schema_version"] == 1 + targets = payload["targets"] + assert set(targets) == set(EXPECTED_TARGETS) + for target_key, expected in EXPECTED_TARGETS.items(): + target = targets[target_key] + assert target["source"] == { + "fork_commit": "1c95b1f5ae72b5f4282f7b6b96722f7c8d69f744", + "upstream_base": "73c032ebff680ca6d2ba06f4006b511491b71ce9", + "url_prefix": "https://updates.solstone.app/providers/nvattest/", + "version": "1.2.2-sol.2", + } + assert target["artifact"] == expected["artifact"] + assert target["companion_manifest"] == expected["companion_manifest"] + + +def test_authority_payload_contains_exact_inventories() -> None: + payload = nvattest_authority.authority_payload() + linux_inventory = [ + { + "executable": True, + "kind": "regular", + "relpath": "bin/nvattest", + "symlink_target": None, + }, + { + "executable": False, + "kind": "regular", + "relpath": "lib/libnvat.so.1.2.2", + "symlink_target": None, + }, + { + "executable": False, + "kind": "symlink", + "relpath": "lib/libnvat.so.1", + "symlink_target": "libnvat.so.1.2.2", + }, + { + "executable": False, + "kind": "symlink", + "relpath": "lib/libnvat.so", + "symlink_target": "libnvat.so.1", + }, + { + "executable": False, + "kind": "regular", + "relpath": "LICENSE", + "symlink_target": None, + }, + { + "executable": False, + "kind": "regular", + "relpath": "share/ca/ca-bundle.pem", + "symlink_target": None, + }, + { + "executable": False, + "kind": "regular", + "relpath": "share/THIRD_PARTY_NOTICES.md", + "symlink_target": None, + }, + ] + macos_inventory = [ + {**linux_inventory[0]}, + { + "executable": False, + "kind": "regular", + "relpath": "lib/libnvat.1.2.2.dylib", + "symlink_target": None, + }, + { + "executable": False, + "kind": "symlink", + "relpath": "lib/libnvat.1.dylib", + "symlink_target": "libnvat.1.2.2.dylib", + }, + { + "executable": False, + "kind": "symlink", + "relpath": "lib/libnvat.dylib", + "symlink_target": "libnvat.1.dylib", + }, + *linux_inventory[4:], + ] + + assert payload["targets"]["linux-x86_64"]["inventory"] == linux_inventory + assert payload["targets"]["linux-aarch64"]["inventory"] == linux_inventory + assert payload["targets"]["macos-arm64"]["inventory"] == macos_inventory + + +def test_authority_json_matches_constants() -> None: + actual = json.loads(MIRROR_PATH.read_text(encoding="utf-8")) + assert actual == nvattest_authority.authority_payload() + + +def test_builder_check_is_bidirectional(monkeypatch, tmp_path, capsys) -> None: + builder = _load_builder() + artifact = ( + tmp_path / "solstone" / "think" / "providers" / "nvattest_authority_v1.json" + ) + monkeypatch.setattr(builder, "ROOT", tmp_path) + monkeypatch.setattr(builder, "ARTIFACT_PATH", artifact) + + builder.write_outputs() + assert builder.check_outputs() == 0 + + artifact.write_text( + artifact.read_text(encoding="utf-8").replace("{", "[", 1), + encoding="utf-8", + ) + assert builder.check_outputs() == 1 + captured = capsys.readouterr() + assert ( + "nvattest authority is stale: " + "solstone/think/providers/nvattest_authority_v1.json. " + "Run: make nvattest-authority" + ) in captured.err + + +def test_authority_rejects_missing_target() -> None: + payload = _payload() + payload["targets"].pop("macos-arm64") + _assert_rejected(payload) + + +def test_authority_rejects_extra_target() -> None: + payload = _payload() + payload["targets"]["linux-ppc64le"] = deepcopy(payload["targets"]["linux-x86_64"]) + _assert_rejected(payload) + + +def test_authority_rejects_missing_inventory_member() -> None: + payload = _payload() + payload["targets"]["linux-x86_64"]["inventory"].pop() + _assert_rejected(payload) + + +def test_authority_rejects_extra_inventory_member() -> None: + payload = _payload() + payload["targets"]["linux-x86_64"]["inventory"].append( + { + "executable": False, + "kind": "regular", + "relpath": "share/extra.txt", + "symlink_target": None, + } + ) + _assert_rejected(payload) + + +def test_authority_rejects_duplicate_artifact_identity() -> None: + payload = _payload() + payload["targets"]["linux-aarch64"]["artifact"] = deepcopy( + payload["targets"]["linux-x86_64"]["artifact"] + ) + _assert_rejected(payload) + + +def test_authority_rejects_duplicate_manifest_identity() -> None: + payload = _payload() + payload["targets"]["linux-aarch64"]["companion_manifest"] = deepcopy( + payload["targets"]["linux-x86_64"]["companion_manifest"] + ) + _assert_rejected(payload) + + +def test_authority_rejects_non_https_url() -> None: + payload = _payload() + payload["targets"]["linux-x86_64"]["artifact"]["url"] = payload["targets"][ + "linux-x86_64" + ]["artifact"]["url"].replace("https://", "http://") + _assert_rejected(payload) + + +def test_authority_rejects_url_not_equal_to_prefix_plus_name() -> None: + payload = _payload() + payload["targets"]["linux-x86_64"]["artifact"]["url"] = ( + "https://updates.solstone.app/providers/nvattest/other.tar.xz" + ) + _assert_rejected(payload) + + +@pytest.mark.parametrize( + "bad_hash", + [ + "0" * 63, + "A" + "0" * 63, + "g" + "0" * 63, + ], +) +def test_authority_rejects_malformed_hashes(bad_hash: str) -> None: + payload = _payload() + payload["targets"]["linux-x86_64"]["artifact"]["sha256"] = bad_hash + _assert_rejected(payload) + + +@pytest.mark.parametrize("bad_size", [0, -1]) +def test_authority_rejects_nonpositive_sizes(bad_size: int) -> None: + payload = _payload() + payload["targets"]["linux-x86_64"]["artifact"]["size_bytes"] = bad_size + _assert_rejected(payload) + + +@pytest.mark.parametrize( + ("field", "value"), + [ + ("version", "1.2.2-sol.1"), + ("fork_commit", "0" * 40), + ("upstream_base", "1" * 40), + ], +) +def test_authority_rejects_source_drift(field: str, value: str) -> None: + payload = _payload() + payload["targets"]["linux-x86_64"]["source"][field] = value + _assert_rejected(payload) + + +def test_authority_rejects_absolute_member_relpath() -> None: + payload = _payload() + payload["targets"]["linux-x86_64"]["inventory"][0]["relpath"] = "/bin/nvattest" + _assert_rejected(payload) + + +def test_authority_rejects_parent_member_relpath() -> None: + payload = _payload() + payload["targets"]["linux-x86_64"]["inventory"][0]["relpath"] = "bin/../nvattest" + _assert_rejected(payload) + + +def test_authority_rejects_absolute_symlink_target() -> None: + payload = _payload() + payload["targets"]["linux-x86_64"]["inventory"][2]["symlink_target"] = ( + "/tmp/libnvat.so" + ) + _assert_rejected(payload) + + +def test_authority_rejects_parent_symlink_target() -> None: + payload = _payload() + payload["targets"]["linux-x86_64"]["inventory"][2]["symlink_target"] = ( + "../libnvat.so" + ) + _assert_rejected(payload) + + +def test_authority_rejects_name_target_disagreement() -> None: + payload = _payload() + payload["targets"]["linux-x86_64"]["artifact"]["name"] = ( + "libnvat-linux-aarch64-1.2.2-sol.2-archive.tar.xz" + ) + _assert_rejected(payload) + + +def test_authority_rejects_linux_target_with_macos_inventory() -> None: + payload = _payload() + payload["targets"]["linux-x86_64"]["inventory"] = deepcopy( + payload["targets"]["macos-arm64"]["inventory"] + ) + _assert_rejected(payload) + + +@pytest.mark.parametrize( + ("os_name", "arch", "expected"), + [ + ("linux", "x86_64", "linux-x86_64"), + ("linux", "amd64", "linux-x86_64"), + ("linux", "x64", "linux-x86_64"), + ("linux", "aarch64", "linux-aarch64"), + ("linux", "arm64", "linux-aarch64"), + ("darwin", "arm64", "macos-arm64"), + ], +) +def test_target_resolver_accepts_supported_aliases( + os_name: str, + arch: str, + expected: str, +) -> None: + assert nvattest_authority.nvattest_target_key(os_name, arch) == expected + + +@pytest.mark.parametrize( + ("os_name", "arch"), + [ + ("macos", "arm64"), + ("Darwin", "arm64"), + ("linux", "armv8"), + ("linux", "arm64-v8a"), + ("linux", "x86"), + ("linux", "i386"), + ("darwin", "aarch64"), + ], +) +def test_target_resolver_rejects_named_near_misses(os_name: str, arch: str) -> None: + assert nvattest_authority.nvattest_target_key(os_name, arch) is None + + +def test_target_resolver_default_host_uses_python_platform_values() -> None: + os_name = "linux" if sys.platform.startswith("linux") else sys.platform + arch = platform.machine().lower() + expected = None + if os_name == "linux" and arch in {"x86_64", "amd64", "x64"}: + expected = "linux-x86_64" + if os_name == "linux" and arch in {"aarch64", "arm64"}: + expected = "linux-aarch64" + if os_name == "darwin" and arch == "arm64": + expected = "macos-arm64" + + assert nvattest_authority.nvattest_target_key() == expected + if expected is not None: + assert expected in nvattest_authority.TARGET_KEYS + + +def test_nvattest_sources_do_not_point_to_legacy_nvidia_artifact() -> None: + source = "\n".join( + [ + AUTHORITY_PATH.read_text(encoding="utf-8"), + INSTALL_PATH.read_text(encoding="utf-8"), + ] + ) + assert "developer.download.nvidia.com" not in source + assert LEGACY_NVIDIA_SHA256 not in source