From cda61cf7dd6fc3741b24504066b4de43ab7c75d3 Mon Sep 17 00:00:00 2001 From: jonahgabriel Date: Sat, 25 Jul 2026 17:46:48 -0400 Subject: [PATCH 1/4] fix(OMN-15137): provision omnibase_spi via a shared sibling-clone manifest ensure_runner_clones.sh never provisioned an omnibase_spi clone under the deploy runner private OMNI_HOME, even though stage_workspace.sh sibling-pin preflight (PREFLIGHT_REPO_ARGS -> check_sibling_lock_pins.py's DEFAULT_PACKAGE_REPO_DIRS) has required a live OMNI_HOME/omnibase_spi clone since OMN-12977. The two lists were independently hardcoded 5-repo and 6-repo bash arrays that silently drifted apart. This blocked OMN-14900 stability deploy-hop re-fire iteration N+3 3 steps downstream of clone provisioning: ERROR: cannot resolve clone pin for omnibase-spi: missing pyproject.toml for omnibase-spi: /data/omninode/runner_omni_home/omnibase_spi/pyproject.toml Fix targets the ROOT pattern, not just the missing repo: adds scripts/runtime_build/sibling_clone_manifest.sh as the single source of truth for the full 6-repo sibling-clone set (omnibase_infra, omnibase_core, omnibase_spi, omnibase_compat, onex_change_control, omnimarket). Both ensure_runner_clones.sh (RUNNER_CLONE_REPOS) and stage_workspace.sh (PREFLIGHT_REPO_ARGS) now source this one file instead of hardcoding their own copy, so the two can never diverge again. ensure_runner_clones.sh's existing existence/operability loop is the fail-fast preflight the ticket asks for -- once fed the complete manifest it already names any missing clone before the deploy proceeds, with no new logic required. Audited full sibling set consumed by the workspace build (Dockerfile.runtime + stage_workspace.sh): omnibase_infra is staged as the Docker build context itself (never a vendored "sibling"); omnibase_spi is installed from the published PyPI wheel via `uv sync` (never staged from local source) but still needs a live OMNI_HOME clone so the pin-preflight can read its pyproject.toml version + git SHA; omnibase_core/omnibase_compat/ onex_change_control/omnimarket are vendored as source via stage_workspace.sh's SIBLING_REPOS. No 7th sibling exists in any lock file (check_sibling_lock_pins.py's DEFAULT_PACKAGE_REPO_DIRS has exactly these 6 entries). Tests (new): - tests/scripts/test_ensure_runner_clones.py: end-to-end against real file:// git remotes -- proves all 6 repos including omnibase_spi are cloned, idempotent re-run, fail-closed + named-repo error on missing upstream, fail-closed on an unwritable clone .git dir, fail-closed on missing OMNI_HOME. Confirmed RED against the pre-fix 5-repo manifest (7/8 assertions fail without omnibase_spi in the list). - tests/scripts/test_sibling_clone_manifest_parity.py: cross-language recurrence ratchet asserting sibling_clone_manifest.sh's bash arrays exactly match check_sibling_lock_pins.py's DEFAULT_PACKAGE_REPO_DIRS, so a future 7th sibling added on one side and forgotten on the other fails CI immediately instead of failing 3 deploy hops deep on a real runner. Verification (.200, env -u PYTHONPATH uv run pytest, homebrew bash 5.3 on PATH -- macOS system /bin/bash 3.2 has a pre-existing, unrelated nounset empty-array bug in stage_workspace.sh line ~204 that reproduces identically on unmodified dev tip; out of scope for this ticket): - New files: 8/8 passed. - Full regression set (new + existing stage_workspace/check_sibling_lock_pins coverage): 67/67 passed. - ruff format --check + ruff check: clean. - shellcheck --severity=warning (all 3 shell files): clean. - bash -n: syntax OK. - pre-commit (scoped to changed files): all hooks passed, including SPDX. dod_evidence: - Ticket: OMN-15137 (parent OMN-14900). - Root cause reproduced live, per ticket run https://github.com/OmniNode-ai/omnibase_infra/actions/runs/30175447119. - Acceptance per ticket: a fresh release-train-lab.yml stability-lane execute=true run reaching past check_sibling_lock_pins.py without an omnibase-spi clone-pin resolution error. This PR does not itself trigger that run (requires a fresh cut/deploy hop, out of scope for this PR); a RED-confirmed test now proves the fix and the next stability deploy hop is the live acceptance readback. Not merging this PR myself -- Codex owns merge queue / merge per repo policy. Evidence-Ticket: OMN-15137 --- scripts/runtime_build/ensure_runner_clones.sh | 28 ++- .../runtime_build/sibling_clone_manifest.sh | 62 +++++ scripts/runtime_build/stage_workspace.sh | 24 +- tests/scripts/test_ensure_runner_clones.py | 227 ++++++++++++++++++ .../test_sibling_clone_manifest_parity.py | 91 +++++++ 5 files changed, 412 insertions(+), 20 deletions(-) create mode 100644 scripts/runtime_build/sibling_clone_manifest.sh create mode 100644 tests/scripts/test_ensure_runner_clones.py create mode 100644 tests/scripts/test_sibling_clone_manifest_parity.py diff --git a/scripts/runtime_build/ensure_runner_clones.sh b/scripts/runtime_build/ensure_runner_clones.sh index deb485f3b5..35e9a645e3 100755 --- a/scripts/runtime_build/ensure_runner_clones.sh +++ b/scripts/runtime_build/ensure_runner_clones.sh @@ -11,19 +11,27 @@ # dubious-ownership, FETCH_HEAD EACCES, busy-branch worktree contention). # Instead it gets a PRIVATE OMNI_HOME bind mount (DEPLOY_RUNNER_OMNI_HOME in # docker/docker-compose.runners.yml) owned by the runner uid. This script -# guarantees the 5 sibling clones exist under that private OMNI_HOME and are -# operable by the CURRENT euid, so provisioning is a committed, automatic step -# inside every entry script -- never a remembered manual step on the host. +# guarantees the sibling clones (see sibling_clone_manifest.sh for the current +# set) exist under that private OMNI_HOME and are operable by the CURRENT +# euid, so provisioning is a committed, automatic step inside every entry +# script -- never a remembered manual step on the host. # # Called at the top of the execute path of cut_release_train_tag.sh, # refresh_stability_lane.sh, and refresh_dev_lane.sh. Safe to re-run: existing # clones are left untouched (the refresh scripts already fetch; this script # only guarantees existence + operability). # -# All 5 repos are public, so cloning needs NO credentials. The base URL is +# All repos are public, so cloning needs NO credentials. The base URL is # overridable (RUNNER_CLONE_BASE_URL) so tests can point it at local file:// # bare fixtures. # +# OMN-15137: the clone set is NOT hardcoded here. It is sourced from +# sibling_clone_manifest.sh -- the single source of truth shared with +# stage_workspace.sh's sibling-pin preflight -- so this script can never again +# provision fewer repos than the pin preflight requires (the omnibase_spi gap +# this ticket fixes: the preflight referenced OMNI_HOME/omnibase_spi 3 deploy +# hops downstream of this script, which never created it). +# # Exit codes: # 0 all clones present and operable by the current euid # 64 precondition failure (OMNI_HOME unset / not a directory, git missing, @@ -31,13 +39,11 @@ set -euo pipefail -RUNNER_CLONE_REPOS=( - "omnibase_infra" - "omnibase_core" - "omnibase_compat" - "onex_change_control" - "omnimarket" -) +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=./sibling_clone_manifest.sh +source "${SCRIPT_DIR}/sibling_clone_manifest.sh" + +RUNNER_CLONE_REPOS=("${SIBLING_CLONE_MANIFEST[@]}") RUNNER_CLONE_BASE_URL="${RUNNER_CLONE_BASE_URL:-https://github.com/OmniNode-ai}" diff --git a/scripts/runtime_build/sibling_clone_manifest.sh b/scripts/runtime_build/sibling_clone_manifest.sh new file mode 100644 index 0000000000..36c5594fc2 --- /dev/null +++ b/scripts/runtime_build/sibling_clone_manifest.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: 2025 OmniNode.ai Inc. +# SPDX-License-Identifier: MIT +# +# sibling_clone_manifest.sh -- OMN-15137: single source of truth for every +# sibling repo that must have a live clone under the deploy runner's OMNI_HOME. +# +# Root cause this file closes: ensure_runner_clones.sh (RUNNER_CLONE_REPOS) +# and stage_workspace.sh's sibling-pin preflight (PREFLIGHT_REPO_ARGS, which +# maps 1:1 onto check_sibling_lock_pins.py's DEFAULT_PACKAGE_REPO_DIRS) each +# hardcoded their OWN independent copy of "the sibling repo set" as two +# separately maintained bash arrays. omnibase_spi was added to the preflight's +# set (OMN-12977) but never mirrored into the clone-provisioning set, so +# check_sibling_lock_pins.py referenced a clone directory +# (OMNI_HOME/omnibase_spi) that ensure_runner_clones.sh never created -- a +# silent coverage gap that surfaced only 3 hops deep into the pipeline +# (OMN-15137: "ERROR: cannot resolve clone pin for omnibase-spi: missing +# pyproject.toml"), after two unrelated defects (OMN-15122, OMN-15131) were +# fixed and a run finally reached this step for the first time. +# +# Both consuming scripts source THIS file instead of hardcoding their own +# list, so the two can never drift apart again: +# - ensure_runner_clones.sh builds RUNNER_CLONE_REPOS from +# SIBLING_CLONE_MANIFEST. +# - stage_workspace.sh builds PREFLIGHT_REPO_ARGS from +# SIBLING_CLONE_MANIFEST + SIBLING_CLONE_MANIFEST_DIST_NAMES. +# tests/scripts/test_sibling_clone_manifest_parity.py additionally asserts +# this file's directory set is IDENTICAL to check_sibling_lock_pins.py's +# DEFAULT_PACKAGE_REPO_DIRS values (the Python side's own canonical mapping), +# so a future 7th sibling added on one side and forgotten on the other fails +# CI immediately instead of failing 3 deploy hops deep on a real runner. +# +# NOT the same list as stage_workspace.sh's SIBLING_REPOS (the narrower +# subset actually vendored as SOURCE into the runtime image via rsync): +# omnibase_infra is the Docker build context itself (never vendored as a +# "sibling"), and omnibase_spi is installed from the published wheel via +# `uv sync` (never staged from a local source tree) -- both still need a +# live OMNI_HOME clone so the pin-preflight can read their pyproject.toml +# version + git HEAD SHA against the consuming lock file. +# +# Arrays below are INDEX-ALIGNED: SIBLING_CLONE_MANIFEST[i] is the OMNI_HOME +# directory name; SIBLING_CLONE_MANIFEST_DIST_NAMES[i] is the corresponding +# uv.lock distribution (package) name for that same repo. +# shellcheck disable=SC2034 # consumed by scripts that `source` this file +SIBLING_CLONE_MANIFEST=( + "omnibase_infra" + "omnibase_core" + "omnibase_spi" + "omnibase_compat" + "onex_change_control" + "omnimarket" +) + +# shellcheck disable=SC2034 # consumed by scripts that `source` this file +SIBLING_CLONE_MANIFEST_DIST_NAMES=( + "omnibase-infra" + "omnibase-core" + "omnibase-spi" + "omnibase-compat" + "onex-change-control" + "omnimarket" +) diff --git a/scripts/runtime_build/stage_workspace.sh b/scripts/runtime_build/stage_workspace.sh index b002a1e7d3..0ef888a56a 100644 --- a/scripts/runtime_build/stage_workspace.sh +++ b/scripts/runtime_build/stage_workspace.sh @@ -125,15 +125,21 @@ fi CONSUMER_LOCK="${CONSUMER_LOCK:-${OMNI_HOME}/omnimarket/uv.lock}" PIN_COMPARISON_OUT="workspace/sibling-pin-comparison.json" -# Foundation + sibling packages the build vendors, mapped to OMNI_HOME clone dirs. -PREFLIGHT_REPO_ARGS=( - --repo "omnibase-infra=${OMNI_HOME}/omnibase_infra" - --repo "omnibase-core=${OMNI_HOME}/omnibase_core" - --repo "omnibase-spi=${OMNI_HOME}/omnibase_spi" - --repo "omnibase-compat=${OMNI_HOME}/omnibase_compat" - --repo "onex-change-control=${OMNI_HOME}/onex_change_control" - --repo "omnimarket=${OMNI_HOME}/omnimarket" -) +# Foundation + sibling packages the build vendors, mapped to OMNI_HOME clone +# dirs. OMN-15137: built from sibling_clone_manifest.sh -- the single source +# of truth shared with ensure_runner_clones.sh's RUNNER_CLONE_REPOS -- instead +# of a second independently hardcoded list, so the two can never drift apart +# again (the omnibase_spi gap this ticket fixes was exactly that drift: this +# list already named omnibase-spi, but ensure_runner_clones.sh never +# provisioned OMNI_HOME/omnibase_spi for it to find). +# shellcheck source=./sibling_clone_manifest.sh +source "${SCRIPT_DIR}/sibling_clone_manifest.sh" +PREFLIGHT_REPO_ARGS=() +for i in "${!SIBLING_CLONE_MANIFEST[@]}"; do + PREFLIGHT_REPO_ARGS+=( + --repo "${SIBLING_CLONE_MANIFEST_DIST_NAMES[$i]}=${OMNI_HOME}/${SIBLING_CLONE_MANIFEST[$i]}" + ) +done preflight_extra=() if [[ "${ALLOW_SIBLING_PIN_DRIFT:-0}" == "1" ]]; then diff --git a/tests/scripts/test_ensure_runner_clones.py b/tests/scripts/test_ensure_runner_clones.py new file mode 100644 index 0000000000..687699be32 --- /dev/null +++ b/tests/scripts/test_ensure_runner_clones.py @@ -0,0 +1,227 @@ +# SPDX-FileCopyrightText: 2025 OmniNode.ai Inc. +# SPDX-License-Identifier: MIT + +"""ensure_runner_clones.sh must provision EVERY sibling the workspace build's +sibling-pin preflight requires -- not just the 5 it hardcoded before this fix +(OMN-15137). + +Live failure: OMN-14900 stability deploy-hop re-fire iteration N+3 (run +https://github.com/OmniNode-ai/omnibase_infra/actions/runs/30175447119, +2026-07-25) got past ensure_runner_clones.sh ("all 5 private clones present +and operable"), then failed 3 steps later in stage_workspace.sh's sibling-pin +preflight: + + ERROR: cannot resolve clone pin for omnibase-spi: missing pyproject.toml + for omnibase-spi: /data/omninode/runner_omni_home/omnibase_spi/pyproject.toml + +Root cause: ``RUNNER_CLONE_REPOS`` in ensure_runner_clones.sh never included +``omnibase_spi``, even though stage_workspace.sh's preflight +(``PREFLIGHT_REPO_ARGS`` -> check_sibling_lock_pins.py's +``DEFAULT_PACKAGE_REPO_DIRS``) has required a live ``OMNI_HOME/omnibase_spi`` +clone since OMN-12977. The two lists were independently hardcoded and drifted +apart silently. + +The fix sources a new shared manifest (``sibling_clone_manifest.sh``) into +both scripts so they can never diverge again. These tests exercise the +REAL ``ensure_runner_clones.sh`` script end-to-end against real (local +``file://``) git remotes -- proving the artifact that runs, not just that the +source text mentions "omnibase_spi". +""" + +from __future__ import annotations + +import subprocess +from pathlib import Path + +import pytest + +REPO_ROOT = Path(__file__).resolve().parents[2] +SCRIPT = REPO_ROOT / "scripts" / "runtime_build" / "ensure_runner_clones.sh" +MANIFEST = REPO_ROOT / "scripts" / "runtime_build" / "sibling_clone_manifest.sh" + +# The full sibling set the workspace build's pin-preflight resolves clones +# for (check_sibling_lock_pins.py's DEFAULT_PACKAGE_REPO_DIRS). Mirrored here +# (not imported) so a regression in the manifest itself still fails this test +# independently -- see test_sibling_clone_manifest_parity.py for the +# cross-language authoritative-source check. +EXPECTED_REPOS = ( + "omnibase_infra", + "omnibase_core", + "omnibase_spi", + "omnibase_compat", + "onex_change_control", + "omnimarket", +) + + +def _git(*args: str, cwd: Path) -> subprocess.CompletedProcess[str]: + return subprocess.run( + ["git", *args], + cwd=cwd, + check=True, + capture_output=True, + text=True, + ) + + +def _make_bare_upstream(upstream_root: Path, repo: str) -> None: + """Create a real bare git repo with one commit, clonable via file://.""" + bare = upstream_root / f"{repo}.git" + _git("init", "-q", "--bare", str(bare), cwd=upstream_root) + + work = upstream_root / f".work-{repo}" + work.mkdir() + _git("init", "-q", cwd=work) + _git( + "-c", + "user.email=test@example.com", + "-c", + "user.name=test", + "commit", + "--allow-empty", + "-q", + "-m", + "init", + cwd=work, + ) + _git("remote", "add", "origin", str(bare), cwd=work) + _git("push", "-q", "origin", "HEAD:refs/heads/main", cwd=work) + + +def _run_ensure_clones( + omni_home: Path, + base_url: str, + *, + extra_env: dict[str, str] | None = None, +) -> subprocess.CompletedProcess[str]: + env = { + "OMNI_HOME": str(omni_home), + "RUNNER_CLONE_BASE_URL": base_url, + "PATH": "/usr/bin:/bin:/usr/local/bin:/opt/homebrew/bin", + "HOME": str(omni_home.parent), + } + if extra_env: + env.update(extra_env) + return subprocess.run( + ["bash", str(SCRIPT)], + env=env, + capture_output=True, + text=True, + check=False, + ) + + +@pytest.fixture +def upstream(tmp_path: Path) -> Path: + """A directory of real bare git repos for all 6 canonical siblings.""" + root = tmp_path / "upstream" + root.mkdir() + for repo in EXPECTED_REPOS: + _make_bare_upstream(root, repo) + return root + + +def test_manifest_declares_all_six_canonical_siblings() -> None: + """sibling_clone_manifest.sh enumerates the full set, including spi.""" + text = MANIFEST.read_text(encoding="utf-8") + for repo in EXPECTED_REPOS: + assert f'"{repo}"' in text, f"{repo} missing from sibling_clone_manifest.sh" + + +def test_ensure_runner_clones_provisions_all_six_including_spi( + tmp_path: Path, upstream: Path +) -> None: + """RED against the exact OMN-15137 gap: omnibase_spi must be cloned.""" + omni_home = tmp_path / "omni_home" + omni_home.mkdir() + + result = _run_ensure_clones(omni_home, f"file://{upstream}") + + assert result.returncode == 0, result.stderr + assert "all 6 private clones present and operable" in result.stderr + + for repo in EXPECTED_REPOS: + clone = omni_home / repo + assert (clone / ".git").exists(), f"{repo} was not cloned under OMNI_HOME" + + # The specific regression: omnibase_spi must exist and be a real repo. + spi = omni_home / "omnibase_spi" + rev_parse = _git("rev-parse", "--git-dir", cwd=spi) + assert rev_parse.returncode == 0 + + +def test_ensure_runner_clones_is_idempotent(tmp_path: Path, upstream: Path) -> None: + omni_home = tmp_path / "omni_home" + omni_home.mkdir() + + first = _run_ensure_clones(omni_home, f"file://{upstream}") + assert first.returncode == 0, first.stderr + + heads_before = { + repo: _git("rev-parse", "HEAD", cwd=omni_home / repo).stdout.strip() + for repo in EXPECTED_REPOS + } + + second = _run_ensure_clones(omni_home, f"file://{upstream}") + assert second.returncode == 0, second.stderr + assert "clone missing" not in second.stderr + + for repo in EXPECTED_REPOS: + head_after = _git("rev-parse", "HEAD", cwd=omni_home / repo).stdout.strip() + assert head_after == heads_before[repo] + + +def test_missing_upstream_repo_fails_closed_naming_the_repo( + tmp_path: Path, upstream: Path +) -> None: + """If ANY sibling's upstream is unreachable, fail loud and name it -- + the exact shape of the OMN-15137 defect (a sibling silently never + provisioned) must be impossible to reproduce for any repo in the set.""" + # Remove the omnibase_spi upstream to simulate "not provisioned anywhere" + import shutil + + shutil.rmtree(upstream / "omnibase_spi.git") + + omni_home = tmp_path / "omni_home" + omni_home.mkdir() + + result = _run_ensure_clones(omni_home, f"file://{upstream}") + + assert result.returncode == 64 + assert "omnibase_spi" in result.stderr + assert "git clone failed" in result.stderr + # Must not silently report success. + assert "all 6 private clones present" not in result.stderr + + +def test_unwritable_clone_git_dir_fails_closed(tmp_path: Path, upstream: Path) -> None: + """A clone whose .git is not writable by the current euid must fail -- + this is the dubious-ownership / FETCH_HEAD-EACCES failure mode the + private-OMNI_HOME provisioning (OMN-14900) exists to catch.""" + omni_home = tmp_path / "omni_home" + omni_home.mkdir() + + first = _run_ensure_clones(omni_home, f"file://{upstream}") + assert first.returncode == 0, first.stderr + + spi_git_dir = omni_home / "omnibase_spi" / ".git" + spi_git_dir.chmod(0o500) + try: + result = _run_ensure_clones(omni_home, f"file://{upstream}") + assert result.returncode == 64 + assert "not writable" in result.stderr + assert "omnibase_spi" in result.stderr + finally: + spi_git_dir.chmod(0o700) + + +def test_missing_omni_home_fails_closed() -> None: + result = subprocess.run( + ["bash", str(SCRIPT)], + env={"PATH": "/usr/bin:/bin:/usr/local/bin:/opt/homebrew/bin"}, + capture_output=True, + text=True, + check=False, + ) + assert result.returncode == 64 + assert "OMNI_HOME must be set" in result.stderr diff --git a/tests/scripts/test_sibling_clone_manifest_parity.py b/tests/scripts/test_sibling_clone_manifest_parity.py new file mode 100644 index 0000000000..e1adec73bb --- /dev/null +++ b/tests/scripts/test_sibling_clone_manifest_parity.py @@ -0,0 +1,91 @@ +# SPDX-FileCopyrightText: 2025 OmniNode.ai Inc. +# SPDX-License-Identifier: MIT + +"""Recurrence ratchet for OMN-15137: the bash sibling-clone manifest +(``sibling_clone_manifest.sh``) must stay in exact parity with the Python +pin-check authority (``check_sibling_lock_pins.py``'s +``DEFAULT_PACKAGE_REPO_DIRS``). + +OMN-15137's root cause was two independently hardcoded repo lists (one in +ensure_runner_clones.sh, one derived ad hoc in stage_workspace.sh) silently +drifting apart -- omnibase_spi was added to one and never mirrored to the +other. The fix makes ``sibling_clone_manifest.sh`` the single bash-side +source of truth that both scripts source. This test is the cross-language +guardrail: if a future 7th sibling is added to +``DEFAULT_PACKAGE_REPO_DIRS`` (Python) but never mirrored into +``sibling_clone_manifest.sh`` (bash), or vice versa, this test fails CI +immediately instead of failing 3 deploy hops deep on a real runner. +""" + +from __future__ import annotations + +import re +import subprocess +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parents[2] +MANIFEST = REPO_ROOT / "scripts" / "runtime_build" / "sibling_clone_manifest.sh" +CHECK_PINS = REPO_ROOT / "scripts" / "runtime_build" / "check_sibling_lock_pins.py" + + +def _bash_array(source: str, name: str) -> list[str]: + match = re.search( + rf"^{re.escape(name)}=\(\n(.*?)\n\)", + source, + re.DOTALL | re.MULTILINE, + ) + assert match, f"could not find bash array {name!r} in {MANIFEST}" + return re.findall(r'"([^"]+)"', match.group(1)) + + +def _python_default_package_repo_dirs() -> dict[str, str]: + """Read DEFAULT_PACKAGE_REPO_DIRS from check_sibling_lock_pins.py via a + real subprocess import -- proves the *actual* module attribute, not a + regex guess at Python source text.""" + result = subprocess.run( + [ + "python3", + "-c", + ( + "import importlib.util, sys, json; " + f"spec = importlib.util.spec_from_file_location('m', {str(CHECK_PINS)!r}); " + "m = importlib.util.module_from_spec(spec); " + "spec.loader.exec_module(m); " + "print(json.dumps(m.DEFAULT_PACKAGE_REPO_DIRS))" + ), + ], + capture_output=True, + text=True, + check=True, + ) + import json + + return json.loads(result.stdout) + + +def test_manifest_dirs_match_python_pin_check_authority_exactly() -> None: + source = MANIFEST.read_text(encoding="utf-8") + manifest_dirs = _bash_array(source, "SIBLING_CLONE_MANIFEST") + manifest_dist_names = _bash_array(source, "SIBLING_CLONE_MANIFEST_DIST_NAMES") + + assert len(manifest_dirs) == len(manifest_dist_names), ( + "SIBLING_CLONE_MANIFEST and SIBLING_CLONE_MANIFEST_DIST_NAMES must be " + "index-aligned and the same length" + ) + + manifest_pairs = dict(zip(manifest_dist_names, manifest_dirs, strict=True)) + python_pairs = _python_default_package_repo_dirs() + + assert manifest_pairs == python_pairs, ( + "sibling_clone_manifest.sh has drifted from " + "check_sibling_lock_pins.py's DEFAULT_PACKAGE_REPO_DIRS -- " + f"bash={manifest_pairs!r} python={python_pairs!r}. Update both " + "together (OMN-15137 recurrence guard)." + ) + + +def test_manifest_includes_omnibase_spi() -> None: + """The specific OMN-15137 regression: omnibase_spi must be in the set.""" + source = MANIFEST.read_text(encoding="utf-8") + manifest_dirs = _bash_array(source, "SIBLING_CLONE_MANIFEST") + assert "omnibase_spi" in manifest_dirs From 7976e41f49e7f2890915e4ce8434faa6419d5736 Mon Sep 17 00:00:00 2001 From: jonahgabriel Date: Sat, 25 Jul 2026 17:50:08 -0400 Subject: [PATCH 2/4] chore(OMN-15137): retrigger CI to pick up Evidence-Source autobind From ff9d57c3498ef8ef8c557a01227c3b28e676d10b Mon Sep 17 00:00:00 2001 From: jonahgabriel Date: Sat, 25 Jul 2026 18:05:46 -0400 Subject: [PATCH 3/4] fix(OMN-15137): close the same drift in compose GIT_CONFIG_* + its test docker-compose.runners.yml GIT_CONFIG_COUNT/GIT_CONFIG_KEY_*/VALUE_* (the container-level defense-in-depth safe.directory list for the deploy runner) was ALSO hardcoded to 5 entries, missing omnibase_spi -- the same independently-hardcoded-list drift this ticket fixes elsewhere. Bumped to 6 and added the omnibase_spi entry. tests/unit/scripts/test_runner_private_omni_home_safe_directory.py (pre-existing OMN-14900 coverage) asserted the old 5-repo shape in three places and its own file:// bare-fixture fixture only seeded 5 upstream repos -- this is what CI Tests (Split 1/1) caught on PR #2450 after the sibling_clone_manifest.sh fix made ensure_runner_clones.sh try to clone a 6th repo the test fixture never provided. Updated _ENSURE_REPOS to 6 (added omnibase_spi), the GIT_CONFIG_COUNT/KEY/VALUE assertions to 6, and renamed the now-inaccurate test_..._all_five_... to test_..._all_and_is_idempotent. Verified (.200): tests/unit/scripts/test_runner_private_omni_home_safe_directory.py 20/20 passed; full regression set (with the two new OMN-15137 test files) 87/87 passed; pre-commit (scoped) all hooks passed. Evidence-Ticket: OMN-15137 --- docker/docker-compose.runners.yml | 15 +++++++---- ...runner_private_omni_home_safe_directory.py | 26 ++++++++++++------- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/docker/docker-compose.runners.yml b/docker/docker-compose.runners.yml index 8f068add9e..cdb070be8b 100644 --- a/docker/docker-compose.runners.yml +++ b/docker/docker-compose.runners.yml @@ -1023,11 +1023,14 @@ services: # false "lane unhealthy" STOP. host.docker.internal resolves to the # host-gateway (extra_hosts below), where the lane ports are published. LANE_PROBE_HOST: host.docker.internal - # Defense-in-depth safe.directory for the 5 PRIVATE clones. The - # load-bearing exemption is the scoped `git -c safe.directory=` - # committed in the runtime_build scripts themselves (OMN-14900); this - # env only covers stray git invocations outside those scripts. - GIT_CONFIG_COUNT: "5" + # Defense-in-depth safe.directory for the 6 PRIVATE clones (OMN-15137: + # omnibase_spi added -- it was missing here too, the same + # independently-hardcoded-list drift this ticket's manifest fixes for + # ensure_runner_clones.sh / stage_workspace.sh). The load-bearing + # exemption is the scoped `git -c safe.directory=` committed in + # the runtime_build scripts themselves (OMN-14900); this env only + # covers stray git invocations outside those scripts. + GIT_CONFIG_COUNT: "6" GIT_CONFIG_KEY_0: safe.directory GIT_CONFIG_VALUE_0: ${DEPLOY_RUNNER_OMNI_HOME:?}/omnibase_infra GIT_CONFIG_KEY_1: safe.directory @@ -1038,6 +1041,8 @@ services: GIT_CONFIG_VALUE_3: ${DEPLOY_RUNNER_OMNI_HOME:?}/onex_change_control GIT_CONFIG_KEY_4: safe.directory GIT_CONFIG_VALUE_4: ${DEPLOY_RUNNER_OMNI_HOME:?}/omnimarket + GIT_CONFIG_KEY_5: safe.directory + GIT_CONFIG_VALUE_5: ${DEPLOY_RUNNER_OMNI_HOME:?}/omnibase_spi volumes: - /var/run/docker.sock:/var/run/docker.sock # PRIVATE clone tree at the identical host:container path. The shared diff --git a/tests/unit/scripts/test_runner_private_omni_home_safe_directory.py b/tests/unit/scripts/test_runner_private_omni_home_safe_directory.py index 4d045aeb99..a9c2cef9a0 100644 --- a/tests/unit/scripts/test_runner_private_omni_home_safe_directory.py +++ b/tests/unit/scripts/test_runner_private_omni_home_safe_directory.py @@ -31,9 +31,12 @@ stage_workspace.sh, deploy_source_ref.py, check_sibling_lock_pins.py) and by a behavioral RED/GREEN pair driven with git's own ``GIT_TEST_ASSUME_DIFFERENT_OWNER`` ownership-mismatch knob. - 3. Automatic provisioning: ensure_runner_clones.sh creates the 5 private - clones idempotently at the top of every entry script (behavioral tests - against file:// bare fixtures). + 3. Automatic provisioning: ensure_runner_clones.sh creates the private + clones (see sibling_clone_manifest.sh for the current set; OMN-15137 + added omnibase_spi, which was missing from this list until the + sibling-pin preflight's own clone requirement caught the gap 3 deploy + hops downstream) idempotently at the top of every entry script + (behavioral tests against file:// bare fixtures). Per reference_git_env_vars_override_c_and_cwd: strip GIT_DIR/GIT_INDEX_FILE/ GIT_WORK_TREE from subprocess env so an inherited pre-push hook export cannot @@ -314,8 +317,8 @@ def test_compose_deploy_runner_env_is_fail_fast_and_repo_scoped() -> None: from THIS file cannot silently drop it: repo-scoped GITHUB_ORG_URL (creds volume was seeded repo-scoped), empty RUNNER_GROUP (repo-scoped registration rejects --runnergroup), fail-fast OMNI_HOME interpolation, - and the defense-in-depth GIT_CONFIG_* safe.directory entries for all 5 - private clones. + and the defense-in-depth GIT_CONFIG_* safe.directory entries for all 6 + private clones (OMN-15137: omnibase_spi added). """ svc = _deploy_runner_service() env = svc["environment"] @@ -336,11 +339,12 @@ def test_compose_deploy_runner_env_is_fail_fast_and_repo_scoped() -> None: f"OMNI_HOME must fail-fast interpolate from DEPLOY_RUNNER_OMNI_HOME " f"(got {omni_home!r}) -- a silent empty default produces broken binds" ) - assert str(env.get("GIT_CONFIG_COUNT")) == "5" - values = [str(env[f"GIT_CONFIG_VALUE_{i}"]) for i in range(5)] + assert str(env.get("GIT_CONFIG_COUNT")) == "6" + values = [str(env[f"GIT_CONFIG_VALUE_{i}"]) for i in range(6)] for repo in ( "omnibase_infra", "omnibase_core", + "omnibase_spi", "omnibase_compat", "onex_change_control", "omnimarket", @@ -351,7 +355,7 @@ def test_compose_deploy_runner_env_is_fail_fast_and_repo_scoped() -> None: assert all("DEPLOY_RUNNER_OMNI_HOME" in v for v in values), ( "safe.directory values must point at the PRIVATE clones" ) - for i in range(5): + for i in range(6): assert env[f"GIT_CONFIG_KEY_{i}"] == "safe.directory" @@ -386,6 +390,7 @@ def test_compose_deploy_runner_has_no_shared_gid_and_has_init_wrapper() -> None: _ENSURE_REPOS = ( "omnibase_infra", "omnibase_core", + "omnibase_spi", "omnibase_compat", "onex_change_control", "omnimarket", @@ -393,7 +398,8 @@ def test_compose_deploy_runner_has_no_shared_gid_and_has_init_wrapper() -> None: def _make_bare_fixtures(tmp_path: Path) -> Path: - """One seed commit, five bare .git fixtures cloneable via file://.""" + """One seed commit, one bare .git fixture per _ENSURE_REPOS entry, + cloneable via file://.""" seed = tmp_path / "seed" _git(["init", "-q", "-b", "dev", str(seed)], cwd=tmp_path) (seed / "README.md").write_text("fixture\n") @@ -422,7 +428,7 @@ def _run_ensure( return _run(["bash", str(ENSURE_SCRIPT)], env=env) -def test_ensure_runner_clones_provisions_all_five_and_is_idempotent( +def test_ensure_runner_clones_provisions_all_and_is_idempotent( tmp_path: Path, ) -> None: base = _make_bare_fixtures(tmp_path) From 5f2427ff2c83a3e508844320b4b7a6d0c8a61e56 Mon Sep 17 00:00:00 2001 From: jonahgabriel Date: Sat, 25 Jul 2026 18:19:43 -0400 Subject: [PATCH 4/4] fix(OMN-15137): revert compose GIT_CONFIG_* change, keep it out of scope The docker-compose.runners.yml GIT_CONFIG_* change from the previous commit is reverted: it is a "runtime path" per deploy-gate, which correctly blocked the PR (DEPLOY GATE FAILED: no cited ticket has a falsifiable deploy probe) since this container-level env only takes effect on a deploy-runner container recreate -- out of scope for a code-only PR, same split as OMN-15134/#2448 (land now, deploy separately). Kept: the test file correction (_ENSURE_REPOS now has 6 entries including omnibase_spi, matching ensure_runner_clones.sh's real behavior -- this part is NOT deploy-gated, it is test-only and was a genuine CI regression introduced by the sibling_clone_manifest.sh fix). Reverted the compose-specific GIT_CONFIG_COUNT/KEY/VALUE assertions in that same test file back to 5, with a note that the container-level omnibase_spi safe.directory gap is a tracked, non-blocking residual (the load-bearing per-op `-c safe.directory=` scoping in the scripts already covers it). Verified (.200): full regression set (unit/scripts + scripts/) 87/87 passed; pre-commit (scoped) all hooks passed. Evidence-Ticket: OMN-15137 --- docker/docker-compose.runners.yml | 15 +++++---------- ...st_runner_private_omni_home_safe_directory.py | 16 ++++++++++------ 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/docker/docker-compose.runners.yml b/docker/docker-compose.runners.yml index cdb070be8b..8f068add9e 100644 --- a/docker/docker-compose.runners.yml +++ b/docker/docker-compose.runners.yml @@ -1023,14 +1023,11 @@ services: # false "lane unhealthy" STOP. host.docker.internal resolves to the # host-gateway (extra_hosts below), where the lane ports are published. LANE_PROBE_HOST: host.docker.internal - # Defense-in-depth safe.directory for the 6 PRIVATE clones (OMN-15137: - # omnibase_spi added -- it was missing here too, the same - # independently-hardcoded-list drift this ticket's manifest fixes for - # ensure_runner_clones.sh / stage_workspace.sh). The load-bearing - # exemption is the scoped `git -c safe.directory=` committed in - # the runtime_build scripts themselves (OMN-14900); this env only - # covers stray git invocations outside those scripts. - GIT_CONFIG_COUNT: "6" + # Defense-in-depth safe.directory for the 5 PRIVATE clones. The + # load-bearing exemption is the scoped `git -c safe.directory=` + # committed in the runtime_build scripts themselves (OMN-14900); this + # env only covers stray git invocations outside those scripts. + GIT_CONFIG_COUNT: "5" GIT_CONFIG_KEY_0: safe.directory GIT_CONFIG_VALUE_0: ${DEPLOY_RUNNER_OMNI_HOME:?}/omnibase_infra GIT_CONFIG_KEY_1: safe.directory @@ -1041,8 +1038,6 @@ services: GIT_CONFIG_VALUE_3: ${DEPLOY_RUNNER_OMNI_HOME:?}/onex_change_control GIT_CONFIG_KEY_4: safe.directory GIT_CONFIG_VALUE_4: ${DEPLOY_RUNNER_OMNI_HOME:?}/omnimarket - GIT_CONFIG_KEY_5: safe.directory - GIT_CONFIG_VALUE_5: ${DEPLOY_RUNNER_OMNI_HOME:?}/omnibase_spi volumes: - /var/run/docker.sock:/var/run/docker.sock # PRIVATE clone tree at the identical host:container path. The shared diff --git a/tests/unit/scripts/test_runner_private_omni_home_safe_directory.py b/tests/unit/scripts/test_runner_private_omni_home_safe_directory.py index a9c2cef9a0..3d195956b8 100644 --- a/tests/unit/scripts/test_runner_private_omni_home_safe_directory.py +++ b/tests/unit/scripts/test_runner_private_omni_home_safe_directory.py @@ -317,8 +317,13 @@ def test_compose_deploy_runner_env_is_fail_fast_and_repo_scoped() -> None: from THIS file cannot silently drop it: repo-scoped GITHUB_ORG_URL (creds volume was seeded repo-scoped), empty RUNNER_GROUP (repo-scoped registration rejects --runnergroup), fail-fast OMNI_HOME interpolation, - and the defense-in-depth GIT_CONFIG_* safe.directory entries for all 6 - private clones (OMN-15137: omnibase_spi added). + and the defense-in-depth GIT_CONFIG_* safe.directory entries for the 5 + clones this env currently covers. (OMN-15137: this container-level list + is also missing omnibase_spi -- tracked as a known residual, out of + scope here since it requires a deploy-runner container recreate to take + effect; the load-bearing per-op `-c safe.directory=` scoping in the + scripts themselves already covers every real git operation against + omnibase_spi.) """ svc = _deploy_runner_service() env = svc["environment"] @@ -339,12 +344,11 @@ def test_compose_deploy_runner_env_is_fail_fast_and_repo_scoped() -> None: f"OMNI_HOME must fail-fast interpolate from DEPLOY_RUNNER_OMNI_HOME " f"(got {omni_home!r}) -- a silent empty default produces broken binds" ) - assert str(env.get("GIT_CONFIG_COUNT")) == "6" - values = [str(env[f"GIT_CONFIG_VALUE_{i}"]) for i in range(6)] + assert str(env.get("GIT_CONFIG_COUNT")) == "5" + values = [str(env[f"GIT_CONFIG_VALUE_{i}"]) for i in range(5)] for repo in ( "omnibase_infra", "omnibase_core", - "omnibase_spi", "omnibase_compat", "onex_change_control", "omnimarket", @@ -355,7 +359,7 @@ def test_compose_deploy_runner_env_is_fail_fast_and_repo_scoped() -> None: assert all("DEPLOY_RUNNER_OMNI_HOME" in v for v in values), ( "safe.directory values must point at the PRIVATE clones" ) - for i in range(6): + for i in range(5): assert env[f"GIT_CONFIG_KEY_{i}"] == "safe.directory"