mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-08 19:56:53 +00:00
Twelve files conflicted; most resolutions keep both sides rather than pick
one.
cli env.py and compose.py: both branches extend the same spots. The tools
override is appended before write_override, because that call filters the
mirror override to the compose files it is handed. compose.py keeps this
branch's CACHE_ROLE_SHELL_DIR, since the cache scripts moved into a role and
main's literal scripts/docker/cache path is their old home, and takes main's
mirrored init image alongside it.
utils/docker/image/discovery.py: this branch's version_variants expansion
plus main's whole runner-image block, which runner_mirror imports.
sys-svc-compose network create: main's side wins on substance and this
branch's on structure. main carries eda6249d7b, the fix for a probe that
matched an id prefix and let a sibling shadow the exact name; this branch had
refactored the task into create/overlay.yml and create/bridge.yml but brought
back the docker_network_info probe that fix replaced. The subfiles stay and
now probe through the exact-name lookup. main's half also read
SWARM_CLUSTER_AVAILABLE, which this branch removed in favour of
IS_SWARM_MODE, as the merge base confirms, so the gates follow this branch.
The lookup's import follows this branch's get_entity_name to entity_name
rename.
web-app-pixelfed: the custom entrypoints stay removed, since the new image
brings its own and the variables behind them no longer exist, and main's
host_alias=true comes along.
web-app-suitecrm: main's session.yaml and its config/packages path, because
only that version wires framework.session and only that directory is read by
Symfony.
web-app-odoo: main's onionOptions import is required by the test.use below
it; this branch's test-seaweedfs registration stays.
Four defects surfaced that no conflict marker shows.
The auto-merge of web-app-suitecrm/vars/main.yml duplicated
SUITECRM_SESSION_CONFIG_HOST, which ansible-lint rejects. Removing it exposed
the second: SUITECRM_SESSION_CONFIG_DOCKER still named config/session.yaml
while the mount had moved to config/packages, so the freshness comparison in
tasks/01_manager_ops.yml would have tested a path nothing writes.
svc-db-elasticsearch carried environment: on two include_tasks, which Ansible
accepts on include_role only. ELASTIC_PW now sits on the five tasks that read
it, which is also the tighter scope for a credential.
The openclaw templates render NETWORK_TRUSTED_PROXY_CIDRS while their test
renderer passes only its explicit kwargs, so seven tests raised Undefined.
That predates the merge: the template line is on this branch and not on main,
and both sides share the same test file.
test_runner_mirror neutralises four environment variables under clear=False
and missed three, so the gpu and tools overrides leaked in from the container
and the assertion on the layered list broke. It now neutralises all seven.
make quality-high passes both phases.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
97 lines
3.2 KiB
Python
97 lines
3.2 KiB
Python
"""OpenClaw refuses to boot on a model entry that misses a key.
|
|
|
|
The gateway validates its whole configuration before it serves anything, and
|
|
a rejected file is not a degraded start but a crash loop: OpenClaw retries,
|
|
trips its own restart breaker after four unclean boots, and the container
|
|
exits 128 while the deploy sits in the health check until it gives up.
|
|
|
|
The provider block reached the repository without ever being rendered in a
|
|
test - every case here passed OPENCLAW_LITELLM_ENABLED=False, so the block was
|
|
skipped and its shape was never checked against anything.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import importlib.util
|
|
import json
|
|
import unittest
|
|
|
|
from jinja2 import Environment, FileSystemLoader, select_autoescape
|
|
|
|
from . import PROJECT_ROOT
|
|
|
|
OPENCLAW = PROJECT_ROOT / "roles/web-app-openclaw/templates"
|
|
FILTERS = PROJECT_ROOT / "plugins/filter/mcp/authorization.py"
|
|
|
|
MODEL = "qwen2.5:0.5b"
|
|
|
|
CONTEXT = {
|
|
"NETWORK_TRUSTED_PROXY_CIDRS": ["127.0.0.1/32", "::1/128", "10.0.0.0/8"],
|
|
"OPENCLAW_URL": "https://claw.example.org",
|
|
"OPENCLAW_LITELLM_ENABLED": True,
|
|
"OPENCLAW_LITELLM_PROVIDER": "litellm",
|
|
"OPENCLAW_LITELLM_MODEL": MODEL,
|
|
"OPENCLAW_LITELLM_BASE_URL": "http://litellm:4000/v1",
|
|
"OPENCLAW_LITELLM_API_KEY": "sk-" + "x" * 20,
|
|
"OPENCLAW_MCP_SERVERS": [],
|
|
}
|
|
|
|
|
|
def _filters() -> dict:
|
|
spec = importlib.util.spec_from_file_location("mcp_authorization", FILTERS)
|
|
module = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(module)
|
|
filters = dict(module.FilterModule().filters())
|
|
filters.update({"to_json": json.dumps, "bool": bool})
|
|
return filters
|
|
|
|
|
|
def _render(**overrides) -> dict:
|
|
"""Return the gateway config as OpenClaw would parse it.
|
|
|
|
Args:
|
|
overrides: context values replacing the defaults.
|
|
"""
|
|
env = Environment(
|
|
loader=FileSystemLoader(str(OPENCLAW)), autoescape=select_autoescape()
|
|
)
|
|
env.filters.update(_filters())
|
|
return json.loads(
|
|
env.get_template("openclaw.json.j2").render(**{**CONTEXT, **overrides})
|
|
)
|
|
|
|
|
|
class TestOpenClawModelProvider(unittest.TestCase):
|
|
def _entry(self, **overrides) -> dict:
|
|
"""Return the provider's single model entry.
|
|
|
|
Args:
|
|
overrides: context values replacing the defaults.
|
|
"""
|
|
return _render(**overrides)["models"]["providers"]["litellm"]["models"][0]
|
|
|
|
def test_the_model_entry_carries_both_keys_the_gateway_validates(self) -> None:
|
|
self.assertEqual({"id": MODEL, "name": MODEL}, self._entry())
|
|
|
|
def test_the_name_matches_the_id(self) -> None:
|
|
"""Which key reaches the API is the upstream's choice, not ours."""
|
|
entry = self._entry()
|
|
self.assertEqual(entry["id"], entry["name"])
|
|
|
|
def test_the_primary_agent_model_names_the_same_provider(self) -> None:
|
|
rendered = _render()
|
|
self.assertEqual(
|
|
f"litellm/{MODEL}",
|
|
rendered["agents"]["defaults"]["model"]["primary"],
|
|
)
|
|
self.assertIn("litellm", rendered["models"]["providers"])
|
|
|
|
def test_the_block_disappears_when_litellm_is_off(self) -> None:
|
|
rendered = _render(OPENCLAW_LITELLM_ENABLED=False)
|
|
self.assertNotIn("models", rendered)
|
|
self.assertNotIn("agents", rendered)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|