Skip to content
Closed
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 deploy/lab/developer-onboarding.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ lane: dev
# docker/docker-compose.principal-issuer.yml, published on the tailnet by the lab
# host's `tailscale serve` (see that file's header). Tailnet-only.
principal_issuer_url: https://omninode-pc.tail75df5e.ts.net:8443
# The CI bus that lab work runs on (omnimarket config/ci_bus_lanes.yaml lane
# `ci-bus`), and the second issuer instance that gives a developer machine its
# own login there (docker/docker-compose.principal-issuer-ci-bus.yml, OMN-20306).
# The script requests this login only when the URL's /healthz answers, so it may
# be declared ahead of the deploy.
ci_bus_lane: ci-bus
ci_bus_issuer_url: https://omninode-pc.tail75df5e.ts.net:8444
# The lab model server, by its tailnet name so it answers on and off the office
# LAN (local-dev walk D3). It serves the model id omnimarket's local rungs declare.
lab_model_url: http://omnipc2.tail75df5e.ts.net:8000/v1/chat/completions
Expand Down
64 changes: 64 additions & 0 deletions deploy/lab/developer-principal-grants.ci-bus.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# SPDX-FileCopyrightText: 2025 OmniNode.ai Inc.
# SPDX-License-Identifier: MIT
#
# What a developer machine's CI-bus principal may do (OMN-20306).
#
# Lab work runs on the CI bus (docker/docker-compose.ci-bus.yml), not the dev
# lane. A developer machine sends a unit there with `onex lab-work run` (the omni
# plugin's `onex_lab_run.py --via bus`) and waits for its receipt. Until now the
# only login for that was the shared `ci-bus-labwork` principal (OMN-20213),
# handed over by hand. A second issuer instance
# (docker/docker-compose.principal-issuer-ci-bus.yml) applies exactly these
# grants to each developer machine instead.
#
# THE MINIMUM FOR ONE LAB-WORK CALL (omnimarket lab_work/bus.py LabWorkCaller):
# * produce the unit request;
# * read the capacity advertisements, to place the unit, and its two terminals;
# * read under the caller's own consumer-group prefix,
# derive_service_group_id("lab_work_client", service="omnimarket") under the
# env token `local`, plus a random suffix per call;
# * IdempotentWrite on the cluster: EventBusKafka producers enable idempotence
# by default, and producer-id allocation needs it (as OMN-20213 found for the
# shared login). It reaches no topic.
#
# DELIBERATELY NOT GRANTED, though the shared lab-work login holds them:
# * write on the terminals or the capacity topic (only a pool host publishes them);
# * the pool hosts' groups (local.omnimarket.node_lab_work_unit_effect...);
# * focused test runs and push validation;
# * any CI topic.
#
# RULES the issuer enforces on this file are the same as the dev lane's
# (developer-principal-grants.yaml), plus: a cluster grant carries
# idempotent_write only.
schema: developer-principal-grants.v1
lane: ci-bus
# The same <prefix><login>-<device> name the dev lane issues: one machine, one
# name, on each broker that knows it.
principal_prefix: dev-
max_devices_per_login: 5
allowed_login_domains: []
grants:
- resource: topic
name: onex.cmd.omnimarket.lab-work-unit-requested.v1
pattern: literal
operations: [write, describe]
- resource: topic
name: onex.evt.omnimarket.lab-work-unit-completed.v1
pattern: literal
operations: [read, describe]
- resource: topic
name: onex.evt.omnimarket.lab-work-unit-failed.v1
pattern: literal
operations: [read, describe]
- resource: topic
name: onex.evt.omnimarket.lab-host-capacity-advertised.v1
pattern: literal
operations: [read, describe]
- resource: group
name: local.omnimarket.lab_work_client.consume.v1.
pattern: prefixed
operations: [read, describe]
- resource: cluster
name: kafka-cluster
pattern: literal
operations: [idempotent_write]
17 changes: 17 additions & 0 deletions deploy/lane-census/lane-manifest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,23 @@ lanes:
kind: service
replicas: 1
# ---------------------------------------------------------------------------
# OMN-20306: the CI-bus developer principal issuer. The same issuer, a second
# instance: issues each developer machine its own CI-bus login for lab work,
# from deploy/lab/developer-principal-grants.ci-bus.yaml. It joins the CI bus's
# network to reach that broker. `optional: true`: DECLARED-AHEAD-OF-STATE until
# the operator's first deploy.
principal-issuer-ci-bus:
hosts: [lab-201]
compose_file: docker/docker-compose.principal-issuer-ci-bus.yml
compose_project: omnibase-infra-principal-issuer-ci-bus
network: omninode-ci-bus-network
image_tag_pattern: '.+'
optional: true
services:
- name: omnibase-infra-principal-issuer-ci-bus
kind: service
replicas: 1
# ---------------------------------------------------------------------------
# Prospective OMN-18693 dogfood boundary: owner Codex; optional lane only.
# No production or runtime evidence is asserted here; it is not deployed.
# verified: 2026-09-25T01:20Z via read-only `docker ps -a --filter
Expand Down
4 changes: 3 additions & 1 deletion docker/Dockerfile.principal-issuer
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,11 @@ RUN set -eu; \
COPY scripts/__init__.py /app/scripts/__init__.py
COPY scripts/lab_principal_issuer.py /app/scripts/lab_principal_issuer.py
COPY deploy/lab/developer-principal-grants.yaml /app/developer-principal-grants.yaml
COPY deploy/lab/developer-principal-grants.ci-bus.yaml /app/developer-principal-grants.ci-bus.yaml

