Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docker/Dockerfile.runtime
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,10 @@ COPY workspace/sibling-repos/ /workspace/sibling-repos/

# Copy provenance helper (always present — used in workspace mode, no-op otherwise)
COPY scripts/runtime_build/compute_workspace_provenance.py /workspace/compute_workspace_provenance.py
# Pin-resolution helper imported by the provenance script (OMN-12989): enforces
# that staged siblings honor the consuming repo's uv.lock pins. Must sit beside
# the provenance script so its sys.path-relative import resolves inside the image.
COPY scripts/runtime_build/resolve_workspace_pins.py /workspace/resolve_workspace_pins.py

# Copy the sibling-pin comparison emitted by the preflight (OMN-12977). A
# committed placeholder makes this COPY always valid; workspace mode overwrites
Expand Down
165 changes: 164 additions & 1 deletion scripts/deploy-agent/tests/unit/test_executor_workspace_provenance.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
PROVENANCE_SCRIPT = (
REPO_ROOT / "scripts" / "runtime_build" / "compute_workspace_provenance.py"
)
# Pin-resolution helper imported by the provenance script (OMN-12989). Isolated
# test copies of the provenance script must place this alongside the copy so the
# sys.path-relative import resolves — mirrors the Dockerfile's dual COPY.
PIN_SCRIPT = REPO_ROOT / "scripts" / "runtime_build" / "resolve_workspace_pins.py"
STAGE_SCRIPT = REPO_ROOT / "scripts" / "runtime_build" / "stage_workspace.sh"


Expand Down Expand Up @@ -222,7 +226,15 @@ def fake_run(cmd: list[str], timeout: int, **kwargs) -> subprocess.CompletedProc

build_cmd = captured_cmds[0]
assert "VCS_REF=deadbeef" in build_cmd
assert "RUNTIME_VERSION=0.38.0" in build_cmd
# RUNTIME_VERSION is sourced from the repo's own pyproject — read it the same
# way the executor does so this assertion never drifts on a version bump.
import tomllib

pyproject = tomllib.loads(
(REPO_ROOT / "pyproject.toml").read_text(encoding="utf-8")
)
expected_runtime_version = pyproject["project"]["version"]
assert f"RUNTIME_VERSION={expected_runtime_version}" in build_cmd
assert any(a.startswith("BUILD_DATE=") for a in build_cmd)


