-
Notifications
You must be signed in to change notification settings - Fork 22
(MOT-4299) fix(ci): run daily Harness benchmarks against Registry #735
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| #!/usr/bin/env python3 | ||
| """Create a deterministic identity for a Registry-resolved iii.lock.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import argparse | ||
| import hashlib | ||
| import json | ||
| import re | ||
| from pathlib import Path | ||
| from typing import Any | ||
|
|
||
| import yaml | ||
|
|
||
|
|
||
| WORKER_NAME = re.compile(r"^[a-z0-9][a-z0-9_-]*$") | ||
| EXACT_VERSION = re.compile( | ||
| r"^[0-9]+\.[0-9]+\.[0-9]+(-(experimental|alpha|beta))?$" | ||
| ) | ||
|
|
||
|
|
||
| def stack_identity(lock_path: Path) -> dict[str, Any]: | ||
| try: | ||
| document = yaml.safe_load(lock_path.read_text()) or {} | ||
| except (OSError, yaml.YAMLError) as error: | ||
| raise SystemExit(f"invalid_lock: cannot read {lock_path}: {error}") from error | ||
|
|
||
| workers = document.get("workers") if isinstance(document, dict) else None | ||
| if not isinstance(workers, dict) or not workers: | ||
| raise SystemExit("invalid_lock: workers must be a non-empty mapping") | ||
|
|
||
| versions: dict[str, str] = {} | ||
| for worker, record in sorted(workers.items()): | ||
| if not isinstance(worker, str) or not WORKER_NAME.fullmatch(worker): | ||
| raise SystemExit(f"invalid_lock: invalid worker name {worker!r}") | ||
| if not isinstance(record, dict): | ||
| raise SystemExit(f"invalid_lock: {worker} must be a mapping") | ||
| version = record.get("version") | ||
| if not isinstance(version, str) or not EXACT_VERSION.fullmatch(version): | ||
| raise SystemExit( | ||
| f"invalid_lock: {worker} must have an exact published version" | ||
| ) | ||
| versions[worker] = version | ||
|
|
||
| if "harness" not in versions: | ||
| raise SystemExit("invalid_lock: harness is required in the resolved stack") | ||
|
|
||
| return { | ||
| "schema_version": 1, | ||
| "lock_digest": hashlib.sha256(lock_path.read_bytes()).hexdigest(), | ||
| "stack_versions": versions, | ||
| } | ||
|
|
||
|
|
||
| def parse_args() -> argparse.Namespace: | ||
| parser = argparse.ArgumentParser() | ||
| parser.add_argument("--lock", type=Path, required=True) | ||
| parser.add_argument("--output", type=Path) | ||
| return parser.parse_args() | ||
|
|
||
|
|
||
| def main() -> None: | ||
| args = parse_args() | ||
| identity = stack_identity(args.lock) | ||
| rendered = json.dumps(identity, sort_keys=True) | ||
| if args.output: | ||
| args.output.parent.mkdir(parents=True, exist_ok=True) | ||
| args.output.write_text(rendered + "\n") | ||
| print(rendered) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| from __future__ import annotations | ||
|
|
||
| import hashlib | ||
| import json | ||
| import subprocess | ||
| import sys | ||
| from pathlib import Path | ||
|
|
||
| import pytest | ||
| import yaml | ||
|
|
||
|
|
||
| SCRIPT = Path(__file__).parents[1] / "registry_stack_identity.py" | ||
|
|
||
|
|
||
| def run_identity( | ||
| tmp_path: Path, workers: dict[str, dict[str, str]] | ||
| ) -> subprocess.CompletedProcess[str]: | ||
| lock = tmp_path / "iii.lock" | ||
| lock.write_text(yaml.safe_dump({"workers": workers}, sort_keys=True)) | ||
| return subprocess.run( | ||
| [sys.executable, str(SCRIPT), "--lock", str(lock)], | ||
| text=True, | ||
| capture_output=True, | ||
| check=False, | ||
| ) | ||
|
|
||
|
|
||
| def test_returns_exact_versions_and_lock_digest(tmp_path: Path) -> None: | ||
| workers = { | ||
| "harness": {"version": "1.8.0"}, | ||
| "state": {"version": "0.22.0"}, | ||
| } | ||
| result = run_identity(tmp_path, workers) | ||
|
|
||
| assert result.returncode == 0, result.stderr | ||
| identity = json.loads(result.stdout) | ||
| lock = tmp_path / "iii.lock" | ||
| assert identity["stack_versions"] == {"harness": "1.8.0", "state": "0.22.0"} | ||
| assert identity["lock_digest"] == hashlib.sha256(lock.read_bytes()).hexdigest() | ||
|
|
||
|
|
||
| @pytest.mark.parametrize( | ||
| ("workers", "message"), | ||
| [ | ||
| ({"state": {"version": "0.22.0"}}, "harness is required"), | ||
| ({"harness": {"version": "latest"}}, "exact published version"), | ||
| ], | ||
| ) | ||
| def test_rejects_non_identity_locks( | ||
| tmp_path: Path, | ||
| workers: dict[str, dict[str, str]], | ||
| message: str, | ||
| ) -> None: | ||
| result = run_identity(tmp_path, workers) | ||
|
|
||
| assert result.returncode != 0 | ||
| assert message in result.stderr |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -231,6 +231,13 @@ fresh stack, and repetitions run sequentially inside that job with unique table, | |
| session, and state namespaces. At most two matrix jobs make live-model calls | ||
| concurrently. | ||
|
|
||
| The daily lane uses the registry mode to measure the currently published live | ||
| stack. It resolves `latest` once per matrix job, records the exact versions and | ||
| SHA-256 digest of the resulting `iii.lock`, and treats Registry resolution | ||
| failures as `infra_failed`; it does not fall back to a source build. The main | ||
| lane keeps the source build and LLVM coverage so operational daily metrics stay | ||
| separate from checkout regression coverage. | ||
|
|
||
|
Comment on lines
+234
to
+240
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win A later paragraph now contradicts this one. Lines 329-331 still state that the daily lane evaluates repository binaries built from the resolved default branch commit, does not install registry artifacts, and leaves registry installation to the quickstart validator. The new paragraph and The table row for 📝 Proposed replacement for lines 329-331-The daily lane evaluates repository binaries built from the resolved default
-branch commit. It does not install registry artifacts; registry installation
-remains the responsibility of the quickstart validator.
+The daily lane installs the published registry stack and evaluates it against
+the resolved default branch commit. The main lane evaluates repository
+binaries built from the checkout. The quickstart validator remains the
+coverage for the `iii worker add` CLI path.🤖 Prompt for AI Agents |
||
| The deployed lane is a separate workflow run dispatched by the release smoke | ||
| workflow. The release publishes first, the smoke validates the published | ||
| installation and exact released version, and only a successful smoke dispatches | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
Each matrix job resolves
latestindependently, so the daily data point can mix stack versions.resolve_stack: truemakes every subject/scenario job run its ownharness@latestresolution. If a release publishes while the matrix runs, jobs resolve different versions.collect_harness_e2e_benchmarks.pythen records more than one entry instack_observations, and lines 704-708 and 850-854 fall back to the configured stack. Because the daily lane passes nostack_versionsand nostack_digest, that fallback is empty. The suite and snapshot metadata for that day would then carry no resolved stack version.The reusable workflow already accepts
stack_digest. Resolvinglatestonce in thebuildjob and passing the resolved digest to the matrix would pin the whole day to one stack.🤖 Prompt for AI Agents