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
10 changes: 9 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2444,8 +2444,16 @@ jobs:
github.event_name != 'pull_request' ||
github.event.pull_request.head.repo.full_name == github.repository
env:
# OMN-16076: no hardcoded fallback SHA. The old pin was a squash
# commit reachable only through a since-deleted stacked branch, so
# the first push-event run of this step (post-merge on main, which
# never has a pull_request/merge_group base) could not resolve it
# and crashed. push event.before is the previous main tip -- for a
# promotion merge that is exactly the promotion PR's base, so the
# push verdict reproduces the proven PR verdict. An empty or
# unreachable base fails loudly inside the script with remediation.
APPLICATION_SQL_BASE_REVISION: >-
${{ github.event.pull_request.base.sha || github.event.merge_group.base_sha || '7228ce0c0934ae096dd6effd0f84ff1913fec6c0' }}
${{ github.event.pull_request.base.sha || github.event.merge_group.base_sha || github.event.before }}
run: >-
uv run python scripts/ci/check_application_database_sql.py
--base-revision "$APPLICATION_SQL_BASE_REVISION"
Expand Down
4 changes: 2 additions & 2 deletions docker/runners/runner-image.lock.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{
"base_image_digest": "sha256:3ba65aa20f86a0fad9df2b2c259c613df006b2e6d0bfcc8a146afb8c525a9751",
"gh_version": "2.67.0",
"identity_digest": "17b97156cc173de6faa32489b88ac961",
"identity_digest": "eee99c0dd59126bbc2751066b31ff191",
"image_version": 7,
"kubectl_version": "1.32.1",
"python_version": "3.12",
"runner_version": "2.336.0",
"shared_env_digest": "bffad7795606a13c66aefdac",
"shared_env_digest": "577478fed88afe4bec35906c",
"shared_env_install_args": "--frozen --all-extras --all-groups --no-install-project",
"uv_version": "0.6.14"
}
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "omnibase_infra"
version = "0.38.6"
version = "0.38.7"
description = "ONEX Infrastructure - Service integration and database infrastructure tools"
authors = [{ name = "OmniNode.ai", email = "contact@omninode.ai" }]
license = {text = "MIT"}
Expand Down
33 changes: 33 additions & 0 deletions scripts/ci/check_application_database_sql.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,12 +204,45 @@ def _is_legacy_default_schema_sql_path(relative_path: Path) -> bool:
return relative_path in _LEGACY_DEFAULT_SCHEMA_SQL_EXACT_PATHS


_ZERO_REVISION = "0" * 40

# The trusted CI step resolves the diff base from workflow event context:
# pull_request.base.sha, merge_group.base_sha, or push event.before. A pinned
# fallback SHA is forbidden -- a commit reachable only through a since-deleted
# stacked branch is absent from every checkout, so the first push-event run of
# this gate crashed on a raw git fatal instead of a diagnosable verdict
# (OMN-16076). Validate the base up front and fail with the remediation.


def _assert_base_revision_resolvable(repository: Path, base_revision: str) -> None:
if not base_revision or base_revision == _ZERO_REVISION:
raise RuntimeError(
"no usable base revision for changed-SQL linting: got "
f"{base_revision!r}; the workflow event context must supply "
"pull_request.base.sha, merge_group.base_sha, or push event.before"
)
probe = subprocess.run(
["git", "rev-parse", "--verify", "--quiet", f"{base_revision}^{{commit}}"],
cwd=repository,
capture_output=True,
check=False,
)
if probe.returncode != 0:
raise RuntimeError(
f"base revision {base_revision} is not reachable in this checkout; "
"it must come from the workflow event context "
"(pull_request.base.sha, merge_group.base_sha, or push "
"event.before), never a hardcoded pin"
)