Expand Down Expand Up @@ -261,6 +273,10 @@ def test_provenance_script_passes_with_valid_local_installs(

patched_script = tmp_path / "compute_workspace_provenance_test.py"
patched_script.write_text(script_src, encoding="utf-8")
# Co-locate the pin-resolution helper so the script's import resolves.
(tmp_path / "resolve_workspace_pins.py").write_text(
PIN_SCRIPT.read_text(encoding="utf-8"), encoding="utf-8"
)

# Patch importlib.metadata to simulate local installs
mock_dist = MagicMock()
Expand Down Expand Up @@ -322,6 +338,9 @@ def test_provenance_script_missing_sibling_repo_exits_nonzero(

patched_script = tmp_path / "compute_prov_missing.py"
patched_script.write_text(script_src, encoding="utf-8")
(tmp_path / "resolve_workspace_pins.py").write_text(
PIN_SCRIPT.read_text(encoding="utf-8"), encoding="utf-8"
)

import sys

Expand Down Expand Up @@ -378,3 +397,147 @@ def test_dockerfile_workspace_copy_and_provenance_present() -> None:
assert "build-provenance.json" in dockerfile
assert "com.omninode.workspace_provenance_manifest" in dockerfile
assert "--if-present" not in dockerfile


def test_dockerfile_copies_pin_resolution_helper() -> None:
"""OMN-12989: the pin-resolution helper must be copied beside the provenance
script so the in-image import resolves."""
dockerfile = (REPO_ROOT / "docker" / "Dockerfile.runtime").read_text(
encoding="utf-8"
)
assert "scripts/runtime_build/resolve_workspace_pins.py" in dockerfile
assert "/workspace/resolve_workspace_pins.py" in dockerfile


def _patched_provenance(tmp_path: Path, sibling_dir: Path, manifest_path: Path) -> Path:
"""Write an isolated copy of the provenance script + helper, path-patched."""
script_src = PROVENANCE_SCRIPT.read_text(encoding="utf-8")
script_src = script_src.replace(
'SIBLING_REPOS_DIR = Path("/workspace/sibling-repos")',
f'SIBLING_REPOS_DIR = Path("{sibling_dir}")',
).replace(
'OUTPUT_MANIFEST = Path("/app/build-provenance.json")',
f'OUTPUT_MANIFEST = Path("{manifest_path}")',
)
patched = tmp_path / "compute_prov_pins.py"
patched.write_text(script_src, encoding="utf-8")
(tmp_path / "resolve_workspace_pins.py").write_text(
PIN_SCRIPT.read_text(encoding="utf-8"), encoding="utf-8"
)
return patched


_OMNIMARKET_LOCK = """
version = 1
requires-python = ">=3.12"

[[package]]
name = "omnibase-compat"
version = "0.5.1"
source = { git = "https://github.com/OmniNode-ai/omnibase_compat.git?rev=4d887307aae34d9d40d389ba91070cb411ce3df5#4d887307aae34d9d40d389ba91070cb411ce3df5" }

[[package]]
name = "omnibase-infra"
version = "0.38.1"
source = { git = "https://github.com/OmniNode-ai/omnibase_infra.git?rev=e2dbdc950540df8bc59ca4370b2d4a0f5b8d6c59#e2dbdc950540df8bc59ca4370b2d4a0f5b8d6c59" }

[[package]]
name = "onex-change-control"
version = "0.9.0"
source = { git = "https://github.com/OmniNode-ai/onex_change_control.git?rev=4877d3c223517cb0c7e1eca462ba0f4d38916314" }

[[package]]
name = "omnimarket"
version = "0.4.3"
source = { editable = "." }
"""


def _stage_siblings(sibling_dir: Path) -> None:
"""Create the three workspace siblings (compat, occ, omnimarket) at lock pin."""
for repo, version in (
("omnibase_compat", "0.5.1"),
("onex_change_control", "0.9.0"),
("omnimarket", "0.4.3"),
):
d = sibling_dir / repo
d.mkdir(parents=True)
name = repo.replace("_", "-")
(d / "pyproject.toml").write_text(
f'[project]\nname = "{name}"\nversion = "{version}"\n', encoding="utf-8"
)
(sibling_dir / "omnimarket" / "uv.lock").write_text(
_OMNIMARKET_LOCK, encoding="utf-8"
)


def test_provenance_emits_pin_comparison_block(tmp_path: Path) -> None:
"""OMN-12989: the manifest carries an expected-vs-actual pin_comparison block."""
import sys

sibling_dir = tmp_path / "workspace" / "sibling-repos"
sibling_dir.mkdir(parents=True)
_stage_siblings(sibling_dir)
manifest_path = tmp_path / "build-provenance.json"
patched = _patched_provenance(tmp_path, sibling_dir, manifest_path)

# Host infra version resolved from importlib.metadata is at/above pin in the
# test venv (>=0.38.1), so the infra self-check passes; here we only assert
# the comparison block is present and the staged siblings are recorded.
subprocess.run(
[sys.executable, str(patched)], capture_output=True, text=True, check=False
)
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
assert "pin_comparison" in manifest
by_pkg = {e["package"]: e for e in manifest["pin_comparison"]}
# omnibase-compat staged at exactly the lock pin.
assert by_pkg["omnibase-compat"]["status"] == "exact"
assert by_pkg["omnibase-compat"]["match"] is True
# The host infra self-check appears only when omnibase-infra is installed in
# the running venv (always true in the build image; not in the deploy-agent
# venv). When present it must carry the lock-pinned expected version.
if "omnibase-infra" in by_pkg:
assert by_pkg["omnibase-infra"]["expected_version"] == "0.38.1"


def test_build_comparisons_flags_host_infra_regression(tmp_path: Path) -> None:
"""OMN-12989 (unit): the exact crash — host infra 0.37.0 vs lock pin 0.38.1.

Exercises build_comparisons directly with an explicit host-infra actual
version so the regression detection does not depend on the test venv.
"""
mod = _load_resolve_module()
omnimarket = tmp_path / "omnimarket"
omnimarket.mkdir()
(omnimarket / "uv.lock").write_text(_OMNIMARKET_LOCK, encoding="utf-8")

# Synthesize a "host infra" pyproject tree at the stale crash version.
infra = tmp_path / "omnibase_infra"
infra.mkdir()
(infra / "pyproject.toml").write_text(
'[project]\nname = "omnibase-infra"\nversion = "0.37.0"\n', encoding="utf-8"
)

comparisons = mod.build_comparisons(
lock_path=omnimarket / "uv.lock",
siblings={"omnibase-infra": infra},
)
infra_cmp = next(c for c in comparisons if c.package == "omnibase-infra")
assert infra_cmp.status == "regression"
with pytest.raises(mod.WorkspacePinError):
mod.assert_pins_satisfied(comparisons)


def _load_resolve_module():
import importlib.util
import sys

spec = importlib.util.spec_from_file_location(
"resolve_workspace_pins_provtest", str(PIN_SCRIPT)
)
assert spec is not None
mod = importlib.util.module_from_spec(spec)
assert spec.loader is not None
sys.modules[spec.name] = mod
spec.loader.exec_module(mod) # type: ignore[attr-defined]
return mod
104 changes: 102 additions & 2 deletions scripts/runtime_build/compute_workspace_provenance.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,17 @@
Run during Docker build (workspace mode only) to:
- Hash each staged sibling repo tree under /workspace/sibling-repos/
- Verify that the installed package content matches the recorded digest
- Write /app/build-provenance.json for operator inspection
- Enforce that every staged sibling honors the consuming repo's uv.lock pin
(fail-fast on a version regression below the pin — the 2026-06-11
stability-crash condition, OMN-12989)
- Write /app/build-provenance.json for operator inspection, including the
expected-vs-actual pin comparison block
- Print a human-readable provenance summary to stdout

Exit codes:
0 all proofs passed
1 digest mismatch (installed content != recorded workspace digest)
1 digest mismatch (installed content != recorded workspace digest) OR a
sibling regressed below its uv.lock pin
2 missing sibling repo (expected path not found)
3 missing installed package (venv does not contain the expected dist)
"""
Expand All @@ -25,9 +30,25 @@
import sys
from pathlib import Path

# Co-located pin-resolution helpers (OMN-12989). Inside the build image this
# module is copied next to the provenance script (see Dockerfile.runtime); in
# the repo it lives in the same scripts/runtime_build/ directory.
sys.path.insert(0, str(Path(__file__).resolve().parent))
from resolve_workspace_pins import (
PinComparison,
WorkspacePinError,
assert_pins_satisfied,
build_comparisons,
compare_pin,
parse_lock_pins,
)

SIBLING_REPOS_DIR = Path("/workspace/sibling-repos")
VENV_DIR = Path("/app/.venv")
OUTPUT_MANIFEST = Path("/app/build-provenance.json")
# The consuming repo whose uv.lock pins the sibling versions. omnimarket dev's
# lock is the authority the runtime image must honor.
CONSUMING_REPO = "omnimarket"
# Expected-vs-actual sibling-pin comparison emitted by the preflight
# (check_sibling_lock_pins.py via stage_workspace.sh). Folded into the manifest
# so deploy verifiers can assert the build honored the consuming repo's lock
Expand Down Expand Up @@ -75,6 +96,32 @@ def _installed_direct_url(dist_name: str) -> dict[str, object] | None:
return parsed


def _host_infra_comparison(lock_path: Path) -> PinComparison | None:
"""Compare the runtime image's installed omnibase_infra against the lock pin.

The host infra package is not staged under sibling-repos (it is the repo
being built), so it bypasses build_comparisons. Resolve the lock's
omnibase-infra pin and compare against the installed version — this is the
direct ratchet for the 2026-06-11 stale-infra crash. Returns a PinComparison
or None if the lock has no infra pin / infra is not installed.
"""
pins = parse_lock_pins(lock_path)
infra_pin = pins.get("omnibase-infra")
if infra_pin is None:
return None
try:
installed_version = importlib.metadata.version("omnibase-infra")
except importlib.metadata.PackageNotFoundError:
return None
return compare_pin(
package="omnibase-infra",
expected_version=infra_pin.version,
expected_rev=infra_pin.rev,
actual_version=installed_version,
actual_rev=None,
)


def _load_pin_comparison(errors: list[str]) -> dict[str, object] | None:
"""Load the preflight's expected-vs-actual sibling-pin comparison.

Expand Down Expand Up @@ -165,6 +212,43 @@ def main() -> int:
f"install={install_url}"
)

# OMN-12989: compare every staged sibling against the consuming repo's
# uv.lock pins. A sibling regressed BELOW its lock pin is the exact
# 2026-06-11 stability-crash condition (vendored infra 0.37.0 vs locked
# 0.38.1) and aborts the build. The comparison is always recorded in the
# manifest so deploy verifiers can check it mechanically.
pin_comparison: list[dict[str, object]] = []
pin_violation: str | None = None
lock_path = SIBLING_REPOS_DIR / CONSUMING_REPO / "uv.lock"
try:
comparisons = build_comparisons(
lock_path=lock_path,
siblings={
"omnibase-compat": SIBLING_REPOS_DIR / "omnibase_compat",
"onex-change-control": SIBLING_REPOS_DIR / "onex_change_control",
"omnimarket": SIBLING_REPOS_DIR / "omnimarket",
},
)
# Host self-check (OMN-12989): the runtime image's OWN omnibase_infra is
# built from the build context, NOT from sibling-repos — that is exactly
# where the 2026-06-11 stale 0.37.0 entered. Compare the installed infra
# version against omnimarket's lock pin so a stale build context aborts.
host_infra = _host_infra_comparison(lock_path)
if host_infra is not None:
comparisons.append(host_infra)
pin_comparison = [c.to_dict() for c in comparisons]
try:
assert_pins_satisfied(comparisons)
except WorkspacePinError as exc:
pin_violation = str(exc)
except FileNotFoundError as exc:
# A missing consuming lock is itself a hard error — it means the build
# cannot prove sibling freshness. Fail rather than silently skip.
pin_violation = (
f"Cannot resolve sibling lock pins: {exc}. "
"Workspace mode requires the consuming repo's uv.lock to be staged."
)

# Fold in the sibling-pin comparison so deploy verifiers can confirm the
# build honored the consuming repo's uv.lock (OMN-12977). A drift here would
# already have aborted the build in the default (no-override) path; when an
Expand All @@ -176,6 +260,7 @@ def main() -> int:
"build_time": os.environ.get("BUILD_DATE", "unknown"),
"vcs_ref": os.environ.get("VCS_REF", "unknown"),
"proofs": proofs,
"pin_comparison": pin_comparison,
"sibling_pin_comparison": sibling_pin_comparison,
}

Expand All @@ -188,6 +273,21 @@ def main() -> int:
print(f" - {err}", file=sys.stderr)
return 1

if pin_violation is not None:
print(
"\nWorkspace provenance FAILED (sibling lock-pin regression):",
file=sys.stderr,
)
print(pin_violation, file=sys.stderr)
return 1

for entry in pin_comparison:
print(
f" pin OK: {entry['package']} "
f"expected>={entry['expected_version'] or '(unpinned)'} "
f"actual={entry['actual_version']} [{entry['status']}]"
)

print(f"\nAll {len(proofs)} workspace provenance proofs passed.")
return 0

Expand Down
Loading
Loading