USER issuer
WORKDIR /app
RUN python3 scripts/lab_principal_issuer.py check --grants /app/developer-principal-grants.yaml
RUN python3 scripts/lab_principal_issuer.py check --grants /app/developer-principal-grants.yaml && \
python3 scripts/lab_principal_issuer.py check --grants /app/developer-principal-grants.ci-bus.yaml
ENTRYPOINT ["python3", "scripts/lab_principal_issuer.py"]
CMD ["serve", "--grants", "/app/developer-principal-grants.yaml", "--ledger", "/data/issuances.jsonl", "--socket", "/run/principal-issuer/issuer.sock", "--proxy-uid", "0"]
64 changes: 64 additions & 0 deletions docker/docker-compose.principal-issuer-ci-bus.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# SPDX-FileCopyrightText: 2025 OmniNode.ai Inc.
# SPDX-License-Identifier: MIT
#
# OMN-20306: issues each developer machine its own CI-bus login, for lab work.
#
# The second instance of the principal issuer (docker-compose.principal-issuer.yml
# is the dev lane's). Same image, same code. It differs in four things:
# * the grants file is developer-principal-grants.ci-bus.yaml;
# * it joins the CI bus's network, to reach that broker's admin API and Kafka
# listener;
# * it authenticates as the CI bus's superuser (CI_BUS_KAFKA_SASL_*), from the
# same operator env file the CI bus's one-shots read;
# * it has its own ledger and socket volumes, published on its own tailscale
# serve port.
#
# Like the dev lane's issuer, it has no TCP listener and publishes no port. It
# trusts the tailnet login header only from the kernel-attested root peer
# (tailscale serve). Deploy it once, on the lab host, after the CI bus is up with
# SASL on:
#
# docker compose -f docker/docker-compose.principal-issuer-ci-bus.yml \
# --env-file <the operator env file> up -d --build
# tailscale serve --bg --https=8444 "unix:$(docker volume inspect -f '{{.Mountpoint}}' omnibase-infra-principal-issuer-ci-bus_principal-issuer-socket)/issuer.sock"
#
# The URL developer machines use is declared in deploy/lab/developer-onboarding.yaml
# (ci_bus_issuer_url). Onboarding skips the CI-bus login until that URL answers.
name: omnibase-infra-principal-issuer-ci-bus
services:
principal-issuer-ci-bus:
build:
context: ..
dockerfile: docker/Dockerfile.principal-issuer
container_name: omnibase-infra-principal-issuer-ci-bus
command: ["serve", "--grants", "/app/developer-principal-grants.ci-bus.yaml", "--ledger", "/data/issuances.jsonl", "--socket", "/run/principal-issuer/issuer.sock", "--proxy-uid", "0"]
environment:
RPK_USER: ${CI_BUS_KAFKA_SASL_USERNAME:?CI_BUS_KAFKA_SASL_USERNAME must be set - the issuer creates principals as the CI bus's superuser (OMN-18691); resolved from the operator env file, never inlined here}
RPK_PASS: ${CI_BUS_KAFKA_SASL_PASSWORD:?CI_BUS_KAFKA_SASL_PASSWORD must be set - the issuer creates principals as the CI bus's superuser (OMN-18691); resolved from the operator env file, never inlined here}
RPK_SASL_MECHANISM: SCRAM-SHA-256
RPK_BROKERS: redpanda:9092
RPK_ADMIN_HOSTS: redpanda:9644
volumes:
- principal-issuer-ledger:/data
- principal-issuer-socket:/run/principal-issuer
networks:
- ci-bus
restart: unless-stopped
read_only: true
healthcheck:
test: ["CMD", "python3", "scripts/lab_principal_issuer.py", "health", "--socket", "/run/principal-issuer/issuer.sock"]
interval: 30s
timeout: 5s
retries: 3
labels:
- "com.omninode.service=principal-issuer-ci-bus"
- "com.omninode.layer=infrastructure"
- "com.omninode.ticket=OMN-20306"
volumes:
principal-issuer-ledger:
principal-issuer-socket:
networks:
ci-bus:
# The network docker/docker-compose.ci-bus.yml declares for the CI bus's broker.
name: omninode-ci-bus-network
external: true
8 changes: 8 additions & 0 deletions scripts/generate_claude_lane_block.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,9 @@
# not a runtime lane: it has no runtime main/effects pair. It publishes no
# port and serves a Unix socket reached only by the host's tailscale serve.
"principal-issuer": _PORTS_NONE,
# OMN-20306 — the same issuer for the CI bus: a credential service, no
# runtime pair, no published port; a Unix socket behind tailscale serve.
"principal-issuer-ci-bus": _PORTS_NONE,
}

_LANE_BOUNDARY: dict[str, str] = {
Expand Down Expand Up @@ -224,6 +227,11 @@
"network, unix socket only, no published port; NOT a runtime lane, "
"never a proof lane, and never sourced for stability/prod grants"
),
"principal-issuer-ci-bus": (
"developer CI-bus login issuer — a credential service on the .201 CI bus "
"network, unix socket only, no published port; NOT a runtime lane, "
"never a proof lane, and never sourced for stability/prod grants"
),
}


