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
3 changes: 2 additions & 1 deletion scripts/validation/check_no_infra_inmemory_import.sh
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ if [[ ${#SCAN_PATHS[@]} -eq 0 ]]; then
SCAN_PATHS=("src/")
fi

# OMN-20700: grep -H preserves filename headers for a single staged file operand.
while IFS= read -r line; do
file="${line%%:*}"
# Normalize any doubled slash from grep's "src/" prefix (src//foo -> src/foo).
Expand All @@ -116,7 +117,7 @@ while IFS= read -r line; do
fi
echo " $line"
VIOLATIONS=$((VIOLATIONS + 1))
done < <(grep -rnE --include="*.py" "$PATTERN" "${SCAN_PATHS[@]}" 2>/dev/null || true)
done < <(grep -HrnE --include="*.py" "$PATTERN" "${SCAN_PATHS[@]}" 2>/dev/null || true)

if [[ $VIOLATIONS -gt 0 ]]; then
echo ""
Expand Down
63 changes: 53 additions & 10 deletions src/omnibase_infra/runtime/service_kernel.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,12 @@
# not hardcoded in the product every customer runs (OMN-19184).
WORKSPACE_RUNTIME_CONTRACTS_RELATIVE_PATH = Path("config") / "onex"

# Bootstrap location for the workspace's tier-1 config owner.
ENV_WORKSPACE_CONFIG_ROOT = "ONEX_WORKSPACE_CONFIG_ROOT"
# OMN-20700: public lab-onboarding.sh phase 2 writes developer tier-1 config
# here, relative to the workspace root.
DEVELOPER_WORKSPACE_CONFIG_RELATIVE_PATH = Path(".onex") / "workspace-config"

# Environment variable name for contracts directory
ENV_CONTRACTS_DIR = "ONEX_CONTRACTS_DIR"
# Marketplace package skill-manifest root.
Expand Down Expand Up @@ -1330,17 +1336,33 @@ def _load_tier0_runtime_config(
) from e


class WorkspaceRuntimeConfigMissingError(ProtocolConfigurationError):
"""OMN-19193 refusal of a bound workspace root with no tier-1 config anywhere.

Typed so callers and tests can distinguish this refusal (OMN-20700).
"""


def workspace_runtime_config_root(workspace_root: Path) -> Path:
"""Resolve the config owner, independently of the registry's state root.

ONEX_WORKSPACE_CONFIG_ROOT is a bootstrap location, never a transport
selector. Registry installs use the sibling operator repository. Developer
installs can name their own config directory. There is no retiring-root read.
selector. Resolution order is a non-blank environment override, the sibling
``../omnibase_internal`` if it is a directory, then ``.onex/workspace-config``
if it is a directory, otherwise the sibling path for the caller to refuse.
All results are resolved. A developer install never needs a private
repository (OMN-20700). There is no retiring-root read.
"""
configured = os.environ.get("ONEX_WORKSPACE_CONFIG_ROOT", "").strip()
return (
Path(configured) if configured else workspace_root / ".." / "omnibase_internal"
).resolve()
configured = os.environ.get(ENV_WORKSPACE_CONFIG_ROOT, "").strip()
if configured:
return Path(configured).resolve()
sibling = workspace_root / ".." / "omnibase_internal"
if sibling.is_dir():
return sibling.resolve()
developer = workspace_root / DEVELOPER_WORKSPACE_CONFIG_RELATIVE_PATH
if developer.is_dir():
return developer.resolve()
return sibling.resolve()


def resolve_embedded_runtime_config(
Expand Down Expand Up @@ -1375,7 +1397,8 @@ def resolve_embedded_runtime_config(
working-tree config under the owning config root
answers (OMN-19193). This is the tier-1 overlay the OMN-17304 ruling
composes on top of tier-0. The file belongs to the config owner, selected by
``ONEX_WORKSPACE_CONFIG_ROOT`` or the sibling operator repository,
``ONEX_WORKSPACE_CONFIG_ROOT``, the sibling operator repository, or the
developer workspace's ``.onex/workspace-config`` (OMN-20700),
never to this package. The retiring registry root is never read. A bound
root with neither copy is REFUSED rather than
answered with tier-0: binding a workspace root is a claim to be a
Expand All @@ -1398,7 +1421,8 @@ def resolve_embedded_runtime_config(
Raises:
ProtocolConfigurationError: the pointed-at, workspace or shipped
config exists but fails validation (e.g. a lane-profile config
declaring the in-memory bus), or a workspace root is bound and
declaring the in-memory bus).
WorkspaceRuntimeConfigMissingError: a workspace root is bound and
declares no runtime config.
"""
pointer = os.environ.get(ENV_CONTRACTS_DIR, "").strip()
Expand Down Expand Up @@ -1471,7 +1495,21 @@ def resolve_embedded_runtime_config(
/ MATERIALIZED_CONTRACTS_RELATIVE_PATH
/ DEFAULT_RUNTIME_CONFIG
)
raise ProtocolConfigurationError(
missing_owner_details = ""
if not os.environ.get(ENV_WORKSPACE_CONFIG_ROOT, "").strip():
sibling = (workspace_root / ".." / "omnibase_internal").resolve()
developer_config = (
workspace_root.resolve()
/ DEVELOPER_WORKSPACE_CONFIG_RELATIVE_PATH
/ WORKSPACE_RUNTIME_CONTRACTS_RELATIVE_PATH
/ DEFAULT_RUNTIME_CONFIG
)
missing_owner_details = (
f" {ENV_WORKSPACE_CONFIG_ROOT} is unset; the sibling operator "
f"repository path is {sibling} and the developer config path "
f"is {developer_config}."
)
raise WorkspaceRuntimeConfigMissingError(
f"workspace root {workspace_root} is bound but has no materialised "
f"runtime config at {materialized_config} (the copy comes from "
f"{SOURCE_REF} of the config owner) and declares no working-tree runtime "
Expand All @@ -1482,7 +1520,12 @@ def resolve_embedded_runtime_config(
f"(OMN-19193). Run `onex delegate` again so the materialiser can "
f"refresh it / declare the config in the config owner, or select a "
f"transport explicitly (onex delegate --bus inmemory runs "
f"offline on purpose).",
f"offline on purpose)."
f"{missing_owner_details}"
f" On a developer machine, phase 2 of the public onboarding script "
f"(bash lab-onboarding.sh) writes that file; or set "
f"ONEX_WORKSPACE_CONFIG_ROOT to the directory that holds "
f"config/onex/runtime/runtime_config.yaml.",
context=ModelInfraErrorContext(
transport_type=EnumInfraTransportType.RUNTIME,
operation="resolve_workspace_runtime_config",
Expand Down
132 changes: 132 additions & 0 deletions tests/integration/cli/test_omn20700_developer_workspace_default_cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# SPDX-FileCopyrightText: 2025 OmniNode.ai Inc.
# SPDX-License-Identifier: MIT

"""End-to-end CLI coverage: a fresh developer workspace delegates (OMN-20700).

Through ``click``, with public clones only, no private sibling repository, and
``ONEX_WORKSPACE_CONFIG_ROOT`` unset. The default invocation binds the workspace
through ``$OMNIBASE_PATH`` and delegates on the tier-1 runtime config that phase
2 of the public ``omniclaude`` onboarding script, ``lab-onboarding.sh``, wrote
at ``.onex/workspace-config/config/onex/runtime/runtime_config.yaml``. Before
OMN-20700 the default invocation was refused naming ``../omnibase_internal``.

Dispatch itself needs a co-installed omnimarket and a live model endpoint,
neither of which belongs in this gate, so the receipt-mode dispatch is captured
rather than run -- the same stand-in the workspace transport integration test
uses. A workspace with no config anywhere is refused naming the public fix.
"""

from __future__ import annotations

from pathlib import Path

import pytest
from click.testing import CliRunner

from omnibase_infra.cli import cli_delegate
from omnibase_infra.cli.cli_delegate import delegate_command

pytestmark = pytest.mark.integration


@pytest.fixture(autouse=True)
def isolated_config_owner(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.delenv("ONEX_WORKSPACE_CONFIG_ROOT", raising=False)
monkeypatch.delenv("ONEX_CONTRACTS_DIR", raising=False)


_DEVELOPER_RUNTIME_CONFIG = """# Written by omninode-dev-setup.
description: "Developer workspace tier-1 runtime configuration: in-memory bus, local profile"
input_topic: "requests"
output_topic: "responses"
group_id: "onex-runtime"
event_bus:
type: "inmemory"
profile: "local"
environment: "local"
max_history: 1000
circuit_breaker_threshold: 5
"""


def _workspace(root: Path, *, with_config: bool) -> Path:
"""A public developer workspace with, optionally, its onboarding config."""
root.mkdir(parents=True)
if with_config:
config = (
root
/ ".onex"
/ "workspace-config"
/ "config"
/ "onex"
/ "runtime"
/ "runtime_config.yaml"
)
config.parent.mkdir(parents=True, exist_ok=True)
config.write_text(_DEVELOPER_RUNTIME_CONFIG, encoding="utf-8")
return root


@pytest.fixture
def captured_dispatch(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> dict[str, object]:
captured: dict[str, object] = {}

def _fake_run_receipt_mode(**kwargs: object) -> int:
captured.update(kwargs)
return 0

monkeypatch.setattr(cli_delegate, "check_omnimarket_drift", lambda **_: None)
monkeypatch.setattr(
cli_delegate,
"_resolve_packaged_contract",
lambda _name: tmp_path / "contract.yaml",
)
monkeypatch.setattr(cli_delegate, "run_receipt_mode", _fake_run_receipt_mode)
home = tmp_path / "home"
home.mkdir()
monkeypatch.setattr(Path, "home", classmethod(lambda _cls: home))
return captured


def _delegate(tmp_path: Path, root: Path) -> object:
# No --bus, --lane, --locus or --omnibase-path: the default invocation.
return CliRunner(env={"OMNIBASE_PATH": str(root)}).invoke(
delegate_command,
[
"Reply with exactly one word: hello",
"--task-type",
"document",
"--state-root",
str(tmp_path / "state"),
],
catch_exceptions=False,
)


def test_a_fresh_developer_workspace_delegates_on_the_config_onboarding_wrote(
tmp_path: Path, captured_dispatch: dict[str, object]
) -> None:
"""RED before OMN-20700: the default invocation required a private repo."""
root = _workspace(tmp_path / "code" / "omni", with_config=True)
result = _delegate(tmp_path, root)
assert result.exit_code == 0, result.output
assert captured_dispatch
assert captured_dispatch["backend_overrides"] == {"event_bus": "inmemory"}
assert "omnibase_internal" not in result.output


def test_no_config_anywhere_is_refused_naming_the_public_fix(
tmp_path: Path, captured_dispatch: dict[str, object]
) -> None:
"""A fresh workspace without onboarding config gets an actionable refusal."""
root = _workspace(tmp_path / "code" / "omni", with_config=False)
result = _delegate(tmp_path, root)
assert result.exit_code != 0
output = " ".join(str(result.output).split())
assert "ONEX_WORKSPACE_CONFIG_ROOT" in output
assert "lab-onboarding.sh" in output
assert ".onex/workspace-config" in output
assert "--bus inmemory" in output
assert captured_dispatch == {}, "nothing may be dispatched on a refusal"
Loading
Loading