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
39 changes: 34 additions & 5 deletions docker/docker-compose.infra.yml
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,15 @@ x-runtime-env: &runtime-env
ONEX_CONTRACTS_DIR: /app/contracts
ONEX_STATE_ROOT: /app/data/.onex_state
ONEX_STATE_DIR: /app/data/.onex_state
BIFROST_CONTRACT_PATH: ${BIFROST_CONTRACT_PATH:-/app/data/delegation/bifrost_delegation.yaml}
# OMN-12864: Hardcoded default — do NOT use ${BIFROST_CONTRACT_PATH:-...} here.
# The :-/empty fallback was a footgun: if a host operator accidentally exported
# BIFROST_CONTRACT_PATH="" in their shell, it propagated to ALL services including
# the runtime containers that need bifrost rendering, silently disabling it and
# causing a 9→4 backend drop on the next restart. Hardcoding the path removes that
# ambient-env override path. Services that legitimately disable rendering
# (projection-api, contract-resolver) explicitly set BIFROST_CONTRACT_PATH: ""
# in their own environment blocks — those overrides remain intentional.
BIFROST_CONTRACT_PATH: /app/data/delegation/bifrost_delegation.yaml
BIFROST_SOURCE_CONTRACT_PATH: ${BIFROST_SOURCE_CONTRACT_PATH:-}
BIFROST_VERIFY_ENDPOINTS: ${BIFROST_VERIFY_ENDPOINTS:?runtime policy contract must set BIFROST_VERIFY_ENDPOINTS}
ONEX_SECRET_RESOLVER_CONFIG_PATH: ${DEV_RUNTIME_MAIN_SECRET_RESOLVER_CONFIG_PATH:?runtime policy contract must set DEV_RUNTIME_MAIN_SECRET_RESOLVER_CONFIG_PATH}
Expand Down Expand Up @@ -229,10 +237,16 @@ x-runtime-env: &runtime-env
LLM_CODER_FAST_URL: ${LLM_CODER_FAST_URL:?LLM_CODER_FAST_URL required for PluginLlm activation}
LLM_EMBEDDING_URL: ${LLM_EMBEDDING_URL:?LLM_EMBEDDING_URL required for PluginLlm activation}
LLM_DEEPSEEK_R1_URL: ${LLM_DEEPSEEK_R1_URL:?LLM_DEEPSEEK_R1_URL required for PluginLlm activation}
BIFROST_LOCAL_CODER_ENDPOINT_URL: ${BIFROST_LOCAL_CODER_ENDPOINT_URL:?BIFROST_LOCAL_CODER_ENDPOINT_URL required for Bifrost local coder}
BIFROST_LOCAL_REASONER_ENDPOINT_URL: ${BIFROST_LOCAL_REASONER_ENDPOINT_URL:?BIFROST_LOCAL_REASONER_ENDPOINT_URL required for Bifrost local reasoner}
BIFROST_LOCAL_EMBEDDING_ENDPOINT_URL: ${BIFROST_LOCAL_EMBEDDING_ENDPOINT_URL:?BIFROST_LOCAL_EMBEDDING_ENDPOINT_URL required for Bifrost local embedding}
BIFROST_LOCAL_DS_V4_FLASH_ENDPOINT_URL: ${BIFROST_LOCAL_DS_V4_FLASH_ENDPOINT_URL:?BIFROST_LOCAL_DS_V4_FLASH_ENDPOINT_URL required for Bifrost local DS V4 Flash}
# OMN-12864: Bifrost local endpoints — committed overlay is the authority.
# Values come from docker/lane-overlays/dev.bifrost.env (service-level env_file
# on the three runtime services that need local inference). Validation at the
# Python layer (ModelBifrostLaneOverlay + render_bifrost_delegation_contract) is
# the enforcement point; compose-level :? is removed so compose config renders
# cleanly in CI without the env vars pre-set.
BIFROST_LOCAL_CODER_ENDPOINT_URL: ${BIFROST_LOCAL_CODER_ENDPOINT_URL:-}
BIFROST_LOCAL_REASONER_ENDPOINT_URL: ${BIFROST_LOCAL_REASONER_ENDPOINT_URL:-}
BIFROST_LOCAL_EMBEDDING_ENDPOINT_URL: ${BIFROST_LOCAL_EMBEDDING_ENDPOINT_URL:-}
BIFROST_LOCAL_DS_V4_FLASH_ENDPOINT_URL: ${BIFROST_LOCAL_DS_V4_FLASH_ENDPOINT_URL:-}
# Cloud-tier GLM route refs are optional at boot but must be present in the
# container env when routing authority selects api_key_ref=LLM_GLM_API_KEY.
LLM_GLM_URL: ${LLM_GLM_URL:?LLM_GLM_URL is required for GLM escalation routing}
Expand Down Expand Up @@ -697,6 +711,13 @@ services:
!!merge <<: *runtime-base
container_name: omninode-runtime
profiles: ["runtime", "full"]
# OMN-12864: Service-level env_file injects committed BIFROST_LOCAL_* overlay.
# The overlay YAML source is docker/lane-overlays/dev.bifrost.yaml; the sidecar
# is generated by: uv run python scripts/render_bifrost_lane_overlay_env.py
# Do NOT override these vars with shell exports — the committed file is the authority.
env_file:
- path: lane-overlays/dev.bifrost.env
required: true
depends_on:
migration-gate:
condition: service_healthy
Expand Down Expand Up @@ -771,6 +792,10 @@ services:
!!merge <<: *runtime-base
container_name: omninode-runtime-effects
profiles: ["runtime", "full"]
# OMN-12864: Service-level env_file injects committed BIFROST_LOCAL_* overlay.
env_file:
- path: lane-overlays/dev.bifrost.env
required: true
depends_on:
omninode-runtime:
condition: service_healthy
Expand Down Expand Up @@ -868,6 +893,10 @@ services:
runtime-worker:
!!merge <<: *runtime-base
profiles: ["runtime", "full"]
# OMN-12864: Service-level env_file injects committed BIFROST_LOCAL_* overlay.
env_file:
- path: lane-overlays/dev.bifrost.env
required: true
depends_on:
omninode-runtime:
condition: service_healthy
Expand Down
10 changes: 8 additions & 2 deletions docker/entrypoint-runtime.sh
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,14 @@ else
fi