Expand Down
43 changes: 34 additions & 9 deletions scripts/lab_principal_issuer.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,11 @@
broker superuser password.

WHAT IT GRANTS
Exactly the grants in ``deploy/lab/developer-principal-grants.yaml``, which
this module validates before serving (see :func:`load_declaration`). A
Exactly the grants in the file passed as ``--grants``, which this module
validates before serving (see :func:`load_declaration`). One instance serves
one lane: ``deploy/lab/developer-principal-grants.yaml`` for the dev lane, and
``deploy/lab/developer-principal-grants.ci-bus.yaml`` for the CI bus that lab
work runs on (OMN-20306), each with its own socket, ledger and broker. A
principal is named ``<prefix><login>-<device>``; a login may hold at most
``max_devices_per_login`` of them. Asking again for the same device ROTATES
its password: the password is never stored here, so re-issue is the only way
Expand Down Expand Up @@ -83,7 +86,12 @@
PRINCIPAL_MAX_LEN = 63
MIN_PREFIXED_READ_GROUP_LEN = 16
ALLOWED_OPERATIONS = frozenset({"read", "write", "describe"})
ALLOWED_RESOURCES = frozenset({"topic", "group"})
ALLOWED_RESOURCES = frozenset({"topic", "group", "cluster"})
# A Kafka producer with idempotence on (the EventBusKafka default) must allocate a
# producer id, which needs IdempotentWrite on the cluster. That grant reaches no
# topic, so it is the one cluster grant a declaration may carry (OMN-20306).
CLUSTER_NAME = "kafka-cluster"
CLUSTER_OPERATIONS = ("idempotent_write",)
ALLOWED_PATTERNS = frozenset({"literal", "prefixed"})


Expand Down Expand Up @@ -172,6 +180,20 @@ def _grant(path: Path, index: int, raw: object) -> Grant:
)
if not isinstance(name, str) or not name:
raise DeclarationError(f"{where}: name must be a non-empty string")
if resource == "cluster":
if (
name != CLUSTER_NAME
or pattern != "literal"
or not isinstance(operations, list)
or tuple(sorted(set(operations))) != CLUSTER_OPERATIONS
):
raise DeclarationError(
f"{where}: a cluster grant is name {CLUSTER_NAME}, pattern literal and "
f"operations [idempotent_write] only (it reaches no topic)"
)
return Grant(
resource=resource, name=name, pattern=pattern, operations=CLUSTER_OPERATIONS
)
if pattern not in ALLOWED_PATTERNS:
raise DeclarationError(
f"{where}: pattern must be one of {sorted(ALLOWED_PATTERNS)}"
Expand Down Expand Up @@ -284,12 +306,15 @@ def grant(self, principal: str, grant: Grant) -> None:
f"User:{principal}",
]
argv += ["--operation", ",".join(grant.operations)]
argv += [
f"--{grant.resource}",
grant.name,
"--resource-pattern-type",
grant.pattern,
]
if grant.resource == "cluster":
argv += ["--cluster"]
else:
argv += [
f"--{grant.resource}",
grant.name,
"--resource-pattern-type",
grant.pattern,
]
result = self._run(argv)
if result.returncode != 0:
raise IssuerError(
Expand Down
Loading
Loading