diff --git a/poetry.lock b/poetry.lock index a5e8fdaa..d606da32 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.4.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.3.2 and should not be changed by hand. [[package]] name = "annotated-doc" @@ -487,7 +487,7 @@ markers = {main = "platform_system == \"Windows\"", dev = "sys_platform == \"win [[package]] name = "constellation-node-sdk" -version = "1.0.1" +version = "1.1.0" description = "L9 Constellation Node SDK — TransportPacket runtime, Gate client, and node factory" optional = false python-versions = ">=3.12" @@ -511,8 +511,8 @@ dev = ["build (>=1.2.0)", "mypy (>=1.11.0)", "pytest (>=8.3.0)", "pytest-asyncio [package.source] type = "git" url = "https://github.com/Quantum-L9/Gate_SDK.git" -reference = "69c6c67060b08440734a61473c03663423709964" -resolved_reference = "69c6c67060b08440734a61473c03663423709964" +reference = "v1" +resolved_reference = "e9f829f982110be13752da8f18c7a9692e8ed908" [[package]] name = "coverage" diff --git a/scripts/validate_sdk_pin.py b/scripts/validate_sdk_pin.py index 0db0466f..a7627888 100644 --- a/scripts/validate_sdk_pin.py +++ b/scripts/validate_sdk_pin.py @@ -1,27 +1,71 @@ #!/usr/bin/env python3 -"""Fail closed unless Gate_SDK pin matches the immutable constellation SHA.""" +"""Fail closed unless Gate_SDK is pinned to the moving major tag. +Policy (CEG#266 / PR #263): constellation-node-sdk tracks ``Quantum-L9/Gate_SDK@v1``. +Manifests name the major tag. poetry.lock records that tag as ``reference`` and +the resolved object as ``resolved_reference``. +""" + +from __future__ import annotations + +import re from pathlib import Path ROOT = Path(__file__).resolve().parents[1] -PIN = "69c6c67060b08440734a61473c03663423709964" -errors: list[str] = [] - -for rel in ["pyproject.toml", "requirements.txt", "poetry.lock"]: - path = ROOT / rel - if not path.exists(): - continue - text = path.read_text() - if "Quantum-L9/Gate_SDK" not in text: - errors.append(f"{rel}: missing Quantum-L9") - if PIN not in text: - errors.append(f"{rel}: missing pin") - if "cryptoxdog/Gate_SDK" in text: - errors.append(f"{rel}: cryptoxdog remains") - -if errors: - print("FAIL") - print("\n".join(errors)) - raise SystemExit(1) - -print("PASS CEG pin", PIN) +MAJOR_TAG = "v1" +CANONICAL_REPO = "Quantum-L9/Gate_SDK" +FORBIDDEN_FORK = "cryptoxdog/Gate_SDK" +SHA_RE = re.compile(r"\b[0-9a-f]{40}\b") +LOCK_REFERENCE_RE = re.compile( + r'name = "constellation-node-sdk".*?\[package\.source\].*?' + r'reference = "([^"]+)".*?resolved_reference = "([0-9a-f]{40})"', + re.DOTALL, +) + + +def check_text(rel: str, text: str) -> list[str]: + errors: list[str] = [] + if CANONICAL_REPO not in text: + errors.append(f"{rel}: missing {CANONICAL_REPO}") + if FORBIDDEN_FORK in text: + errors.append(f"{rel}: {FORBIDDEN_FORK} remains") + if rel == "poetry.lock": + match = LOCK_REFERENCE_RE.search(text) + if match is None: + errors.append(f"{rel}: constellation-node-sdk source block missing") + else: + reference, resolved = match.group(1), match.group(2) + if reference != MAJOR_TAG: + errors.append(f"{rel}: reference={reference!r} (want {MAJOR_TAG!r})") + if not SHA_RE.fullmatch(resolved): + errors.append(f"{rel}: resolved_reference is not a SHA") + else: + if MAJOR_TAG not in text: + errors.append(f"{rel}: missing moving major tag {MAJOR_TAG}") + if SHA_RE.search(text): + errors.append(f"{rel}: immutable SHA pin leftover") + return errors + + +def check_tree(root: Path) -> list[str]: + errors: list[str] = [] + for rel in ("pyproject.toml", "requirements.txt", "poetry.lock"): + path = root / rel + if not path.exists(): + continue + errors.extend(check_text(rel, path.read_text(encoding="utf-8"))) + return errors + + +def main() -> int: + errors = check_tree(ROOT) + if errors: + print("FAIL") + print("\n".join(errors)) + return 1 + print(f"PASS CEG pin {CANONICAL_REPO}@{MAJOR_TAG}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/unit/test_node_app.py b/tests/unit/test_node_app.py index d2c02d2b..585fa790 100644 --- a/tests/unit/test_node_app.py +++ b/tests/unit/test_node_app.py @@ -317,7 +317,7 @@ def test_max_attachments_without_schemes_is_rejected() -> None: def test_sdk_default_attachment_caps_construct() -> None: - """Pinned SDK (69c6c67 = main a0827f2 + verifying-keys env fix) ships mutually valid default attachment/packet caps. + """Pinned SDK (@v1 / 1.1.0) ships mutually valid default attachment/packet caps. Earlier pins (a770e853) defaulted max_attachment_size_bytes above max_packet_bytes and failed closed on a bare construct. The current pin diff --git a/tests/unit/test_validate_sdk_pin.py b/tests/unit/test_validate_sdk_pin.py new file mode 100644 index 00000000..4e2b4e4e --- /dev/null +++ b/tests/unit/test_validate_sdk_pin.py @@ -0,0 +1,64 @@ +"""Unit tests — moving-major Gate_SDK pin validator (CEG#266).""" + +from __future__ import annotations + +import importlib.util +from pathlib import Path + +import pytest + +_ROOT = Path(__file__).resolve().parents[2] +_SPEC = importlib.util.spec_from_file_location( + "validate_sdk_pin", + _ROOT / "scripts" / "validate_sdk_pin.py", +) +if _SPEC is None: + raise RuntimeError("validate_sdk_pin.py did not load") +if _SPEC.loader is None: + raise RuntimeError("validate_sdk_pin.py has no loader") +_mod = importlib.util.module_from_spec(_SPEC) +_SPEC.loader.exec_module(_mod) +MAJOR_TAG = _mod.MAJOR_TAG +check_text = _mod.check_text +check_tree = _mod.check_tree + + +@pytest.mark.unit +def test_manifests_accept_major_tag() -> None: + pyproject = 'constellation-node-sdk = {git = "https://github.com/Quantum-L9/Gate_SDK.git", rev = "v1"}\n' + requirements = "constellation-node-sdk @ git+https://github.com/Quantum-L9/Gate_SDK.git@v1\n" + assert check_text("pyproject.toml", pyproject) == [] + assert check_text("requirements.txt", requirements) == [] + + +@pytest.mark.unit +def test_manifests_reject_sha_and_fork() -> None: + sha = "69c6c67060b08440734a61473c03663423709964" + text = f'constellation-node-sdk = {{git = "https://github.com/cryptoxdog/Gate_SDK.git", rev = "{sha}"}}\n' + errors = check_text("pyproject.toml", text) + assert any("cryptoxdog" in item for item in errors) + assert any("SHA" in item for item in errors) + assert any("Quantum-L9" in item for item in errors) + + +@pytest.mark.unit +def test_lock_requires_tag_reference_and_resolved_sha() -> None: + lock = ( + 'name = "constellation-node-sdk"\n' + 'version = "1.1.0"\n' + "[package.source]\n" + 'type = "git"\n' + 'url = "https://github.com/Quantum-L9/Gate_SDK.git"\n' + 'reference = "v1"\n' + 'resolved_reference = "e9f829f982110be13752da8f18c7a9692e8ed908"\n' + ) + assert check_text("poetry.lock", lock) == [] + stale = lock.replace('reference = "v1"', 'reference = "69c6c67060b08440734a61473c03663423709964"') + assert check_text("poetry.lock", stale) + + +@pytest.mark.unit +def test_repo_tree_matches_v1_policy() -> None: + errors = check_tree(_ROOT) + assert errors == [], errors + assert MAJOR_TAG == "v1"