Add determinism golden index
- SHA
15b524b009882f1a9987832462467a38feb6b34f- Parents
-
54e7e6a - Tree
b77dab1
15b524b
15b524b009882f1a9987832462467a38feb6b34f54e7e6a
b77dab1| Status | File | + | - |
|---|---|---|---|
| A |
.determinism/lock.json
|
5 | 0 |
| M |
docs/determinism.md
|
8 | 3 |
| M |
scripts/regen-determinism-golden.py
|
17 | 6 |
| M |
src/dlm/base_models/license.py
|
1 | 1 |
| M |
src/dlm/lock/__init__.py
|
34 | 6 |
| M |
src/dlm/lock/errors.py
|
18 | 0 |
| A |
src/dlm/lock/golden_index.py
|
136 | 0 |
| A |
tests/unit/lock/test_golden_index.py
|
128 | 0 |
.determinism/lock.jsonadded@@ -0,0 +1,5 @@ | ||
| 1 | +{ | |
| 2 | + "goldens": [], | |
| 3 | + "lock_version": 1, | |
| 4 | + "updated_at": "2026-04-22T04:25:32" | |
| 5 | +} | |
docs/determinism.mdmodified@@ -19,7 +19,8 @@ training produces a byte-identical `adapter_model.safetensors`. | ||
| 19 | 19 | |
| 20 | 20 | Proved by `tests/integration/lock/test_determinism_golden.py`, which |
| 21 | 21 | runs two fresh training cycles on the tiny model and asserts the |
| 22 | -adapter SHAs match. | |
| 22 | +adapter SHAs match. Approved tuple goldens are tracked at the repo | |
| 23 | +level in `.determinism/lock.json`. | |
| 23 | 24 | |
| 24 | 25 | ## What's in `dlm.lock` |
| 25 | 26 | |
@@ -120,10 +121,14 @@ The script: | ||
| 120 | 121 | 2. Runs the tiny-model training twice; confirms the two SHAs match. |
| 121 | 122 | 3. Writes `tests/golden/determinism/tuple-<hash>.json` keyed by a |
| 122 | 123 | SHA-256 of the sorted version tuple + platform. |
| 124 | +4. Upserts `.determinism/lock.json` with the tuple path, adapter SHA, | |
| 125 | + platform, and pinned versions. | |
| 123 | 126 | |
| 124 | 127 | Each tuple gets its own golden; the tuple file is keyed by content so |
| 125 | -running on a new platform simply writes a new golden file. The | |
| 126 | -reviewer checks in the new golden alongside the dep bump. | |
| 128 | +running on a new platform simply writes a new golden file. The repo-level | |
| 129 | +index keeps the checked-in set explicit and avoids overloading the | |
| 130 | +per-store `dlm.lock` name with a second meaning. The reviewer checks in | |
| 131 | +the tuple file and the index update alongside the dep bump. | |
| 127 | 132 | |
| 128 | 133 | ## Non-goals |
| 129 | 134 | |
scripts/regen-determinism-golden.pymodified@@ -17,8 +17,9 @@ Flow: | ||
| 17 | 17 | - `regenerated_at` — UTC timestamp |
| 18 | 18 | - `dlm_sha256` — hash of the synthetic training doc (reproducible |
| 19 | 19 | across runs when the factory's ULID seed is pinned) |
| 20 | -5. Compare against the prior golden (if one existed) and print a diff. | |
| 21 | -6. Exit non-zero unless `--approve` is passed. The default is | |
| 20 | +5. Upsert `.determinism/lock.json` with the checked-in tuple metadata. | |
| 21 | +6. Compare against the prior golden (if one existed) and print a diff. | |
| 22 | +7. Exit non-zero unless `--approve` is passed. The default is | |
| 22 | 23 | dry-run-and-report so a stray script invocation doesn't silently |
| 23 | 24 | overwrite a baseline. |
| 24 | 25 | |
@@ -26,10 +27,9 @@ Usage: | ||
| 26 | 27 | uv run python scripts/regen-determinism-golden.py # dry run |
| 27 | 28 | uv run python scripts/regen-determinism-golden.py --approve # write |
| 28 | 29 | |
| 29 | -The matching root-level `dlm.lock` (distinct from the per-store | |
| 30 | -`dlm.lock`) records which tuples have a checked-in golden. CI computes | |
| 31 | -the current golden and fails iff that lock asserts a tuple has a | |
| 32 | -golden but the on-disk file differs (catches silent drift on dep bump). | |
| 30 | +The matching repo-level `.determinism/lock.json` records which tuples | |
| 31 | +have a checked-in golden. It is distinct from the per-store | |
| 32 | +`dlm.lock`, which captures one training run's determinism contract. | |
| 33 | 33 | """ |
| 34 | 34 | |
| 35 | 35 | from __future__ import annotations |
@@ -140,9 +140,12 @@ def main() -> int: | ||
| 140 | 140 | |
| 141 | 141 | import tempfile |
| 142 | 142 | |
| 143 | + from dlm.lock.golden_index import GOLDEN_INDEX_RELATIVE_PATH, upsert_golden_index | |
| 144 | + | |
| 143 | 145 | versions = _current_versions() |
| 144 | 146 | filename = _tuple_filename(versions) |
| 145 | 147 | target = _GOLDEN_DIR / filename |
| 148 | + golden_relpath = target.relative_to(_REPO_ROOT).as_posix() | |
| 146 | 149 | prior = None |
| 147 | 150 | if target.is_file(): |
| 148 | 151 | try: |
@@ -192,7 +195,15 @@ def main() -> int: | ||
| 192 | 195 | |
| 193 | 196 | _GOLDEN_DIR.mkdir(parents=True, exist_ok=True) |
| 194 | 197 | target.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8") |
| 198 | + upsert_golden_index( | |
| 199 | + _REPO_ROOT, | |
| 200 | + golden_relpath=golden_relpath, | |
| 201 | + adapter_sha256=sha_a, | |
| 202 | + platform=payload["platform"], | |
| 203 | + pinned_versions=versions, | |
| 204 | + ) | |
| 195 | 205 | print(f"[wrote] {target.relative_to(_REPO_ROOT)}") |
| 206 | + print(f"[wrote] {GOLDEN_INDEX_RELATIVE_PATH}") | |
| 196 | 207 | return 0 |
| 197 | 208 | |
| 198 | 209 | |
src/dlm/base_models/license.pymodified@@ -11,7 +11,7 @@ an `accept_license` flag against the spec. | ||
| 11 | 11 | - `manifest.json.license_acceptance`: the per-store durable record; |
| 12 | 12 | read on every subsequent `dlm train` to verify the acceptance |
| 13 | 13 | fingerprint is still present. |
| 14 | -- Repo-level `dlm.lock.license_acceptance`: the determinism-contract | |
| 14 | +- Per-store `dlm.lock.license_acceptance`: the determinism-contract | |
| 15 | 15 | mirror; divergence between the two triggers a lock re-check. |
| 16 | 16 | |
| 17 | 17 | The interactive prompt in `dlm init` lives in the CLI layer; this |
src/dlm/lock/__init__.pymodified@@ -1,10 +1,11 @@ | ||
| 1 | 1 | """Per-store `dlm.lock` — determinism contract for one `.dlm`. |
| 2 | 2 | |
| 3 | -Separate from the repo-level `uv.lock` (tool-dep pins) and from the | |
| 4 | -`manifest.json` (training run narrative). The store-level `dlm.lock` | |
| 5 | -pins the tuple `(torch, transformers, peft, trl, bitsandbytes, | |
| 6 | -accelerate, llama.cpp tag, cuda/rocm, hardware_tier, seed, | |
| 7 | -determinism_flags, determinism_class)` and carries: | |
| 3 | +Separate from the repo-level `uv.lock` (tool-dep pins), from the | |
| 4 | +repo-level determinism-golden index at `.determinism/lock.json`, and | |
| 5 | +from `manifest.json` (training run narrative). The store-level | |
| 6 | +`dlm.lock` pins the tuple `(torch, transformers, peft, trl, | |
| 7 | +bitsandbytes, accelerate, llama.cpp tag, cuda/rocm, hardware_tier, | |
| 8 | +seed, determinism_flags, determinism_class)` and carries: | |
| 8 | 9 | |
| 9 | 10 | - the hash of the `.dlm` source at the time the lock was written |
| 10 | 11 | - the base-model revision + content hash |
@@ -20,7 +21,24 @@ table in `policy.py`. | ||
| 20 | 21 | from __future__ import annotations |
| 21 | 22 | |
| 22 | 23 | from dlm.lock.builder import build_lock, hardware_tier_from_backend, hash_dlm_file |
| 23 | -from dlm.lock.errors import LockError, LockSchemaError, LockValidationError, LockWriteError | |
| 24 | +from dlm.lock.errors import ( | |
| 25 | + GoldenIndexSchemaError, | |
| 26 | + GoldenIndexWriteError, | |
| 27 | + LockError, | |
| 28 | + LockSchemaError, | |
| 29 | + LockValidationError, | |
| 30 | + LockWriteError, | |
| 31 | +) | |
| 32 | +from dlm.lock.golden_index import ( | |
| 33 | + CURRENT_GOLDEN_INDEX_VERSION, | |
| 34 | + GOLDEN_INDEX_RELATIVE_PATH, | |
| 35 | + DeterminismGoldenEntry, | |
| 36 | + DeterminismGoldenIndex, | |
| 37 | + golden_index_path, | |
| 38 | + load_golden_index, | |
| 39 | + upsert_golden_index, | |
| 40 | + write_golden_index, | |
| 41 | +) | |
| 24 | 42 | from dlm.lock.policy import Severity, classify_mismatches |
| 25 | 43 | from dlm.lock.schema import CURRENT_LOCK_VERSION, LOCK_FILENAME, DlmLock |
| 26 | 44 | from dlm.lock.validator import LockDecision, LockMode, validate_lock |
@@ -28,6 +46,12 @@ from dlm.lock.writer import load_lock, lock_path, write_lock | ||
| 28 | 46 | |
| 29 | 47 | __all__ = [ |
| 30 | 48 | "CURRENT_LOCK_VERSION", |
| 49 | + "CURRENT_GOLDEN_INDEX_VERSION", | |
| 50 | + "GOLDEN_INDEX_RELATIVE_PATH", | |
| 51 | + "DeterminismGoldenEntry", | |
| 52 | + "DeterminismGoldenIndex", | |
| 53 | + "GoldenIndexSchemaError", | |
| 54 | + "GoldenIndexWriteError", | |
| 31 | 55 | "LOCK_FILENAME", |
| 32 | 56 | "DlmLock", |
| 33 | 57 | "LockDecision", |
@@ -40,9 +64,13 @@ __all__ = [ | ||
| 40 | 64 | "build_lock", |
| 41 | 65 | "classify_mismatches", |
| 42 | 66 | "hardware_tier_from_backend", |
| 67 | + "golden_index_path", | |
| 43 | 68 | "hash_dlm_file", |
| 44 | 69 | "load_lock", |
| 70 | + "load_golden_index", | |
| 45 | 71 | "lock_path", |
| 72 | + "upsert_golden_index", | |
| 46 | 73 | "validate_lock", |
| 74 | + "write_golden_index", | |
| 47 | 75 | "write_lock", |
| 48 | 76 | ] |
src/dlm/lock/errors.pymodified@@ -51,3 +51,21 @@ class LockValidationError(LockError): | ||
| 51 | 51 | self.reasons = list(reasons) |
| 52 | 52 | joined = "; ".join(reasons) |
| 53 | 53 | super().__init__(f"{path}: lock validation failed ({joined})") |
| 54 | + | |
| 55 | + | |
| 56 | +class GoldenIndexSchemaError(LockError): | |
| 57 | + """Repo-level determinism-golden index is unreadable or schema-invalid.""" | |
| 58 | + | |
| 59 | + def __init__(self, path: Path, reason: str) -> None: | |
| 60 | + self.path = path | |
| 61 | + self.reason = reason | |
| 62 | + super().__init__(f"{path}: {reason}") | |
| 63 | + | |
| 64 | + | |
| 65 | +class GoldenIndexWriteError(LockError): | |
| 66 | + """Programmer error on the repo-level determinism-golden index write path.""" | |
| 67 | + | |
| 68 | + def __init__(self, *, path: Path, reason: str) -> None: | |
| 69 | + self.path = path | |
| 70 | + self.reason = reason | |
| 71 | + super().__init__(f"{path}: write refused: {reason}") | |
src/dlm/lock/golden_index.pyadded@@ -0,0 +1,136 @@ | ||
| 1 | +"""Repo-level index of checked-in determinism goldens. | |
| 2 | + | |
| 3 | +Separate from the per-store `dlm.lock`: this file tracks which | |
| 4 | +runtime tuples have an approved golden under `tests/golden/determinism/`. | |
| 5 | +The canonical path is `.determinism/lock.json`. | |
| 6 | +""" | |
| 7 | + | |
| 8 | +from __future__ import annotations | |
| 9 | + | |
| 10 | +import json | |
| 11 | +from collections.abc import Mapping | |
| 12 | +from datetime import UTC, datetime | |
| 13 | +from pathlib import Path | |
| 14 | +from typing import Final | |
| 15 | + | |
| 16 | +from pydantic import BaseModel, ConfigDict, Field | |
| 17 | + | |
| 18 | +from dlm.io.atomic import write_text | |
| 19 | +from dlm.lock.errors import GoldenIndexSchemaError, GoldenIndexWriteError | |
| 20 | + | |
| 21 | +GOLDEN_INDEX_RELATIVE_PATH: Final[str] = ".determinism/lock.json" | |
| 22 | +CURRENT_GOLDEN_INDEX_VERSION: Final[int] = 1 | |
| 23 | + | |
| 24 | + | |
| 25 | +class DeterminismGoldenEntry(BaseModel): | |
| 26 | + """One approved tuple golden tracked at repo scope.""" | |
| 27 | + | |
| 28 | + model_config = ConfigDict(extra="forbid", frozen=True) | |
| 29 | + | |
| 30 | + golden_relpath: str = Field( | |
| 31 | + ..., | |
| 32 | + pattern=r"^tests/golden/determinism/tuple-[0-9a-f]{16}\.json$", | |
| 33 | + ) | |
| 34 | + adapter_sha256: str = Field(..., pattern=r"^[0-9a-f]{64}$") | |
| 35 | + platform: str = Field(..., min_length=1) | |
| 36 | + pinned_versions: dict[str, str] = Field(default_factory=dict) | |
| 37 | + | |
| 38 | + | |
| 39 | +class DeterminismGoldenIndex(BaseModel): | |
| 40 | + """Checked-in set of approved determinism goldens.""" | |
| 41 | + | |
| 42 | + model_config = ConfigDict(extra="forbid", frozen=True) | |
| 43 | + | |
| 44 | + lock_version: int = Field(CURRENT_GOLDEN_INDEX_VERSION, ge=1) | |
| 45 | + updated_at: datetime | |
| 46 | + goldens: tuple[DeterminismGoldenEntry, ...] = () | |
| 47 | + | |
| 48 | + | |
| 49 | +def golden_index_path(repo_root: Path) -> Path: | |
| 50 | + """Return `<repo_root>/.determinism/lock.json`.""" | |
| 51 | + | |
| 52 | + return repo_root / GOLDEN_INDEX_RELATIVE_PATH | |
| 53 | + | |
| 54 | + | |
| 55 | +def write_golden_index(repo_root: Path, index: DeterminismGoldenIndex) -> Path: | |
| 56 | + """Atomically persist the repo-level determinism-golden index.""" | |
| 57 | + | |
| 58 | + target = golden_index_path(repo_root) | |
| 59 | + if index.lock_version != CURRENT_GOLDEN_INDEX_VERSION: | |
| 60 | + raise GoldenIndexWriteError( | |
| 61 | + path=target, | |
| 62 | + reason=( | |
| 63 | + f"lock_version={index.lock_version!r} != writer's " | |
| 64 | + f"CURRENT_GOLDEN_INDEX_VERSION={CURRENT_GOLDEN_INDEX_VERSION}" | |
| 65 | + ), | |
| 66 | + ) | |
| 67 | + target.parent.mkdir(parents=True, exist_ok=True) | |
| 68 | + payload = index.model_dump(mode="json") | |
| 69 | + text = json.dumps(payload, indent=2, sort_keys=True) + "\n" | |
| 70 | + write_text(target, text) | |
| 71 | + return target | |
| 72 | + | |
| 73 | + | |
| 74 | +def load_golden_index(repo_root: Path) -> DeterminismGoldenIndex | None: | |
| 75 | + """Read `.determinism/lock.json`, returning `None` when absent.""" | |
| 76 | + | |
| 77 | + path = golden_index_path(repo_root) | |
| 78 | + if not path.is_file(): | |
| 79 | + return None | |
| 80 | + | |
| 81 | + try: | |
| 82 | + raw = path.read_text(encoding="utf-8") | |
| 83 | + except OSError as exc: | |
| 84 | + raise GoldenIndexSchemaError(path, f"unreadable: {exc}") from exc | |
| 85 | + | |
| 86 | + try: | |
| 87 | + payload = json.loads(raw) | |
| 88 | + except json.JSONDecodeError as exc: | |
| 89 | + raise GoldenIndexSchemaError(path, f"invalid JSON: {exc}") from exc | |
| 90 | + | |
| 91 | + if not isinstance(payload, dict): | |
| 92 | + raise GoldenIndexSchemaError( | |
| 93 | + path, | |
| 94 | + f"top-level JSON must be an object, got {type(payload).__name__}", | |
| 95 | + ) | |
| 96 | + | |
| 97 | + version = payload.get("lock_version") | |
| 98 | + if version != CURRENT_GOLDEN_INDEX_VERSION: | |
| 99 | + raise GoldenIndexSchemaError( | |
| 100 | + path, | |
| 101 | + f"unsupported lock_version {version!r} (reader expects {CURRENT_GOLDEN_INDEX_VERSION})", | |
| 102 | + ) | |
| 103 | + | |
| 104 | + try: | |
| 105 | + return DeterminismGoldenIndex.model_validate(payload) | |
| 106 | + except Exception as exc: | |
| 107 | + raise GoldenIndexSchemaError(path, f"schema validation: {exc}") from exc | |
| 108 | + | |
| 109 | + | |
| 110 | +def upsert_golden_index( | |
| 111 | + repo_root: Path, | |
| 112 | + *, | |
| 113 | + golden_relpath: str, | |
| 114 | + adapter_sha256: str, | |
| 115 | + platform: str, | |
| 116 | + pinned_versions: Mapping[str, str], | |
| 117 | +) -> Path: | |
| 118 | + """Insert or replace one tuple golden in `.determinism/lock.json`.""" | |
| 119 | + | |
| 120 | + current = load_golden_index(repo_root) | |
| 121 | + entries = {} if current is None else {entry.golden_relpath: entry for entry in current.goldens} | |
| 122 | + entries[golden_relpath] = DeterminismGoldenEntry( | |
| 123 | + golden_relpath=golden_relpath, | |
| 124 | + adapter_sha256=adapter_sha256, | |
| 125 | + platform=platform, | |
| 126 | + pinned_versions=dict(sorted(pinned_versions.items())), | |
| 127 | + ) | |
| 128 | + updated = DeterminismGoldenIndex( | |
| 129 | + updated_at=_utcnow(), | |
| 130 | + goldens=tuple(sorted(entries.values(), key=lambda entry: entry.golden_relpath)), | |
| 131 | + ) | |
| 132 | + return write_golden_index(repo_root, updated) | |
| 133 | + | |
| 134 | + | |
| 135 | +def _utcnow() -> datetime: | |
| 136 | + return datetime.now(UTC).replace(tzinfo=None, microsecond=0) | |
tests/unit/lock/test_golden_index.pyadded@@ -0,0 +1,128 @@ | ||
| 1 | +"""Repo-level determinism-golden index I/O.""" | |
| 2 | + | |
| 3 | +from __future__ import annotations | |
| 4 | + | |
| 5 | +from datetime import UTC, datetime | |
| 6 | +from pathlib import Path | |
| 7 | + | |
| 8 | +import pytest | |
| 9 | + | |
| 10 | +from dlm.lock.errors import GoldenIndexSchemaError | |
| 11 | +from dlm.lock.golden_index import ( | |
| 12 | + GOLDEN_INDEX_RELATIVE_PATH, | |
| 13 | + DeterminismGoldenEntry, | |
| 14 | + DeterminismGoldenIndex, | |
| 15 | + golden_index_path, | |
| 16 | + load_golden_index, | |
| 17 | + upsert_golden_index, | |
| 18 | + write_golden_index, | |
| 19 | +) | |
| 20 | + | |
| 21 | + | |
| 22 | +def _index(*entries: DeterminismGoldenEntry) -> DeterminismGoldenIndex: | |
| 23 | + return DeterminismGoldenIndex( | |
| 24 | + updated_at=datetime(2026, 4, 22, 4, 25, 32, tzinfo=UTC), | |
| 25 | + goldens=entries, | |
| 26 | + ) | |
| 27 | + | |
| 28 | + | |
| 29 | +def _entry( | |
| 30 | + *, | |
| 31 | + golden_relpath: str = "tests/golden/determinism/tuple-0123456789abcdef.json", | |
| 32 | + adapter_sha256: str = "a" * 64, | |
| 33 | + platform: str = "darwin-arm64", | |
| 34 | +) -> DeterminismGoldenEntry: | |
| 35 | + return DeterminismGoldenEntry( | |
| 36 | + golden_relpath=golden_relpath, | |
| 37 | + adapter_sha256=adapter_sha256, | |
| 38 | + platform=platform, | |
| 39 | + pinned_versions={"peft": "0.14.0", "torch": "2.5.1"}, | |
| 40 | + ) | |
| 41 | + | |
| 42 | + | |
| 43 | +class TestGoldenIndexPath: | |
| 44 | + def test_returns_repo_relative_path(self, tmp_path: Path) -> None: | |
| 45 | + assert golden_index_path(tmp_path) == tmp_path / GOLDEN_INDEX_RELATIVE_PATH | |
| 46 | + | |
| 47 | + | |
| 48 | +class TestWriteGoldenIndex: | |
| 49 | + def test_writes_readable_json(self, tmp_path: Path) -> None: | |
| 50 | + written = write_golden_index(tmp_path, _index(_entry())) | |
| 51 | + assert written.is_file() | |
| 52 | + text = written.read_text(encoding="utf-8") | |
| 53 | + assert text.endswith("\n") | |
| 54 | + assert text.index('"golden_relpath"') < text.index('"platform"') | |
| 55 | + | |
| 56 | + def test_round_trip_equal(self, tmp_path: Path) -> None: | |
| 57 | + original = _index(_entry()) | |
| 58 | + write_golden_index(tmp_path, original) | |
| 59 | + loaded = load_golden_index(tmp_path) | |
| 60 | + assert loaded == original | |
| 61 | + | |
| 62 | + | |
| 63 | +class TestLoadGoldenIndex: | |
| 64 | + def test_missing_file_returns_none(self, tmp_path: Path) -> None: | |
| 65 | + assert load_golden_index(tmp_path) is None | |
| 66 | + | |
| 67 | + def test_invalid_json_raises(self, tmp_path: Path) -> None: | |
| 68 | + golden_index_path(tmp_path).parent.mkdir(parents=True) | |
| 69 | + golden_index_path(tmp_path).write_text("{not valid", encoding="utf-8") | |
| 70 | + with pytest.raises(GoldenIndexSchemaError, match="invalid JSON"): | |
| 71 | + load_golden_index(tmp_path) | |
| 72 | + | |
| 73 | + def test_non_object_top_level_raises(self, tmp_path: Path) -> None: | |
| 74 | + golden_index_path(tmp_path).parent.mkdir(parents=True) | |
| 75 | + golden_index_path(tmp_path).write_text("[]", encoding="utf-8") | |
| 76 | + with pytest.raises(GoldenIndexSchemaError, match="must be an object"): | |
| 77 | + load_golden_index(tmp_path) | |
| 78 | + | |
| 79 | + def test_newer_version_is_rejected(self, tmp_path: Path) -> None: | |
| 80 | + golden_index_path(tmp_path).parent.mkdir(parents=True) | |
| 81 | + golden_index_path(tmp_path).write_text('{"lock_version": 99}', encoding="utf-8") | |
| 82 | + with pytest.raises(GoldenIndexSchemaError, match="unsupported lock_version"): | |
| 83 | + load_golden_index(tmp_path) | |
| 84 | + | |
| 85 | + | |
| 86 | +class TestUpsertGoldenIndex: | |
| 87 | + def test_creates_index_when_absent(self, tmp_path: Path) -> None: | |
| 88 | + upsert_golden_index( | |
| 89 | + tmp_path, | |
| 90 | + golden_relpath="tests/golden/determinism/tuple-0123456789abcdef.json", | |
| 91 | + adapter_sha256="a" * 64, | |
| 92 | + platform="darwin-arm64", | |
| 93 | + pinned_versions={"torch": "2.5.1", "peft": "0.14.0"}, | |
| 94 | + ) | |
| 95 | + loaded = load_golden_index(tmp_path) | |
| 96 | + assert loaded is not None | |
| 97 | + assert [entry.golden_relpath for entry in loaded.goldens] == [ | |
| 98 | + "tests/golden/determinism/tuple-0123456789abcdef.json" | |
| 99 | + ] | |
| 100 | + | |
| 101 | + def test_overwrites_existing_entry_and_sorts(self, tmp_path: Path) -> None: | |
| 102 | + write_golden_index( | |
| 103 | + tmp_path, | |
| 104 | + _index( | |
| 105 | + _entry(golden_relpath="tests/golden/determinism/tuple-ffffffffffffffff.json"), | |
| 106 | + _entry( | |
| 107 | + golden_relpath="tests/golden/determinism/tuple-aaaaaaaaaaaaaaaa.json", | |
| 108 | + adapter_sha256="b" * 64, | |
| 109 | + ), | |
| 110 | + ), | |
| 111 | + ) | |
| 112 | + | |
| 113 | + upsert_golden_index( | |
| 114 | + tmp_path, | |
| 115 | + golden_relpath="tests/golden/determinism/tuple-ffffffffffffffff.json", | |
| 116 | + adapter_sha256="c" * 64, | |
| 117 | + platform="linux-x86_64", | |
| 118 | + pinned_versions={"torch": "2.6.0"}, | |
| 119 | + ) | |
| 120 | + | |
| 121 | + loaded = load_golden_index(tmp_path) | |
| 122 | + assert loaded is not None | |
| 123 | + assert [entry.golden_relpath for entry in loaded.goldens] == [ | |
| 124 | + "tests/golden/determinism/tuple-aaaaaaaaaaaaaaaa.json", | |
| 125 | + "tests/golden/determinism/tuple-ffffffffffffffff.json", | |
| 126 | + ] | |
| 127 | + assert loaded.goldens[1].adapter_sha256 == "c" * 64 | |
| 128 | + assert loaded.goldens[1].platform == "linux-x86_64" | |