if [ -n "${BIFROST_CONTRACT_PATH:-}" ]; then
echo "[entrypoint] Rendering Bifrost delegation contract..."
python -m omnibase_infra.runtime.render_bifrost_delegation_contract
# OMN-12945: Force re-seed from packaged source on every container restart.
# BIFROST_FORCE_RESEED=1 bypasses the stale-volume early-return path so the
# named-volume copy is always rebuilt from the committed packaged source
# merged with the lane-overlay BIFROST_LOCAL_* endpoint vars. This eliminates
# the volume-drift defect where a stale /app/data/delegation/bifrost_delegation.yaml
# silently survived image rebuilds and exposed wrong/missing backends.
echo "[entrypoint] Rendering Bifrost delegation contract (force-reseed)..."
BIFROST_FORCE_RESEED=1 python -m omnibase_infra.runtime.render_bifrost_delegation_contract
fi

if [ -n "${ONEX_SECRET_RESOLVER_CONFIG_PATH:-}" ]; then
Expand Down
10 changes: 10 additions & 0 deletions docker/lane-overlays/dev.bifrost.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Generated from docker/lane-overlays/dev.bifrost.yaml.
# Do not edit directly — edit the YAML source and re-render:
# uv run python scripts/render_bifrost_lane_overlay_env.py
#
# OMN-12864: committed endpoint authority for BIFROST_LOCAL_* vars.
# OMN-12815: every URL is the COMPLETE final chat-completions URL.
BIFROST_LOCAL_CODER_ENDPOINT_URL=http://192.168.86.201:8000/v1/chat/completions
BIFROST_LOCAL_DS_V4_FLASH_ENDPOINT_URL=http://192.168.86.200:8101/v1/chat/completions
BIFROST_LOCAL_EMBEDDING_ENDPOINT_URL=http://192.168.86.201:8100/v1/chat/completions
BIFROST_LOCAL_REASONER_ENDPOINT_URL=http://192.168.86.201:8001/v1/chat/completions
38 changes: 38 additions & 0 deletions docker/lane-overlays/dev.bifrost.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# docker/lane-overlays/dev.bifrost.yaml
#
# OMN-12864: Committed lane overlay — Bifrost local backend endpoint URLs.
#
# These four values were previously only available via ephemeral shell exports
# on .201. Moving them into this committed YAML file makes the deployment
# authority auditable, diff-able, and CI-checked.
#
# AUTHORITY: This file is the single source of truth for the dev lane's
# BIFROST_LOCAL_* endpoint URL bindings. Values here are TYPED and validated
# by ModelBifrostLaneOverlay (OMN-12815: every URL must be the COMPLETE final
# chat-completions URL, not a bare base).
#
# RENDERED FORM: docker/lane-overlays/dev.bifrost.env is generated from this
# file via `uv run python scripts/render_bifrost_lane_overlay_env.py`.
# The compose stack reads the .env sidecar via an `env_file:` reference.
# Never edit the .env sidecar directly — edit this YAML and re-render.
#
# RE-SEED: On each deploy restart, entrypoint-runtime.sh re-renders the
# Bifrost delegation contract FROM the packaged source (OMN-12945). The volume
# copy is never used as an authority on a fresh restart — it is always rebuilt
# from the packaged bifrost_delegation.yaml merged with the env-var endpoints
# supplied by this overlay.
#
# INTERNAL IPs: the .201 and .200 addresses below are local-lane runtime
# endpoints. They are intentionally committed here — this overlay is the
# canonical config authority, not ephemeral shell state.
# onex-allow-internal-ip OMN-12864 reason="committed lane overlay — dev lane local LLM endpoint bindings"
lane: dev
# Coder: Qwen3.6-35B-A3B on .201 port 8000 (RTX 5090)
coder_endpoint_url: "http://192.168.86.201:8000/v1/chat/completions"
# Reasoner: Qwen3.6-27B-MTP-IQ4_XS on .201 port 8001 (RTX 4090)
reasoner_endpoint_url: "http://192.168.86.201:8001/v1/chat/completions"
# Embedding: local embedding server on .201 port 8100 (M2 Ultra)
embedding_endpoint_url: "http://192.168.86.201:8100/v1/chat/completions"
# DS-V4-Flash: DeepSeek V4 Flash 284B MoE on .200 port 8101 (OMN-12492)
# onex-allow-internal-ip OMN-12492 reason="DS-V4-Flash (.200) endpoint in committed lane overlay"
ds4_flash_endpoint_url: "http://192.168.86.200:8101/v1/chat/completions"
110 changes: 110 additions & 0 deletions scripts/render_bifrost_lane_overlay_env.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
#!/usr/bin/env python3
# SPDX-FileCopyrightText: 2025 OmniNode.ai Inc.
# SPDX-License-Identifier: MIT
"""Render Bifrost lane overlay YAML to dotenv sidecar (OMN-12864).

Usage:
# Render dev lane overlay to dev.bifrost.env (default)
uv run python scripts/render_bifrost_lane_overlay_env.py

# Check that the rendered env file is in sync with the YAML source
uv run python scripts/render_bifrost_lane_overlay_env.py --check

# Render a specific overlay file
uv run python scripts/render_bifrost_lane_overlay_env.py \\
--overlay docker/lane-overlays/dev.bifrost.yaml \\
--output docker/lane-overlays/dev.bifrost.env

CI contract: the rendered .env sidecar MUST be committed alongside the YAML
source. The --check flag fails with exit code 1 when the sidecar is stale.
"""

