diff --git a/.github/workflows/quality-gates.yml b/.github/workflows/quality-gates.yml index 2abab83..d53f35c 100644 --- a/.github/workflows/quality-gates.yml +++ b/.github/workflows/quality-gates.yml @@ -136,7 +136,7 @@ jobs: --wheel-dir dist --dependency-lock candidate-dependencies.lock --output-dir candidate-base - --commit "${CANDIDATE_COMMIT}" + --commit "${{ env.CANDIDATE_COMMIT }}" --workflow-run-id "${{ github.run_id }}" --workflow-run-attempt "${{ github.run_attempt }}" - name: Upload same-run Candidate base @@ -184,7 +184,7 @@ jobs: --base-dir candidate-base --target "${{ matrix.target }}" --output-dir candidate-wheelhouse - --commit "${CANDIDATE_COMMIT}" + --commit "${{ env.CANDIDATE_COMMIT }}" --workflow-run-id "${{ github.run_id }}" --workflow-run-attempt "${{ github.run_attempt }}" - name: Upload same-run target wheelhouse @@ -234,7 +234,7 @@ jobs: --windows-wheelhouse wheelhouse-windows --output-dir candidate-channel --base-url . - --commit "${CANDIDATE_COMMIT}" + --commit "${{ env.CANDIDATE_COMMIT }}" --workflow-run-id "${{ github.run_id }}" --workflow-run-attempt "${{ github.run_attempt }}" - name: Upload exact Candidate channel diff --git a/agent-contract/README.md b/agent-contract/README.md index 6ab0e96..27e5d78 100644 --- a/agent-contract/README.md +++ b/agent-contract/README.md @@ -122,8 +122,10 @@ keyring, null pip configuration, no ambient proxy/source/cache/target/prefix/use settings, and wheel-only strict hashes. Because the supported Python interface is a minor version rather than one patch release, locks containing `python_full_version` or `implementation_version` markers fail closed. Compatibility -uses packaging's CPython tag model; macOS arm64 is conservatively bounded to its -macOS 11 deployment baseline, including compatible `abi3` and `universal2` tags. +uses packaging's CPython tag model; the macOS wheelhouse is bounded to deployment +version 15.0 and requires a trusted local macOS version of 15.0 or newer before target +selection. Compatible deployment tags through macOS 15, including `abi3` and +`universal2`, are accepted. The public prompt remains anchored to the exact canonical immutable GitHub Release ID, tag, commit, and asset set. Builders accept only local `.` for Candidate assets and the diff --git a/agent-contract/tests/test_candidate_wheelhouse.py b/agent-contract/tests/test_candidate_wheelhouse.py index 63166f8..14fb2e9 100644 --- a/agent-contract/tests/test_candidate_wheelhouse.py +++ b/agent-contract/tests/test_candidate_wheelhouse.py @@ -233,7 +233,8 @@ def test_target_tags_accept_abi3_and_reject_newer_macos_or_wrong_architecture(wh assert compatible("cp310-abi3-macosx_11_0_arm64", "darwin-arm64-py311") assert compatible("cp310-abi3-macosx_11_0_universal2", "darwin-arm64-py311") assert compatible("cp310-abi3-win_amd64", "win32-amd64-py312") - assert not compatible("cp310-abi3-macosx_12_0_arm64", "darwin-arm64-py311") + assert compatible("cp310-abi3-macosx_12_0_arm64", "darwin-arm64-py311") + assert not compatible("cp310-abi3-macosx_16_0_arm64", "darwin-arm64-py311") assert not compatible("cp311-cp311-macosx_11_0_x86_64", "darwin-arm64-py311") assert not compatible("cp312-cp312-win32", "win32-amd64-py312") @@ -280,6 +281,7 @@ def download(command, **kwargs): monkeypatch.setattr(module["platform"], "machine", lambda: "arm64") monkeypatch.setattr(module["sys"], "platform", "darwin") monkeypatch.setattr(module["sys"], "version_info", SimpleNamespace(major=3, minor=11)) + monkeypatch.setattr(module["platform"], "mac_ver", lambda: ("15.7.1", ("", "", ""), "")) output = tmp_path / "acquired" module["acquire_wheelhouse"](base_dir, "darwin-arm64-py311", output, *COMMIT_RUN) command = captured["command"] @@ -303,6 +305,26 @@ def download(command, **kwargs): )[0]["source"] == {"host": "pypi.org", "class": "default-public"} +@pytest.mark.parametrize("macos_version", ["15.0", "15.7.1", "16.0"]) +def test_supported_macos_deployment_selects_target(wheelhouse_module, monkeypatch, macos_version): + module = wheelhouse_module + monkeypatch.setattr(module["platform"], "machine", lambda: "arm64") + monkeypatch.setattr(module["platform"], "mac_ver", lambda: (macos_version, ("", "", ""), "")) + monkeypatch.setattr(module["sys"], "platform", "darwin") + monkeypatch.setattr(module["sys"], "version_info", SimpleNamespace(major=3, minor=11)) + module["_assert_target_host"]("darwin-arm64-py311") + + +def test_older_macos_deployment_fails_before_acquisition(wheelhouse_module, monkeypatch): + module = wheelhouse_module + monkeypatch.setattr(module["platform"], "machine", lambda: "arm64") + monkeypatch.setattr(module["platform"], "mac_ver", lambda: ("14.7", ("", "", ""), "")) + monkeypatch.setattr(module["sys"], "platform", "darwin") + monkeypatch.setattr(module["sys"], "version_info", SimpleNamespace(major=3, minor=11)) + with pytest.raises(module["WheelhouseError"], match="does not match"): + module["_assert_target_host"]("darwin-arm64-py311") + + @pytest.mark.parametrize("target, machine, sys_platform, version", [ ("darwin-arm64-py311", "x86_64", "darwin", (3, 11)), ("darwin-arm64-py311", "arm64", "darwin", (3, 12)), @@ -344,6 +366,7 @@ def test_candidate_builder_covers_nested_wheelhouses_and_offline_prompt( for relative in ("agent-contract", "appium-mcp-server", "pywinauto-mcp-server", "autogenesis-cli"): shutil.copytree(repo_root / relative, source / relative, ignore=shutil.ignore_patterns("__pycache__", ".pytest_cache", ".venv")) subprocess.run(["git", "init", "-q", str(source)], check=True) + subprocess.run(["git", "-C", str(source), "config", "core.longpaths", "true"], check=True) subprocess.run(["git", "-C", str(source), "add", "."], check=True) subprocess.run([ "git", "-C", str(source), "-c", "user.name=Candidate Contract Test", diff --git a/agent-contract/tools/build_candidate_release.py b/agent-contract/tools/build_candidate_release.py index 42e8b5a..ca75ced 100644 --- a/agent-contract/tools/build_candidate_release.py +++ b/agent-contract/tools/build_candidate_release.py @@ -240,9 +240,10 @@ def main() -> None: acquire=( "Use only the two manifest-bound target wheelhouses, universal lock, complete offline-install lock, " "first-party wheels, and Skill bytes already present in the exact Actions artifact. Prove the native " - "host from local facts, then select exactly one compatible target: native macOS arm64 with CPython 3.11 " - "selects `darwin-arm64-py311`; native Windows x64 with CPython 3.12 selects `win32-amd64-py312`. " - "Reject Linux, WSL, another Python implementation/version/architecture, zero matches, or multiple matches. " + "host from trusted local facts, then select exactly one compatible target: native macOS 15 or newer on " + "arm64 with CPython 3.11 selects `darwin-arm64-py311`; native Windows x64 with CPython 3.12 selects " + "`win32-amd64-py312`. Reject older macOS, Linux, WSL, another Python implementation/version/architecture, " + "zero matches, or multiple matches. " "Verify the selected target manifest and every listed wheel against the Candidate manifest and complete " "checksums before installation. Do not query or use a package index, mirror, proxy, credential, package " "configuration, cache, source tree, VCS checkout, sdist, editable input, or installed distribution." diff --git a/agent-contract/tools/candidate_wheelhouse.py b/agent-contract/tools/candidate_wheelhouse.py index 8efa3ee..e771704 100644 --- a/agent-contract/tools/candidate_wheelhouse.py +++ b/agent-contract/tools/candidate_wheelhouse.py @@ -53,6 +53,7 @@ "sys_platform": "darwin", "platform": "macos", "architecture": "arm64", + "deployment_version": "15.0", "os_name": "posix", "platform_system": "Darwin", "platform_machine": "arm64", @@ -275,9 +276,8 @@ def _target_tags(target_key: str) -> frozenset[str]: target = TARGETS[target_key] python_version = tuple(int(value) for value in target["python_version"].split(".")) if target["sys_platform"] == "darwin": - # Native arm64 exists from macOS 11 onward; this conservative baseline - # prevents a wheelhouse from claiming support via a newer deployment tag. - platforms = tuple(mac_platforms(version=(11, 0), arch="arm64")) + deployment_version = tuple(int(value) for value in target["deployment_version"].split(".")) + platforms = tuple(mac_platforms(version=deployment_version, arch="arm64")) else: platforms = ("win_amd64",) tags = set(cpython_tags(python_version=python_version, platforms=platforms)) @@ -457,11 +457,18 @@ def _assert_target_host(target_key: str) -> None: machine = platform.machine().lower() architecture = "arm64" if machine in {"arm64", "aarch64"} else "amd64" if machine in {"amd64", "x86_64"} else machine observed_version = f"{sys.version_info.major}.{sys.version_info.minor}" + deployment_ok = True + if target["sys_platform"] == "darwin": + try: + deployment_ok = Version(platform.mac_ver()[0]) >= Version(target["deployment_version"]) + except Exception: + deployment_ok = False if ( sys.implementation.name != "cpython" or observed_version != target["python_version"] or sys.platform != target["sys_platform"] or architecture != target["architecture"] + or not deployment_ok ): raise WheelhouseError("acquisition host does not match the exact Candidate target")