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
7 changes: 7 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -982,6 +982,13 @@ jobs:
echo "================================================================"
uv run python scripts/check_dockerfile_pins.py

- name: Check runtime image identity stamping
run: |
echo "================================================================"
echo "Runtime Image Identity Check (OMN-12965)"
echo "================================================================"
uv run python scripts/check_runtime_image_identity.py

# ============================================================================
# ONEX Compliance Check
# Runs `onex compliance check` to validate contracts, handlers, and schemas.
Expand Down
10 changes: 10 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,16 @@ repos:
files: ^src/omnibase_infra/nodes/[^/]+/handlers/.*\.py$
pass_filenames: false
stages: [pre-commit]
# OMN-12965: enforce runtime image identity stamping (version + revision).
# A blank/placeholder identity degrades every proof packet. Guards both the
# Dockerfile workspace-mode identity guard and the cmd_up build-arg wiring.
- id: check-runtime-image-identity
name: Enforce runtime image identity labels (OMN-12965)
entry: uv run --frozen python scripts/check_runtime_image_identity.py
language: system
files: ^(docker/Dockerfile\.runtime|src/omnibase_infra/docker/catalog/cli\.py)$
pass_filenames: false
stages: [pre-commit]
# OMN-11069: Block new os.environ/os.getenv reads outside approved overlay modules
- id: check-env-reads
name: Block new os.environ reads (use overlay-resolved config)
Expand Down
19 changes: 19 additions & 0 deletions docker/Dockerfile.runtime
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,25 @@ RUN set -eu; \
exit 64; \
fi

# OMN-12965: workspace-mode builds must stamp a real image identity. A blank
# VCS_REF (-> blank org.opencontainers.image.revision) or the placeholder
# RUNTIME_VERSION default (0.1.0) degrades every proof packet, since the runtime
# SHA + image digest are required citations in accepted evidence. Fail the build
# here rather than ship a blank-identity image. Release-mode builds may rely on
# the placeholder default (operator-driven release tooling pins them explicitly),
# so the guard is scoped to workspace mode.
RUN set -eu; \
if [ "${BUILD_SOURCE}" = "workspace" ]; then \
if [ -z "${VCS_REF}" ] || [ "${VCS_REF}" = "unknown" ]; then \
echo "workspace build requires a non-empty VCS_REF for org.opencontainers.image.revision (OMN-12965)" >&2; \
exit 64; \
fi; \
if [ -z "${RUNTIME_VERSION}" ] || [ "${RUNTIME_VERSION}" = "0.1.0" ]; then \
echo "workspace build requires RUNTIME_VERSION from pyproject (not placeholder 0.1.0) for org.opencontainers.image.version (OMN-12965)" >&2; \
exit 64; \
fi; \
fi

# OCI Image Labels (https://github.com/opencontainers/image-spec/blob/main/annotations.md)
LABEL org.opencontainers.image.title="OmniBase Infra Runtime" \
org.opencontainers.image.description="ONEX Infrastructure Runtime Service - Contract-driven kernel for event-based infrastructure orchestration" \
Expand Down
141 changes: 141 additions & 0 deletions scripts/check_runtime_image_identity.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
#!/usr/bin/env python3
# SPDX-FileCopyrightText: 2025 OmniNode.ai Inc.
# SPDX-License-Identifier: MIT

"""Enforce runtime image identity stamping (OMN-12965).

The deep dive found the main runtime image stamped
``org.opencontainers.image.version=0.1.0`` with a blank
``org.opencontainers.image.revision`` after workspace rebuilds. A blank identity
degrades every proof packet — the runtime SHA + image digest are required
citations in accepted evidence.

This is the enforcement ratchet (pre-commit + CI) that prevents the build paths
from regressing to a blank/placeholder identity. It is a static check: no docker
build, no network. It verifies:

1. ``docker/Dockerfile.runtime`` labels ``org.opencontainers.image.revision``
from ``VCS_REF`` and ``org.opencontainers.image.version`` from
``RUNTIME_VERSION``, and carries a workspace-mode guard that fails the build
when either would be blank/``unknown``/placeholder ``0.1.0``.
2. The operator rebuild path ``cmd_up`` in
``src/omnibase_infra/docker/catalog/cli.py`` stamps the identity quad via
``_image_identity_build_args()`` rather than a bare ``GIT_SHA`` build-arg.

Usage::

# From the repo root
uv run python scripts/check_runtime_image_identity.py

Exit codes:
0 — all checks passed
1 — one or more checks failed (stderr has details)
"""