from __future__ import annotations

import argparse
import sys
from pathlib import Path

import yaml

_REPO_ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(_REPO_ROOT / "src"))

from omnibase_infra.runtime.models.model_bifrost_lane_overlay import (
ModelBifrostLaneOverlay,
)

_DEFAULT_OVERLAY = _REPO_ROOT / "docker" / "lane-overlays" / "dev.bifrost.yaml"
_DEFAULT_OUTPUT = _REPO_ROOT / "docker" / "lane-overlays" / "dev.bifrost.env"


def load_overlay(path: Path) -> ModelBifrostLaneOverlay:
"""Load and validate the Bifrost lane overlay YAML."""
raw = yaml.safe_load(path.read_text(encoding="utf-8"))
return ModelBifrostLaneOverlay.model_validate(raw)


def render_env_file(overlay: ModelBifrostLaneOverlay) -> str:
"""Render the env-var binding as a dotenv file."""
env = overlay.as_env_dict()
lines = [
f"# Generated from docker/lane-overlays/{overlay.lane}.bifrost.yaml.",
"# Do not edit directly — edit the YAML source and re-render:",
"# uv run python scripts/render_bifrost_lane_overlay_env.py",
"#",
"# OMN-12864: committed endpoint authority for BIFROST_LOCAL_* vars.",
"# OMN-12815: every URL is the COMPLETE final chat-completions URL.",
]
lines.extend(f"{key}={value}" for key, value in sorted(env.items()))
return "\n".join(lines) + "\n"


def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--overlay",
type=Path,
default=_DEFAULT_OVERLAY,
help="Path to the bifrost lane overlay YAML.",
)
parser.add_argument(
"--output",
type=Path,
default=_DEFAULT_OUTPUT,
help="Path to write the rendered dotenv sidecar.",
)
parser.add_argument(
"--check",
action="store_true",
help="Check that the sidecar is in sync with the YAML; exit 1 if not.",
)
args = parser.parse_args(argv)

overlay = load_overlay(args.overlay)
rendered = render_env_file(overlay)

