diff --git a/docs/WWPGD_COMMIT_PIN.md b/docs/WWPGD_COMMIT_PIN.md new file mode 100644 index 0000000..31e6f8a --- /dev/null +++ b/docs/WWPGD_COMMIT_PIN.md @@ -0,0 +1,42 @@ +# Optional WW-PGD runtime commit verification + +The repository continues to install WW-PGD from a floating Git dependency: + +```text +ww-pgd @ git+https://github.com/CalculatedContent/WW_PGD.git +``` + +For a frozen or jointly reproduced experiment, require the installed package to resolve to a particular Git commit by setting: + +```bash +export WWPGD_COMMIT_PIN= +``` + +The value must be a hexadecimal Git SHA prefix containing at least 12 and at most 64 characters. A full SHA is accepted. Matching is one-directional: the installed PEP 610 `commit_id` must start with the requested value. + +## Enforcement + +Verification occurs at the shared pip adapter boundary used by: + +- the main scientific Level 0–2 runner; +- the isolated Level Zero WW-PGD runner; +- Experiment 2 adaptive WW-PGD; +- direct callers of the common WW-PGD configuration or candidate functions. + +A requested pin is checked before constructing a WW-PGD configuration and again before invoking the installed projector. A mismatch, malformed pin, or installation without a PEP 610 VCS commit fails loudly before a projection is accepted. + +When the environment variable is unset or empty, behavior is unchanged and the floating installation is allowed. + +## Manifest fields + +WW-PGD manifests distinguish the installation specification from runtime verification: + +- `wwpgd_resolved_commit`: installed PEP 610 VCS commit; +- `wwpgd_commit_pin_requested`: normalized requested SHA or `null`; +- `wwpgd_commit_pin_verified`: `true` only after a successful comparison; +- `wwpgd_commit_pin_status`: `floating` or `verified`; +- `wwpgd_dependency_pinned`: always `false` while `pyproject.toml` remains floating. + +The provenance mapping evaluates the environment when the manifest is built rather than only when Python imports `wwgpt.ww`. This prevents notebooks and long-lived processes from verifying one pin state while recording stale import-time metadata. + +This feature changes no spectral target, dose, trust-region rule, optimizer update, model weight, or default training behavior. diff --git a/src/wwgpt/pip_wwpgd_adapter.py b/src/wwgpt/pip_wwpgd_adapter.py index 69a475f..4abd347 100644 --- a/src/wwgpt/pip_wwpgd_adapter.py +++ b/src/wwgpt/pip_wwpgd_adapter.py @@ -3,15 +3,33 @@ import inspect import json +import os +import re +from collections.abc import Iterator, Mapping from importlib import metadata from pathlib import Path from typing import Any +WWPGD_COMMIT_PIN_ENV = "WWPGD_COMMIT_PIN" +WWPGD_COMMIT_PIN_MIN_LENGTH = 12 +WWPGD_COMMIT_PIN_MAX_LENGTH = 64 +_HEXADECIMAL_COMMIT = re.compile(r"^[0-9a-f]+$") + REQUIRED_PROJECTOR_PARAMETERS = frozenset( {"model", "cfg", "epoch", "num_epochs", "global_step", "ww_logs", "layer_selector"} ) REQUIRED_CONFIG_OPTIONS = frozenset( - {"enable_tail_pgd", "q", "blend_eta", "cayley_eta", "min_tail", "use_detx", "warmup_epochs", "ramp_epochs", "verbose"} + { + "enable_tail_pgd", + "q", + "blend_eta", + "cayley_eta", + "min_tail", + "use_detx", + "warmup_epochs", + "ramp_epochs", + "verbose", + } ) @@ -37,7 +55,9 @@ def inspect_pip_wwpgd_api() -> dict[str, Any]: projector_names = set(projector_signature.parameters) missing = sorted(REQUIRED_PROJECTOR_PARAMETERS - projector_names) if missing: - raise RuntimeError(f"incompatible pip-installed ww_pgd projector; missing parameters: {missing}") + raise RuntimeError( + f"incompatible pip-installed ww_pgd projector; missing parameters: {missing}" + ) return { "module": ww_pgd, "config_class": config, @@ -48,8 +68,28 @@ def inspect_pip_wwpgd_api() -> dict[str, Any]: } -def resolve_pip_wwpgd_provenance() -> dict[str, Any]: - """Return package metadata, including PEP 610 VCS provenance when supplied.""" +def _requested_wwpgd_commit_pin() -> str | None: + requested = (os.environ.get(WWPGD_COMMIT_PIN_ENV) or "").strip().lower() + return requested or None + + +def _validate_requested_commit_pin(requested_pin: str) -> str: + pin = str(requested_pin).strip().lower() + if not ( + WWPGD_COMMIT_PIN_MIN_LENGTH + <= len(pin) + <= WWPGD_COMMIT_PIN_MAX_LENGTH + ) or _HEXADECIMAL_COMMIT.fullmatch(pin) is None: + raise ValueError( + f"{WWPGD_COMMIT_PIN_ENV} must be a hexadecimal Git SHA prefix " + f"between {WWPGD_COMMIT_PIN_MIN_LENGTH} and " + f"{WWPGD_COMMIT_PIN_MAX_LENGTH} characters" + ) + return pin + + +def _resolve_pip_wwpgd_base_provenance() -> dict[str, Any]: + """Resolve package metadata that is stable for the lifetime of this process.""" api = inspect_pip_wwpgd_api() ww_pgd = api["module"] dist = _distribution("ww-pgd", "ww_pgd") @@ -71,7 +111,11 @@ def resolve_pip_wwpgd_provenance() -> dict[str, Any]: else: mode = "pypi" ww_dist_name = dist.metadata.get("Name") if dist is not None else "ww-pgd" - ww_version = dist.version if dist is not None else getattr(ww_pgd, "__version__", "unknown") + ww_version = ( + dist.version + if dist is not None + else getattr(ww_pgd, "__version__", "unknown") + ) import weightwatcher @@ -85,15 +129,132 @@ def resolve_pip_wwpgd_provenance() -> dict[str, Any]: "wwpgd_resolved_commit": vcs.get("commit_id"), "wwpgd_projector_signature": str(api["projector_signature_object"]), "wwpgd_config_signature": str(api["config_signature_object"]), - "wwpgd_native_internal_diagnostics": api["native_internal_diagnostics"], - "wwpgd_dependency_pinned": False, - "weightwatcher_installed_version": str(weightwatcher_dist.version if weightwatcher_dist else getattr(weightwatcher, "__version__", "unknown")), + "wwpgd_native_internal_diagnostics": api[ + "native_internal_diagnostics" + ], + "weightwatcher_installed_version": str( + weightwatcher_dist.version + if weightwatcher_dist + else getattr(weightwatcher, "__version__", "unknown") + ), "weightwatcher_module_path": str(Path(weightwatcher.__file__).resolve()), } +def verify_wwpgd_commit_pin( + provenance: Mapping[str, Any], + *, + requested_pin: str | None = None, +) -> dict[str, Any]: + """Return explicit floating/verified provenance or fail on an invalid pin. + + A commit pin is runtime verification of a floating Git dependency. It does + not rewrite the installation specification and therefore never makes + ``wwpgd_dependency_pinned`` true. + """ + info = dict(provenance) + raw_pin = _requested_wwpgd_commit_pin() if requested_pin is None else requested_pin + pin = str(raw_pin or "").strip().lower() or None + info.update( + { + "wwpgd_commit_pin_env": WWPGD_COMMIT_PIN_ENV, + "wwpgd_commit_pin_requested": pin, + "wwpgd_commit_pin_verified": False, + "wwpgd_commit_pin_status": "floating" if pin is None else "requested", + "wwpgd_dependency_pinned": False, + } + ) + if pin is None: + return info + + pin = _validate_requested_commit_pin(pin) + info["wwpgd_commit_pin_requested"] = pin + resolved = str(info.get("wwpgd_resolved_commit") or "").strip().lower() + if not resolved: + raise RuntimeError( + f"{WWPGD_COMMIT_PIN_ENV}={pin!r} is set but the installed ww_pgd " + "package has no VCS commit in PEP 610 direct_url.json. Install " + "ww_pgd from Git or clear the pin." + ) + if _HEXADECIMAL_COMMIT.fullmatch(resolved) is None: + raise RuntimeError( + "the installed ww_pgd PEP 610 commit_id is not a hexadecimal Git SHA: " + f"{resolved!r}" + ) + if not resolved.startswith(pin): + raise RuntimeError( + f"ww_pgd commit pin mismatch: {WWPGD_COMMIT_PIN_ENV}={pin!r} " + f"but resolved commit is {resolved!r}" + ) + info["wwpgd_resolved_commit"] = resolved + info["wwpgd_commit_pin_verified"] = True + info["wwpgd_commit_pin_status"] = "verified" + return info + + +def resolve_and_verify_pip_wwpgd_provenance( + provenance: Mapping[str, Any] | None = None, + *, + requested_pin: str | None = None, +) -> dict[str, Any]: + """Resolve fresh provenance and apply the optional runtime commit check.""" + base = ( + _resolve_pip_wwpgd_base_provenance() + if provenance is None + else dict(provenance) + ) + return verify_wwpgd_commit_pin(base, requested_pin=requested_pin) + + +class _LiveWWPGDProvenance(Mapping[str, Any]): + """Mapping whose pin fields reflect the environment at access time. + + ``wwgpt.ww`` retains a provenance object at import time. Keeping the package + metadata stable while evaluating the pin dynamically prevents notebooks or + long-lived processes from verifying one environment state and recording a + different, stale state in their manifests. + """ + + def __init__(self, base: Mapping[str, Any]): + self._base = dict(base) + + def snapshot(self) -> dict[str, Any]: + return verify_wwpgd_commit_pin(self._base) + + def __getitem__(self, key: str) -> Any: + return self.snapshot()[key] + + def __iter__(self) -> Iterator[str]: + return iter(self.snapshot()) + + def __len__(self) -> int: + return len(self.snapshot()) + + def __repr__(self) -> str: + return repr(self.snapshot()) + + +def resolve_pip_wwpgd_provenance() -> Mapping[str, Any]: + """Return live manifest provenance with explicit pin verification status.""" + return _LiveWWPGDProvenance(_resolve_pip_wwpgd_base_provenance()) + + +def assert_wwpgd_commit_pin( + provenance: Mapping[str, Any] | None = None, +) -> dict[str, Any]: + """Compatibility name for explicit runtime verification.""" + return resolve_and_verify_pip_wwpgd_provenance(provenance) + + +def enforce_wwpgd_commit_pin() -> None: + """Fail at a shared adapter boundary only when a pin was requested.""" + if _requested_wwpgd_commit_pin() is not None: + resolve_and_verify_pip_wwpgd_provenance() + + def construct_pip_wwpgd_config(spec: object) -> tuple[object, dict[str, Any]]: """Map every mathematical experiment option into the installed config.""" + enforce_wwpgd_commit_pin() api = inspect_pip_wwpgd_api() target_alpha = float(getattr(spec, "target_alpha")) if target_alpha <= 1.0: @@ -111,24 +272,48 @@ def construct_pip_wwpgd_config(spec: object) -> tuple[object, dict[str, Any]]: } limit = getattr(spec, "max_relative_frobenius_change", None) params = api["config_signature_object"].parameters - has_kwargs = any(p.kind is inspect.Parameter.VAR_KEYWORD for p in params.values()) - missing = sorted(name for name in REQUIRED_CONFIG_OPTIONS if name not in params and not has_kwargs) + has_kwargs = any( + parameter.kind is inspect.Parameter.VAR_KEYWORD + for parameter in params.values() + ) + missing = sorted( + name + for name in REQUIRED_CONFIG_OPTIONS + if name not in params and not has_kwargs + ) if missing: - raise RuntimeError(f"incompatible pip-installed ww_pgd WWTailConfig; required options unsupported: {missing}") + raise RuntimeError( + "incompatible pip-installed ww_pgd WWTailConfig; required options " + f"unsupported: {missing}" + ) if limit is not None: if "max_relative_frobenius_change" not in params and not has_kwargs: - raise RuntimeError("pip-installed ww_pgd cannot enforce requested max_relative_frobenius_change") + raise RuntimeError( + "pip-installed ww_pgd cannot enforce requested " + "max_relative_frobenius_change" + ) requested["max_relative_frobenius_change"] = float(limit) config = api["config_class"](**requested) - resolved = {name: getattr(config, name, value) for name, value in requested.items()} - ignored = [name for name, value in requested.items() if resolved[name] != value] + resolved = { + name: getattr(config, name, value) for name, value in requested.items() + } + ignored = [ + name for name, value in requested.items() if resolved[name] != value + ] if ignored: - raise RuntimeError(f"pip-installed ww_pgd silently changed requested options: {ignored}") + raise RuntimeError( + f"pip-installed ww_pgd silently changed requested options: {ignored}" + ) return config, {"requested": requested, "resolved": resolved} -def run_pip_wwpgd_candidate(model: object, config: object, **kwargs: Any) -> dict[str, Any]: +def run_pip_wwpgd_candidate( + model: object, + config: object, + **kwargs: Any, +) -> dict[str, Any]: """Invoke the installed projector exactly once with its supported diagnostics.""" + enforce_wwpgd_commit_pin() api = inspect_pip_wwpgd_api() ww_logs: list[Any] = [] diagnostics: list[dict[str, Any]] = [] @@ -136,4 +321,8 @@ def run_pip_wwpgd_candidate(model: object, config: object, **kwargs: Any) -> dic if api["native_internal_diagnostics"]: call["diagnostic_logs"] = diagnostics api["projector"](model, config, **call) - return {"ww_logs": ww_logs, "diagnostic_logs": diagnostics, "native_internal_diagnostics": api["native_internal_diagnostics"]} + return { + "ww_logs": ww_logs, + "diagnostic_logs": diagnostics, + "native_internal_diagnostics": api["native_internal_diagnostics"], + } diff --git a/tests/test_wwpgd_commit_pin.py b/tests/test_wwpgd_commit_pin.py new file mode 100644 index 0000000..9330afe --- /dev/null +++ b/tests/test_wwpgd_commit_pin.py @@ -0,0 +1,192 @@ +"""Deterministic coverage for optional WWPGD runtime commit verification.""" +from __future__ import annotations + +from pathlib import Path +from types import SimpleNamespace + +import pytest + +import wwgpt.pip_wwpgd_adapter as adapter +import wwgpt.ww as ww + +RESOLVED_COMMIT = "abcdef0123456789abcdef0123456789abcdef01" + + +def _provenance(*, resolved: str | None = RESOLVED_COMMIT) -> dict[str, object]: + return { + "wwpgd_distribution_name": "ww-pgd", + "wwpgd_installed_version": "test", + "wwpgd_resolved_commit": resolved, + "wwpgd_install_mode": "pip-vcs" if resolved else "pypi", + } + + +def test_unset_pin_reports_floating_install_without_false_pin_claim() -> None: + info = adapter.verify_wwpgd_commit_pin(_provenance(), requested_pin="") + assert info["wwpgd_commit_pin_requested"] is None + assert info["wwpgd_commit_pin_verified"] is False + assert info["wwpgd_commit_pin_status"] == "floating" + assert info["wwpgd_dependency_pinned"] is False + + +@pytest.mark.parametrize( + "requested", + [RESOLVED_COMMIT, RESOLVED_COMMIT[:12], RESOLVED_COMMIT[:20].upper()], +) +def test_full_or_valid_hex_prefix_is_verified(requested: str) -> None: + info = adapter.verify_wwpgd_commit_pin( + _provenance(), + requested_pin=requested, + ) + assert info["wwpgd_commit_pin_requested"] == requested.lower() + assert info["wwpgd_commit_pin_verified"] is True + assert info["wwpgd_commit_pin_status"] == "verified" + assert info["wwpgd_resolved_commit"] == RESOLVED_COMMIT + # The install requirement remains floating even after runtime verification. + assert info["wwpgd_dependency_pinned"] is False + + +@pytest.mark.parametrize( + "requested", + [ + "a" * 11, + "g" * 12, + "abc def012345", + "a" * 65, + ], +) +def test_malformed_or_dangerously_short_pin_is_rejected(requested: str) -> None: + with pytest.raises(ValueError, match="hexadecimal Git SHA prefix"): + adapter.verify_wwpgd_commit_pin( + _provenance(), + requested_pin=requested, + ) + + +def test_mismatch_is_rejected_without_reverse_prefix_acceptance() -> None: + with pytest.raises(RuntimeError, match="commit pin mismatch"): + adapter.verify_wwpgd_commit_pin( + _provenance(), + requested_pin="0" * 12, + ) + + # A requested value longer than the resolved commit is not accepted merely + # because the requested text starts with the resolved value. + with pytest.raises(RuntimeError, match="commit pin mismatch"): + adapter.verify_wwpgd_commit_pin( + _provenance(resolved=RESOLVED_COMMIT[:12]), + requested_pin=RESOLVED_COMMIT, + ) + + +def test_requested_pin_requires_pep610_vcs_commit() -> None: + with pytest.raises(RuntimeError, match="no VCS commit"): + adapter.verify_wwpgd_commit_pin( + _provenance(resolved=None), + requested_pin=RESOLVED_COMMIT[:12], + ) + + +def test_nonhexadecimal_resolved_commit_is_rejected() -> None: + with pytest.raises(RuntimeError, match="not a hexadecimal Git SHA"): + adapter.verify_wwpgd_commit_pin( + _provenance(resolved="not-a-git-sha"), + requested_pin=RESOLVED_COMMIT[:12], + ) + + +def test_live_provenance_tracks_environment_changes_after_import(monkeypatch) -> None: + live = adapter._LiveWWPGDProvenance(_provenance()) + + monkeypatch.delenv(adapter.WWPGD_COMMIT_PIN_ENV, raising=False) + first = dict(live) + assert first["wwpgd_commit_pin_status"] == "floating" + + monkeypatch.setenv( + adapter.WWPGD_COMMIT_PIN_ENV, + RESOLVED_COMMIT[:12].upper(), + ) + second = dict(live) + assert second["wwpgd_commit_pin_requested"] == RESOLVED_COMMIT[:12] + assert second["wwpgd_commit_pin_verified"] is True + + monkeypatch.setenv(adapter.WWPGD_COMMIT_PIN_ENV, "0" * 12) + with pytest.raises(RuntimeError, match="commit pin mismatch"): + dict(live) + + +def test_config_and_candidate_boundaries_both_enforce_requested_pin(monkeypatch) -> None: + class BoundaryReached(RuntimeError): + pass + + def fail() -> None: + raise BoundaryReached("shared pin boundary reached") + + monkeypatch.setattr(adapter, "enforce_wwpgd_commit_pin", fail) + + with pytest.raises(BoundaryReached, match="shared pin boundary reached"): + adapter.construct_pip_wwpgd_config(object()) + + with pytest.raises(BoundaryReached, match="shared pin boundary reached"): + adapter.run_pip_wwpgd_candidate(object(), object()) + + +def test_manifest_uses_fresh_verified_provenance_not_import_time_state(monkeypatch) -> None: + live = adapter._LiveWWPGDProvenance(_provenance()) + monkeypatch.setattr(ww, "_WWPGD_PROVENANCE", live) + monkeypatch.setattr( + ww, + "construct_pip_wwpgd_config", + lambda _cfg: (object(), {"resolved": {"q": 1.0}}), + ) + requested = SimpleNamespace( + enabled=True, + extension="wwpgd", + target_alpha=2.0, + blend_eta=0.5, + cayley_eta=0.25, + min_tail=5, + use_detx=True, + verbose=False, + candidate_device="cpu", + max_relative_frobenius_change=None, + ) + + monkeypatch.delenv(adapter.WWPGD_COMMIT_PIN_ENV, raising=False) + floating = ww.external_wwpgd_manifest_fields(True, requested) + assert floating["wwpgd_commit_pin_status"] == "floating" + assert floating["wwpgd_commit_pin_verified"] is False + + monkeypatch.setenv( + adapter.WWPGD_COMMIT_PIN_ENV, + RESOLVED_COMMIT[:12], + ) + verified = ww.external_wwpgd_manifest_fields(True, requested) + assert verified["wwpgd_commit_pin_requested"] == RESOLVED_COMMIT[:12] + assert verified["wwpgd_commit_pin_verified"] is True + assert verified["wwpgd_dependency_pinned"] is False + + monkeypatch.setenv(adapter.WWPGD_COMMIT_PIN_ENV, "0" * 12) + with pytest.raises(RuntimeError, match="commit pin mismatch"): + ww.external_wwpgd_manifest_fields(True, requested) + + +def test_supported_wwpgd_runners_delegate_to_verified_shared_boundaries() -> None: + expected = { + "src/wwgpt/ww.py": ( + "construct_pip_wwpgd_config", + "run_pip_wwpgd_candidate", + ), + "level_0_wwpgd/src/level0_wwpgd/wwpgd_extension.py": ( + "external_wwpgd_manifest_fields", + "run_pip_wwpgd_candidate", + ), + "experiment_2/src/experiment_2/adaptive_extension.py": ( + "external_wwpgd_manifest_fields", + "run_pip_wwpgd_candidate", + ), + } + for path, required_names in expected.items(): + text = Path(path).read_text(encoding="utf-8") + for required_name in required_names: + assert required_name in text, f"{path} bypasses {required_name}"