from __future__ import annotations

import argparse
import re
import sys
from pathlib import Path

_REPO_ROOT = Path(__file__).resolve().parents[1]
_DOCKERFILE = _REPO_ROOT / "docker" / "Dockerfile.runtime"
_CLI = _REPO_ROOT / "src" / "omnibase_infra" / "docker" / "catalog" / "cli.py"


def _collapse_continuations(text: str) -> str:
"""Collapse shell ``\\``-continued physical lines into single logical lines."""
return re.sub(r"\\\n\s*", " ", text)


def _check_dockerfile() -> list[str]:
errors: list[str] = []
if not _DOCKERFILE.is_file():
return [f"missing {_DOCKERFILE}"]
collapsed = _collapse_continuations(_DOCKERFILE.read_text(encoding="utf-8"))

if 'org.opencontainers.image.revision="${VCS_REF}"' not in collapsed:
errors.append(
"Dockerfile.runtime must label org.opencontainers.image.revision "
'from "${VCS_REF}" (OMN-12965).'
)
if 'org.opencontainers.image.version="${RUNTIME_VERSION}"' not in collapsed:
errors.append(
"Dockerfile.runtime must label org.opencontainers.image.version "
'from "${RUNTIME_VERSION}" (OMN-12965).'
)

guard = re.search(
r'if \[ "\$\{BUILD_SOURCE\}" = "workspace" \];.*?VCS_REF.*?'
r"exit 64.*?RUNTIME_VERSION.*?0\.1\.0.*?exit 64",
collapsed,
re.DOTALL,
)
if not guard:
errors.append(
"Dockerfile.runtime must guard workspace builds against blank VCS_REF "
"and placeholder RUNTIME_VERSION=0.1.0 (OMN-12965)."
)
return errors


def _function_body(source: str, name: str) -> str | None:
lines = source.splitlines()
start = next(
(i for i, line in enumerate(lines) if line.startswith(f"def {name}(")),
None,
)
if start is None:
return None
end = next(
(
i
for i in range(start + 1, len(lines))
if lines[i] and not lines[i][0].isspace()
),
len(lines),
)
return "\n".join(lines[start:end])


def _check_cli() -> list[str]:
errors: list[str] = []
if not _CLI.is_file():
return [f"missing {_CLI}"]
source = _CLI.read_text(encoding="utf-8")

body = _function_body(source, "cmd_up")
if body is None:
return ["cmd_up not found in cli.py"]

if "_image_identity_build_args()" not in body:
errors.append(
"cmd_up --build must stamp the identity quad via "
"_image_identity_build_args() (OMN-12965)."
)
if "GIT_SHA=" in body:
errors.append(
"cmd_up must not hand-roll a GIT_SHA build-arg; delegate to "
"_image_identity_build_args() so VCS_REF + RUNTIME_VERSION are "
"stamped (OMN-12965)."
)
return errors


def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.parse_args(argv)

errors = _check_dockerfile() + _check_cli()
if errors:
print("Runtime image identity check FAILED (OMN-12965):", file=sys.stderr)
for err in errors:
print(f" - {err}", file=sys.stderr)
return 1
print("Runtime image identity check passed (OMN-12965).")
return 0


if __name__ == "__main__":
sys.exit(main())
12 changes: 12 additions & 0 deletions scripts/deploy-agent/tests/unit/test_executor_build_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,18 @@ def fake_run(cmd: list[str], timeout: int, **kwargs) -> subprocess.CompletedProc
assert "EXPECTED_BUILD_SOURCE=workspace" in build_cmd
assert "OMNI_HOME=/data/omninode/omni_home" in build_cmd

# OMN-12965: the workspace build must stamp the full OCI image-identity quad
# so org.opencontainers.image.{version,revision,created} populate. A blank
# revision / placeholder version degrades every proof packet.
assert "GIT_SHA=abc1234" in build_cmd
assert "VCS_REF=abc1234" in build_cmd
runtime_version_args = [a for a in build_cmd if a.startswith("RUNTIME_VERSION=")]
assert runtime_version_args, "missing RUNTIME_VERSION build-arg (OMN-12965)"
assert runtime_version_args[0] != "RUNTIME_VERSION=0.1.0", (
"RUNTIME_VERSION must come from pyproject, not the Dockerfile placeholder"
)
assert any(a.startswith("BUILD_DATE=") for a in build_cmd)


def test_unknown_build_source_fails_before_compose_build(
monkeypatch: pytest.MonkeyPatch,
Expand Down
23 changes: 22 additions & 1 deletion scripts/deploy-runtime.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1349,6 +1349,11 @@ build_images() {

local build_date
build_date="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
# OMN-12965: stamp org.opencontainers.image.version from pyproject so the
# runtime image carries a real version instead of the Dockerfile placeholder
# (0.1.0). A placeholder version degrades every proof packet.
local runtime_version
runtime_version="$(read_version "${deploy_target}")"
local omni_home="${OMNI_HOME:-}"
local build_source
build_source="$(resolve_build_source)"
Expand All @@ -1375,7 +1380,9 @@ build_images() {
--profile "${COMPOSE_PROFILE}"
build
--progress=plain
--build-arg "GIT_SHA=${git_sha}"
--build-arg "VCS_REF=${git_sha}"
--build-arg "RUNTIME_VERSION=${runtime_version}"
--build-arg "BUILD_DATE=${build_date}"
--build-arg "RUNTIME_SOURCE_HASH=${git_sha}"
--build-arg "COMPOSE_PROJECT=${compose_project}"
Expand All @@ -1387,7 +1394,7 @@ build_images() {
--build-arg "ONEX_CHANGE_CONTROL_REF=${occ_ref}"
)

log_info "Building images with VCS_REF=${git_sha} RUNTIME_SOURCE_HASH=${git_sha} COMPOSE_PROJECT=${compose_project}..."
log_info "Building images with VCS_REF=${git_sha} RUNTIME_VERSION=${runtime_version} RUNTIME_SOURCE_HASH=${git_sha} COMPOSE_PROJECT=${compose_project}..."
log_info "Build source: BUILD_SOURCE=${build_source} EXPECTED_BUILD_SOURCE=${expected_build_source} OMNI_HOME=${omni_home}"
log_info "Plugin refs: OMNIBASE_COMPAT_REF=${compat_ref} OMNIMARKET_REF=${omnimarket_ref} ONEX_CHANGE_CONTROL_REF=${occ_ref}"
log_info "Build timeout: ${build_timeout}s (set DOCKER_BUILD_TIMEOUT_SECONDS to override)"
Expand Down Expand Up @@ -1552,6 +1559,20 @@ verify_deployment() {
log_warn "Could not read image label (container may not exist yet)."
fi

# OMN-12965: verify org.opencontainers.image.version is a real version, not
# the Dockerfile placeholder (0.1.0) or blank. A placeholder/blank identity
# degrades every proof packet (runtime SHA + image digest are required
# citations in accepted evidence).
local version_label
version_label="$(docker inspect "${container_id}" \
--format='{{index .Config.Labels "org.opencontainers.image.version"}}' 2>/dev/null || true)"
if [[ -z "${version_label}" || "${version_label}" == "0.1.0" ]]; then
log_error "Image version label is blank/placeholder: org.opencontainers.image.version='${version_label}'"
log_error "Runtime image identity is degraded (OMN-12965). Rebuild with RUNTIME_VERSION from pyproject."
exit 1
fi
log_info "Image version label OK: org.opencontainers.image.version=${version_label}"

# 4. Log sentinel: entrypoint ran
log_info "Checking log sentinels..."
local logs
Expand Down
66 changes: 63 additions & 3 deletions src/omnibase_infra/docker/catalog/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@

from __future__ import annotations

import datetime
import os
import subprocess
import sys
import tomllib
from pathlib import Path

import yaml
Expand Down Expand Up @@ -183,6 +185,66 @@ def _current_git_sha() -> str:
return "unknown"


def _runtime_version() -> str:
"""Return the runtime package version stamped as the OCI image version.

Read from the repo ``pyproject.toml`` so ``org.opencontainers.image.version``
reflects the real package version rather than the Dockerfile placeholder
default (``0.1.0``). Fails fast if the version cannot be resolved so a build
never silently stamps a placeholder identity (OMN-12965).
"""
pyproject = _REPO_ROOT / "pyproject.toml"
try:
data = tomllib.loads(pyproject.read_text(encoding="utf-8"))
version = str(data["project"]["version"]).strip()
except (FileNotFoundError, KeyError, TypeError, tomllib.TOMLDecodeError) as exc:
raise RuntimeError(
f"Could not resolve RUNTIME_VERSION from {pyproject}; "
"refusing to build an image with placeholder identity (OMN-12965)."
) from exc
if not version:
raise RuntimeError(
f"Empty RUNTIME_VERSION in {pyproject}; "
"refusing to build an image with placeholder identity (OMN-12965)."
)
return version


def _image_identity_build_args() -> list[str]:
"""Return the OCI image-identity ``--build-arg`` pairs for a runtime build.

Stamps the full quad consumed by the runtime-stage OCI labels and provenance
manifest: ``GIT_SHA`` (busts the COPY src/ layer + builder-stage revision
label), ``VCS_REF`` (runtime-stage ``org.opencontainers.image.revision``),
``RUNTIME_VERSION`` (``org.opencontainers.image.version``), and ``BUILD_DATE``
(``org.opencontainers.image.created``).

Fails fast when the git SHA is unresolved: a blank/``unknown`` revision
produces a blank-identity image that degrades every proof packet (the runtime
SHA + image digest are required citations in accepted evidence). This is the
enforcement point for OMN-12965 — never stamp a placeholder identity.
"""
git_sha = _current_git_sha()
if not git_sha or git_sha == "unknown":
raise RuntimeError(
"Cannot resolve git revision for the runtime image build. A blank or "
"'unknown' org.opencontainers.image.revision degrades every proof "
"packet (OMN-12965). Run the build from a clean git checkout of "
f"{_REPO_ROOT}."
)
build_date = datetime.datetime.now(datetime.UTC).strftime("%Y-%m-%dT%H:%M:%SZ")
return [
"--build-arg",
f"GIT_SHA={git_sha}",
"--build-arg",
f"VCS_REF={git_sha}",
"--build-arg",
f"RUNTIME_VERSION={_runtime_version()}",
"--build-arg",
f"BUILD_DATE={build_date}",
]


def cmd_seed(_args: list[str]) -> int:
"""Seed Infisical with config keys from contracts."""
return _run_seed()
Expand Down Expand Up @@ -236,16 +298,14 @@ def cmd_up(args: list[str]) -> int:
# Build if requested (OMN-7214)
if force_build:
print("Rebuilding images (--build)...")
git_sha = _current_git_sha()
build_proc = subprocess.run(
[
"docker",
"compose",
"-f",
_DEFAULT_OUTPUT,
"build",
"--build-arg",
f"GIT_SHA={git_sha}",
*_image_identity_build_args(),
],
cwd=str(_REPO_ROOT),
check=False,
Expand Down
Loading
Loading