def changed_sql_paths(
repository: Path,
base_revision: str,
head_revision: str,
) -> tuple[Path, ...]:
"""Return changed deployable SQL, excluding the exact ephemeral proof seed."""
_assert_base_revision_resolvable(repository, base_revision)
result = subprocess.run(
[
"git",
Expand Down
81 changes: 81 additions & 0 deletions tests/ci/test_application_database_sql_gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@

from __future__ import annotations

import re
import subprocess
from pathlib import Path

import pytest

from scripts.ci.check_application_database_sql import (
changed_sql_paths,
validate_changed_sql,
violation_key,
)
Expand Down Expand Up @@ -563,3 +565,82 @@ def test_violation_key_is_content_addressed_not_positional() -> None:
assert violation_key(same) == violation_key(same)
assert violation_key(same) != violation_key(same.replace("a.sql", "b.sql"))
assert violation_key(same) != violation_key(same.replace("'x'", "'y'"))


# ---------------------------------------------------------------------------
# OMN-16076: push-event base-revision resolution. The trusted CI step has no
# pull_request/merge_group base on a push event; its fallback must be push
# event.before, never a hardcoded SHA. The old pin was reachable only through
# a since-deleted stacked branch, so the first main-push run of the gate died
# on a raw git fatal ("Invalid symmetric difference expression") instead of a
# verdict. These tests hold both the script's error contract and the workflow
# expression itself.
# ---------------------------------------------------------------------------


def _two_commit_repository(tmp_path: Path) -> tuple[Path, str, str]:
repository = tmp_path / "repository"
repository.mkdir()
_git(repository, "init", "--initial-branch=main")
(repository / "seed.sql").write_text(
"CREATE TABLE tenant.seed_table (id uuid);\n", encoding="utf-8"
)
base = _commit(repository, "baseline")
(repository / "next.sql").write_text(
"CREATE TABLE tenant.next_table (id uuid);\n", encoding="utf-8"
)
head = _commit(repository, "next")
return repository, base, head


def test_push_event_previous_commit_base_resolves_changed_sql(
tmp_path: Path,
) -> None:
repository, base, head = _two_commit_repository(tmp_path)
changed = changed_sql_paths(repository, base, head)
assert [path.name for path in changed] == ["next.sql"]


def test_an_empty_base_revision_fails_with_remediation(tmp_path: Path) -> None:
repository, _base, head = _two_commit_repository(tmp_path)
with pytest.raises(RuntimeError, match="event context"):
changed_sql_paths(repository, "", head)


def test_a_zero_base_revision_fails_with_remediation(tmp_path: Path) -> None:
repository, _base, head = _two_commit_repository(tmp_path)
with pytest.raises(RuntimeError, match="event context"):
changed_sql_paths(repository, "0" * 40, head)


def test_an_unreachable_base_revision_fails_with_remediation(
tmp_path: Path,
) -> None:
repository, _base, head = _two_commit_repository(tmp_path)
unreachable = "7228ce0c0934ae096dd6effd0f84ff1913fec6c0"
with pytest.raises(RuntimeError) as excinfo:
changed_sql_paths(repository, unreachable, head)
message = str(excinfo.value)
assert "not reachable in this checkout" in message
assert "hardcoded pin" in message
assert "Invalid symmetric difference" not in message


def test_ci_workflow_base_revision_has_no_hardcoded_pin() -> None:
workflow = (_ROOT / ".github" / "workflows" / "ci.yml").read_text(encoding="utf-8")
lines = workflow.splitlines()
expression_lines: list[str] = []
for index, line in enumerate(lines):
if "APPLICATION_SQL_BASE_REVISION" not in line:
continue
expression_lines.extend(lines[index : index + 2])
assert expression_lines, "trusted step no longer sets the base revision"
for line in expression_lines:
assert not re.search(r"[0-9a-f]{40}", line), (
"APPLICATION_SQL_BASE_REVISION must resolve from event context, "
f"never a hardcoded SHA: {line.strip()}"
)
assert any("github.event.before" in line for line in expression_lines), (
"push events have no pull_request/merge_group base; the fallback "
"must be github.event.before"
)
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading