diff --git a/CHANGELOG.md b/CHANGELOG.md index f24ead6df..44ab04ada 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Format adapted from [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), al ### Added +- journal installs now carry a native `solstone-core` helper on Linux x86_64, Linux aarch64, and Apple Silicon macOS. the Linux wheels are static manylinux2014 builds, and startup now checks that the helper beside your active Python belongs to the installed `solstone-core` package before the supervisor touches the journal. the helper is installed and checked now; the journal does not use it for work yet. - your journal can now keep older media in your backup instead of on disk. it's off until you turn it on: you set a media budget and a free-space floor, and when the original screen and audio files pass them, the oldest fully-processed media your backup has confirmed moves out, oldest first, with the local copy removed only after that confirmation. transcripts and everything else your journal holds stay where they are, and any offloaded day restores on demand. once media has moved, your backup holds the only copy of it: turning offload on walks through your recovery key first, offloaded media is pinned so your backup's normal rotation can't age it out, and a weekly read-back check samples the backup itself. if a backup lapses or a check fails, offload halts with nothing deleted and says so on the backup page, and turning your backup off now tells you what it holds the only copy of and offers to restore everything first. - timeline, search, speakers, and reflections now show your days as a calendar grid. timeline reaches back through your whole journal instead of stopping twelve months ago, search lets you drag a date range right on the grid, speakers shows which days still need names, and reflections shows which weeks have one. diff --git a/Makefile b/Makefile index 0a9f05d90..fa8b6308f 100644 --- a/Makefile +++ b/Makefile @@ -332,6 +332,17 @@ wheel-macos: parakeet-helper @echo "==> building macosx_14_0_arm64 platform wheel" rm -rf build/ *.egg-info/ $(UV) build --wheel -C--build-option=--plat-name=macosx_14_0_arm64 + @echo "==> building macosx_14_0_arm64 solstone-core wheel" + MACOSX_DEPLOYMENT_TARGET=14.0 MATURIN_PEP517_ARGS="--target aarch64-apple-darwin" $(UV) build --package solstone-core --wheel + @echo "==> signing and notarizing solstone-core" + @CORE_MAC_WHEEL=$$(ls dist/solstone_core-*-macosx_14_0_arm64.whl); \ + CORE_TMP=$$(mktemp -d); \ + trap 'rm -rf "$$CORE_TMP"' EXIT; \ + python3 -m zipfile -e "$$CORE_MAC_WHEEL" "$$CORE_TMP"; \ + CORE_BINARY=$$(find "$$CORE_TMP" -path '*/.data/scripts/solstone-core' -type f -print -quit); \ + test -n "$$CORE_BINARY" || { echo "missing solstone-core binary in $$CORE_MAC_WHEEL" >&2; exit 1; }; \ + ./scripts/sign-and-notarize-helper.sh "$$CORE_BINARY"; \ + python3 scripts/repack_wheel_record.py "$$CORE_TMP" "$$CORE_MAC_WHEEL" else wheel-macos: @echo "wheel-macos: only supported on Darwin/arm64 (got $(shell uname -s)/$(shell uname -m))" >&2 diff --git a/packages/solstone-core/pyproject.toml b/packages/solstone-core/pyproject.toml new file mode 100644 index 000000000..bec03f734 --- /dev/null +++ b/packages/solstone-core/pyproject.toml @@ -0,0 +1,16 @@ +[build-system] +requires = ["maturin>=1.14,<2"] +build-backend = "maturin" + +[project] +name = "solstone-core" +version = "0.8.9" +description = "solstone helper binary. Linux wheels are tagged manylinux2014 / glibc 2.17+, while the binary is fully static and has no libc runtime dependency; Alpine installs fall back to the sdist and cargo." +requires-python = ">=3.12" +license = {text = "AGPL-3.0-only"} + +[tool.maturin] +bindings = "bin" +manifest-path = "../../core/crates/solstone-core/Cargo.toml" +profile = "release" +strip = true diff --git a/pyproject.toml b/pyproject.toml index a4d3b8b65..a29a8f057 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -94,6 +94,9 @@ pdf = [ # user-facing extra — always pulled in by a leaf package. journal-host = [ "solstone-journal-models==1.0.0", + "solstone-core==0.8.9; sys_platform == 'linux' and platform_machine == 'x86_64'", + "solstone-core==0.8.9; sys_platform == 'linux' and platform_machine == 'aarch64'", + "solstone-core==0.8.9; sys_platform == 'darwin' and platform_machine == 'arm64'", "python-dotenv", "pyhpke==0.6.4", "Flask[async]", @@ -211,12 +214,13 @@ include = ["solstone*"] override-dependencies = ["solstone-journal-host==0.7.0; python_version < '3.12'"] [tool.uv.workspace] -members = ["packages/solstone-journal", "packages/solstone-journal-cuda", "packages/solstone-journal-models"] +members = ["packages/solstone-journal", "packages/solstone-journal-cuda", "packages/solstone-journal-models", "packages/solstone-core"] [tool.uv.sources] solstone-journal = {workspace = true} solstone-journal-cuda = {workspace = true} solstone-journal-models = {workspace = true} +solstone-core = {workspace = true} # Development tools configuration [tool.ruff] diff --git a/scripts/check_extras_consistency.py b/scripts/check_extras_consistency.py index 3a79c688d..7cbba85bb 100755 --- a/scripts/check_extras_consistency.py +++ b/scripts/check_extras_consistency.py @@ -18,8 +18,9 @@ The invariants are: 3. Root `[journal]` and `[journal-cuda]` are tombstones pinned exactly to `solstone-journal-host==0.7.0`. 4. `[journal-host]` stays in root, folds in the `[pdf]` building block, pins - `solstone-journal-models==`, and pins the tested - LiteLLM runtime used by OpenHands. + `solstone-journal-models==`, marker-gated + `solstone-core==` for the covered binary platforms, and + pins the tested LiteLLM runtime used by OpenHands. 5. The CPU leaf depends on `solstone[journal-host]==`, pulls CPU `onnxruntime`, and does not pull `onnxruntime-gpu`. 6. The CUDA leaf depends on `solstone[journal-host]==`, pulls @@ -30,8 +31,8 @@ The invariants are: 9. Root scripts stay exactly the thin access scripts. 10. Each leaf has metadata-only setuptools config, a workspace source for `solstone`, the expected package name, and the root version. - 11. uv workspace members/sources are exactly the two journal leaves plus models; - `solstone-journal-host` is absent. + 11. uv workspace members/sources are exactly the two journal leaves plus + models plus core; `solstone-journal-host` is absent. 12. `[tool.uv].override-dependencies` contains the tombstone pin. 13. The Makefile no longer uses root journal extra spellings. """ @@ -41,6 +42,7 @@ import tomllib from pathlib import Path from solstone.think.features import FEATURES +from solstone.think.probe import solstone_core_marker_pins # The thin access partition. Adding anything here must keep the `sol` access # commands import-clean (scripts/check_access_imports_clean.py) — keep this in @@ -92,11 +94,13 @@ WORKSPACE_MEMBERS = [ "packages/solstone-journal", "packages/solstone-journal-cuda", "packages/solstone-journal-models", + "packages/solstone-core", ] WORKSPACE_SOURCES = { "solstone-journal", "solstone-journal-cuda", "solstone-journal-models", + "solstone-core", } @@ -135,6 +139,23 @@ def _check_models_pin(extras: dict, member_version: str | None) -> list[str]: return [] +def _check_core_pins(extras: dict, root_version: str | None) -> list[str]: + host = extras.get("journal-host", []) + pins = sorted(dep for dep in host if dep.startswith("solstone-core==")) + expected = sorted(solstone_core_marker_pins(root_version or "")) + if len(pins) != len(expected): + return [ + "[journal-host] must contain exactly " + f"{len(expected)} marker-gated solstone-core== pins; found {len(pins)}" + ] + if root_version is not None and pins != expected: + return [ + "[journal-host] solstone-core marker pins must be exactly " + f"{expected}; found {pins}" + ] + return [] + + def _read_toml(path: Path, root: Path, errors: list[str]) -> dict: try: return tomllib.loads(path.read_text(encoding="utf-8")) @@ -191,12 +212,51 @@ def _leaf_dependencies( return deps +def _check_core_leaf( + *, + data: dict, + root_version: str | None, + errors: list[str], +) -> None: + project = data.get("project", {}) + build_system = data.get("build-system", {}) + tool = data.get("tool", {}) + maturin = tool.get("maturin", {}) + + if project.get("name") != "solstone-core": + errors.append("core leaf [project].name must be 'solstone-core'") + if project.get("version") != root_version: + errors.append( + f"core leaf [project].version must match root version {root_version}; " + f"found {project.get('version')!r}" + ) + if project.get("scripts", {}) != {}: + errors.append("core leaf must not define [project.scripts]") + if build_system.get("build-backend") != "maturin": + errors.append("core leaf [build-system].build-backend must be 'maturin'") + requires = build_system.get("requires", []) + if not any(str(req).startswith("maturin") for req in requires): + errors.append("core leaf [build-system].requires must include maturin") + if maturin.get("bindings") != "bin": + errors.append("core leaf [tool.maturin].bindings must be 'bin'") + if maturin.get("manifest-path") != "../../core/crates/solstone-core/Cargo.toml": + errors.append( + "core leaf [tool.maturin].manifest-path must be " + "'../../core/crates/solstone-core/Cargo.toml'" + ) + if maturin.get("profile") != "release": + errors.append("core leaf [tool.maturin].profile must be 'release'") + if maturin.get("strip") is not True: + errors.append("core leaf [tool.maturin].strip must be true") + + def main(root: Path | None = None) -> int: root = Path(root) if root is not None else Path(__file__).resolve().parent.parent pyproject = root / "pyproject.toml" cpu_pyproject = root / "packages" / "solstone-journal" / "pyproject.toml" cuda_pyproject = root / "packages" / "solstone-journal-cuda" / "pyproject.toml" models_pyproject = root / "packages" / "solstone-journal-models" / "pyproject.toml" + core_pyproject = root / "packages" / "solstone-core" / "pyproject.toml" makefile = root / "Makefile" errors: list[str] = [] @@ -204,6 +264,7 @@ def main(root: Path | None = None) -> int: cpu_data = _read_toml(cpu_pyproject, root, errors) cuda_data = _read_toml(cuda_pyproject, root, errors) models_data = _read_toml(models_pyproject, root, errors) + core_data = _read_toml(core_pyproject, root, errors) project = data.get("project", {}) root_version = project.get("version") @@ -276,6 +337,7 @@ def main(root: Path | None = None) -> int: if "solstone[pdf]" not in host: errors.append("[journal-host] must fold in solstone[pdf]") errors.extend(_check_models_pin(extras, models_version)) + errors.extend(_check_core_pins(extras, root_version)) litellm_requirements = [ dep for dep in host @@ -307,6 +369,7 @@ def main(root: Path | None = None) -> int: root_version=root_version, errors=errors, ) + _check_core_leaf(data=core_data, root_version=root_version, errors=errors) # 5. CPU leaf runtime split. missing_cpu_runtime = sorted(CPU_ONNXRUNTIME_DEPS - set(cpu_deps)) diff --git a/scripts/check_wheel_contents.py b/scripts/check_wheel_contents.py index 02a252689..a623829b0 100644 --- a/scripts/check_wheel_contents.py +++ b/scripts/check_wheel_contents.py @@ -7,12 +7,20 @@ from __future__ import annotations import argparse +import base64 import hashlib import re import sys +import tarfile import zipfile from pathlib import Path +from solstone.think.probe import ( + SOLSTONE_CORE_PLATFORM_TAGS, + current_solstone_core_platform, + is_solstone_core_covered_platform, +) + EXPECTED_MODEL_SHA256 = { "silero_vad_v6.onnx": ( "4cbf549b8326f60f80f2536d9eefeb450a9abe83365a098031c89719f1be17d2" @@ -25,6 +33,17 @@ EXPECTED_MODEL_SHA256 = { ), } MAX_BASE_WHEEL_BYTES = 4 * 1024 * 1024 +MAX_CORE_WHEEL_BYTES = 30 * 1024 * 1024 +CORE_REQUIRED_SDIST_MEMBERS = { + "core/Cargo.lock", + "core/Cargo.toml", + "core/crates/solstone-core/Cargo.toml", + "core/crates/solstone-core/src/main.rs", + "core/crates/solstone-core-cli/Cargo.toml", + "core/crates/solstone-core-cli/src/lib.rs", + "core/crates/solstone-core-journal/Cargo.toml", + "core/crates/solstone-core-journal/src/lib.rs", +} def _is_base_wheel(path: Path) -> bool: @@ -35,6 +54,19 @@ def _is_models_wheel(path: Path) -> bool: return path.name.startswith("solstone_journal_models-") +def _is_core_wheel(path: Path) -> bool: + return path.name.startswith("solstone_core-") and path.name.endswith(".whl") + + +def _is_core_sdist(path: Path) -> bool: + return path.name.startswith("solstone_core-") and path.name.endswith(".tar.gz") + + +def _core_wheel_tag(path: Path) -> str: + stem = path.name.removesuffix(".whl") + return stem.split("-")[-1] + + def _onnx_members(path: Path) -> list[str]: with zipfile.ZipFile(path) as wheel: return [name for name in wheel.namelist() if name.endswith(".onnx")] @@ -76,21 +108,134 @@ def check_models_wheel(path: Path, expected: dict[str, str]) -> list[str]: return errors +def _record_hash(content: bytes) -> str: + digest = hashlib.sha256(content).digest() + encoded = base64.urlsafe_b64encode(digest).decode("ascii").rstrip("=") + return f"sha256={encoded}" + + +def _check_record(path: Path, wheel: zipfile.ZipFile) -> list[str]: + errors: list[str] = [] + names = set(wheel.namelist()) + record_names = [name for name in names if name.endswith(".dist-info/RECORD")] + if len(record_names) != 1: + return [f"{path.name}: expected exactly one RECORD, found {len(record_names)}"] + + record_name = record_names[0] + rows = wheel.read(record_name).decode("utf-8").splitlines() + seen: set[str] = set() + for row in rows: + columns = row.split(",") + if len(columns) != 3: + errors.append(f"{path.name}: malformed RECORD row: {row!r}") + continue + member, expected_hash, expected_size = columns + if member not in names: + errors.append(f"{path.name}: RECORD references missing member {member}") + continue + seen.add(member) + if member == record_name: + if expected_hash or expected_size: + errors.append( + f"{path.name}: RECORD row for RECORD must have empty hash/size" + ) + continue + content = wheel.read(member) + if expected_hash != _record_hash(content): + errors.append(f"{path.name}: RECORD hash mismatch for {member}") + if expected_size != str(len(content)): + errors.append(f"{path.name}: RECORD size mismatch for {member}") + + missing = sorted(names - seen) + if missing: + errors.append(f"{path.name}: members missing from RECORD: {missing}") + return errors + + +def check_core_wheel(path: Path, max_bytes: int) -> list[str]: + errors: list[str] = [] + size = path.stat().st_size + if size > max_bytes: + errors.append(f"{path.name}: core wheel is {size} bytes; max is {max_bytes}") + + tag = _core_wheel_tag(path) + allowed_tags = set(SOLSTONE_CORE_PLATFORM_TAGS.values()) + if tag not in allowed_tags: + errors.append(f"{path.name}: unsupported solstone-core wheel tag {tag}") + if "-linux_" in path.name: + errors.append(f"{path.name}: bare linux tag is not publishable") + + with zipfile.ZipFile(path) as wheel: + scripts = [ + info + for info in wheel.infolist() + if info.filename.endswith(".data/scripts/solstone-core") + ] + if len(scripts) != 1: + errors.append( + f"{path.name}: expected exactly one .data/scripts/solstone-core; " + f"found {len(scripts)}" + ) + elif ((scripts[0].external_attr >> 16) & 0o111) == 0: + errors.append(f"{path.name}: solstone-core script is not executable") + errors.extend(_check_record(path, wheel)) + + return errors + + +def check_core_sdist(path: Path) -> list[str]: + errors: list[str] = [] + with tarfile.open(path, "r:gz") as archive: + names = archive.getnames() + prefixes = {name.split("/", 1)[0] for name in names if "/" in name} + if len(prefixes) != 1: + return [ + f"{path.name}: expected one top-level sdist directory, found {sorted(prefixes)}" + ] + prefix = next(iter(prefixes)) + normalized = { + name.removeprefix(prefix + "/") + for name in names + if name.startswith(prefix + "/") + } + missing = sorted(CORE_REQUIRED_SDIST_MEMBERS - normalized) + if missing: + errors.append( + f"{path.name}: core sdist missing Rust workspace members: {missing}" + ) + return errors + + def check_dist(dist_dir: Path, expected: dict[str, str], max_bytes: int) -> list[str]: errors: list[str] = [] wheels = sorted(dist_dir.glob("*.whl")) base_wheels = [path for path in wheels if _is_base_wheel(path)] models_wheels = [path for path in wheels if _is_models_wheel(path)] + core_wheels = [path for path in wheels if _is_core_wheel(path)] + core_sdists = sorted( + path for path in dist_dir.glob("*.tar.gz") if _is_core_sdist(path) + ) if not base_wheels: errors.append(f"{dist_dir}: no solstone base wheel found") if not models_wheels: errors.append(f"{dist_dir}: no solstone_journal_models wheel found") + system, machine = current_solstone_core_platform() + if is_solstone_core_covered_platform(system, machine) and not core_wheels: + errors.append( + f"{dist_dir}: no solstone_core wheel found for {system}/{machine}" + ) + if not core_sdists: + errors.append(f"{dist_dir}: no solstone_core sdist found") for path in base_wheels: errors.extend(check_base_wheel(path, max_bytes)) for path in models_wheels: errors.extend(check_models_wheel(path, expected)) + for path in core_wheels: + errors.extend(check_core_wheel(path, MAX_CORE_WHEEL_BYTES)) + for path in core_sdists: + errors.extend(check_core_sdist(path)) return errors diff --git a/scripts/release.sh b/scripts/release.sh index 25d6c4d8a..02e90f95f 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -4,10 +4,14 @@ # # Multi-wheel solstone release. # -# Builds and uploads the seven lockstep artifacts to PyPI: +# Builds and uploads the ten lockstep artifacts to PyPI: # - solstone-${VERSION}.tar.gz (root sdist) # - solstone-${VERSION}-py3-none-any.whl (root any wheel) # - solstone-${VERSION}-py3-none-macosx_14_0_arm64.whl (Apple Silicon macOS 14+) +# - solstone_core-${VERSION}.tar.gz (core helper sdist) +# - solstone_core-${VERSION}-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl +# - solstone_core-${VERSION}-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl +# - solstone_core-${VERSION}-py3-none-macosx_14_0_arm64.whl # - solstone_journal-${VERSION}.tar.gz (journal CPU sdist) # - solstone_journal-${VERSION}-py3-none-any.whl (journal CPU wheel) # - solstone_journal_cuda-${VERSION}.tar.gz (journal CUDA sdist) @@ -93,6 +97,21 @@ if [[ "$PUBLISH" == "yes" ]]; then fi PRO5E_HOST="${PRO5E_HOST:-pro5e.local}" NOTARY_PROFILE="${NOTARY_KEYCHAIN_PROFILE:-sol-pbc-notary}" +CORE_X86_64_MATURIN_ARGS="--compatibility manylinux2014 --target x86_64-unknown-linux-musl" +CORE_AARCH64_MATURIN_ARGS="--compatibility manylinux2014 --target aarch64-unknown-linux-musl" +CORE_AARCH64_LINKER="rust-lld" + +require_rust_target() { + local target="$1" + if ! command -v rustup >/dev/null 2>&1; then + echo "rustup is required to build solstone-core ${target}; install rustup and retry" >&2 + exit 1 + fi + if ! rustup target list --installed 2>/dev/null | grep -qx "$target"; then + echo "Rust target ${target} is required; run rustup target add ${target}" >&2 + exit 1 + fi +} # Capture the git ref we're publishing from. pro5e checks out the same ref # so the macOS wheel's source matches the local sdist. Reject ANY tracked or @@ -110,7 +129,12 @@ fi echo "==> [1/5] building local lockstep artifacts" python3 scripts/render_packaging.py --check rm -rf build/ dist/ *.egg-info/ -uv build --all-packages +require_rust_target x86_64-unknown-linux-musl +require_rust_target aarch64-unknown-linux-musl +MATURIN_PEP517_ARGS="$CORE_X86_64_MATURIN_ARGS" uv build --all-packages +CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER="$CORE_AARCH64_LINKER" \ + MATURIN_PEP517_ARGS="$CORE_AARCH64_MATURIN_ARGS" \ + uv build --package solstone-core --wheel python3 scripts/check_wheel_contents.py dist/ # Pre-flight the CHANGELOG block now — before the expensive pro5e leg and the @@ -128,6 +152,9 @@ echo "models gate: solstone-journal-models ${MODELS_VERSION} -> ${MODELS_DECISIO LOCAL_LOCKSTEP_ARTIFACTS=( "dist/solstone-${VERSION}.tar.gz" "dist/solstone-${VERSION}-py3-none-any.whl" + "dist/solstone_core-${VERSION}.tar.gz" + "dist/solstone_core-${VERSION}-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + "dist/solstone_core-${VERSION}-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" "dist/solstone_journal-${VERSION}.tar.gz" "dist/solstone_journal-${VERSION}-py3-none-any.whl" "dist/solstone_journal_cuda-${VERSION}.tar.gz" @@ -151,6 +178,9 @@ if [[ "$PUBLISH" == "no" ]]; then echo "build/check complete (--no-publish); skipped pro5e, upload, git tag, git push, and GitHub release" echo " root sdist: dist/solstone-${VERSION}.tar.gz" echo " root any: dist/solstone-${VERSION}-py3-none-any.whl" + echo " core sdist: dist/solstone_core-${VERSION}.tar.gz" + echo " core x86: dist/solstone_core-${VERSION}-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl" + echo " core arm64: dist/solstone_core-${VERSION}-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl" echo " journal sdist: dist/solstone_journal-${VERSION}.tar.gz" echo " journal any: dist/solstone_journal-${VERSION}-py3-none-any.whl" echo " cuda sdist: dist/solstone_journal_cuda-${VERSION}.tar.gz" @@ -159,6 +189,7 @@ if [[ "$PUBLISH" == "no" ]]; then echo " models any: dist/solstone_journal_models-${MODELS_VERSION}-py3-none-any.whl" echo " models gate: ${MODELS_DECISION}" echo " macos: pro5e/publish-time-only dist/solstone-${VERSION}-py3-none-macosx_14_0_arm64.whl" + echo " core macos: pro5e/publish-time-only dist/solstone_core-${VERSION}-py3-none-macosx_14_0_arm64.whl" exit 0 fi @@ -184,6 +215,7 @@ ssh "$PRO5E_HOST" "tmux-run hopper ~/projects/solstone 'set -e; \ echo "==> [3/5] rsyncing macOS wheel back" rsync -av --include='*macosx_14_0_arm64.whl' --exclude='*' \ "$PRO5E_HOST:projects/solstone/dist/" ./dist/ +python3 scripts/check_wheel_contents.py dist/ echo echo "release artifacts:" @@ -192,12 +224,14 @@ ls -la dist/ ARTIFACTS=( "dist/solstone-${VERSION}.tar.gz" dist/solstone-${VERSION}-*.whl + "dist/solstone_core-${VERSION}.tar.gz" + dist/solstone_core-${VERSION}-*.whl "dist/solstone_journal-${VERSION}.tar.gz" "dist/solstone_journal-${VERSION}-py3-none-any.whl" "dist/solstone_journal_cuda-${VERSION}.tar.gz" "dist/solstone_journal_cuda-${VERSION}-py3-none-any.whl" ) -GH_RELEASE_ARTIFACT_HINT="dist/solstone-${VERSION}.tar.gz dist/solstone-${VERSION}-*.whl dist/solstone_journal-${VERSION}.tar.gz dist/solstone_journal-${VERSION}-*.whl dist/solstone_journal_cuda-${VERSION}.tar.gz dist/solstone_journal_cuda-${VERSION}-*.whl" +GH_RELEASE_ARTIFACT_HINT="dist/solstone-${VERSION}.tar.gz dist/solstone-${VERSION}-*.whl dist/solstone_core-${VERSION}.tar.gz dist/solstone_core-${VERSION}-*.whl dist/solstone_journal-${VERSION}.tar.gz dist/solstone_journal-${VERSION}-*.whl dist/solstone_journal_cuda-${VERSION}.tar.gz dist/solstone_journal_cuda-${VERSION}-*.whl" if [[ "$MODELS_DECISION" == "publish" ]]; then ARTIFACTS+=( "dist/solstone_journal_models-${MODELS_VERSION}.tar.gz" @@ -217,6 +251,8 @@ echo echo "published solstone ${VERSION} to ${TARGET}:" echo " root sdist: dist/solstone-${VERSION}.tar.gz" echo " root any: dist/solstone-${VERSION}-py3-none-any.whl" +echo " core sdist: dist/solstone_core-${VERSION}.tar.gz" +echo " core wheels: dist/solstone_core-${VERSION}-*.whl" echo " journal sdist: dist/solstone_journal-${VERSION}.tar.gz" echo " journal any: dist/solstone_journal-${VERSION}-py3-none-any.whl" echo " cuda sdist: dist/solstone_journal_cuda-${VERSION}.tar.gz" diff --git a/scripts/render_packaging.py b/scripts/render_packaging.py index c70706ef6..17af11eb6 100644 --- a/scripts/render_packaging.py +++ b/scripts/render_packaging.py @@ -9,15 +9,23 @@ from __future__ import annotations import argparse import logging import re +import sys import tomllib from pathlib import Path ROOT = Path(__file__).resolve().parent.parent +sys.path.insert(0, str(ROOT)) + +from solstone.think.probe import solstone_core_marker_pins # noqa: E402 + ROOT_PYPROJECT = ROOT / "pyproject.toml" CPU_PYPROJECT = ROOT / "packages" / "solstone-journal" / "pyproject.toml" CUDA_PYPROJECT = ROOT / "packages" / "solstone-journal-cuda" / "pyproject.toml" TOMBSTONE_PIN = "solstone-journal-host==0.7.0" HOST_PIN_RE = re.compile(r'(?P")solstone\[journal-host\]==[^"]+(?P=quote)') +CORE_PIN_RE = re.compile( + r'(?P")solstone-core==[^";]+; (?P[^"]+)(?P=quote)' +) VERSION_RE = re.compile(r'(?m)^version = "[^"]+"') CARGO_WORKSPACE_VERSION_RE = re.compile( r'(?ms)(?P^\[workspace\.package\]\n(?:(?!^\[).)*?^version = )"[^"]+"' @@ -56,6 +64,10 @@ def _leaf_paths(root: Path) -> tuple[Path, Path]: ) +def _core_leaf_path(root: Path) -> Path: + return root / "packages" / "solstone-core" / "pyproject.toml" + + def _rewrite_leaf(text: str, version: str) -> str: text, version_count = VERSION_RE.subn(f'version = "{version}"', text) if version_count != 1: @@ -71,6 +83,43 @@ def _rewrite_leaf(text: str, version: str) -> str: return text +def _rewrite_core_leaf(text: str, version: str) -> str: + text, version_count = VERSION_RE.subn(f'version = "{version}"', text) + if version_count != 1: + raise PackagingRenderError( + f"core leaf pyproject must contain exactly one [project].version line; found {version_count}" + ) + if "solstone[journal-host]==" in text: + raise PackagingRenderError( + "core leaf pyproject must not contain a solstone[journal-host]== pin" + ) + return text + + +def _rewrite_root_core_pins(text: str, version: str) -> str: + expected = set(solstone_core_marker_pins(version)) + seen_markers: list[str] = [] + + def replacement(match: re.Match[str]) -> str: + marker = match.group("marker") + seen_markers.append(marker) + return f"{match.group('quote')}solstone-core=={version}; {marker}{match.group('quote')}" + + rewritten, pin_count = CORE_PIN_RE.subn(replacement, text) + if pin_count != len(expected): + raise PackagingRenderError( + "root pyproject must contain exactly " + f"{len(expected)} marker-gated solstone-core== pins; found {pin_count}" + ) + actual = {f"solstone-core=={version}; {marker}" for marker in seen_markers} + if actual != expected: + raise PackagingRenderError( + "root pyproject solstone-core marker pins must be exactly " + + ", ".join(sorted(expected)) + ) + return rewritten + + def _rewrite_cargo_workspace_manifest(text: str, version: str) -> str: def replacement(match: re.Match[str]) -> str: return f'{match.group("prefix")}"{version}"' @@ -231,9 +280,17 @@ def render(root: Path = ROOT) -> dict[Path, str]: root_text = (root / "pyproject.toml").read_text(encoding="utf-8") version = _read_version(root_text) expected = { - path: _rewrite_leaf(path.read_text(encoding="utf-8"), version) - for path in _leaf_paths(root) + root / "pyproject.toml": _rewrite_root_core_pins(root_text, version), + _core_leaf_path(root): _rewrite_core_leaf( + _core_leaf_path(root).read_text(encoding="utf-8"), version + ), } + expected.update( + { + path: _rewrite_leaf(path.read_text(encoding="utf-8"), version) + for path in _leaf_paths(root) + } + ) expected.update(_render_cargo(root, version)) return expected diff --git a/scripts/repack_wheel_record.py b/scripts/repack_wheel_record.py new file mode 100755 index 000000000..8b81791ae --- /dev/null +++ b/scripts/repack_wheel_record.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: AGPL-3.0-only +# Copyright (c) 2026 sol pbc + +"""Repack an unpacked wheel directory after rewriting RECORD.""" + +from __future__ import annotations + +import argparse +import base64 +import csv +import hashlib +import os +import tempfile +import zipfile +from pathlib import Path + + +def _record_hash(path: Path) -> str: + digest = hashlib.sha256(path.read_bytes()).digest() + encoded = base64.urlsafe_b64encode(digest).decode("ascii").rstrip("=") + return f"sha256={encoded}" + + +def _relative_files(root: Path) -> list[Path]: + return sorted(path for path in root.rglob("*") if path.is_file()) + + +def _rewrite_record(root: Path) -> None: + record_paths = list(root.glob("*.dist-info/RECORD")) + if len(record_paths) != 1: + raise SystemExit( + f"expected exactly one *.dist-info/RECORD, found {len(record_paths)}" + ) + record_path = record_paths[0] + + rows: list[list[str]] = [] + for path in _relative_files(root): + arcname = path.relative_to(root).as_posix() + if path == record_path: + continue + rows.append([arcname, _record_hash(path), str(path.stat().st_size)]) + rows.append([record_path.relative_to(root).as_posix(), "", ""]) + + with record_path.open("w", encoding="utf-8", newline="") as handle: + csv.writer(handle).writerows(rows) + + +def repack(unpacked_dir: Path, wheel_path: Path) -> None: + unpacked_dir = unpacked_dir.resolve() + wheel_path = wheel_path.resolve() + if not unpacked_dir.is_dir(): + raise SystemExit(f"unpacked wheel directory does not exist: {unpacked_dir}") + if not wheel_path.name.endswith(".whl"): + raise SystemExit(f"wheel path must end in .whl: {wheel_path}") + + _rewrite_record(unpacked_dir) + fd, tmp_name = tempfile.mkstemp( + prefix=f".{wheel_path.name}.", suffix=".tmp", dir=str(wheel_path.parent) + ) + os.close(fd) + tmp_path = Path(tmp_name) + try: + with zipfile.ZipFile( + tmp_path, "w", compression=zipfile.ZIP_DEFLATED + ) as archive: + for path in _relative_files(unpacked_dir): + archive.write(path, path.relative_to(unpacked_dir).as_posix()) + os.replace(tmp_path, wheel_path) + finally: + tmp_path.unlink(missing_ok=True) + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("unpacked_dir", type=Path) + parser.add_argument("wheel_path", type=Path) + args = parser.parse_args() + repack(args.unpacked_dir, args.wheel_path) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/solstone/think/core_handshake.py b/solstone/think/core_handshake.py new file mode 100644 index 000000000..89d876e35 --- /dev/null +++ b/solstone/think/core_handshake.py @@ -0,0 +1,141 @@ +# SPDX-License-Identifier: AGPL-3.0-only +# Copyright (c) 2026 sol pbc + +"""Install-skew check for the solstone-core helper binary.""" + +from __future__ import annotations + +import os +import re +import subprocess +import sys +from dataclasses import dataclass +from importlib.metadata import PackageNotFoundError +from importlib.metadata import version as distribution_version +from pathlib import Path +from typing import Callable, Literal + +from solstone.think.probe import ( + current_solstone_core_platform, + is_solstone_core_covered_platform, +) +from solstone.think.utils import is_source_checkout + +EX_CONFIG = 78 +CORE_DIST_NAME = "solstone-core" +CORE_BINARY_NAME = "solstone-core" +CORE_VERSION_RE = re.compile(r"^solstone-core\s+(\S+)\s*$") + +HandshakeStatus = Literal["ok", "skip", "fail"] + + +@dataclass(frozen=True) +class CoreHandshakeResult: + status: HandshakeStatus + message: str | None = None + + +def helper_path_for_executable(executable: str | Path | None = None) -> Path: + return Path(executable or sys.executable).with_name(CORE_BINARY_NAME) + + +def _run_version( + helper_path: Path, + *, + runner: Callable[..., subprocess.CompletedProcess[str]] = subprocess.run, +) -> tuple[str | None, str | None]: + try: + completed = runner( + [str(helper_path), "--version"], + capture_output=True, + text=True, + timeout=5, + check=False, + ) + except OSError as exc: + return None, f"{type(exc).__name__}: {exc}" + except subprocess.TimeoutExpired: + return None, "timed out running --version" + + stdout = completed.stdout.strip() + stderr = completed.stderr.strip() + if completed.returncode != 0: + detail = stderr or stdout or "" + return None, f"--version exited {completed.returncode}: {detail}" + + match = CORE_VERSION_RE.match(stdout) + if not match: + detail = stdout or stderr or "" + return None, f"could not parse --version output: {detail}" + return match.group(1), None + + +def check_solstone_core_handshake( + *, + executable: str | Path | None = None, + source_checkout: Callable[[], bool] | None = None, + version_reader: Callable[[str], str] | None = None, + platform_reader: Callable[[], tuple[str, str]] | None = None, + coverage_predicate: Callable[[str, str], bool] | None = None, + executable_predicate: Callable[[Path], bool] = lambda path: os.access( + path, os.X_OK + ), + runner: Callable[..., subprocess.CompletedProcess[str]] = subprocess.run, +) -> CoreHandshakeResult: + source_checkout = source_checkout or is_source_checkout + version_reader = version_reader or distribution_version + platform_reader = platform_reader or current_solstone_core_platform + coverage_predicate = coverage_predicate or is_solstone_core_covered_platform + + source = source_checkout() + system, machine = platform_reader() + covered = coverage_predicate(system, machine) + + try: + dist_version = version_reader(CORE_DIST_NAME) + except PackageNotFoundError: + if source: + return CoreHandshakeResult( + "skip", + "solstone-core handshake skipped: source checkout without solstone-core distribution metadata", + ) + if not covered: + return CoreHandshakeResult( + "skip", + f"solstone-core handshake skipped: {system}/{machine} is not covered by solstone-core wheel markers", + ) + return CoreHandshakeResult( + "fail", + f"solstone-core install check failed: missing {CORE_DIST_NAME} distribution metadata on covered platform {system}/{machine}; reinstall solstone-journal", + ) + + if not source and not covered: + return CoreHandshakeResult( + "skip", + f"solstone-core handshake skipped: {system}/{machine} is not covered by solstone-core wheel markers", + ) + + helper_path = helper_path_for_executable(executable) + if not helper_path.exists(): + return CoreHandshakeResult( + "fail", + f"solstone-core install check failed: missing binary {helper_path} for {CORE_DIST_NAME} {dist_version}; reinstall solstone-journal", + ) + if not executable_predicate(helper_path): + return CoreHandshakeResult( + "fail", + f"solstone-core install check failed: binary {helper_path} is not executable for {CORE_DIST_NAME} {dist_version}; reinstall solstone-journal", + ) + + binary_version, error = _run_version(helper_path, runner=runner) + if error is not None: + return CoreHandshakeResult( + "fail", + f"solstone-core install check failed: {helper_path} --version failed for {CORE_DIST_NAME} {dist_version}: {error}; reinstall solstone-journal", + ) + if binary_version != dist_version: + return CoreHandshakeResult( + "fail", + f"solstone-core install check failed: {CORE_DIST_NAME} metadata is {dist_version} but {helper_path} reports {binary_version}; reinstall solstone-journal", + ) + return CoreHandshakeResult("ok") diff --git a/solstone/think/preflight.py b/solstone/think/preflight.py index 6420078f3..5259d1d01 100644 --- a/solstone/think/preflight.py +++ b/solstone/think/preflight.py @@ -22,6 +22,7 @@ from solstone.think.probe import ( DISK_SPACE_CHECK, LOCAL_BIN_SOL_REACHABLE_CHECK, PYTHON_VERSION_CHECK, + SOLSTONE_CORE_RUST_TOOLCHAIN_CHECK, UV_INSTALLED_CHECK, VENV_CONSISTENT_CHECK, Check, @@ -32,6 +33,7 @@ from solstone.think.probe import ( make_result, platform_tag, python_version_check, + solstone_core_rust_toolchain_check, uv_installed_check, venv_consistent_check, ) @@ -46,6 +48,7 @@ class Args: CHECKS: list[tuple[Check, Callable[[Args], CheckResult]]] = [ (PYTHON_VERSION_CHECK, python_version_check), (UV_INSTALLED_CHECK, uv_installed_check), + (SOLSTONE_CORE_RUST_TOOLCHAIN_CHECK, solstone_core_rust_toolchain_check), (VENV_CONSISTENT_CHECK, venv_consistent_check), (LOCAL_BIN_SOL_REACHABLE_CHECK, local_bin_sol_reachable_check), (DISK_SPACE_CHECK, disk_space_check), diff --git a/solstone/think/probe.py b/solstone/think/probe.py index 59e964210..93a1aed1c 100644 --- a/solstone/think/probe.py +++ b/solstone/think/probe.py @@ -10,6 +10,7 @@ checks can run before `.venv` or `uv` exist. from __future__ import annotations import os +import platform import re import shutil import subprocess @@ -35,6 +36,7 @@ LOCAL_BIN_SOL_FIX = ( Severity = Literal["blocker", "advisory"] Status = Literal["ok", "fail", "warn", "skip"] Platform = Literal["linux", "darwin"] +CorePlatform = tuple[Platform, str] @dataclass(frozen=True) @@ -69,6 +71,25 @@ LOCAL_BIN_SOL_REACHABLE_CHECK = Check( ) DISK_SPACE_CHECK = Check("disk_space", "advisory", ("linux", "darwin")) CONFIG_DIR_READABLE_CHECK = Check("config_dir_readable", "blocker", ("linux", "darwin")) +SOLSTONE_CORE_RUST_TOOLCHAIN_CHECK = Check( + "solstone_core_rust_toolchain", "blocker", ("linux", "darwin") +) + +SOLSTONE_CORE_COVERED_PLATFORMS: tuple[CorePlatform, ...] = ( + ("linux", "x86_64"), + ("linux", "aarch64"), + ("darwin", "arm64"), +) +SOLSTONE_CORE_PLATFORM_MARKERS: tuple[str, ...] = ( + "sys_platform == 'linux' and platform_machine == 'x86_64'", + "sys_platform == 'linux' and platform_machine == 'aarch64'", + "sys_platform == 'darwin' and platform_machine == 'arm64'", +) +SOLSTONE_CORE_PLATFORM_TAGS: dict[CorePlatform, str] = { + ("linux", "x86_64"): "manylinux_2_17_x86_64.manylinux2014_x86_64", + ("linux", "aarch64"): "manylinux_2_17_aarch64.manylinux2014_aarch64", + ("darwin", "arm64"): "macosx_14_0_arm64", +} def platform_tag() -> Platform: @@ -77,6 +98,36 @@ def platform_tag() -> Platform: return "linux" +def normalize_solstone_core_machine(system: str, machine: str) -> str: + system_name = "darwin" if system == "darwin" else "linux" + machine_name = machine.lower() + if system_name == "linux" and machine_name == "arm64": + return "aarch64" + if system_name == "darwin" and machine_name == "aarch64": + return "arm64" + return machine_name + + +def current_solstone_core_platform() -> CorePlatform: + system = platform_tag() + return (system, normalize_solstone_core_machine(system, platform.machine())) + + +def is_solstone_core_covered_platform(system: str, machine: str) -> bool: + platform_tuple = ( + "darwin" if system == "darwin" else "linux", + normalize_solstone_core_machine(system, machine), + ) + return platform_tuple in SOLSTONE_CORE_COVERED_PLATFORMS + + +def solstone_core_marker_pins(version: str) -> tuple[str, ...]: + return tuple( + f"solstone-core=={version}; {marker}" + for marker in SOLSTONE_CORE_PLATFORM_MARKERS + ) + + def make_result( check: Check, status: Status, @@ -303,6 +354,53 @@ def uv_installed_check(args: object) -> CheckResult: ) +def solstone_core_rust_toolchain_check(args: object) -> CheckResult: + del args + check = SOLSTONE_CORE_RUST_TOOLCHAIN_CHECK + if not _is_source_checkout(): + return make_result( + check, + "skip", + "packaged install already carries solstone-core wheel metadata", + ) + + system, machine = current_solstone_core_platform() + if not is_solstone_core_covered_platform(system, machine): + return make_result( + check, + "skip", + f"solstone-core is not required on {system}/{machine}", + platform=system, + ) + + fix = "install Rust with cargo and rustc, then retry `make install`" + cargo_output = run_probe( + check, + ["cargo", "--version"], + timeout=5, + fix=fix, + ) + if isinstance(cargo_output, CheckResult): + return cargo_output + + rustc_output = run_probe( + check, + ["rustc", "--version"], + timeout=5, + fix=fix, + ) + if isinstance(rustc_output, CheckResult): + return rustc_output + + return make_result( + check, + "ok", + f"Rust toolchain available for solstone-core on {system}/{machine}: " + f"{cargo_output.stdout.strip()}; {rustc_output.stdout.strip()}", + platform=system, + ) + + def venv_consistent_check(args: object) -> CheckResult: del args check = VENV_CONSISTENT_CHECK diff --git a/solstone/think/supervisor.py b/solstone/think/supervisor.py index dabdb113f..e23dd0cf9 100644 --- a/solstone/think/supervisor.py +++ b/solstone/think/supervisor.py @@ -35,7 +35,7 @@ from solstone.observe.transcribe.resource import ( resolve_stt_backend_choice, stt_local_floor_bytes, ) -from solstone.think import maintenance, scheduler +from solstone.think import core_handshake, maintenance, scheduler from solstone.think.app_supervised import FLAG, is_app_supervised, resolve_parent_fd from solstone.think.backup.engine import BACKUP_MAX_RUNTIME, BACKUP_RUN_CMD from solstone.think.callosum import CallosumConnection, CallosumServer, callosum_send @@ -3584,6 +3584,15 @@ def main() -> None: log_path.parent.mkdir(parents=True, exist_ok=True) _configure_supervisor_logging(log_path, log_level) + core_result = core_handshake.check_solstone_core_handshake() + if core_result.status == "skip": + print(core_result.message) + logging.info(core_result.message) + elif core_result.status == "fail": + print(core_result.message, file=sys.stderr) + logging.error(core_result.message) + sys.exit(core_handshake.EX_CONFIG) + if args.verbose or args.debug: console_handler = logging.StreamHandler() console_handler.setLevel(log_level) diff --git a/tests/test_check_wheel_contents.py b/tests/test_check_wheel_contents.py new file mode 100644 index 000000000..d6fdac841 --- /dev/null +++ b/tests/test_check_wheel_contents.py @@ -0,0 +1,128 @@ +# SPDX-License-Identifier: AGPL-3.0-only +# Copyright (c) 2026 sol pbc + +from __future__ import annotations + +import base64 +import hashlib +import tarfile +import zipfile +from io import BytesIO +from pathlib import Path + +import scripts.check_wheel_contents as checker + + +def _record_hash(content: bytes) -> str: + digest = hashlib.sha256(content).digest() + encoded = base64.urlsafe_b64encode(digest).decode("ascii").rstrip("=") + return f"sha256={encoded}" + + +def _write_member( + wheel: zipfile.ZipFile, + name: str, + content: bytes, + *, + mode: int = 0o644, +) -> None: + info = zipfile.ZipInfo(name) + info.external_attr = mode << 16 + wheel.writestr(info, content) + + +def _write_core_wheel( + path: Path, + *, + tag: str = "manylinux_2_17_x86_64.manylinux2014_x86_64", + executable: bool = True, + record_ok: bool = True, +) -> Path: + wheel_path = path / f"solstone_core-1.2.3-py3-none-{tag}.whl" + members = { + "solstone_core-1.2.3.dist-info/METADATA": b"Name: solstone-core\nVersion: 1.2.3\n", + "solstone_core-1.2.3.dist-info/WHEEL": b"Wheel-Version: 1.0\n", + "solstone_core-1.2.3.data/scripts/solstone-core": b"#!/bin/sh\n", + } + rows = [ + f"{name},{_record_hash(content)},{len(content)}" + for name, content in members.items() + ] + rows.append("solstone_core-1.2.3.dist-info/RECORD,,") + record = "\n".join(rows).encode() + if not record_ok: + record = record.replace(b"sha256=", b"sha256=broken", 1) + with zipfile.ZipFile(wheel_path, "w") as wheel: + for name, content in members.items(): + mode = 0o755 if name.endswith("/solstone-core") and executable else 0o644 + _write_member(wheel, name, content, mode=mode) + _write_member(wheel, "solstone_core-1.2.3.dist-info/RECORD", record) + return wheel_path + + +def test_core_wheel_validator_accepts_static_manylinux_wheel(tmp_path: Path) -> None: + wheel = _write_core_wheel(tmp_path) + + assert checker.check_core_wheel(wheel, checker.MAX_CORE_WHEEL_BYTES) == [] + + +def test_core_wheel_validator_rejects_bare_linux_tag(tmp_path: Path) -> None: + wheel = _write_core_wheel(tmp_path, tag="linux_x86_64") + + errors = checker.check_core_wheel(wheel, checker.MAX_CORE_WHEEL_BYTES) + + assert any("unsupported solstone-core wheel tag" in error for error in errors) + assert any("bare linux tag" in error for error in errors) + + +def test_core_wheel_validator_rejects_non_executable_binary( + tmp_path: Path, +) -> None: + wheel = _write_core_wheel(tmp_path, executable=False) + + errors = checker.check_core_wheel(wheel, checker.MAX_CORE_WHEEL_BYTES) + + assert any("not executable" in error for error in errors) + + +def test_core_wheel_validator_rejects_record_drift(tmp_path: Path) -> None: + wheel = _write_core_wheel(tmp_path, record_ok=False) + + errors = checker.check_core_wheel(wheel, checker.MAX_CORE_WHEEL_BYTES) + + assert any("RECORD hash mismatch" in error for error in errors) + + +def _add_tar_member(archive: tarfile.TarFile, name: str) -> None: + content = b"x" + info = tarfile.TarInfo(name) + info.size = len(content) + archive.addfile(info, BytesIO(content)) + + +def _write_core_sdist(path: Path, *, missing: str | None = None) -> Path: + sdist = path / "solstone_core-1.2.3.tar.gz" + with tarfile.open(sdist, "w:gz") as archive: + for member in sorted(checker.CORE_REQUIRED_SDIST_MEMBERS): + if member == missing: + continue + _add_tar_member(archive, f"solstone_core-1.2.3/{member}") + return sdist + + +def test_core_sdist_validator_requires_rust_workspace_sources( + tmp_path: Path, +) -> None: + sdist = _write_core_sdist(tmp_path) + + assert checker.check_core_sdist(sdist) == [] + + +def test_core_sdist_validator_rejects_missing_workspace_source( + tmp_path: Path, +) -> None: + sdist = _write_core_sdist(tmp_path, missing="core/crates/solstone-core/src/main.rs") + + errors = checker.check_core_sdist(sdist) + + assert any("missing Rust workspace members" in error for error in errors) diff --git a/tests/test_core_handshake.py b/tests/test_core_handshake.py new file mode 100644 index 000000000..d2b671660 --- /dev/null +++ b/tests/test_core_handshake.py @@ -0,0 +1,162 @@ +# SPDX-License-Identifier: AGPL-3.0-only +# Copyright (c) 2026 sol pbc + +from __future__ import annotations + +import subprocess +from importlib.metadata import PackageNotFoundError +from pathlib import Path + +from solstone.think import core_handshake + + +def _raise_missing(_name: str) -> str: + raise PackageNotFoundError("solstone-core") + + +def _write_helper(tmp_path: Path, version: str, *, executable: bool = True) -> Path: + python = tmp_path / "venv" / "bin" / "python" + python.parent.mkdir(parents=True) + python.write_text("", encoding="utf-8") + helper = python.with_name("solstone-core") + helper.write_text(f"#!/bin/sh\necho solstone-core {version}\n", encoding="utf-8") + helper.chmod(0o755 if executable else 0o644) + return python + + +def _runner(stdout: str, returncode: int = 0): + def run(cmd, **_kwargs): + return subprocess.CompletedProcess(cmd, returncode, stdout=stdout, stderr="") + + return run + + +def test_source_checkout_without_core_dist_metadata_skips() -> None: + result = core_handshake.check_solstone_core_handshake( + source_checkout=lambda: True, + version_reader=_raise_missing, + platform_reader=lambda: ("linux", "x86_64"), + ) + + assert result.status == "skip" + assert result.message is not None + assert "source checkout" in result.message + + +def test_source_checkout_with_core_dist_metadata_compares_binary( + tmp_path: Path, +) -> None: + python = _write_helper(tmp_path, "1.2.3") + + result = core_handshake.check_solstone_core_handshake( + executable=python, + source_checkout=lambda: True, + version_reader=lambda _name: "1.2.3", + platform_reader=lambda: ("linux", "x86_64"), + ) + + assert result.status == "ok" + + +def test_packaged_covered_platform_requires_core_dist_metadata() -> None: + result = core_handshake.check_solstone_core_handshake( + source_checkout=lambda: False, + version_reader=_raise_missing, + platform_reader=lambda: ("linux", "x86_64"), + ) + + assert result.status == "fail" + assert result.message is not None + assert "missing solstone-core distribution metadata" in result.message + + +def test_packaged_uncovered_platform_skips_without_metadata() -> None: + result = core_handshake.check_solstone_core_handshake( + source_checkout=lambda: False, + version_reader=_raise_missing, + platform_reader=lambda: ("linux", "riscv64"), + ) + + assert result.status == "skip" + assert result.message is not None + assert "linux/riscv64" in result.message + assert "not covered" in result.message + + +def test_packaged_covered_platform_requires_binary(tmp_path: Path) -> None: + python = tmp_path / "venv" / "bin" / "python" + python.parent.mkdir(parents=True) + + result = core_handshake.check_solstone_core_handshake( + executable=python, + source_checkout=lambda: False, + version_reader=lambda _name: "1.2.3", + platform_reader=lambda: ("linux", "x86_64"), + ) + + assert result.status == "fail" + assert result.message is not None + assert "missing binary" in result.message + assert "1.2.3" in result.message + + +def test_packaged_covered_platform_rejects_non_executable_binary( + tmp_path: Path, +) -> None: + python = _write_helper(tmp_path, "1.2.3", executable=False) + + result = core_handshake.check_solstone_core_handshake( + executable=python, + source_checkout=lambda: False, + version_reader=lambda _name: "1.2.3", + platform_reader=lambda: ("linux", "x86_64"), + ) + + assert result.status == "fail" + assert result.message is not None + assert "not executable" in result.message + + +def test_packaged_covered_platform_rejects_unparseable_version( + tmp_path: Path, +) -> None: + python = _write_helper(tmp_path, "1.2.3") + + result = core_handshake.check_solstone_core_handshake( + executable=python, + source_checkout=lambda: False, + version_reader=lambda _name: "1.2.3", + platform_reader=lambda: ("linux", "x86_64"), + runner=_runner("unexpected\n"), + ) + + assert result.status == "fail" + assert result.message is not None + assert "could not parse" in result.message + assert "1.2.3" in result.message + + +def test_packaged_covered_platform_rejects_version_mismatch( + tmp_path: Path, +) -> None: + python = _write_helper(tmp_path, "9.9.9") + + result = core_handshake.check_solstone_core_handshake( + executable=python, + source_checkout=lambda: False, + version_reader=lambda _name: "1.2.3", + platform_reader=lambda: ("linux", "x86_64"), + ) + + assert result.status == "fail" + assert result.message is not None + assert "1.2.3" in result.message + assert "9.9.9" in result.message + + +def test_helper_locator_uses_executable_scripts_dir(tmp_path: Path) -> None: + executable = tmp_path / "env" / "bin" / "python" + + assert core_handshake.helper_path_for_executable(executable) == ( + tmp_path / "env" / "bin" / "solstone-core" + ) diff --git a/tests/test_preflight.py b/tests/test_preflight.py index 8467bc801..38816c69b 100644 --- a/tests/test_preflight.py +++ b/tests/test_preflight.py @@ -59,6 +59,10 @@ def fake_probe_dispatcher(probe, repo: Path): def run_probe(_check, cmd, **_kwargs): if list(cmd) == ["uv", "--version"]: return probe.ProbeOutput("uv 0.7.12\n", "", 0) + if list(cmd) == ["cargo", "--version"]: + return probe.ProbeOutput("cargo 1.87.0\n", "", 0) + if list(cmd) == ["rustc", "--version"]: + return probe.ProbeOutput("rustc 1.87.0\n", "", 0) if len(cmd) >= 3 and cmd[1:] == ["-c", "import sys; print(sys.prefix)"]: return probe.ProbeOutput(f"{repo / '.venv'}\n", "", 0) raise AssertionError(f"unexpected probe command: {cmd}") @@ -119,6 +123,81 @@ def test_uv_installed_ok(preflight, probe, monkeypatch): assert result.status == "ok" +def test_solstone_core_rust_toolchain_ok(preflight, probe, monkeypatch): + monkeypatch.setattr(probe, "_is_source_checkout", lambda: True) + monkeypatch.setattr( + probe, + "current_solstone_core_platform", + lambda: ("linux", "x86_64"), + ) + + def run_probe(_check, cmd, **_kwargs): + if list(cmd) == ["cargo", "--version"]: + return probe.ProbeOutput("cargo 1.87.0\n", "", 0) + if list(cmd) == ["rustc", "--version"]: + return probe.ProbeOutput("rustc 1.87.0\n", "", 0) + raise AssertionError(f"unexpected probe command: {cmd}") + + monkeypatch.setattr(probe, "run_probe", run_probe) + + result = preflight.solstone_core_rust_toolchain_check(args(preflight)) + + assert result.status == "ok" + assert "cargo 1.87.0" in result.detail + assert "rustc 1.87.0" in result.detail + + +def test_solstone_core_rust_toolchain_skips_packaged_install( + preflight, probe, monkeypatch +): + monkeypatch.setattr(probe, "_is_source_checkout", lambda: False) + + result = preflight.solstone_core_rust_toolchain_check(args(preflight)) + + assert result.status == "skip" + assert "packaged install" in result.detail + + +def test_solstone_core_rust_toolchain_skips_uncovered_platform( + preflight, probe, monkeypatch +): + monkeypatch.setattr(probe, "_is_source_checkout", lambda: True) + monkeypatch.setattr( + probe, + "current_solstone_core_platform", + lambda: ("linux", "riscv64"), + ) + + result = preflight.solstone_core_rust_toolchain_check(args(preflight)) + + assert result.status == "skip" + assert "linux/riscv64" in result.detail + + +def test_solstone_core_rust_toolchain_failure_names_rust_toolchain( + preflight, probe, monkeypatch +): + def raise_missing(*_args, **_kwargs): + raise FileNotFoundError + + monkeypatch.setattr(probe, "_is_source_checkout", lambda: True) + monkeypatch.setattr( + probe, + "current_solstone_core_platform", + lambda: ("linux", "x86_64"), + ) + monkeypatch.setattr(probe.subprocess, "run", raise_missing) + + result = preflight.solstone_core_rust_toolchain_check(args(preflight)) + + assert result.status == "fail" + assert "cargo" in result.detail + assert result.fix is not None + assert "Rust" in result.fix + assert "cargo" in result.fix + assert "rustc" in result.fix + + def test_venv_consistent_ok(preflight, probe, monkeypatch, tmp_path): repo = make_repo(tmp_path, with_venv=True) monkeypatch.setattr(probe, "ROOT", repo) diff --git a/tests/test_render_packaging.py b/tests/test_render_packaging.py index 789d45ada..74806050f 100644 --- a/tests/test_render_packaging.py +++ b/tests/test_render_packaging.py @@ -4,6 +4,9 @@ from __future__ import annotations import importlib.util +import os +import subprocess +import sys from pathlib import Path from textwrap import dedent @@ -17,6 +20,25 @@ assert SPEC.loader is not None SPEC.loader.exec_module(render_packaging) +def test_script_runs_without_site_packages_from_outside_repo(tmp_path: Path) -> None: + env = os.environ.copy() + env.pop("PYTHONPATH", None) + env.pop("VIRTUAL_ENV", None) + + result = subprocess.run( + [sys.executable, "-S", "-E", str(SCRIPT), "--check"], + cwd=tmp_path, + env=env, + capture_output=True, + text=True, + check=False, + timeout=15, + ) + + assert result.returncode == 0, result.stderr + assert "packaging metadata is up to date" in result.stdout + + def _write(path: Path, text: str) -> None: path.parent.mkdir(parents=True, exist_ok=True) path.write_text(dedent(text).lstrip(), encoding="utf-8") @@ -29,6 +51,16 @@ def _fixture_root(tmp_path: Path, *, root_version: str = "1.2.3") -> Path: [project] name = "solstone" version = "{root_version}" + + [project.optional-dependencies] + journal-host = [ + "solstone-journal-models==1.0.0", + "solstone-core==0.0.1; sys_platform == 'linux' and platform_machine == 'x86_64'", + "solstone-core==0.0.1; sys_platform == 'linux' and platform_machine == 'aarch64'", + "solstone-core==0.0.1; sys_platform == 'darwin' and platform_machine == 'arm64'", + ] + journal = ["solstone-journal-host==0.7.0"] + journal-cuda = ["solstone-journal-host==0.7.0"] """, ) for package_name in ("solstone-journal", "solstone-journal-cuda"): @@ -41,6 +73,24 @@ def _fixture_root(tmp_path: Path, *, root_version: str = "1.2.3") -> Path: dependencies = ["solstone[journal-host]==0.0.1"] """, ) + _write( + tmp_path / "packages" / "solstone-core" / "pyproject.toml", + """ + [build-system] + requires = ["maturin>=1.14,<2"] + build-backend = "maturin" + + [project] + name = "solstone-core" + version = "0.0.1" + + [tool.maturin] + bindings = "bin" + manifest-path = "../../core/crates/solstone-core/Cargo.toml" + profile = "release" + strip = true + """, + ) _write( tmp_path / "core" / "Cargo.toml", """ @@ -160,6 +210,38 @@ def test_render_updates_python_leaves_and_cargo_lockstep(tmp_path: Path) -> None leaf = rendered[root / "packages" / package_name / "pyproject.toml"] assert 'version = "2.3.4"' in leaf assert '"solstone[journal-host]==2.3.4"' in leaf + core_leaf = rendered[root / "packages" / "solstone-core" / "pyproject.toml"] + assert 'version = "2.3.4"' in core_leaf + assert "solstone[journal-host]==" not in core_leaf + root_pyproject = rendered[root / "pyproject.toml"] + assert ( + "\"solstone-core==2.3.4; sys_platform == 'linux' and platform_machine == 'x86_64'\"" + in root_pyproject + ) + assert ( + "\"solstone-core==2.3.4; sys_platform == 'linux' and platform_machine == 'aarch64'\"" + in root_pyproject + ) + assert ( + "\"solstone-core==2.3.4; sys_platform == 'darwin' and platform_machine == 'arm64'\"" + in root_pyproject + ) + + +def test_check_reports_synthetic_packaging_drift( + tmp_path: Path, + capsys: pytest.CaptureFixture[str], +) -> None: + root = _fixture_root(tmp_path, root_version="2.3.4") + + rc = render_packaging.check(root) + + assert rc == 1 + out = capsys.readouterr().out + assert "packaging metadata is stale" in out + assert "drifted: pyproject.toml" in out + assert "drifted: packages/solstone-core/pyproject.toml" in out + assert "drifted: core/Cargo.lock" in out def test_render_raises_when_cargo_lock_is_missing_member_block(tmp_path: Path) -> None: diff --git a/tests/test_solstone_core_platforms.py b/tests/test_solstone_core_platforms.py new file mode 100644 index 000000000..3c631353e --- /dev/null +++ b/tests/test_solstone_core_platforms.py @@ -0,0 +1,52 @@ +# SPDX-License-Identifier: AGPL-3.0-only +# Copyright (c) 2026 sol pbc + +from __future__ import annotations + +import tomllib +from pathlib import Path + +from packaging.markers import Marker + +from solstone.think.probe import ( + SOLSTONE_CORE_COVERED_PLATFORMS, + SOLSTONE_CORE_PLATFORM_MARKERS, + is_solstone_core_covered_platform, +) + +ROOT = Path(__file__).resolve().parents[1] + + +def _core_pin_markers() -> list[str]: + data = tomllib.loads((ROOT / "pyproject.toml").read_text(encoding="utf-8")) + host = data["project"]["optional-dependencies"]["journal-host"] + return [ + dep.split(";", 1)[1].strip() + for dep in host + if dep.startswith("solstone-core==") + ] + + +def test_core_pin_markers_match_probe_covered_platforms() -> None: + marker_texts = _core_pin_markers() + assert sorted(marker_texts) == sorted(SOLSTONE_CORE_PLATFORM_MARKERS) + markers = [Marker(text) for text in marker_texts] + platform_tuples = [ + *SOLSTONE_CORE_COVERED_PLATFORMS, + ("linux", "riscv64"), + ("darwin", "x86_64"), + ] + + for system, machine in platform_tuples: + marker_matches = [ + marker.evaluate( + { + "sys_platform": system, + "platform_machine": machine, + } + ) + for marker in markers + ] + assert sum(marker_matches) == int( + is_solstone_core_covered_platform(system, machine) + ) diff --git a/tests/test_solstone_core_wheel_install.py b/tests/test_solstone_core_wheel_install.py new file mode 100644 index 000000000..2a0028a3f --- /dev/null +++ b/tests/test_solstone_core_wheel_install.py @@ -0,0 +1,82 @@ +# SPDX-License-Identifier: AGPL-3.0-only +# Copyright (c) 2026 sol pbc + +from __future__ import annotations + +import os +import platform +import shutil +import subprocess +import sys +from pathlib import Path + +import pytest + +ROOT = Path(__file__).resolve().parents[1] + + +@pytest.mark.skipif( + sys.platform != "linux" or platform.machine() != "x86_64", + reason="local Linux wheel install test runs on linux/x86_64 only", +) +def test_locally_built_linux_core_wheel_installs_and_runs( + tmp_path: Path, +) -> None: + if shutil.which("uv") is None: + pytest.skip("uv is not installed") + if shutil.which("rustup") is None: + pytest.skip("rustup is not installed") + installed_targets = subprocess.run( + ["rustup", "target", "list", "--installed"], + cwd=ROOT, + capture_output=True, + text=True, + check=True, + ).stdout.splitlines() + if "x86_64-unknown-linux-musl" not in installed_targets: + pytest.skip("x86_64-unknown-linux-musl target is not installed") + + dist_dir = tmp_path / "dist" + env = os.environ.copy() + env["MATURIN_PEP517_ARGS"] = ( + "--compatibility manylinux2014 --target x86_64-unknown-linux-musl" + ) + subprocess.run( + [ + "uv", + "build", + "--offline", + "--package", + "solstone-core", + "--wheel", + "--out-dir", + str(dist_dir), + ], + cwd=ROOT, + env=env, + check=True, + ) + wheels = sorted(dist_dir.glob("solstone_core-*manylinux2014_x86_64.whl")) + assert len(wheels) == 1 + + venv = tmp_path / "venv" + subprocess.run([sys.executable, "-m", "venv", str(venv)], check=True) + bin_dir = venv / "bin" + subprocess.run( + [str(bin_dir / "pip"), "install", "--no-index", str(wheels[0])], + check=True, + ) + metadata_version = subprocess.check_output( + [ + str(bin_dir / "python"), + "-c", + "from importlib.metadata import version; print(version('solstone-core'))", + ], + text=True, + ).strip() + binary_version = subprocess.check_output( + [str(bin_dir / "solstone-core"), "--version"], + text=True, + ).strip() + + assert binary_version == f"solstone-core {metadata_version}" diff --git a/tests/test_supervisor_sync_gate.py b/tests/test_supervisor_sync_gate.py index b3517e1ce..9fbefc400 100644 --- a/tests/test_supervisor_sync_gate.py +++ b/tests/test_supervisor_sync_gate.py @@ -121,6 +121,53 @@ def test_startup_check_live_foreign_exits_1_prints_message_no_pid( assert not (tmp_path / "health" / "supervisor.pid").exists() +def test_core_handshake_skew_exits_before_pending_tasks(tmp_path, monkeypatch, capsys): + _set_identity(monkeypatch) + venv_bin = tmp_path / "venv" / "bin" + venv_bin.mkdir(parents=True) + python = venv_bin / "python" + python.write_text("", encoding="utf-8") + helper = venv_bin / "solstone-core" + helper.write_text("#!/bin/sh\necho solstone-core 9.9.9\n", encoding="utf-8") + helper.chmod(0o755) + + mod = _load_supervisor(tmp_path, monkeypatch) + monkeypatch.setattr(mod.core_handshake.sys, "executable", str(python)) + monkeypatch.setattr(mod.core_handshake, "is_source_checkout", lambda: False) + monkeypatch.setattr( + mod.core_handshake, + "distribution_version", + lambda _name: "1.2.3", + ) + monkeypatch.setattr( + mod.core_handshake, + "current_solstone_core_platform", + lambda: ("linux", "x86_64"), + ) + + pending_tasks_ran = False + + def fail_if_pending_tasks_run(*_args, **_kwargs): + nonlocal pending_tasks_ran + pending_tasks_ran = True + return (0, 0) + + monkeypatch.setattr(mod, "run_pending_tasks", fail_if_pending_tasks_run) + + with pytest.raises(SystemExit) as exc: + mod.main() + + assert exc.value.code == mod.core_handshake.EX_CONFIG + assert pending_tasks_ran is False + assert not (tmp_path / "health" / "supervisor.pid").exists() + captured = capsys.readouterr() + assert "1.2.3" in captured.err + assert "9.9.9" in captured.err + log_text = (tmp_path / "health" / "supervisor.log").read_text(encoding="utf-8") + assert "1.2.3" in log_text + assert "9.9.9" in log_text + + def test_mid_run_foreign_heartbeat_sets_shutdown_emits_event_returns( tmp_path, monkeypatch ): diff --git a/uv.lock b/uv.lock index b7293fe06..a31f5000a 100644 --- a/uv.lock +++ b/uv.lock @@ -4,21 +4,28 @@ requires-python = ">=3.12" resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'win32'", "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "(python_full_version >= '3.14' and platform_machine != 'x86_64' and sys_platform == 'linux') or (python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')", + "python_full_version >= '3.14' and platform_machine == 'arm64' and sys_platform == 'darwin'", + "python_full_version >= '3.14' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "(python_full_version >= '3.14' and platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux') or (python_full_version >= '3.14' and platform_machine != 'arm64' and sys_platform == 'darwin') or (python_full_version >= '3.14' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')", "python_full_version >= '3.14' and platform_machine == 'x86_64' and sys_platform == 'linux'", "python_full_version == '3.13.*' and sys_platform == 'win32'", "python_full_version < '3.13' and sys_platform == 'win32'", "python_full_version == '3.13.*' and sys_platform == 'emscripten'", "python_full_version < '3.13' and sys_platform == 'emscripten'", - "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and sys_platform == 'linux') or (python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')", + "python_full_version == '3.13.*' and platform_machine == 'arm64' and sys_platform == 'darwin'", + "python_full_version == '3.13.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "(python_full_version == '3.13.*' and platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux') or (python_full_version == '3.13.*' and platform_machine != 'arm64' and sys_platform == 'darwin') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')", "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'linux'", - "(python_full_version < '3.13' and platform_machine != 'x86_64' and sys_platform == 'linux') or (python_full_version < '3.13' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')", + "python_full_version < '3.13' and platform_machine == 'arm64' and sys_platform == 'darwin'", + "python_full_version < '3.13' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "(python_full_version < '3.13' and platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux') or (python_full_version < '3.13' and platform_machine != 'arm64' and sys_platform == 'darwin') or (python_full_version < '3.13' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')", "python_full_version < '3.13' and platform_machine == 'x86_64' and sys_platform == 'linux'", ] [manifest] members = [ "solstone", + "solstone-core", "solstone-journal", "solstone-journal-cuda", "solstone-journal-models", @@ -746,20 +753,20 @@ name = "datasets" version = "4.8.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "dill", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "filelock", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "fsspec", extra = ["http"], marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "httpx", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "huggingface-hub", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "multiprocess", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "numpy", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "packaging", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "pandas", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "pyarrow", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "pyyaml", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "requests", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "tqdm", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "xxhash", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, + { name = "dill", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "filelock", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "fsspec", extra = ["http"], marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "httpx", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "huggingface-hub", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "multiprocess", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "numpy", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "packaging", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "pandas", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "pyarrow", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "pyyaml", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "requests", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "tqdm", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "xxhash", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/66/34/14cd8e76f907f7d4dca2334cfeec9f81d30fd15c25a015f99aaea694eaed/datasets-4.8.5.tar.gz", hash = "sha256:0f0c1c3d56ffff2c93b2f4c63c95bac94f3d7e8621aea2a2a576275233bba772", size = 605649, upload-time = "2026-04-27T15:43:57.384Z" } wheels = [ @@ -871,11 +878,11 @@ name = "fastapi" version = "0.136.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "annotated-doc", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "pydantic", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "starlette", version = "1.2.1", source = { registry = "https://pypi.org/simple" }, marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "typing-extensions", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "typing-inspection", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, + { name = "annotated-doc", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "pydantic", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "starlette", version = "1.2.1", source = { registry = "https://pypi.org/simple" }, marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "typing-extensions", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "typing-inspection", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/5d/45/c130091c2dfa061bbfe3150f2a5091ef1adf149f2a8d2ae769ecaf6e99a2/fastapi-0.136.1.tar.gz", hash = "sha256:7af665ad7acfa0a3baf8983d393b6b471b9da10ede59c60045f49fbc89a0fa7f", size = 397448, upload-time = "2026-04-23T16:49:44.046Z" } wheels = [ @@ -1182,7 +1189,7 @@ wheels = [ [package.optional-dependencies] http = [ - { name = "aiohttp", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, + { name = "aiohttp", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, ] [[package]] @@ -2203,7 +2210,7 @@ name = "miniaudio" version = "1.71" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cffi", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, + { name = "cffi", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d8/d5/e5439dc08561f73656bfeb3340fc64ab63163e101426593d8fb9a025ff1e/miniaudio-1.71.tar.gz", hash = "sha256:ff51e2887bb673e2e757752b586b3dc924d59aa5fbcae9bbc45f4a111bd3262b", size = 1116480, upload-time = "2026-04-29T21:20:38.182Z" } wheels = [ @@ -2262,7 +2269,7 @@ name = "mlx" version = "0.31.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "mlx-metal", marker = "sys_platform == 'darwin'" }, + { name = "mlx-metal", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/c3/47/5f33906cb03d6a378a697cd2d2641a26b37dea17ee3d9124d7e39e8eca01/mlx-0.31.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:e5067aaf2be1f3d7bba5be52348775804f111173c1ed04639618fd713b1a530f", size = 584863, upload-time = "2026-04-22T03:14:38.211Z" }, @@ -2281,15 +2288,15 @@ name = "mlx-audio" version = "0.4.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "huggingface-hub", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "miniaudio", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "mlx", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "mlx-lm", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "numpy", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "scipy", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "sounddevice", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "tqdm", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "transformers", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, + { name = "huggingface-hub", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "miniaudio", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "mlx", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "mlx-lm", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "numpy", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "scipy", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "sounddevice", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "tqdm", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "transformers", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/95/db/a9f95e3794eca373d681220c8b9f8f84451a0d14959f85cc341ca592394c/mlx_audio-0.4.3.tar.gz", hash = "sha256:8e87badf56a0f73bf91e3797b1195c01440a181cf0b64a2a08dc1bda4b037f54", size = 1144947, upload-time = "2026-04-28T20:18:12.09Z" } wheels = [ @@ -2301,13 +2308,13 @@ name = "mlx-lm" version = "0.31.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "jinja2", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "mlx", marker = "sys_platform == 'darwin'" }, - { name = "numpy", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "protobuf", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "pyyaml", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "sentencepiece", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "transformers", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, + { name = "jinja2", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "mlx", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "numpy", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "protobuf", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "pyyaml", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "sentencepiece", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "transformers", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/84/94/9a38d6b0c6fcca995b9136c94eb7da1e9c5165652edf228b96b29960fa7a/mlx_lm-0.31.3.tar.gz", hash = "sha256:61eb0e3ba09444f77f874aff295401d7ccd20b39495cbbce0c782a15474ce733", size = 304318, upload-time = "2026-04-22T07:37:27.922Z" } wheels = [ @@ -2329,21 +2336,21 @@ name = "mlx-vlm" version = "0.6.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "datasets", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "fastapi", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "llguidance", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "miniaudio", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "mlx", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "mlx-audio", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "mlx-lm", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "numpy", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "opencv-python", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "pillow", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "requests", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "starlette", version = "1.2.1", source = { registry = "https://pypi.org/simple" }, marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "tqdm", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "transformers", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "uvicorn", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, + { name = "datasets", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "fastapi", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "llguidance", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "miniaudio", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "mlx", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "mlx-audio", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "mlx-lm", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "numpy", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "opencv-python", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "pillow", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "requests", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "starlette", version = "1.2.1", source = { registry = "https://pypi.org/simple" }, marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "tqdm", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "transformers", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "uvicorn", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/87/42/392ab6b3a80bf207182c9092787c3074123db85d8385f855f33f362b62e2/mlx_vlm-0.6.2.tar.gz", hash = "sha256:e249792461c59fd38f84fea30de93a1482374dd6dc8a0da88fe5dcfa9f4a4ffc", size = 1340542, upload-time = "2026-06-05T22:12:43.415Z" } wheels = [ @@ -2463,7 +2470,7 @@ name = "multiprocess" version = "0.70.19" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "dill", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, + { name = "dill", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a2/f2/e783ac7f2aeeed14e9e12801f22529cc7e6b7ab80928d6dcce4e9f00922d/multiprocess-0.70.19.tar.gz", hash = "sha256:952021e0e6c55a4a9fe4cd787895b86e239a40e76802a789d6305398d3975897", size = 2079989, upload-time = "2026-01-19T06:47:39.744Z" } wheels = [ @@ -2774,7 +2781,7 @@ name = "opencv-python" version = "4.13.0.92" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, + { name = "numpy", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/fc/6f/5a28fef4c4a382be06afe3938c64cc168223016fa520c5abaf37e8862aa5/opencv_python-4.13.0.92-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:caf60c071ec391ba51ed00a4a920f996d0b64e3e46068aac1f646b5de0326a19", size = 46247052, upload-time = "2026-02-05T07:01:25.046Z" }, @@ -3031,8 +3038,8 @@ name = "pandas" version = "3.0.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "python-dateutil", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, + { name = "numpy", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "python-dateutil", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f8/87/4341c6252d1c47b08768c3d25ac487362bf403f0313ddae4a2a26c9b1b4c/pandas-3.0.3.tar.gz", hash = "sha256:696a4a00a2a2a35d4e5deb3fc946641b96c944f02230e4f76137fe35d806c4fc", size = 4651414, upload-time = "2026-05-11T18:54:29.21Z" } wheels = [ @@ -4234,8 +4241,8 @@ name = "secretstorage" version = "3.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cryptography", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, - { name = "jeepney", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "cryptography", marker = "(platform_machine != 'arm64' and sys_platform == 'darwin') or (sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32')" }, + { name = "jeepney", marker = "(platform_machine != 'arm64' and sys_platform == 'darwin') or (sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32')" }, ] sdist = { url = "https://files.pythonhosted.org/packages/1c/03/e834bcd866f2f8a49a85eaff47340affa3bfa391ee9912a952a1faa68c7b/secretstorage-3.5.0.tar.gz", hash = "sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be", size = 19884, upload-time = "2025-11-23T19:02:53.191Z" } wheels = [ @@ -4417,6 +4424,7 @@ journal-host = [ { name = "python-slugify" }, { name = "rapidfuzz" }, { name = "scikit-learn" }, + { name = "solstone-core", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "solstone-journal-models" }, { name = "soundfile" }, { name = "tokenizers" }, @@ -4505,6 +4513,9 @@ requires-dist = [ { name = "solstone", extras = ["pdf"], marker = "extra == 'journal-host'" }, { name = "solstone", extras = ["pdf-export"], marker = "extra == 'pdf'" }, { name = "solstone", extras = ["pdf-import"], marker = "extra == 'pdf'" }, + { name = "solstone-core", marker = "platform_machine == 'arm64' and sys_platform == 'darwin' and extra == 'journal-host'", editable = "packages/solstone-core" }, + { name = "solstone-core", marker = "platform_machine == 'aarch64' and sys_platform == 'linux' and extra == 'journal-host'", editable = "packages/solstone-core" }, + { name = "solstone-core", marker = "platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'journal-host'", editable = "packages/solstone-core" }, { name = "solstone-journal-host", marker = "extra == 'journal'", specifier = "==0.7.0" }, { name = "solstone-journal-host", marker = "extra == 'journal-cuda'", specifier = "==0.7.0" }, { name = "solstone-journal-models", marker = "extra == 'journal-host'", editable = "packages/solstone-journal-models" }, @@ -4537,6 +4548,11 @@ dev = [ journal-cpu = [{ name = "solstone-journal", editable = "packages/solstone-journal" }] journal-cuda = [{ name = "solstone-journal-cuda", editable = "packages/solstone-journal-cuda" }] +[[package]] +name = "solstone-core" +version = "0.8.9" +source = { editable = "packages/solstone-core" } + [[package]] name = "solstone-journal" version = "0.8.9" @@ -4601,7 +4617,7 @@ name = "sounddevice" version = "0.5.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cffi", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, + { name = "cffi", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/2a/f9/2592608737553638fca98e21e54bfec40bf577bb98a61b2770c912aab25e/sounddevice-0.5.5.tar.gz", hash = "sha256:22487b65198cb5bf2208755105b524f78ad173e5ab6b445bdab1c989f6698df3", size = 143191, upload-time = "2026-01-23T18:36:43.529Z" } wheels = [ @@ -4671,9 +4687,15 @@ name = "starlette" version = "1.2.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "(python_full_version >= '3.14' and platform_machine != 'x86_64' and sys_platform == 'linux') or (python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')", - "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and sys_platform == 'linux') or (python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')", - "(python_full_version < '3.13' and platform_machine != 'x86_64' and sys_platform == 'linux') or (python_full_version < '3.13' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')", + "python_full_version >= '3.14' and platform_machine == 'arm64' and sys_platform == 'darwin'", + "python_full_version >= '3.14' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "(python_full_version >= '3.14' and platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux') or (python_full_version >= '3.14' and platform_machine != 'arm64' and sys_platform == 'darwin') or (python_full_version >= '3.14' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')", + "python_full_version == '3.13.*' and platform_machine == 'arm64' and sys_platform == 'darwin'", + "python_full_version == '3.13.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "(python_full_version == '3.13.*' and platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux') or (python_full_version == '3.13.*' and platform_machine != 'arm64' and sys_platform == 'darwin') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')", + "python_full_version < '3.13' and platform_machine == 'arm64' and sys_platform == 'darwin'", + "python_full_version < '3.13' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "(python_full_version < '3.13' and platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux') or (python_full_version < '3.13' and platform_machine != 'arm64' and sys_platform == 'darwin') or (python_full_version < '3.13' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')", ] dependencies = [ { name = "anyio", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, @@ -4854,15 +4876,15 @@ name = "transformers" version = "5.8.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "huggingface-hub", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "numpy", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "packaging", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "pyyaml", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "regex", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "safetensors", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "tokenizers", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "tqdm", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "typer", marker = "(platform_machine != 'x86_64' and sys_platform == 'linux') or (sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, + { name = "huggingface-hub", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "numpy", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "packaging", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "pyyaml", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "regex", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "safetensors", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "tokenizers", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "tqdm", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "typer", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e7/e6/4134ea2fbea322cddc7ffc94a0d8ee47fe32ce8e876b320cd37d88edfc4d/transformers-5.8.1.tar.gz", hash = "sha256:4dd5b6de4105725104d84fd6abd74b305f4debfc251b38c648ee5dd087cf543b", size = 8532019, upload-time = "2026-05-13T03:21:57.234Z" } wheels = [