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
5 changes: 4 additions & 1 deletion deploy/lane-census/lane-manifest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -689,7 +689,10 @@ lanes:
# Its compose file is an overlay on the dev composition (infra.yml plus
# dev-lane.yml) that renames every container, the network, every volume and
# the project, and republishes every port on the loopback address in the
# 61xxx block. Its header carries the render command and the reasons.
# 61xxx block; runtime main, effects and the projection API are also published
# on the docker bridge gateway, where a fleet verify runner container reaches
# them as host.docker.internal (OMN-19507 AC3), and nothing binds the LAN.
# Its header carries the render command and the reasons.
#
# Its own Postgres, Redpanda and Valkey, never the .201 shared broker. It is
# NOT in GOVERNED_LANES or GRANT_INTERLOCK_LANES
Expand Down
23 changes: 22 additions & 1 deletion docker/docker-compose.dev-202.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@
# ISOLATION. Its own Postgres, Redpanda and Valkey, never the .201 shared
# broker. The ONLY tie to .201 is the deploy agent's read of the dev control
# topic, and that is the agent's connection, not this stack's. So:
# * every port binds the loopback address;
# * every port binds the loopback address, and the three ports a fleet
# verify job reads (runtime main 61085, effects 61086, projection API
# 61002) also bind the docker bridge gateway 172.17.0.1 -- never all
# interfaces, never the LAN address (see VERIFY-RUNNER REACH below);
# * runtime-effects leaves the external omnimemory-network, which does not
# exist on .202;
# * the four runtime-kernel services mount this lane's own Bifrost overlay
Expand Down Expand Up @@ -59,6 +62,21 @@
# discharged for .202 (C29's move into a disposable container is
# omninode_infra#1663).
#
# VERIFY-RUNNER REACH (OMN-19507 AC3, operator RULING 2026-09-25T06:57:54Z).
# A per-merge verify job runs in a runner CONTAINER, where localhost is the
# container itself, and reads a lane at host.docker.internal, the runner's
# `host.docker.internal:host-gateway` alias. That alias resolves to the default
# bridge network's gateway, 172.17.0.1 on .202 (read 2026-09-25: `docker network
# inspect bridge`, and daemon.json sets no host-gateway-ip). A loopback-only
# publish refuses it. So 61085, 61086 and 61002 are published twice: on
# loopback, where the deploy agent's own localhost health probe reads them, and
# on the gateway, which no other host routes to. Measured on .202 the same day
# with throwaway containers on those two binds: a container on a user network
# with the alias reached the gateway bind, got connection refused from a
# loopback-only one, and the LAN address refused both. The broker, databases
# and consumers stay loopback-only: a verify job reads those through the docker
# socket, not over TCP.
#
# PORT BLOCK (read free on .202 by `ss -ltn`, recorded in the lane manifest's
# verified: line). 61000-61999 sits above .202's ephemeral range (32768-60999)
# and below sim-202's 62xxx and the reserved pre-PR pool's 63xxx/64xxx. No
Expand Down Expand Up @@ -170,6 +188,7 @@ services:
BIFROST_LANE_OVERLAY_PATH: /app/config/delegation/dev-202.bifrost.yaml
ports: !override
- "127.0.0.1:61085:8085"
- "172.17.0.1:61085:8085"
volumes: !override
- runtime_logs:/app/logs
- runtime_data:/app/data
Expand All @@ -183,6 +202,7 @@ services:
BIFROST_LANE_OVERLAY_PATH: /app/config/delegation/dev-202.bifrost.yaml
ports: !override
- "127.0.0.1:61086:8085"
- "172.17.0.1:61086:8085"
networks: !override
- omnibase-infra-network
volumes: !override
Expand Down Expand Up @@ -217,6 +237,7 @@ services:
container_name: omnimarket-dev-202-projection-api
ports: !override
- "127.0.0.1:61002:3002"
- "172.17.0.1:61002:3002"
agent-actions-consumer:
container_name: omninode-dev-202-agent-actions-consumer
ports: !override
Expand Down
87 changes: 73 additions & 14 deletions tests/ci/test_dev_202_lane_boundary_omn19505.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,15 @@
only host, so the host-aware census evaluates it on .202 and reports every
.201 lane not-applicable (ticket AC3).
* **Nothing reachable from another host, nothing reaching .201.** Every port the
overlay publishes binds the loopback address and is one of the declared
block; the overlay names no .201 address and mounts no .201-bound Bifrost
overlay.
overlay publishes binds the loopback address or the docker bridge gateway,
never all interfaces or a LAN address, and is one of the declared block; the
overlay names no .201 address and mounts no .201-bound Bifrost overlay.
* **Reachable by a fleet verify runner (OMN-19507 AC3).** A verify job runs in a
runner CONTAINER and reads the lane at ``host.docker.internal``, the runner's
host-gateway alias, which resolves to the docker bridge gateway. The three
ports that job reads (runtime main, effects, projection API) are published on
that gateway as well as on loopback, which the deploy agent's own
``localhost`` health probe reads. No other port is.
* **Every service the .201 dev lane declares, under a dev-202 name** (ticket
AC1), and no Keycloak or Infisical (the roles plan's non-roles on .202).
"""
Expand Down Expand Up @@ -87,6 +93,18 @@
)
GOVERNED_LANE_PORT_LITERALS = ("28085", "28086", "18085", "18086")

#: The docker bridge gateway on .202: the default ``bridge`` network's gateway,
#: which is where a runner container's ``host.docker.internal:host-gateway``
#: alias resolves when the daemon sets no ``host-gateway-ip`` (read on .202
#: 2026-09-25 by ``docker network inspect bridge`` and ``/etc/docker/daemon.json``).
DOCKER_BRIDGE_GATEWAY = "172.17.0.1"
LOOPBACK = "127.0.0.1"

#: The ports a fleet verify job reads through ``host.docker.internal`` (the
#: dev-202 instance's main, effects and projection URLs in
#: ``config/deploy_lane_routing.yaml``).
GATEWAY_REACHED_PORTS = {"61085", "61086", "61002"}

#: The roles plan's non-roles on .202.
FORBIDDEN_SERVICE_FRAGMENTS = ("keycloak", "infisical")

Expand Down Expand Up @@ -177,20 +195,61 @@ def test_dev_202_lane_boundary_declares_no_forbidden_service() -> None:
assert "container_name" not in block.group(1)


def test_dev_202_lane_boundary_every_published_port_is_loopback_and_in_the_block() -> (
def _bindings() -> dict[str, set[str]]:
"""Host port -> the set of host addresses it is published on."""
bindings: dict[str, set[str]] = {}
for line in _published_port_lines():
host_ip, host_port, _container_port = line.split(":")
bindings.setdefault(host_port, set()).add(host_ip)
return bindings


def test_dev_202_lane_boundary_every_port_entry_names_a_host_address() -> None:
"""A port entry without a host address publishes on every interface."""
raw = OVERLAY_PATH.read_text(encoding="utf-8")
entries = re.findall(r'^\s+-\s+"([^"]*:\d+)"\s*$', raw, re.MULTILINE)
assert entries, "positive control: the overlay publishes ports"
assert sorted(entries) == sorted(_published_port_lines()), (
"every published entry has the host-address:host-port:container-port form"
)


def test_dev_202_lane_boundary_every_published_port_binds_loopback_or_the_gateway() -> (
None
):
lines = _published_port_lines()
assert lines, "positive control: the overlay publishes ports"
published: set[str] = set()
for line in lines:
host_ip, host_port, _container_port = line.split(":")
assert host_ip == "127.0.0.1", (
f"{line!r} binds {host_ip!r}; every dev-202 port binds the loopback "
"address so no other host can reach its broker or databases"
bindings = _bindings()
assert bindings, "positive control: the overlay publishes ports"
for port, addresses in bindings.items():
assert addresses <= {LOOPBACK, DOCKER_BRIDGE_GATEWAY}, (
f"port {port} binds {sorted(addresses)}; a dev-202 port binds only the "
"loopback address or the docker bridge gateway, never all interfaces "
"or a LAN address, so no other host can reach its broker or databases"
)
assert LOOPBACK in addresses, (
f"port {port} is not on loopback, where the deploy agent's localhost "
"health probe and host-side readers reach it"
)
published.add(host_port)
assert published == EXPECTED_PUBLISHED_PORTS
assert set(bindings) == EXPECTED_PUBLISHED_PORTS


def test_dev_202_lane_boundary_verify_ports_are_reachable_from_a_runner_container() -> (
None
):
"""OMN-19507 AC3: a runner container reaches the lane at host.docker.internal,
the bridge gateway; a loopback-only bind refuses it (measured on .202
2026-09-25: loopback-only answered connection refused, the gateway bind
answered)."""
bindings = _bindings()
on_gateway = {
port
for port, addresses in bindings.items()
if DOCKER_BRIDGE_GATEWAY in addresses
}
assert on_gateway == GATEWAY_REACHED_PORTS, (
f"gateway-published ports {sorted(on_gateway)}; exactly the verify job's "
f"{sorted(GATEWAY_REACHED_PORTS)} are, and the broker, databases and "
"consumers stay loopback-only"
)


def test_dev_202_lane_boundary_block_is_clear_of_every_other_lane() -> None:
Expand Down
Loading