if args.check:
if not args.output.exists():
print(
f"ERROR: {args.output} does not exist. Run without --check to generate.",
file=sys.stderr,
)
return 1
existing = args.output.read_text(encoding="utf-8")
if rendered != existing:
print(
f"ERROR: {args.output} is out of sync with {args.overlay}. "
"Re-render: uv run python scripts/render_bifrost_lane_overlay_env.py",
file=sys.stderr,
)
return 1
print(f"OK: {args.output} matches {args.overlay}")
return 0

args.output.write_text(rendered, encoding="utf-8")
print(f"Rendered: {args.output}")
return 0


if __name__ == "__main__":
raise SystemExit(main())
101 changes: 101 additions & 0 deletions src/omnibase_infra/runtime/models/model_bifrost_lane_overlay.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# SPDX-FileCopyrightText: 2025 OmniNode.ai Inc.
# SPDX-License-Identifier: MIT
"""Typed model for the Bifrost lane overlay YAML (OMN-12864).

The lane overlay commits the four BIFROST_LOCAL_* endpoint URL bindings that
were previously only available via ephemeral shell exports on .201. Storing
them as a committed typed YAML file under docker/lane-overlays/ makes the
deployment authority auditable, diff-able, and CI-checked.

Each endpoint_url is the COMPLETE final URL including the /v1/chat/completions
path (OMN-12815). The overlay file is rendered to a dotenv sidecar
(docker/lane-overlays/<lane>.bifrost.env) consumed by the compose stack at
runtime. Any mismatch between the YAML source and rendered env sidecar fails
CI via test_bifrost_lane_overlay_env_in_sync.

Validation enforces OMN-12815: every URL must end in /v1/chat/completions or
/chat/completions to catch bare-base misconfiguration early.
"""

from __future__ import annotations

from pydantic import BaseModel, ConfigDict, Field, model_validator


class ModelBifrostLaneOverlay(BaseModel):
"""Typed deployment bindings for Bifrost local backend endpoint URLs.

All four endpoint_url values are COMPLETE final URLs (OMN-12815):
they are posted VERBATIM at every call site — no in-code construction.
"""

model_config = ConfigDict(frozen=True, extra="forbid", from_attributes=True)

lane: str = Field(
min_length=1,
description="Lane identifier (e.g. 'dev', 'stability-test', 'prod').",
)
coder_endpoint_url: str = Field(
min_length=1,
description=(
"COMPLETE chat-completions URL for the local coder backend "
"(vLLM on .201:8000). Posted VERBATIM — no path appended."
),
)
reasoner_endpoint_url: str = Field(
min_length=1,
description=(
"COMPLETE chat-completions URL for the local reasoner backend "
"(vLLM on .201:8001). Posted VERBATIM — no path appended."
),
)
embedding_endpoint_url: str = Field(
min_length=1,
description=(
"COMPLETE chat-completions URL for the local embedding backend "
"(.201:8100). Posted VERBATIM — no path appended."
),
)
ds4_flash_endpoint_url: str = Field(
min_length=1,
description=(
"COMPLETE chat-completions URL for the DS-V4-Flash backend "
"(.200:8101). Posted VERBATIM — no path appended."
),
)

@model_validator(mode="after")
def _validate_complete_endpoint_urls(self) -> ModelBifrostLaneOverlay:
"""Reject bare-base URLs that lack the /chat/completions path."""
fields = {
"coder_endpoint_url": self.coder_endpoint_url,
"reasoner_endpoint_url": self.reasoner_endpoint_url,
"embedding_endpoint_url": self.embedding_endpoint_url,
"ds4_flash_endpoint_url": self.ds4_flash_endpoint_url,
}
errors: list[str] = []
for field_name, url in fields.items():
stripped = url.rstrip("/")
if not (stripped.endswith(("/v1/chat/completions", "/chat/completions"))):
errors.append(
f"{field_name}: endpoint_url must end in /v1/chat/completions "
f"or /chat/completions (OMN-12815), got {url!r}"
)
if errors:
msg = "Bifrost lane overlay endpoint_url completeness errors: " + "; ".join(
errors
)
raise ValueError(msg)
return self

def as_env_dict(self) -> dict[str, str]:
"""Return the env-var mapping consumed by the compose stack."""
return {
"BIFROST_LOCAL_CODER_ENDPOINT_URL": self.coder_endpoint_url,
"BIFROST_LOCAL_REASONER_ENDPOINT_URL": self.reasoner_endpoint_url,
"BIFROST_LOCAL_EMBEDDING_ENDPOINT_URL": self.embedding_endpoint_url,
"BIFROST_LOCAL_DS_V4_FLASH_ENDPOINT_URL": self.ds4_flash_endpoint_url,
}


__all__: list[str] = ["ModelBifrostLaneOverlay"]
Loading
Loading